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

commonMain.com.sunnychung.lib.multiplatform.kotlite.model.SemanticAnalyzerSymbolTable.kt Maven / Gradle / Ivy

Go to download

A Kotlin Multiplatform library to interpret Kotlite code, which is a subset of Kotlin language, in runtime in a safe way.

The newest version!
package com.sunnychung.lib.multiplatform.kotlite.model

import com.sunnychung.lib.multiplatform.kotlite.error.IdentifierClassifier
import com.sunnychung.lib.multiplatform.kotlite.extension.resolveGenericParameterTypeToUpperBound
import com.sunnychung.lib.multiplatform.kotlite.log
import com.sunnychung.lib.multiplatform.kotlite.util.ClassMemberResolver

class SemanticAnalyzerSymbolTable(
    scopeLevel: Int,
    scopeName: String,
    scopeType: ScopeType,
    parentScope: SymbolTable?,
    returnType: DataType? = null,
) : SymbolTable(
    scopeLevel = scopeLevel,
    scopeName = scopeName,
    scopeType = scopeType,
    parentScope = parentScope,
    returnType = returnType
) {
    /**
     * For resolving type parameters in function declarations only
     */
    private val tempTypeAlias = mutableListOf>()

    fun TypeNode.toClass(): ClassDefinition {
        return (findClass(name) ?: throw RuntimeException("Could not find class `$name`"))
            .first
    }

    internal fun declareTempTypeAlias(typeAliasAndUpperBounds: List>) {
        val alias = mutableMapOf()
        // update tempTypeAlias first, because other type parameters may depend on previous type parameters in the same event
        // e.g. `>`
        tempTypeAlias += alias
        typeAliasAndUpperBounds.forEach {
            alias[it.first] = SymbolTableTypeVisitCache(it.first).let { cache ->
                assertToDataType(it.second, visitCache = cache).also { result ->
                    cache.postVisit(it.first, result)
                }
            }
        }
    }

    internal fun popTempTypeAlias() {
        tempTypeAlias.removeLast()
    }

    override fun findTypeAlias(name: String): Pair? {
        tempTypeAlias.indices.reversed().forEach {  index ->
            tempTypeAlias[index][name]?.let { return it to this }
        }
        return super.findTypeAlias(name)
    }

    override fun functionNameTransform(name: String, function: FunctionDeclarationNode) = function.toSignature(this)

    fun findFunctionsByOriginalName(originalName: String, isThisScopeOnly: Boolean = false): List> {
        return functionDeclarations.filter { it.value.name == originalName }
            .map { it.value to this } +
                (Unit.takeIf { !isThisScopeOnly }?.let {
                    (parentScope as? SemanticAnalyzerSymbolTable)?.findFunctionsByOriginalName(originalName, isThisScopeOnly)
                } ?: emptyList())
    }

    // only use in semantic analyzer
    private fun findAllMatchingCallables(currentSymbolTable: SymbolTable, originalName: String, receiverClass: ClassDefinition?, receiverType: DataType?, arguments: List, modifierFilter: SearchFunctionModifier): List {
        var thisScopeCandidates = mutableListOf()
        if (receiverClass == null) {
            if (modifierFilter != SearchFunctionModifier.ConstructorOnly) findFunctionsByOriginalName(originalName, isThisScopeOnly = true).map {
                val owner = findFunctionOwner(functionNameTransform(originalName, it.first))
                FindCallableResult(
                    transformedName = it.first.transformedRefName!!,
                    originalName = it.first.name,
                    owner = owner,
                    type = if (owner == null) CallableType.Function else CallableType.ClassMemberFunction,
                    isVararg = it.first.isVararg,
                    arguments = it.first.valueParameters,
                    typeParameters = it.first.typeParameters,
                    receiverType = null,
                    returnType = it.first.returnType,
                    signature = it.first.toSignature(this),
                    definition = it.first,
                    scope = this
                )
            }.let { thisScopeCandidates += it }
            findClass(originalName, isThisScopeOnly = true)?.let {
                thisScopeCandidates += FindCallableResult(
                    transformedName = it.first.fullQualifiedName,
                    originalName = it.first.name,
                    owner = null,
                    type = CallableType.Constructor,
                    isVararg = false,
                    arguments = it.first.primaryConstructor?.parameters?.map { it.parameter } ?: emptyList(),
                    typeParameters = it.first.typeParameters,
                    receiverType = null,
                    returnType = TypeNode(SourcePosition.NONE, it.first.fullQualifiedName, null, false),
                    signature = it.first.fullQualifiedName,
                    definition = it.first,
                    scope = this
                )
            }
            if (modifierFilter != SearchFunctionModifier.ConstructorOnly) getPropertyTypeOrNull(originalName, isThisScopeOnly = true)?.let {
                if (it.first.type !is FunctionType || it.first.type.isNullable) {
                    return@let
                }
//                val transformedName = "$originalName/${this.scopeLevel}"
                val transformedName = it.second.transformedSymbolsByDeclaredName[IdentifierClassifier.Property to originalName]!!
                val owner = findPropertyOwner(transformedName)?.ownerRefName
                thisScopeCandidates += FindCallableResult(
                    transformedName = transformedName,
                    originalName = originalName,
                    owner = owner,
                    type = CallableType.Property,
                    isVararg = false,
                    arguments = (it.first.type as FunctionType).arguments,
                    typeParameters = emptyList(),
                    receiverType = null,
                    returnType = (it.first.type as FunctionType).returnType.toTypeNode(),
                    signature = "$owner//$transformedName",
                    definition = it.first,
                    scope = this
                )
            }
        } else if (modifierFilter != SearchFunctionModifier.ConstructorOnly) {
//            receiverClass.findMemberFunctionsByDeclaredName(originalName).map {
//                val it = it.value
//                FindCallableResult(
//                    transformedName = it.transformedRefName!!,
//                    owner = null,
//                    type = CallableType.ClassMemberFunction,
//                    isVararg = it.isVararg,
//                    arguments = it.valueParameters,
//                    typeParameters = it.typeParameters,
//                    receiverType = it.receiver,
//                    returnType = it.returnType,
//                    definition = it,
//                    scope = this
//                )
//            }.let { thisScopeCandidates += it }
            ClassMemberResolver.create(this, receiverClass, null, createOnlyIfClassExists = true)?.findMemberFunctionsAndTypeUpperBoundsByDeclaredName(originalName)?.mapNotNull { lookup ->
                val it = lookup.value.function
                val receiverType = when (receiverType) {
                    is TypeParameterType -> receiverType.upperBound
                    is ObjectType -> receiverType
                    else -> null
                }
                if (receiverType !is ObjectType) return@mapNotNull null

                // TODO this is slow, O(n^2). optimize this
                ClassMemberResolver.create(
                    symbolTable = this,
                    clazz = receiverClass,
                    typeArguments = (receiverType as ObjectType).arguments.map { it.toTypeNode() },
                    createOnlyIfClassExists = true
                )?.findMemberFunctionWithIndexByTransformedNameLinearSearch(it.transformedRefName!!)?.let { lookup2 ->
                    FindCallableResult(
                        transformedName = it.transformedRefName!!,
                        originalName = it.name,
                        owner = null,
                        type = CallableType.ClassMemberFunction,
                        isVararg = it.isVararg,
                        arguments = lookup2!!.resolvedValueParameterTypes,
                        typeParameters = it.typeParameters,
                        receiverType = it.receiver ?: receiverType.toTypeNode(),
                        returnType = lookup2!!.resolvedReturnType,
                        signature = it.toSignature(this),
                        definition = it,
                        scope = this
                    )
                }
            }?.let { thisScopeCandidates += it }

            findExtensionFunctionsIncludingSuperClasses(receiverType!!, originalName, isThisScopeOnly = true).map {
                FindCallableResult(
                    transformedName = it.function.transformedRefName!!,
                    originalName = it.function.name,
                    owner = null,
                    type = CallableType.ExtensionFunction,
                    isVararg = it.function.isVararg,
                    arguments = it.function.valueParameters,
                    typeParameters = it.function.typeParameters,
                    receiverType = it.function.receiver, //it.resolvedReceiverType.toTypeNode(), //it.function.receiver,
                    returnType = it.function.returnType,
                    signature = it.function.toSignature(this),
                    definition = it.function,
                    scope = this
                )
            }.let { thisScopeCandidates += it }
        }
        thisScopeCandidates = thisScopeCandidates
            .filter { callable ->
                when (modifierFilter.typeFilter) {
                    SearchFunctionModifier.Type.OperatorFunctionOnly -> {
                        if (callable.definition is FunctionDeclarationNode) {
                            callable.definition.modifiers.contains(FunctionModifier.operator)
                        } else {
                            false
                        }
                    }

                    SearchFunctionModifier.Type.InfixFunctionOnly -> {
                        if (callable.definition is FunctionDeclarationNode) {
                            callable.definition.modifiers.contains(FunctionModifier.infix)
                        } else {
                            false
                        }
                    }

                    else -> true
                }
            }
            .filter { callable ->
                declareTempTypeAlias(callable.typeParameters.map {
                    it.name to it.typeUpperBoundOrAny()
                })
                try {
                    if (callable.isVararg) {
                        val functionArgType = currentSymbolTable.typeNodeToDataType(
                            (callable.arguments.first() as FunctionValueParameterNode).type.resolveGenericParameterTypeToUpperBound(
                                callable.typeParameters + (receiverClass?.typeParameters ?: emptyList())
                            )
                        )!!
                        return@filter arguments.all { functionArgType.isConvertibleFrom(it.type) }
                    }

                    if (callable.arguments.isEmpty()) {
                        return@filter arguments.isEmpty()
                    }
                    when (callable.arguments.first()) {
                        is DataType /* is a lambda */ -> return@filter arguments.size == callable.arguments.size &&
                                arguments.all { it.name == null } &&
                                callable.arguments.foldIndexed(true) { i, acc, it -> acc && (it as DataType).isAssignableFrom(arguments[i].type) }
                        is FunctionValueParameterNode -> {
                            if (arguments.size > callable.arguments.size) return@filter false
                            val argumentsReordered = arrayOfNulls(callable.arguments.size)
                            val typeParameterMapping = mutableMapOf()
                            arguments.forEachIndexed { i, arg ->
                                val newIndex = if (arg.name == null) {
                                    if (i == arguments.lastIndex && arg.type is FunctionType) {
                                        callable.arguments.lastIndex
                                    } else {
                                        i
                                    }
                                } else {
                                    val findIndex = callable.arguments.indexOfFirst { (it as FunctionValueParameterNode).name == arg.name }
                                    if (findIndex < 0) {
                                        return@filter false
                                    }
                                    findIndex
                                }
                                argumentsReordered[newIndex] = arg
                            }
                            callable.arguments.foldIndexed(true) { i, acc, it ->
                                val functionArg = it as FunctionValueParameterNode
                                val callArg = argumentsReordered[i]
                                acc && if (callArg == null) {
                                    functionArg.defaultValue != null
                                } else {
                                    currentSymbolTable.typeNodeToDataType(functionArg.type)?.isConvertibleFrom(callArg.type) == true
                                        || currentSymbolTable.assertToDataType(functionArg.type.resolveGenericParameterTypeToUpperBound(callable.typeParameters + (receiverClass?.typeParameters ?: emptyList()) )).isConvertibleFrom(callArg.type)
                                    // TODO filter whether same type parameter always map to same argument
                                }
                            }
                        }
                        else -> throw UnsupportedOperationException()
                    }
                } finally {
                    popTempTypeAlias()
                }
            }.toMutableList()
        return thisScopeCandidates + ((parentScope as? SemanticAnalyzerSymbolTable)?.findAllMatchingCallables(currentSymbolTable, originalName, receiverClass, receiverType, arguments, modifierFilter) ?: emptyList())
    }

    fun assertToDataTypeWithTypeParameters(type: TypeNode, typeParameters: List): DataType {
        declareTempTypeAlias(typeParameters.flatMap {
            listOf(
                it.name to it.typeUpperBoundOrAny(),
                "${it.name}?" to it.typeUpperBoundOrAny().copy(isNullable = true),
            )
        }.distinctBy { it.first })
        try {
            return assertToDataType(type)
        } finally {
            popTempTypeAlias()
        }
    }

    fun toTypeNode(type: Any): TypeNode =
        when (type) {
            is FunctionValueParameterNode -> type.type
            is DataType -> type.toTypeNode()
            else -> throw UnsupportedOperationException()
        }

    fun toDataType(type: Any, typeParameters: List): DataType =
        when (type) {
            is FunctionValueParameterNode -> assertToDataTypeWithTypeParameters(type.type, typeParameters)
            is DataType -> type
            else -> throw UnsupportedOperationException()
        }

    // only use in semantic analyzer
    // this operation is expensive
    fun findMatchingCallables(currentSymbolTable: SymbolTable, originalName: String, receiverType: DataType?, arguments: List, modifierFilter: SearchFunctionModifier): List {
        data class FunctionDistinctId(val receiverType: TypeNode?, val fucnctionName: String, val arguments: List)

        val receiverClass = receiverType
            ?.let { if (it is TypeParameterType) it.upperBound else it }
            ?.let { (findClass(it.nameWithNullable) ?: throw RuntimeException("Class ${it.nameWithNullable} not found")).first }
        return findAllMatchingCallables(currentSymbolTable, originalName, receiverClass, receiverType, arguments, modifierFilter)
            .distinctBy { it.definition }
            .let { result -> // prioritize extension function first if they are special functions (toString/hashCode/equals)
                val biasedResult = mutableListOf()
                val otherCandidates = mutableListOf()
                result.forEach {
                    var isSpecialFunctionAndExtensionFunction = false
                    if (it.type == CallableType.ExtensionFunction) {
                        loop@ for (sf in SpecialFunction.Name.entries) {
                            if (sf.functionName != it.originalName) continue
                            for (acceptableValueParameters in sf.acceptableValueParameterTypes) {
                                if (acceptableValueParameters.size != it.arguments.size) continue
                                if (acceptableValueParameters.withIndex()
                                        .all { (index, vp) -> vp == (it.arguments[index] as? FunctionValueParameterNode)?.type }
                                ) {
                                    isSpecialFunctionAndExtensionFunction = true
                                    it.isSpecialFunction = true
                                    break@loop
                                }
                            }
                        }
                    }
                    if (isSpecialFunctionAndExtensionFunction) {
                        biasedResult += it
                    } else {
                        otherCandidates += it
                    }
                }
                biasedResult + otherCandidates
            }
            .distinctBy { FunctionDistinctId(it.receiverType, it.originalName, it.arguments.map { toTypeNode(it) }) }
            .let { callables ->
                modifierFilter.returnType?.let { requiredReturnType ->
                    callables.filter {
                        requiredReturnType.isConvertibleFrom(assertToDataTypeWithTypeParameters(it.returnType, it.typeParameters))
                    }
                } ?: callables
            }
            .let { callables -> // subclass callables override superclass
                callables.filterNot { callable ->
                    if (callable.receiverType != null) {
                        val callableReceiverType = assertToDataTypeWithTypeParameters(callable.receiverType, callable.typeParameters)
                        callables.any { it.receiverType != null && assertToDataTypeWithTypeParameters(it.receiverType, it.typeParameters).isSubTypeOf(callableReceiverType) }
                    } else {
                        false
                    }
                }
            }
            .let { callables -> // class member functions override extension methods
                callables.filterNot { callable ->
                    if (callable.receiverType != null && callable.type == CallableType.ExtensionFunction) {
                        val callableReceiverType = assertToDataTypeWithTypeParameters(callable.receiverType, callable.typeParameters)
                        callables.any { it.receiverType != null && assertToDataTypeWithTypeParameters(it.receiverType, it.typeParameters) == callableReceiverType && it.type == CallableType.ClassMemberFunction }
                    } else {
                        false
                    }
                }
            }
            .let { callables -> // filter for functions that have strictly more specific value parameters
                callables.filterNot { callable ->
                    // find for any eligible other callable that is strictly more specific to callable
                    callables.any { otherCallable ->
                        if (callable === otherCallable) return@any false
                        if (callable.arguments.size != otherCallable.arguments.size) return@any false
                        val valueParameterTypes = callable.arguments.map { toDataType(it, callable.typeParameters) }
                        val otherValueParameterTypes = otherCallable.arguments.map { toDataType(it, otherCallable.typeParameters) }
                        var isOtherMoreSpecific = false
                        valueParameterTypes.indices.forEach {  i ->
                            if (!otherValueParameterTypes[i].isConvertibleTo(valueParameterTypes[i])) {
                                return@any false
                            }
                            if (!isOtherMoreSpecific && otherValueParameterTypes[i].isSubTypeOf(valueParameterTypes[i])) {
                                isOtherMoreSpecific = true
                            }
                        }
                        isOtherMoreSpecific // return true if otherCallable is more specific than callable
                    }
                }
            }
            .distinctBy {
                if (it.type == CallableType.ExtensionFunction) {
                    it.transformedName
                } else {
                    it.signature
                }
            }
            .also { log.v { "Matching functions:\n${it.joinToString("\n")}" } }
    }

    fun findExtensionFunctionsIncludingSuperClasses(receiverType: DataType, functionName: String, isThisScopeOnly: Boolean = false): List {
        val result = mutableListOf()

//        var type: DataType = receiverType
//        val resolver = (type as? ObjectType)?.let { ClassMemberResolver(this, it.clazz, it.arguments.map { it.toTypeNode() }) }
//        var classTreeIndex = (type as? ObjectType)?.clazz?.index ?: 0

        val type = receiverType
//        (listOf(receiverType) + ((receiverType as? ObjectType)?.superTypes ?: emptyList())).forEach { type ->
            findExtensionFunctions(type, functionName, isThisScopeOnly)
                .let { lookups ->
                    result.addAll(lookups.map {
                        ExtensionFunctionLookupResult(
                            function = it.first,
                            resolvedReceiverType = type!!,
                            symbolTable = it.second,
                        )
                    })
                }
//        }

        return result
    }

    fun findExtensionFunctions(receiverType: DataType, functionName: String, isThisScopeOnly: Boolean = false): List> {
        return extensionFunctionDeclarations.values.filter { (funcReceiverType, func) ->
            func.name == functionName && (funcReceiverType.let {
                if (!it.isConvertibleFrom(receiverType)) {
                    return@let false
                }
//                if (
//                    !(receiverType.name == "Nothing" && receiverType.isNullable && it.isNullable) &&
//                    (it.name != receiverType.name || it.isNullable != receiverType.isNullable)
//                ) return@let false
//                // at here, class name and nullability matched, or receiver is null and nullability matched
//                val objectTypeReceiver = receiverType as? ObjectType
//                if (it.arguments.isNullOrEmpty() && (objectTypeReceiver == null || objectTypeReceiver.arguments.isEmpty())) {
//                    return@let true
//                }
//                // at here, function receiver type has some type parameter, so receiverType can only be class instance
//                if (objectTypeReceiver == null) {
//                    throw RuntimeException("Receiver must be a class instance")
//                }
//                if (it.arguments!!.size != objectTypeReceiver.arguments.size) {
//                    throw RuntimeException("Number of type arguments mismatch")
//                }
//                declareTempTypeAlias(func.typeParameters.map {
//                    it.name to it.typeUpperBoundOrAny()
//                })
//                try {
//                    it.arguments.forEachIndexed { index, argType ->
//                        if (argType.name == "*") return@forEachIndexed
//                        val typeParameter = func.typeParameters.firstOrNull { it.name == argType.name }
//                        val functionTypeParameterType = if (typeParameter != null) {
//                            typeParameter.typeUpperBound ?: TypeNode(SourcePosition.NONE, "Any", null, true)
//                        } else {
//                            argType
//                        }.let { typeNodeToDataType(it) } // TODO generic type
//                            ?: throw SemanticException(argType.position, "Unknown type ${argType.descriptiveName()}")
//                        if (!functionTypeParameterType.isConvertibleFrom(objectTypeReceiver.arguments[index])) {
//                            return@let false // type mismatch
//                        }
//                    }
//                } finally {
//                    popTempTypeAlias()
//                }
                true
            } ?: false)
        }.map {
            it.second to this
        } + (Unit.takeIf { !isThisScopeOnly }?.let {
            (parentScope as? SemanticAnalyzerSymbolTable)
                ?.findExtensionFunctions(receiverType, functionName, isThisScopeOnly)
        } ?: emptyList())
    }

    fun findExtensionPropertyByDeclarationIncludingSuperClasses(resolvedReceiver: TypeNode, declaredName: String, isThisScopeOnly: Boolean = false): Pair? {
        var receiverType: DataType = assertToDataType(resolvedReceiver)
//        while (receiverType != null) {
        (listOf(receiverType) + ((receiverType as? ObjectType)?.superTypes ?: emptyList())).forEach { type ->
            findExtensionPropertyByDeclaration(type.toTypeNode(), declaredName)?.let {
                return it
            }
//            receiverType = (receiverType as? ObjectType)?.superType
        }
        return null
    }

    fun DataType.toTypeNode(): TypeNode =
        if (this is NothingType) {
            TypeNode(SourcePosition.NONE, "Nothing", null, true)
        } else if (this !is ObjectType && this !is FunctionType) {
            TypeNode(SourcePosition.NONE, name, null, isNullable)
        } else if (this is FunctionType) {
            FunctionTypeNode(
                position = SourcePosition.NONE,
                parameterTypes = arguments.map { it.toTypeNode() },
                returnType = returnType.toTypeNode(),
                isNullable = isNullable,
            )
        } else {
            TypeNode(SourcePosition.NONE, name, null, isNullable)
        }
}

fun FunctionDeclarationNode.toSignature(symbolTable: SemanticAnalyzerSymbolTable): String {
    with (symbolTable) {
        val typeParameters = (symbolTable.listTypeAliasInAllScopes() + typeParameters /* typeParameters has higher precedence */)
            .associateBy { it.name }
        return (receiver?.let { "${it.descriptiveName()}/" } ?: "") + name + "//" + valueParameters.joinToString("/") {
            if (typeParameters.containsKey(it.type.name)) {
                val typeUpperBound = typeParameters[it.type.name]!!.typeUpperBound
                typeUpperBound?.toClass()?.fullQualifiedName ?: "Any?"
            } else {
                it.type
                    .let { findClass(it.name)?.first }
                    ?.fullQualifiedName
                    ?: it.type.descriptiveName() // TODO this is dirty. any way to get upper bound type name?
            }
        }
    }
}

data class SearchFunctionModifier(val typeFilter: Type? = null, val returnType: DataType? = null) {
    enum class Type {
        OperatorFunctionOnly,
        InfixFunctionOnly,
        ConstructorOnly,
        NoRestriction,
    }

    companion object {
        val OperatorFunctionOnly = SearchFunctionModifier(typeFilter = Type.OperatorFunctionOnly)
        val InfixFunctionOnly = SearchFunctionModifier(typeFilter = Type.InfixFunctionOnly)
        val ConstructorOnly = SearchFunctionModifier(typeFilter = Type.ConstructorOnly)
        val NoRestriction = SearchFunctionModifier()
    }
}

data class ExtensionFunctionLookupResult(
    val function: FunctionDeclarationNode,
    val resolvedReceiverType: DataType,
    val symbolTable: SymbolTable,
)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy