All Downloads are FREE. Search and download functionalities are using the official Maven repository.

samlang.checker.ProgramTypeChecker.kt Maven / Gradle / Ivy

package samlang.checker

import samlang.ast.checked.CheckedExpr
import samlang.ast.checked.CheckedModule
import samlang.ast.checked.CheckedModule.CheckedMemberDefinition
import samlang.ast.checked.CheckedModule.CheckedTypeDef
import samlang.ast.checked.CheckedProgram
import samlang.ast.checked.CheckedTypeExpr
import samlang.ast.raw.RawExpr
import samlang.ast.raw.RawModule
import samlang.ast.raw.RawProgram
import samlang.errors.CollisionError
import samlang.errors.IllegalMethodDefinitionError
import samlang.parser.Position

internal object ProgramTypeChecker {

    private data class PartiallyCheckedMemberDefinition(
        val isPublic: Boolean,
        val isMethod: Boolean,
        val typeParameters: List?,
        val name: String,
        val typeAnnotation: CheckedTypeExpr.FunctionType,
        val value: RawExpr.Lambda
    )

    private fun List.checkNameCollision() {
        val nameSet = hashSetOf()
        forEach { nameWithPos ->
            if (!nameSet.add(element = nameWithPos.name)) {
                throw CollisionError(collidedName = nameWithPos)
            }
        }
    }

    fun typeCheck(program: RawProgram, ctx: TypeCheckingContext): CheckedProgram {
        val checkedModules = arrayListOf()
        var currentCtx = ctx
        for (module in program.modules) {
            val (checkedModule, newCtx) = typeCheck(module = module, ctx = currentCtx)
            checkedModules.add(element = checkedModule)
            currentCtx = newCtx
        }
        return CheckedProgram(modules = checkedModules)
    }

    private fun typeCheck(module: RawModule, ctx: TypeCheckingContext): Pair {
        val (modulePosition, moduleNameWithPos, moduleTypeDef, moduleMembers) = module
        val (checkedTypeDef, partialCtx) = createContextWithCurrentModuleDefOnly(
            moduleNameWithPos = moduleNameWithPos, moduleTypeDef = moduleTypeDef, ctx = ctx
        )
        val (fullCtx, partiallyCheckedMembers) = processCurrentContextWithMembersAndMethods(
            modulePosition = modulePosition,
            moduleName = moduleNameWithPos.name,
            moduleMembers = moduleMembers,
            isUtilModule = moduleTypeDef == null,
            ctx = partialCtx
        )
        val checkedModule = CheckedModule(
            name = moduleNameWithPos.name,
            typeDef = checkedTypeDef,
            members = partiallyCheckedMembers.map { typeCheckMember(member = it, ctx = fullCtx) }
        )
        return checkedModule to fullCtx
    }

    private fun createContextWithCurrentModuleDefOnly(
        moduleNameWithPos: Position.WithName,
        moduleTypeDef: RawModule.RawTypeDef?,
        ctx: TypeCheckingContext
    ): Pair {
        // new context with type def but empty members and extensions
        val (moduleNamePos, moduleName) = moduleNameWithPos
        return if (moduleTypeDef == null) {
            null to ctx.addNewEmptyUtilModule(name = moduleName, namePosition = moduleNamePos)
        } else {
            val typeParams = moduleTypeDef.typeParams
            val (isObject, mappings) = when (moduleTypeDef) {
                is RawModule.RawTypeDef.ObjectType -> {
                    true to moduleTypeDef.mappings
                }
                is RawModule.RawTypeDef.VariantType -> {
                    false to moduleTypeDef.mappings
                }
            }
            // check name collisions
            typeParams?.checkNameCollision()
            mappings.map { (name, o) ->
                val (namePos, _) = o
                Position.WithName(position = namePos, name = name)
            }.checkNameCollision()
            val params = typeParams?.map { it.name }
            // create checked type def based on a temp
            ctx.addNewModuleTypeDef(name = moduleName, namePosition = moduleNamePos, params = params) { tempCtx ->
                val checkedMappings = mappings.map { (name, o) ->
                    val (_, rawType) = o
                    name to rawType.accept(visitor = RawToCheckedTypeVisitor, context = tempCtx)
                }.toMap()
                if (isObject) {
                    CheckedTypeDef.ObjectType(typeParams = params, mappings = checkedMappings)
                } else {
                    CheckedTypeDef.VariantType(typeParams = params, mappings = checkedMappings)
                }
            }
        }
    }

    private fun processCurrentContextWithMembersAndMethods(
        modulePosition: Position,
        moduleName: String,
        moduleMembers: List,
        isUtilModule: Boolean,
        ctx: TypeCheckingContext
    ): Pair> {
        moduleMembers.map { it.name }.checkNameCollision()
        val partiallyCheckedMembers = moduleMembers.map { member ->
            member.typeParameters?.checkNameCollision()
            val typeParams = member.typeParameters?.map { it.name }
            var newCtx = typeParams?.let { ctx.addLocalGenericTypes(genericTypes = it) } ?: ctx
            if (member.isMethod) {
                if (isUtilModule) {
                    throw IllegalMethodDefinitionError(
                        name = moduleName,
                        position = modulePosition
                    )
                }
                val updatedNewCtx = newCtx.getCurrentModuleTypeDef()
                    ?.typeParams
                    ?.let { newCtx.addLocalGenericTypes(genericTypes = it) }
                if (updatedNewCtx != null) {
                    newCtx = updatedNewCtx
                }
            }
            val type = member.typeAnnotation.accept(visitor = RawToCheckedTypeVisitor, context = newCtx)
            PartiallyCheckedMemberDefinition(
                isPublic = member.isPublic,
                isMethod = member.isMethod,
                typeParameters = typeParams,
                name = member.name.name,
                typeAnnotation = type as CheckedTypeExpr.FunctionType,
                value = member.value
            )
        }
        val memberTypeInfo = partiallyCheckedMembers.map { member ->
            Triple(
                first = member.name,
                second = member.isMethod,
                third = TypeCheckingContext.TypeInfo(
                    isPublic = member.isPublic,
                    typeParams = member.typeParameters,
                    type = member.typeAnnotation
                )
            )
        }
        val newCtx = ctx.addMembersAndMethodsToCurrentModule(members = memberTypeInfo)
        return newCtx to partiallyCheckedMembers
    }

    private fun typeCheckExpr(expr: RawExpr, ctx: TypeCheckingContext, expectedType: CheckedTypeExpr): CheckedExpr {
        val manager = UndecidedTypeManager()
        val visitor = ExprTypeCheckerVisitor(manager = manager)
        val checkedExpr = expr.accept(visitor = visitor, context = ctx to expectedType)
        return CheckedExprTypeFixer.fixType(
            expr = checkedExpr,
            expectedType = expectedType,
            manager = manager,
            ctx = ctx,
            errorPosition = expr.position
        )
    }

    private fun typeCheckMember(
        member: PartiallyCheckedMemberDefinition, ctx: TypeCheckingContext
    ): CheckedMemberDefinition {
        val (isPublic, isMethod, typeParameters, name, typeAnnotation, value) = member
        val type = member.typeParameters to member.typeAnnotation
        var ctxForTypeCheckingValue = if (member.isMethod) ctx.addThisType() else ctx
        if (typeParameters != null) {
            ctxForTypeCheckingValue = ctxForTypeCheckingValue.addLocalGenericTypes(genericTypes = typeParameters)
        }
        val checkedValue = typeCheckExpr(
            expr = value,
            ctx = ctxForTypeCheckingValue,
            expectedType = typeAnnotation
        ) as CheckedExpr.Lambda
        return CheckedMemberDefinition(
            isPublic = isPublic,
            isMethod = isMethod,
            name = name,
            type = type,
            value = checkedValue
        )
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy