FIR: Support DEBUG_INFO_CALL calls in tests

This commit is contained in:
Denis Zharkov
2020-04-08 17:03:06 +03:00
parent 22dc837e26
commit b62400124a
10 changed files with 201 additions and 11 deletions
@@ -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.<!DEBUG_INFO_CALL("fqName: A.foo; typeCall: function")!>foo(1)<!>
a.<!DEBUG_INFO_CALL("fqName: foo; typeCall: extension function")!>foo("")<!>
a.<!DEBUG_INFO_CALL("fqName: B.invoke; typeCall: variable&invoke")!>bar(1)<!>
}
@@ -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<R|kotlin/Any|>()
}
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|<local>/a|.R|/A.foo|(Int(1))
R|<local>/a|.R|/foo|(String())
R|<local>/a|.R|/A.bar|.R|/B.invoke|(Int(1))
}
@@ -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");
@@ -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");
@@ -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("<local>")), null, callableName)
fun asFqName(): FqName {
if (className == null) return packageName.child(callableName)
return classId!!.asSingleFqName().child(callableName)
}
override fun toString(): String {
return buildString {
@@ -16,7 +16,7 @@ sealed class FirSourceElement {
abstract val endOffset: Int
}
class FirPsiSourceElement<P : PsiElement>(val psi: P) : FirSourceElement() {
class FirPsiSourceElement<out P : PsiElement>(val psi: P) : FirSourceElement() {
override val elementType: IElementType
get() = psi.node.elementType
@@ -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"),
@@ -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<TestModule, Tes
directives: Directives
) : KotlinBaseTest.TestFile(fileName, textWithMarkers, directives) {
val diagnosedRanges: MutableList<DiagnosedRange> = mutableListOf()
private val diagnosedRangesToDiagnosticNames: MutableMap<IntRange, MutableSet<String>> = mutableMapOf()
val diagnosedRangesToDiagnosticNames: MutableMap<IntRange, MutableSet<String>> = mutableMapOf()
val actualDiagnostics: MutableList<ActualDiagnostic> = mutableListOf()
val expectedText: String
val clearText: String
@@ -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<FirDiagnostic<*>> =
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<IntRange, MutableSet<String>>
): List<FirDiagnostic<*>> {
val result = mutableListOf<FirDiagnostic<*>>()
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<IntRange, MutableSet<String>>
): FirDiagnosticWithParameters1<FirSourceElement, String>? {
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<FirSourceElement, PsiElement, String>(
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<FirFile>): Map<FirFile, List<FirDiagnostic<*>>> {
val collectors = mutableMapOf<FirSession, AbstractDiagnosticCollector>()
val result = mutableMapOf<FirFile, List<FirDiagnostic<*>>>()
@@ -165,4 +288,4 @@ abstract class AbstractFirDiagnosticsTest : AbstractFirBaseDiagnosticsTest() {
}
}
}
}
@@ -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<FirDiagnostic<*>> =
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
}
}
}
}