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

org.jetbrains.kotlin.analysis.api.symbols.DebugSymbolRenderer.kt Maven / Gradle / Ivy

There is a newer version: 2.0.20
Show newest version
/*
 * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.analysis.api.symbols

import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.*
import org.jetbrains.kotlin.analysis.api.annotations.*
import org.jetbrains.kotlin.analysis.api.base.KtContextReceiver
import org.jetbrains.kotlin.analysis.api.components.KtSymbolContainingDeclarationProviderMixIn
import org.jetbrains.kotlin.analysis.api.components.KtSymbolInfoProviderMixIn
import org.jetbrains.kotlin.analysis.api.contracts.description.Context
import org.jetbrains.kotlin.analysis.api.contracts.description.KtContractEffectDeclaration
import org.jetbrains.kotlin.analysis.api.contracts.description.renderKtContractEffectDeclaration
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossiblyNamedSymbol
import org.jetbrains.kotlin.analysis.api.types.KtClassErrorType
import org.jetbrains.kotlin.analysis.api.types.KtClassTypeQualifier
import org.jetbrains.kotlin.analysis.api.types.KtErrorType
import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.utils.printer.PrettyPrinter
import org.jetbrains.kotlin.analysis.utils.printer.prettyPrint
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.types.Variance
import java.lang.reflect.InvocationTargetException
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
import kotlin.reflect.KVisibility
import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible

public class DebugSymbolRenderer(
    public val renderExtra: Boolean = false,
    public val renderTypeByProperties: Boolean = false,
    public val renderExpandedTypes: Boolean = false,
) {

    context(KtAnalysisSession)
    public fun render(symbol: KtSymbol): String = prettyPrint { renderSymbol(symbol) }

    context(KtAnalysisSession)
    public fun renderAnnotationApplication(application: KtAnnotationApplication): String =
        prettyPrint { renderAnnotationApplication(application) }

    context(KtAnalysisSession)
    public fun renderType(type: KtType): String = prettyPrint { renderType(type) }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderSymbol(symbol: KtSymbol) {
        renderSymbolInternals(symbol)

        if (!renderExtra) return
        withIndent {
            @Suppress("DEPRECATION")
            (symbol as? KtCallableSymbol)?.getDispatchReceiverType()?.let { dispatchType ->
                appendLine().append("getDispatchReceiver()").append(": ")
                renderType(dispatchType)
            }

            KtSymbolContainingDeclarationProviderMixIn::class
                .declaredMemberExtensionFunctions
                .filterNot {
                    // Rendering a containing symbol is prone to stack overflow.
                    // * function symbol will render its value parameter symbol(s)
                    //   whose containing symbol is that function symbol.
                    // * property symbol contains accessor symbol(s) and/or backing field symbol
                    //   whose containing symbol is that property symbol.
                    it.name == "getContainingSymbol"
                }
                .forEach {
                    if (it.name == "getContainingJvmClassName") {
                        if (symbol is KtCallableSymbol) {
                            appendLine()
                            renderFunction(it, renderSymbolsFully = false, this@KtAnalysisSession, symbol)
                        }
                    } else {
                        appendLine()
                        renderFunction(it, renderSymbolsFully = false, this@KtAnalysisSession, symbol)
                    }
                }

            KtSymbolInfoProviderMixIn::class.declaredMemberExtensionProperties
                .asSequence()
                .filter { (it.extensionReceiverParameter?.type?.classifier as? KClass<*>)?.isInstance(symbol) == true }
                .forEach {
                    appendLine()
                    renderProperty(it, renderSymbolsFully = false, this@KtAnalysisSession, symbol)
                }
        }
    }

    public fun KtAnalysisSession.renderForSubstitutionOverrideUnwrappingTest(symbol: KtSymbol): String = prettyPrint {
        if (symbol !is KtCallableSymbol) return@prettyPrint

        renderFrontendIndependentKClassNameOf(symbol)

        withIndent {
            appendLine()
            renderProperty(KtCallableSymbol::callableIdIfNonLocal, renderSymbolsFully = false, symbol)
            if (symbol is KtNamedSymbol) {
                appendLine()
                renderProperty(KtNamedSymbol::name, renderSymbolsFully = false, symbol)
            }
            appendLine()
            renderProperty(KtCallableSymbol::origin, renderSymbolsFully = false, symbol)
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderFunction(function: KFunction<*>, renderSymbolsFully: Boolean, vararg args: Any) {
        append(function.name).append(": ")
        renderFunctionCall(function, renderSymbolsFully, args)
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderProperty(property: KProperty<*>, renderSymbolsFully: Boolean, vararg args: Any) {
        append(property.name).append(": ")
        renderFunctionCall(property.getter, renderSymbolsFully, args)
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderFunctionCall(function: KFunction<*>, renderSymbolsFully: Boolean, args: Array) {
        try {
            function.isAccessible = true
            renderValue(function.call(*args), renderSymbolsFully)
        } catch (e: InvocationTargetException) {
            append("Could not render due to ").appendLine(e.cause.toString())
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderSymbolInternals(symbol: KtSymbol) {
        renderFrontendIndependentKClassNameOf(symbol)
        val apiClass = getFrontendIndependentKClassOf(symbol)
        withIndent {
            val members = apiClass.members
                .filterIsInstance>()
                .filter { it.name !in ignoredPropertyNames }
                .sortedBy { it.name }
            appendLine()
            printCollectionIfNotEmpty(members, separator = "\n") { member ->
                renderProperty(
                    member,
                    renderSymbolsFully = member.name == KtValueParameterSymbol::generatedPrimaryConstructorProperty.name,
                    symbol
                )
            }
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderFrontendIndependentKClassNameOf(instanceOfClassToRender: Any) {
        val apiClass = getFrontendIndependentKClassOf(instanceOfClassToRender)
        append(apiClass.simpleName).append(':')
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderList(values: List<*>, renderSymbolsFully: Boolean) {
        if (values.isEmpty()) {
            append("[]")
            return
        }

        withIndentInSquareBrackets {
            printCollection(values, separator = "\n") { renderValue(it, renderSymbolsFully) }
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderSymbolTag(symbol: KtSymbol, renderSymbolsFully: Boolean) {
        fun renderId(id: Any?, symbol: KtSymbol) {
            if (id != null) {
                renderValue(id, renderSymbolsFully)
            } else {
                val outerName = (symbol as? KtPossiblyNamedSymbol)?.name ?: SpecialNames.NO_NAME_PROVIDED
                append("/" + outerName.asString())
            }
        }

        if (renderSymbolsFully || symbol is KtBackingFieldSymbol ||
            symbol is KtPropertyGetterSymbol || symbol is KtPropertySetterSymbol ||
            symbol is KtValueParameterSymbol || symbol is KtReceiverParameterSymbol
        ) {
            renderSymbol(symbol)
            return
        }

        append(getFrontendIndependentKClassOf(symbol).simpleName)
        append("(")
        when (symbol) {
            is KtClassLikeSymbol -> renderId(symbol.classIdIfNonLocal, symbol)
            is KtCallableSymbol -> renderId(symbol.callableIdIfNonLocal, symbol)
            is KtNamedSymbol -> renderValue(symbol.name, renderSymbolsFully = false)
            is KtFileSymbol -> renderValue((symbol.psi as KtFile).name, renderSymbolsFully = false)
            else -> error("Unsupported symbol ${symbol::class}")
        }
        append(")")
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderAnnotationValue(value: KtAnnotationValue) {
        append(KtAnnotationValueRenderer.render(value))
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderNamedConstantValue(value: KtNamedAnnotationValue) {
        append(value.name.render()).append(" = ")
        renderValue(value.expression, renderSymbolsFully = false)
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderType(type: KtType) {
        val typeToRender = if (renderExpandedTypes) type.fullyExpandedType else type
        if (renderTypeByProperties) {
            renderByPropertyNames(typeToRender)
            return
        }

        renderFrontendIndependentKClassNameOf(typeToRender)
        withIndent {
            appendLine()
            append("annotationsList: ")
            renderAnnotationsList(typeToRender.annotationsList)

            if (typeToRender is KtNonErrorClassType) {
                appendLine()
                append("ownTypeArguments: ")
                renderList(typeToRender.ownTypeArguments, renderSymbolsFully = false)
            }

            appendLine()
            append("type: ")
            when (typeToRender) {
                is KtErrorType -> append("ERROR_TYPE")
                else -> append(typeToRender.asStringForDebugging())
            }
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderByPropertyNames(value: Any) {
        val members = value::class.members
            .filter { it.name !in ignoredPropertyNames }
            .filter { it.visibility != KVisibility.PRIVATE && it.visibility != KVisibility.INTERNAL }
            .sortedBy { it.name }
            .filterIsInstance>()
        printCollectionIfNotEmpty(members, separator = "\n") { member ->
            renderProperty(
                member,
                renderSymbolsFully = false,
                value
            )
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderAnnotationApplication(call: KtAnnotationApplication) {
        renderValue(call.classId, renderSymbolsFully = false)
        append('(')
        if (call is KtAnnotationApplicationWithArgumentsInfo) {
            call.arguments.sortedBy { it.name }.forEachIndexed { index, value ->
                if (index > 0) {
                    append(", ")
                }
                renderValue(value, renderSymbolsFully = false)
            }
        } else {
            append("isCallWithArguments=${call.isCallWithArguments}")
        }
        append(')')

        withIndent {
            appendLine().append("psi: ")
            val psi =
                if (call.psi?.containingKtFile?.isCompiled == true) {
                    null
                } else call.psi
            renderValue(psi?.javaClass?.simpleName, renderSymbolsFully = false)
        }
    }

    private fun PrettyPrinter.renderDeprecationInfo(info: DeprecationInfo) {
        append("DeprecationInfo(")
        append("deprecationLevel=${info.deprecationLevel}, ")
        append("propagatesToOverrides=${info.propagatesToOverrides}, ")
        append("message=${info.message}")
        append(")")
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderValue(value: Any?, renderSymbolsFully: Boolean) {
        when (value) {
            // Symbol-related values
            is KtSymbol -> renderSymbolTag(value, renderSymbolsFully)
            is KtType -> renderType(value)
            is KtTypeProjection -> renderTypeProjection(value)
            is KtClassTypeQualifier -> renderTypeQualifier(value)
            is KtAnnotationValue -> renderAnnotationValue(value)
            is KtContractEffectDeclaration -> Context(this@KtAnalysisSession, this@renderValue, this@DebugSymbolRenderer)
                .renderKtContractEffectDeclaration(value, endWithNewLine = false)
            is KtNamedAnnotationValue -> renderNamedConstantValue(value)
            is KtInitializerValue -> renderKtInitializerValue(value)
            is KtContextReceiver -> renderContextReceiver(value)
            is KtAnnotationApplication -> renderAnnotationApplication(value)
            is KtAnnotationsList -> renderAnnotationsList(value)
            is KtModule -> renderKtModule(value)
            // Other custom values
            is Name -> append(value.asString())
            is FqName -> append(value.asString())
            is ClassId -> append(value.asString())
            is DeprecationInfo -> renderDeprecationInfo(value)
            is Visibility -> append(value::class.java.simpleName)
            // Unsigned integers
            is UByte -> append(value.toString())
            is UShort -> append(value.toString())
            is UInt -> append(value.toString())
            is ULong -> append(value.toString())
            // Java values
            is Enum<*> -> append(value.name)
            is List<*> -> renderList(value, renderSymbolsFully = false)
            else -> append(value.toString())
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderTypeProjection(value: KtTypeProjection) {
        when (value) {
            is KtStarTypeProjection -> append("*")
            is KtTypeArgumentWithVariance -> {
                if (value.variance != Variance.INVARIANT) {
                    append("${value.variance.label} ")
                }
                renderType(value.type)
            }
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderTypeQualifier(value: KtClassTypeQualifier) {
        appendLine("qualifier:")
        withIndent {
            renderByPropertyNames(value)
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderContextReceiver(receiver: KtContextReceiver) {
        append("KtContextReceiver:")
        withIndent {
            appendLine()
            append("label: ")
            renderValue(receiver.label, renderSymbolsFully = false)
            appendLine()
            append("type: ")
            renderType(receiver.type)
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderKtModule(ktModule: KtModule) {
        val ktModuleClass = ktModule::class.allSuperclasses.first { it in ktModuleSubclasses }
        append("${ktModuleClass.simpleName} \"${ktModule.moduleDescription}\"")
    }

    private fun KClass<*>.allSealedSubClasses(): List> = buildList {
        add(this@allSealedSubClasses)
        sealedSubclasses.flatMapTo(this) { it.allSealedSubClasses() }
    }

    private val ktModuleSubclasses = KtModule::class.allSealedSubClasses().distinct().sortedWith { a, b ->
        when {
            a == b -> 0
            a.isSubclassOf(b) -> -1
            b.isSubclassOf(a) -> 1
            else -> 0
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderKtInitializerValue(value: KtInitializerValue) {
        when (value) {
            is KtConstantInitializerValue -> {
                append("KtConstantInitializerValue(")
                append(value.constant.renderAsKotlinConstant())
                append(")")
            }

            is KtNonConstantInitializerValue -> {
                append("KtNonConstantInitializerValue(")
                append(value.initializerPsi?.firstLineOfPsi() ?: "NO_PSI")
                append(")")
            }

            is KtConstantValueForAnnotation -> {
                append("KtConstantValueForAnnotation(")
                append(value.annotationValue.renderAsSourceCode())
                append(")")
            }
        }
    }

    context(KtAnalysisSession)
    private fun PrettyPrinter.renderAnnotationsList(value: KtAnnotationsList) {
        renderList(value.annotations, renderSymbolsFully = false)
    }

    private fun getFrontendIndependentKClassOf(instanceOfClass: Any): KClass<*> {
        var current: Class<*> = instanceOfClass.javaClass

        while (true) {
            val className = current.name
            if (symbolImplementationPackageNames.none { className.startsWith("$it.") }) {
                return current.kotlin
            }
            current = current.superclass
        }
    }

    context(KtAnalysisSession)
    private fun PsiElement.firstLineOfPsi(): String {
        val text = text
        val lines = text.lines()
        return if (lines.count() <= 1) text
        else lines.first() + " ..."
    }

    private val ignoredPropertyNames = setOf(
        "psi",
        "token",
        "builder",
        "coneType",
        "analysisContext",
        "fe10Type"
    )

    private val symbolImplementationPackageNames = listOf(
        "org.jetbrains.kotlin.analysis.api.fir",
        "org.jetbrains.kotlin.analysis.api.descriptors",
    )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy