From 61d8b91c8962ce8411acf5d535d4ecf5cac58760 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Fri, 16 Dec 2022 13:55:05 +0100 Subject: [PATCH] [Analysis API] add tests for checking KtType internals --- ...AbstractTypeByDeclarationReturnTypeTest.kt | 22 +++++ .../base/test/cases/types/AbstractTypeTest.kt | 28 ++++++ .../api/symbols/DebugSymbolRenderer.kt | 89 ++++++++++++++++--- 3 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/types/AbstractTypeByDeclarationReturnTypeTest.kt create mode 100644 analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/types/AbstractTypeTest.kt diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/types/AbstractTypeByDeclarationReturnTypeTest.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/types/AbstractTypeByDeclarationReturnTypeTest.kt new file mode 100644 index 00000000000..f4ff13f5cc3 --- /dev/null +++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/types/AbstractTypeByDeclarationReturnTypeTest.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2022 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.impl.base.test.cases.types + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.analysis.test.framework.services.expressionMarkerProvider +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices + +abstract class AbstractTypeByDeclarationReturnTypeTest : AbstractTypeTest() { + context(KtAnalysisSession) + override fun getType(ktFile: KtFile, module: TestModule, testServices: TestServices): KtType { + val declarationAtCaret = testServices.expressionMarkerProvider.getElementOfTypeAtCaret(ktFile) + return declarationAtCaret.getReturnKtType() + } +} \ No newline at end of file diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/types/AbstractTypeTest.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/types/AbstractTypeTest.kt new file mode 100644 index 00000000000..f03fbc89ad7 --- /dev/null +++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/types/AbstractTypeTest.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2022 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.impl.base.test.cases.types + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.DebugSymbolRenderer +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.analysis.test.framework.base.AbstractAnalysisApiSingleFileTest +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.assertions + +abstract class AbstractTypeTest : AbstractAnalysisApiSingleFileTest() { + override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) { + val actual = analyseForTest(ktFile.declarations.first()) { + val type = getType(ktFile, module, testServices) + DebugSymbolRenderer(renderTypeByProperties = true).renderType(type) + } + testServices.assertions.assertEqualsToTestDataFileSibling(actual) + } + + context(KtAnalysisSession) + protected abstract fun getType(ktFile: KtFile, module: TestModule, testServices: TestServices): KtType +} \ No newline at end of file diff --git a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/symbols/DebugSymbolRenderer.kt b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/symbols/DebugSymbolRenderer.kt index 370dfcc1569..03f3691af5b 100644 --- a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/symbols/DebugSymbolRenderer.kt +++ b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/symbols/DebugSymbolRenderer.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.analysis.api.contracts.description.renderKtContractE 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.KtType import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.analysis.utils.printer.PrettyPrinter @@ -28,14 +29,18 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames 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, ) { context(KtAnalysisSession) @@ -64,13 +69,17 @@ public class DebugSymbolRenderer( .declaredMemberExtensionFunctions .filter { it.name == "getContainingModule" } .forEach { + appendLine() renderFunction(it, renderSymbolsFully = false, this@KtAnalysisSession, symbol) } KtSymbolInfoProviderMixIn::class.declaredMemberExtensionProperties .asSequence() .filter { (it.extensionReceiverParameter?.type?.classifier as? KClass<*>)?.isInstance(symbol) == true } - .forEach { renderProperty(it, renderSymbolsFully = false, this@KtAnalysisSession, symbol) } + .forEach { + appendLine() + renderProperty(it, renderSymbolsFully = false, this@KtAnalysisSession, symbol) + } } } @@ -80,29 +89,33 @@ public class DebugSymbolRenderer( renderSymbolHeader(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) { - appendLine().append(function.name).append(": ") + append(function.name).append(": ") renderFunctionCall(function, renderSymbolsFully, args) } context(KtAnalysisSession) private fun PrettyPrinter.renderProperty(property: KProperty<*>, renderSymbolsFully: Boolean, vararg args: Any) { - appendLine().append(property.name).append(": ") + 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()) @@ -114,18 +127,18 @@ public class DebugSymbolRenderer( renderSymbolHeader(symbol) val apiClass = getSymbolApiClass(symbol) withIndent { - apiClass.members - .asSequence() + val members = apiClass.members .filterIsInstance>() .filter { it.name !in ignoredPropertyNames } .sortedBy { it.name } - .forEach { member -> - renderProperty( - member, - renderSymbolsFully = member.name == KtValueParameterSymbol::generatedPrimaryConstructorProperty.name, - symbol - ) - } + appendLine() + printCollectionIfNotEmpty(members, separator = "\n") { member -> + renderProperty( + member, + renderSymbolsFully = member.name == KtValueParameterSymbol::generatedPrimaryConstructorProperty.name, + symbol + ) + } } } @@ -187,6 +200,10 @@ public class DebugSymbolRenderer( context(KtAnalysisSession) private fun PrettyPrinter.renderType(type: KtType) { + if (renderTypeByProperties) { + renderByPropertyNames(type) + return + } if (type.annotations.isNotEmpty()) { renderList(type.annotations, renderSymbolsFully = false) append(' ') @@ -197,6 +214,22 @@ public class DebugSymbolRenderer( } } + 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) @@ -229,6 +262,8 @@ public class DebugSymbolRenderer( // 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) @@ -256,6 +291,27 @@ public class DebugSymbolRenderer( } } + 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.rendeContextReceiver(receiver: KtContextReceiver) { append("ContextReceiver(") @@ -335,7 +391,14 @@ public class DebugSymbolRenderer( else lines.first() + " ..." } - private val ignoredPropertyNames = setOf("psi", "token") + private val ignoredPropertyNames = setOf( + "psi", + "token", + "builder", + "coneType", + "analysisContext", + "fe10Type" + ) private val symbolImplementationPackageNames = listOf( "org.jetbrains.kotlin.analysis.api.fir",