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

org.jetbrains.kotlin.analysis.decompiler.stub.TypeClsStubBuilder.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.

package org.jetbrains.kotlin.analysis.decompiler.stub

import com.intellij.psi.PsiElement
import com.intellij.psi.stubs.StubElement
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.builtins.isBuiltinFunctionClass
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.ProtoBuf.Type
import org.jetbrains.kotlin.metadata.ProtoBuf.Type.Argument.Projection
import org.jetbrains.kotlin.metadata.ProtoBuf.TypeParameter.Variance
import org.jetbrains.kotlin.metadata.deserialization.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.protobuf.MessageLite
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.stubs.KotlinUserTypeStub
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
import org.jetbrains.kotlin.psi.stubs.impl.*
import org.jetbrains.kotlin.serialization.deserialization.DYNAMIC_TYPE_DESERIALIZER_ID
import org.jetbrains.kotlin.serialization.deserialization.ProtoContainer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
import org.jetbrains.kotlin.serialization.deserialization.getName
import org.jetbrains.kotlin.utils.doNothing

// TODO: see DescriptorRendererOptions.excludedTypeAnnotationClasses for decompiler
private val ANNOTATIONS_NOT_LOADED_FOR_TYPES = setOf(StandardNames.FqNames.parameterName)

class TypeClsStubBuilder(private val c: ClsStubBuilderContext) {
    fun createTypeReferenceStub(
        parent: StubElement,
        type: Type,
        additionalAnnotations: () -> List = { emptyList() }
    ) {
        val abbreviatedType = type.abbreviatedType(c.typeTable)
        if (abbreviatedType != null) {
            return createTypeReferenceStub(parent, abbreviatedType, additionalAnnotations)
        }

        val typeReference = KotlinPlaceHolderStubImpl(parent, KtStubElementTypes.TYPE_REFERENCE)

        val annotations = c.components.annotationLoader.loadTypeAnnotations(type, c.nameResolver).filterNot {
            val isTopLevelClass = !it.isNestedClass
            isTopLevelClass && it.asSingleFqName() in ANNOTATIONS_NOT_LOADED_FOR_TYPES
        }

        val allAnnotations = additionalAnnotations() + annotations.map { ClassIdWithTarget(it, null) }

        when {
            type.hasClassName() || type.hasTypeAliasName() ->
                createClassReferenceTypeStub(typeReference, type, allAnnotations)
            type.hasTypeParameter() ->
                createTypeParameterStub(typeReference, type, c.typeParameters[type.typeParameter], allAnnotations)
            type.hasTypeParameterName() ->
                createTypeParameterStub(typeReference, type, c.nameResolver.getName(type.typeParameterName), allAnnotations)
            else -> {
                doNothing()
            }
        }
    }

    private fun nullableTypeParent(parent: KotlinStubBaseImpl<*>, type: Type): KotlinStubBaseImpl<*> = if (type.nullable)
        KotlinPlaceHolderStubImpl(parent, KtStubElementTypes.NULLABLE_TYPE)
    else
        parent

    private fun createTypeParameterStub(parent: KotlinStubBaseImpl<*>, type: Type, name: Name, annotations: List) {
        createTypeAnnotationStubs(parent, type, annotations)
        if (Flags.DEFINITELY_NOT_NULL_TYPE.get(type.flags)) {
            createDefinitelyNotNullTypeStub(parent, FqName.topLevel(name))
        } else {
            val nullableParentWrapper = nullableTypeParent(parent, type)
            createStubForTypeName(ClassId.topLevel(FqName.topLevel(name)), nullableParentWrapper)
        }
    }

    private fun createDefinitelyNotNullTypeStub(parent: KotlinStubBaseImpl<*>, name: FqName) {
        val intersectionType = KotlinPlaceHolderStubImpl(parent, KtStubElementTypes.INTERSECTION_TYPE)
        val leftReference = KotlinPlaceHolderStubImpl(intersectionType, KtStubElementTypes.TYPE_REFERENCE)
        createStubForTypeName(ClassId.topLevel(name), leftReference)
        val rightReference = KotlinPlaceHolderStubImpl(intersectionType, KtStubElementTypes.TYPE_REFERENCE)
        val userType = KotlinUserTypeStubImpl(rightReference)
        KotlinNameReferenceExpressionStubImpl(userType, StandardNames.FqNames.any.shortName().ref())
    }

    private fun createClassReferenceTypeStub(parent: KotlinStubBaseImpl<*>, type: Type, annotations: List) {
        if (type.hasFlexibleTypeCapabilitiesId()) {
            val id = c.nameResolver.getString(type.flexibleTypeCapabilitiesId)

            if (id == DYNAMIC_TYPE_DESERIALIZER_ID) {
                KotlinPlaceHolderStubImpl(nullableTypeParent(parent, type), KtStubElementTypes.DYNAMIC_TYPE)
                return
            }
        }

        assert(type.hasClassName() || type.hasTypeAliasName()) {
            "Class reference stub must have either class or type alias name"
        }

        val classId = c.nameResolver.getClassId(if (type.hasClassName()) type.className else type.typeAliasName)
        val shouldBuildAsFunctionType = isBuiltinFunctionClass(classId) && type.argumentList.none { it.projection == Projection.STAR }
        if (shouldBuildAsFunctionType) {
            val (extensionAnnotations, notExtensionAnnotations) = annotations.partition {
                it.classId.asSingleFqName() == StandardNames.FqNames.extensionFunctionType
            }

            val isExtension = extensionAnnotations.isNotEmpty()
            val isSuspend = Flags.SUSPEND_TYPE.get(type.flags)

            val nullableWrapper = if (isSuspend) {
                val wrapper = nullableTypeParent(parent, type)
                createTypeAnnotationStubs(wrapper, type, notExtensionAnnotations)
                wrapper
            } else {
                createTypeAnnotationStubs(parent, type, notExtensionAnnotations)
                nullableTypeParent(parent, type)
            }

            createFunctionTypeStub(nullableWrapper, type, isExtension, isSuspend)

            return
        }

        createTypeAnnotationStubs(parent, type, annotations)

        val outerTypeChain = generateSequence(type) { it.outerType(c.typeTable) }.toList()

        createStubForTypeName(classId, nullableTypeParent(parent, type)) { userTypeStub, index ->
            outerTypeChain.getOrNull(index)?.let { createTypeArgumentListStub(userTypeStub, it.argumentList) }
        }
    }

    private fun createTypeAnnotationStubs(parent: KotlinStubBaseImpl<*>, type: Type, annotations: List) {
        val typeModifiers = getTypeModifiersAsWritten(type)
        if (annotations.isEmpty() && typeModifiers.isEmpty()) return
        val typeModifiersMask = ModifierMaskUtils.computeMask { it in typeModifiers }
        val modifiersList = KotlinModifierListStubImpl(parent, typeModifiersMask, KtStubElementTypes.MODIFIER_LIST)
        createTargetedAnnotationStubs(annotations, modifiersList)
    }

    private fun getTypeModifiersAsWritten(type: Type): Set {
        if (!type.hasClassName() && !type.hasTypeAliasName()) return emptySet()

        val result = hashSetOf()

        if (Flags.SUSPEND_TYPE.get(type.flags)) {
            result.add(KtTokens.SUSPEND_KEYWORD)
        }

        return result
    }

    private fun createTypeArgumentListStub(typeStub: KotlinUserTypeStub, typeArgumentProtoList: List) {
        if (typeArgumentProtoList.isEmpty()) {
            return
        }
        val typeArgumentsListStub = KotlinPlaceHolderStubImpl(typeStub, KtStubElementTypes.TYPE_ARGUMENT_LIST)
        typeArgumentProtoList.forEach { typeArgumentProto ->
            val projectionKind = typeArgumentProto.projection.toProjectionKind()
            val typeProjection = KotlinTypeProjectionStubImpl(typeArgumentsListStub, projectionKind.ordinal)
            if (projectionKind != KtProjectionKind.STAR) {
                val modifierKeywordToken = projectionKind.token as? KtModifierKeywordToken
                createModifierListStub(typeProjection, listOfNotNull(modifierKeywordToken))
                createTypeReferenceStub(typeProjection, typeArgumentProto.type(c.typeTable)!!)
            }
        }
    }

    private fun Projection.toProjectionKind() = when (this) {
        Projection.IN -> KtProjectionKind.IN
        Projection.OUT -> KtProjectionKind.OUT
        Projection.INV -> KtProjectionKind.NONE
        Projection.STAR -> KtProjectionKind.STAR
    }

    private fun createFunctionTypeStub(
        parent: StubElement,
        type: Type,
        isExtensionFunctionType: Boolean,
        isSuspend: Boolean
    ) {
        val typeArgumentList = type.argumentList
        val functionType = KotlinPlaceHolderStubImpl(parent, KtStubElementTypes.FUNCTION_TYPE)
        if (isExtensionFunctionType) {
            val functionTypeReceiverStub =
                KotlinPlaceHolderStubImpl(functionType, KtStubElementTypes.FUNCTION_TYPE_RECEIVER)
            val receiverTypeProto = typeArgumentList.first().type(c.typeTable)!!
            createTypeReferenceStub(functionTypeReceiverStub, receiverTypeProto)
        }

        val parameterList = KotlinPlaceHolderStubImpl(functionType, KtStubElementTypes.VALUE_PARAMETER_LIST)
        val typeArgumentsWithoutReceiverAndReturnType =
            typeArgumentList.subList(if (isExtensionFunctionType) 1 else 0, typeArgumentList.size - 1)
        var suspendParameterType: Type? = null

        for ((index, argument) in typeArgumentsWithoutReceiverAndReturnType.withIndex()) {
            if (isSuspend && index == typeArgumentsWithoutReceiverAndReturnType.size - 1) {
                val parameterType = argument.type(c.typeTable)!!
                if (parameterType.hasClassName() && parameterType.argumentCount == 1) {
                    val classId = c.nameResolver.getClassId(parameterType.className)
                    val fqName = classId.asSingleFqName()
                    assert(
                        fqName == FqName("kotlin.coroutines.Continuation") ||
                                fqName == FqName("kotlin.coroutines.experimental.Continuation")
                    ) {
                        "Last parameter type of suspend function must be Continuation, but it is $fqName"
                    }
                    suspendParameterType = parameterType
                    continue
                }
            }
            val parameter = KotlinParameterStubImpl(
                parameterList, fqName = null, name = null, isMutable = false, hasValOrVar = false, hasDefaultValue = false
            )

            createTypeReferenceStub(parameter, argument.type(c.typeTable)!!)
        }


        if (suspendParameterType == null) {
            val returnType = typeArgumentList.last().type(c.typeTable)!!
            createTypeReferenceStub(functionType, returnType)
        } else {
            val continuationArgumentType = suspendParameterType.getArgument(0).type(c.typeTable)!!
            createTypeReferenceStub(functionType, continuationArgumentType)
        }
    }

    fun createValueParameterListStub(
        parent: StubElement,
        callableProto: MessageLite,
        parameters: List,
        container: ProtoContainer
    ) {
        val parameterListStub = KotlinPlaceHolderStubImpl(parent, KtStubElementTypes.VALUE_PARAMETER_LIST)
        for ((index, valueParameterProto) in parameters.withIndex()) {
            val name = c.nameResolver.getName(valueParameterProto.name)
            val parameterStub = KotlinParameterStubImpl(
                parameterListStub,
                name = name.ref(),
                fqName = null,
                hasDefaultValue = false,
                hasValOrVar = false,
                isMutable = false
            )
            val varargElementType = valueParameterProto.varargElementType(c.typeTable)
            val typeProto = varargElementType ?: valueParameterProto.type(c.typeTable)
            val modifiers = arrayListOf()

            if (varargElementType != null) {
                modifiers.add(KtTokens.VARARG_KEYWORD)
            }
            if (Flags.IS_CROSSINLINE.get(valueParameterProto.flags)) {
                modifiers.add(KtTokens.CROSSINLINE_KEYWORD)
            }
            if (Flags.IS_NOINLINE.get(valueParameterProto.flags)) {
                modifiers.add(KtTokens.NOINLINE_KEYWORD)
            }

            val modifierList = createModifierListStub(parameterStub, modifiers)

            if (Flags.HAS_ANNOTATIONS.get(valueParameterProto.flags)) {
                val parameterAnnotations = c.components.annotationLoader.loadValueParameterAnnotations(
                    container, callableProto, callableProto.annotatedCallableKind, index, valueParameterProto
                )
                if (parameterAnnotations.isNotEmpty()) {
                    createAnnotationStubs(parameterAnnotations, modifierList ?: createEmptyModifierListStub(parameterStub))
                }
            }

            createTypeReferenceStub(parameterStub, typeProto)
        }
    }

    fun createTypeParameterListStub(
        parent: StubElement,
        typeParameterProtoList: List
    ): List> {
        if (typeParameterProtoList.isEmpty()) return listOf()

        val typeParameterListStub = KotlinPlaceHolderStubImpl(parent, KtStubElementTypes.TYPE_PARAMETER_LIST)
        val protosForTypeConstraintList = arrayListOf>()
        for (proto in typeParameterProtoList) {
            val name = c.nameResolver.getName(proto.name)
            val typeParameterStub = KotlinTypeParameterStubImpl(
                typeParameterListStub,
                name = name.ref(),
                isInVariance = proto.variance == Variance.IN,
                isOutVariance = proto.variance == Variance.OUT
            )
            createTypeParameterModifierListStub(typeParameterStub, proto)
            val upperBoundProtos = proto.upperBounds(c.typeTable)
            if (upperBoundProtos.isNotEmpty()) {
                val upperBound = upperBoundProtos.first()
                if (!upperBound.isDefaultUpperBound()) {
                    createTypeReferenceStub(typeParameterStub, upperBound)
                }
                protosForTypeConstraintList.addAll(upperBoundProtos.drop(1).map { Pair(name, it) })
            }
        }
        return protosForTypeConstraintList
    }

    fun createTypeConstraintListStub(
        parent: StubElement,
        protosForTypeConstraintList: List>
    ) {
        if (protosForTypeConstraintList.isEmpty()) {
            return
        }
        val typeConstraintListStub = KotlinPlaceHolderStubImpl(parent, KtStubElementTypes.TYPE_CONSTRAINT_LIST)
        for ((name, type) in protosForTypeConstraintList) {
            val typeConstraintStub = KotlinPlaceHolderStubImpl(typeConstraintListStub, KtStubElementTypes.TYPE_CONSTRAINT)
            KotlinNameReferenceExpressionStubImpl(typeConstraintStub, name.ref())
            createTypeReferenceStub(typeConstraintStub, type)
        }
    }

    private fun createTypeParameterModifierListStub(
        typeParameterStub: KotlinTypeParameterStubImpl,
        typeParameterProto: ProtoBuf.TypeParameter
    ) {
        val modifiers = ArrayList()
        when (typeParameterProto.variance) {
            Variance.IN -> modifiers.add(KtTokens.IN_KEYWORD)
            Variance.OUT -> modifiers.add(KtTokens.OUT_KEYWORD)
            Variance.INV -> { /* do nothing */
            }
            null -> { /* do nothing */
            }
        }
        if (typeParameterProto.reified) {
            modifiers.add(KtTokens.REIFIED_KEYWORD)
        }

        val modifierList = createModifierListStub(typeParameterStub, modifiers)

        val annotations = c.components.annotationLoader.loadTypeParameterAnnotations(typeParameterProto, c.nameResolver)
        if (annotations.isNotEmpty()) {
            createAnnotationStubs(
                annotations,
                modifierList ?: createEmptyModifierListStub(typeParameterStub)
            )
        }
    }

    private fun Type.isDefaultUpperBound(): Boolean {
        return this.hasClassName() &&
                c.nameResolver.getClassId(className).let { StandardNames.FqNames.any == it.asSingleFqName().toUnsafe() } &&
                this.nullable
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy