diff --git a/compiler/fir/analysis-tests/testData/resolve/callResolution/debugInfoCall.kt b/compiler/fir/analysis-tests/testData/resolve/callResolution/debugInfoCall.kt new file mode 100644 index 00000000000..e09b103f10c --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/callResolution/debugInfoCall.kt @@ -0,0 +1,17 @@ +interface B { + operator fun invoke(x: Int): String +} +class A { + fun foo(x: Int) {} + + val bar: B = TODO() +} + +fun A.foo(x: String) {} + +fun main() { + val a = A() + a.foo(1) + a.foo("") + a.bar(1) +} diff --git a/compiler/fir/analysis-tests/testData/resolve/callResolution/debugInfoCall.txt b/compiler/fir/analysis-tests/testData/resolve/callResolution/debugInfoCall.txt new file mode 100644 index 00000000000..22199236745 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/callResolution/debugInfoCall.txt @@ -0,0 +1,25 @@ +FILE: debugInfoCall.kt + public abstract interface B : R|kotlin/Any| { + public abstract operator fun invoke(x: R|kotlin/Int|): R|kotlin/String| + + } + public final class A : R|kotlin/Any| { + public constructor(): R|A| { + super() + } + + public final fun foo(x: R|kotlin/Int|): R|kotlin/Unit| { + } + + public final val bar: R|B| = R|kotlin/TODO|() + public get(): R|B| + + } + public final fun R|A|.foo(x: R|kotlin/String|): R|kotlin/Unit| { + } + public final fun main(): R|kotlin/Unit| { + lval a: R|A| = R|/A.A|() + R|/a|.R|/A.foo|(Int(1)) + R|/a|.R|/foo|(String()) + R|/a|.R|/A.bar|.R|/B.invoke|(Int(1)) + } diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java index d65832ea4ce..09fe1e4be65 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java @@ -584,6 +584,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest { runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/companionInvoke.kt"); } + @TestMetadata("debugInfoCall.kt") + public void testDebugInfoCall() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/debugInfoCall.kt"); + } + @TestMetadata("invokeAmbiguity.kt") public void testInvokeAmbiguity() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/invokeAmbiguity.kt"); diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java index 40c3a3c6d4f..9dc356fbc66 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java @@ -584,6 +584,11 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/companionInvoke.kt"); } + @TestMetadata("debugInfoCall.kt") + public void testDebugInfoCall() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/debugInfoCall.kt"); + } + @TestMetadata("invokeAmbiguity.kt") public void testInvokeAmbiguity() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/callResolution/invokeAmbiguity.kt"); diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/CallableId.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/CallableId.kt index dfe3cc2e2dd..44cdddb5d9f 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/CallableId.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/CallableId.kt @@ -28,6 +28,10 @@ data class CallableId(val packageName: FqName, val className: FqName?, val calla @Deprecated("TODO: Better solution for local callables?") constructor(callableName: Name) : this(FqName.topLevel(Name.special("")), null, callableName) + fun asFqName(): FqName { + if (className == null) return packageName.child(callableName) + return classId!!.asSingleFqName().child(callableName) + } override fun toString(): String { return buildString { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirSourceElement.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirSourceElement.kt index 760094f48f0..fbfc966345b 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirSourceElement.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirSourceElement.kt @@ -16,7 +16,7 @@ sealed class FirSourceElement { abstract val endOffset: Int } -class FirPsiSourceElement

(val psi: P) : FirSourceElement() { +class FirPsiSourceElement(val psi: P) : FirSourceElement() { override val elementType: IElementType get() = psi.node.elementType diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt index 6b9979cef4a..75f399adf58 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt @@ -27,7 +27,9 @@ import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.isCommon import org.jetbrains.kotlin.platform.oldFashionedDescription -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtReferenceExpression import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.AnalyzingUtils @@ -709,7 +711,7 @@ object CheckerTestUtil { } } -private enum class TypeOfCall(val nameToRender: String) { +enum class TypeOfCall(val nameToRender: String) { VARIABLE_THROUGH_INVOKE("variable&invoke"), PROPERTY_GETTER("variable"), FUNCTION("function"), diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.kt index 5deb887deb8..5c5d7ed8a31 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.kt @@ -54,8 +54,8 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactoryImpl import org.jetbrains.kotlin.test.Directives import org.jetbrains.kotlin.test.InTextDirectivesUtils.isDirectiveDefined -import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinBaseTest +import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.utils.addIfNotNull import org.junit.Assert import java.io.File @@ -141,7 +141,7 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava = mutableListOf() - private val diagnosedRangesToDiagnosticNames: MutableMap> = mutableMapOf() + val diagnosedRangesToDiagnosticNames: MutableMap> = mutableMapOf() val actualDiagnostics: MutableList = mutableListOf() val expectedText: String val clearText: String diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticTest.kt index 24a7a5493c6..4f5fcad1bb9 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticTest.kt @@ -5,21 +5,39 @@ package org.jetbrains.kotlin.fir +import com.intellij.psi.PsiElement import junit.framework.TestCase +import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1 +import org.jetbrains.kotlin.checkers.utils.TypeOfCall +import org.jetbrains.kotlin.diagnostics.rendering.Renderers import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector import org.jetbrains.kotlin.fir.analysis.collectors.FirDiagnosticsCollector -import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic +import org.jetbrains.kotlin.fir.analysis.diagnostics.* +import org.jetbrains.kotlin.fir.declarations.FirCallableMemberDeclaration import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference +import org.jetbrains.kotlin.fir.references.FirNamedReference +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.dfa.FirControlFlowGraphReferenceImpl import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeKind import org.jetbrains.kotlin.fir.resolve.dfa.cfg.FirControlFlowGraphRenderVisitor import org.jetbrains.kotlin.fir.resolve.transformers.FirTotalResolveTransformer +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol +import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid +import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase +import org.jetbrains.kotlin.util.OperatorNameConventions +import org.jetbrains.kotlin.utils.addIfNotNull import java.io.File /* @@ -84,7 +102,12 @@ abstract class AbstractFirDiagnosticsTest : AbstractFirBaseDiagnosticsTest() { for (testFile in testFiles) { val firFile = firFiles.firstOrNull { it.psi == testFile.ktFile } if (firFile != null) { - testFile.getActualText(diagnostics.getValue(firFile), actualText) + val debugInfoDiagnostics: List> = + collectDebugInfoDiagnostics(firFile, testFile.diagnosedRangesToDiagnosticNames) + testFile.getActualText( + diagnostics.getValue(firFile) + debugInfoDiagnostics, + actualText, + ) } else { actualText.append(testFile.expectedText) } @@ -92,6 +115,106 @@ abstract class AbstractFirDiagnosticsTest : AbstractFirBaseDiagnosticsTest() { KotlinTestUtils.assertEqualsToFile(file, actualText.toString()) } + protected fun collectDebugInfoDiagnostics( + firFile: FirFile, + diagnosedRangesToDiagnosticNames: MutableMap> + ): List> { + val result = mutableListOf>() + + + object : FirDefaultVisitorVoid() { + override fun visitElement(element: FirElement) { + element.acceptChildren(this) + } + + override fun visitFunctionCall(functionCall: FirFunctionCall) { + result.addIfNotNull( + createCallDiagnosticIfExpected(functionCall, functionCall.calleeReference, diagnosedRangesToDiagnosticNames) + ) + + super.visitFunctionCall(functionCall) + } + }.let(firFile::accept) + + return result + } + + fun createCallDiagnosticIfExpected( + element: FirElement, + reference: FirNamedReference, + diagnosedRangesToDiagnosticNames: MutableMap> + ): FirDiagnosticWithParameters1? { + val resolvedSymbol = (reference as? FirResolvedNamedReference)?.resolvedSymbol + val sourceElement = element.source ?: return null + + val factory = DebugInfoDiagnosticFactory1.CALL + if (diagnosedRangesToDiagnosticNames[sourceElement.startOffset..sourceElement.endOffset]?.contains(factory.name) != true) return null + + val fqName = resolvedSymbol?.fqNameUnsafe() + + val argument = Renderers.renderCallInfo(fqName, getTypeOfCall(reference, resolvedSymbol)) + + return when (sourceElement) { + is FirPsiSourceElement<*> -> FirPsiDiagnosticWithParameters1( + sourceElement, + argument, + factory.severity, + FirDiagnosticFactory1( + factory.name, + factory.severity, + factory + ) + ) + is FirLightSourceElement -> FirLightDiagnosticWithParameters1( + sourceElement, + argument, + factory.severity, + FirDiagnosticFactory1( + factory.name, + factory.severity, + factory + ) + ) + } + } + + + private fun AbstractFirBasedSymbol<*>.fqNameUnsafe(): FqNameUnsafe? = when (this) { + is FirClassLikeSymbol<*> -> classId.asSingleFqName().toUnsafe() + is FirCallableSymbol<*> -> callableId.asFqName().toUnsafe() + else -> null + } + + private fun getTypeOfCall( + reference: FirNamedReference, + resolvedSymbol: AbstractFirBasedSymbol<*>? + ): String { + if (resolvedSymbol == null) return TypeOfCall.UNRESOLVED.nameToRender + + if ((resolvedSymbol as? FirFunctionSymbol)?.callableId?.callableName == OperatorNameConventions.INVOKE + && reference.name != OperatorNameConventions.INVOKE + ) { + return TypeOfCall.VARIABLE_THROUGH_INVOKE.nameToRender + } + + return when (val fir = resolvedSymbol.fir) { + is FirProperty -> { + TypeOfCall.PROPERTY_GETTER.nameToRender + } + is FirFunction<*> -> buildString { + if (fir is FirCallableMemberDeclaration<*>) { + if (fir.status.isInline) append("inline ") + if (fir.status.isInfix) append("infix ") + if (fir.status.isOperator) append("operator ") + if (fir.receiverTypeRef != null) append("extension ") + } + append(TypeOfCall.FUNCTION.nameToRender) + } + else -> TypeOfCall.OTHER.nameToRender + } + } + + protected fun collectDiagnostics(firFiles: List): Map>> { val collectors = mutableMapOf() val result = mutableMapOf>>() @@ -165,4 +288,4 @@ abstract class AbstractFirDiagnosticsTest : AbstractFirBaseDiagnosticsTest() { } } -} \ No newline at end of file +} diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticsWithLightTreeTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticsWithLightTreeTest.kt index de3d7dcea23..b06b67b49dc 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticsWithLightTreeTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticsWithLightTreeTest.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir import com.intellij.openapi.util.TextRange import org.jetbrains.kotlin.diagnostics.DiagnosticUtils +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic import org.jetbrains.kotlin.fir.analysis.diagnostics.FirLightDiagnostic import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.psi.KtFile @@ -33,7 +34,11 @@ abstract class AbstractFirDiagnosticsWithLightTreeTest : AbstractFirDiagnosticsT for (testFile in testFiles) { val firFile = firFiles.firstOrNull { it.name == testFile.name } ?: continue val ktFile = testFile.ktFile!! - val diagnostics = fileToDiagnostics[firFile] ?: emptyList() + val basicDiagnostics = fileToDiagnostics[firFile] ?: emptyList() + val debugInfoDiagnostics: List> = + collectDebugInfoDiagnostics(firFile, testFile.diagnosedRangesToDiagnosticNames) + + val diagnostics = basicDiagnostics + debugInfoDiagnostics val actualDiagnostics = diagnostics.groupBy { require(it is FirLightDiagnostic) @@ -42,7 +47,11 @@ abstract class AbstractFirDiagnosticsWithLightTreeTest : AbstractFirDiagnosticsT val existingDiagnostics = testFile.diagnosedRanges.groupBy { it.start - }.mapValues { (_, ranges) -> ranges.flatMap { it.getDiagnostics().map { it.name } }.countEntries() } + }.mapValues { (_, ranges) -> + ranges.flatMap { diagnosedRange -> + diagnosedRange.getDiagnostics().filterNot { it.name.contains("DEBUG_INFO_") }.map { it.name } + }.countEntries() + } for (startOffset in actualDiagnostics.keys + existingDiagnostics.keys) { val expected = existingDiagnostics[startOffset] ?: emptyMap() @@ -102,4 +111,4 @@ abstract class AbstractFirDiagnosticsWithLightTreeTest : AbstractFirDiagnosticsT IsActual } } -} \ No newline at end of file +}