[FIR JS] Support DEBUG_INFO_DYNAMIC

This commit is contained in:
Nikolay Lunyak
2023-01-04 12:20:10 +02:00
committed by Space Team
parent eed2fada1f
commit 95b589fd94
22 changed files with 263 additions and 204 deletions
@@ -17,7 +17,7 @@ interface WithDeprecation {
} }
class A : J(), WithDeprecation { class A : J(), WithDeprecation {
override fun <!OVERRIDE_DEPRECATION("")!>foo<!>() {} override fun <!OVERRIDE_DEPRECATION!>foo<!>() {}
} }
fun main() { fun main() {
@@ -3,8 +3,8 @@
fun withDynamicReceiver(d: dynamic.() -> Unit) {} fun withDynamicReceiver(d: dynamic.() -> Unit) {}
fun test() = withDynamicReceiver { fun test() = withDynamicReceiver {
foo <!DEBUG_INFO_DYNAMIC!>foo<!>
bar = 1 <!DEBUG_INFO_DYNAMIC!>bar<!> = 1
} }
fun test2() = withDynamicReceiver(fun dynamic.() {}) fun test2() = withDynamicReceiver(fun dynamic.() {})
@@ -12,6 +12,6 @@ fun test2() = withDynamicReceiver(fun dynamic.() {})
val dynamicProperty: dynamic.() -> Unit = TODO() val dynamicProperty: dynamic.() -> Unit = TODO()
fun test(d: dynamic, dynamicParameter: dynamic.() -> Unit) { fun test(d: dynamic, dynamicParameter: dynamic.() -> Unit) {
d.dynamicProperty() d.<!DEBUG_INFO_DYNAMIC!>dynamicProperty<!>()
d.dynamicParameter() d.<!DEBUG_INFO_DYNAMIC!>dynamicParameter<!>()
} }
@@ -2,8 +2,8 @@
fun test() { fun test() {
dynamic { dynamic {
foo() <!DEBUG_INFO_DYNAMIC!>foo<!>()
bar.baz(0) <!DEBUG_INFO_DYNAMIC!>bar<!>.<!DEBUG_INFO_DYNAMIC!>baz<!>(0)
} }
} }
@@ -2,43 +2,43 @@
// !MARK_DYNAMIC_CALLS // !MARK_DYNAMIC_CALLS
fun test(d: dynamic) { fun test(d: dynamic) {
+d <!DEBUG_INFO_DYNAMIC!>+<!>d
-d <!DEBUG_INFO_DYNAMIC!>-<!>d
! d <!DEBUG_INFO_DYNAMIC!>!<!> d
d + d d <!DEBUG_INFO_DYNAMIC!>+<!> d
d + 1 d <!DEBUG_INFO_DYNAMIC!>+<!> 1
"" + d "" + d
d - d d <!DEBUG_INFO_DYNAMIC!>-<!> d
d * d d <!DEBUG_INFO_DYNAMIC!>*<!> d
d / d d <!DEBUG_INFO_DYNAMIC!>/<!> d
d % d d <!DEBUG_INFO_DYNAMIC!>%<!> d
d and d d <!DEBUG_INFO_DYNAMIC!>and<!> d
d[1] <!DEBUG_INFO_DYNAMIC!>d[1]<!>
d[1] = 2 <!DEBUG_INFO_DYNAMIC!>d[1]<!> = 2
d[1]++ <!DEBUG_INFO_DYNAMIC, DEBUG_INFO_DYNAMIC!>d[1]<!><!DEBUG_INFO_DYNAMIC!>++<!>
++d[1] <!DEBUG_INFO_DYNAMIC!>++<!><!DEBUG_INFO_DYNAMIC, DEBUG_INFO_DYNAMIC!>d[1]<!>
d[1]-- <!DEBUG_INFO_DYNAMIC, DEBUG_INFO_DYNAMIC!>d[1]<!><!DEBUG_INFO_DYNAMIC!>--<!>
--d[1] <!DEBUG_INFO_DYNAMIC!>--<!><!DEBUG_INFO_DYNAMIC, DEBUG_INFO_DYNAMIC!>d[1]<!>
d() <!DEBUG_INFO_DYNAMIC!>d()<!>
d(1) <!DEBUG_INFO_DYNAMIC!>d(1)<!>
d(name = 1) <!DEBUG_INFO_DYNAMIC!>d(name = 1)<!>
d <!VARARG_OUTSIDE_PARENTHESES!>{}<!> <!DEBUG_INFO_DYNAMIC!>d <!VARARG_OUTSIDE_PARENTHESES!>{}<!><!>
class C { class C {
val plus: dynamic = null val plus: dynamic = null
} }
C() <!PROPERTY_AS_OPERATOR!>+<!> 5 // todo should be marked as DEBUG_INFO_DYNAMIC C() <!PROPERTY_AS_OPERATOR!>+<!> 5 // todo should be marked as DEBUG_INFO_DYNAMIC
C().plus(5) C().<!DEBUG_INFO_DYNAMIC!>plus(5)<!>
d == d d == d
d != d d != d
@@ -46,37 +46,38 @@ fun test(d: dynamic) {
d === d d === d
d !== d d !== d
d < d d <!DEBUG_INFO_DYNAMIC!><<!> d
d <= d d <!DEBUG_INFO_DYNAMIC!><=<!> d
d >= d d <!DEBUG_INFO_DYNAMIC!>>=<!> d
d > d d <!DEBUG_INFO_DYNAMIC!>><!> d
for (i in d) { for (i in d) {
i.foo() i.<!DEBUG_INFO_DYNAMIC!>foo<!>()
} }
var dVar = d var dVar = d
dVar++ dVar<!DEBUG_INFO_DYNAMIC!>++<!>
++dVar <!DEBUG_INFO_DYNAMIC!>++<!>dVar
dVar-- dVar<!DEBUG_INFO_DYNAMIC!>--<!>
--dVar <!DEBUG_INFO_DYNAMIC!>--<!>dVar
dVar += 1 dVar <!DEBUG_INFO_DYNAMIC!>+=<!> 1
dVar -= 1 dVar <!DEBUG_INFO_DYNAMIC!>-=<!> 1
dVar *= 1 dVar <!DEBUG_INFO_DYNAMIC!>*=<!> 1
dVar /= 1 dVar <!DEBUG_INFO_DYNAMIC!>/=<!> 1
dVar %= 1 dVar <!DEBUG_INFO_DYNAMIC!>%=<!> 1
d += 1 d <!DEBUG_INFO_DYNAMIC!>+=<!> 1
d -= 1 d <!DEBUG_INFO_DYNAMIC!>-=<!> 1
d *= 1 d <!DEBUG_INFO_DYNAMIC!>*=<!> 1
d /= 1 d <!DEBUG_INFO_DYNAMIC!>/=<!> 1
d %= 1 d <!DEBUG_INFO_DYNAMIC!>%=<!> 1
d[1] += 1 <!DEBUG_INFO_DYNAMIC!>d[1]<!> <!DEBUG_INFO_DYNAMIC!>+=<!> 1
d[1] -= 1 <!DEBUG_INFO_DYNAMIC!>d[1]<!> <!DEBUG_INFO_DYNAMIC!>-=<!> 1
d[1] *= 1 <!DEBUG_INFO_DYNAMIC!>d[1]<!> <!DEBUG_INFO_DYNAMIC!>*=<!> 1
d[1] /= 1 <!DEBUG_INFO_DYNAMIC!>d[1]<!> <!DEBUG_INFO_DYNAMIC!>/=<!> 1
d[1] %= 1 <!DEBUG_INFO_DYNAMIC!>d[1]<!> <!DEBUG_INFO_DYNAMIC!>%=<!> 1
} }
@@ -2,24 +2,24 @@
// !MARK_DYNAMIC_CALLS // !MARK_DYNAMIC_CALLS
fun test(d: dynamic) { fun test(d: dynamic) {
d.onAnyVal d.<!DEBUG_INFO_DYNAMIC!>onAnyVal<!>
d.onAnyVal = 1 d.<!DEBUG_INFO_DYNAMIC!>onAnyVal<!> = 1
d?.onAnyVal d?.<!DEBUG_INFO_DYNAMIC!>onAnyVal<!>
d?.onAnyVal = 1 d?.<!DEBUG_INFO_DYNAMIC!>onAnyVal<!> = 1
run { run {
d!!.onAnyVal d!!.<!DEBUG_INFO_DYNAMIC!>onAnyVal<!>
} }
run { run {
d!!.<!UNRESOLVED_REFERENCE!>onAnyVal<!> = 1 d!!.<!UNRESOLVED_REFERENCE!>onAnyVal<!> = 1
} }
d.onNullableAnyVal = 1 d.<!DEBUG_INFO_DYNAMIC!>onNullableAnyVal<!> = 1
d.onStringVal = 1 d.<!DEBUG_INFO_DYNAMIC!>onStringVal<!> = 1
d.onDynamicVal = 1 d.<!DEBUG_INFO_DYNAMIC!>onDynamicVal<!> = 1
(d as String).onStringVal (d as String).onStringVal
(d as Any).onAnyVal (d as Any).onAnyVal
@@ -28,7 +28,7 @@ fun test(d: dynamic) {
} }
fun testReassignmentWithSafeCall(d: dynamic) { fun testReassignmentWithSafeCall(d: dynamic) {
d?.onDynamicVal = 1 d?.<!DEBUG_INFO_DYNAMIC!>onDynamicVal<!> = 1
} }
fun testReassignmentWithStaticCalls(d: dynamic) { fun testReassignmentWithStaticCalls(d: dynamic) {
@@ -45,11 +45,11 @@ val dynamic.onDynamicVal: Int get() = 1
class C { class C {
fun test(d: dynamic) { fun test(d: dynamic) {
d.memberVal d.<!DEBUG_INFO_DYNAMIC!>memberVal<!>
d.memberVal = 1 d.<!DEBUG_INFO_DYNAMIC!>memberVal<!> = 1
d.memberExtensionVal d.<!DEBUG_INFO_DYNAMIC!>memberExtensionVal<!>
d.memberExtensionVal = 1 d.<!DEBUG_INFO_DYNAMIC!>memberExtensionVal<!> = 1
} }
val memberVal = 1 val memberVal = 1
@@ -2,25 +2,25 @@
// !DIAGNOSTICS:-USELESS_CAST // !DIAGNOSTICS:-USELESS_CAST
fun test(d: dynamic) { fun test(d: dynamic) {
d.onAnyVar d.<!DEBUG_INFO_DYNAMIC!>onAnyVar<!>
d.onAnyVar = 1 d.<!DEBUG_INFO_DYNAMIC!>onAnyVar<!> = 1
d?.onAnyVar d?.<!DEBUG_INFO_DYNAMIC!>onAnyVar<!>
d?.onAnyVar = 1 d?.<!DEBUG_INFO_DYNAMIC!>onAnyVar<!> = 1
run { run {
d!!.onAnyVar d!!.<!DEBUG_INFO_DYNAMIC!>onAnyVar<!>
} }
run { run {
d!!.<!UNRESOLVED_REFERENCE!>onAnyVar<!> = 1 d!!.<!UNRESOLVED_REFERENCE!>onAnyVar<!> = 1
} }
d.onNullableAnyVar = 1 d.<!DEBUG_INFO_DYNAMIC!>onNullableAnyVar<!> = 1
d.onStringVar = 1 d.<!DEBUG_INFO_DYNAMIC!>onStringVar<!> = 1
d.onDynamicVar = 1 d.<!DEBUG_INFO_DYNAMIC!>onDynamicVar<!> = 1
d?.<!UNRESOLVED_REFERENCE!>onDynamicVar<!> = 1 d?.<!UNRESOLVED_REFERENCE!>onDynamicVar<!> = 1
@@ -53,11 +53,11 @@ var dynamic.onDynamicVar: Int
class C { class C {
fun test(d: dynamic) { fun test(d: dynamic) {
d.memberVar d.<!DEBUG_INFO_DYNAMIC!>memberVar<!>
d.memberExtensionVar d.<!DEBUG_INFO_DYNAMIC!>memberExtensionVar<!>
d.memberVar = 1 d.<!DEBUG_INFO_DYNAMIC!>memberVar<!> = 1
d.memberExtensionVar = 1 d.<!DEBUG_INFO_DYNAMIC!>memberExtensionVar<!> = 1
} }
var memberVar = 1 var memberVar = 1
@@ -2,18 +2,18 @@
// !MARK_DYNAMIC_CALLS // !MARK_DYNAMIC_CALLS
fun test(d: dynamic) { fun test(d: dynamic) {
d.onAny() d.<!DEBUG_INFO_DYNAMIC!>onAny<!>()
d?.onAny() d?.<!DEBUG_INFO_DYNAMIC!>onAny<!>()
run { run {
d!!.onAny() d!!.<!DEBUG_INFO_DYNAMIC!>onAny<!>()
} }
d.onAny(1) d.<!DEBUG_INFO_DYNAMIC!>onAny<!>(1)
d.onNullableAny() d.<!DEBUG_INFO_DYNAMIC!>onNullableAny<!>()
d.onString() d.<!DEBUG_INFO_DYNAMIC!>onString<!>()
d.onDynamic() d.<!DEBUG_INFO_DYNAMIC!>onDynamic<!>()
d?.<!UNRESOLVED_REFERENCE!>onDynamic<!>() d?.<!UNRESOLVED_REFERENCE!>onDynamic<!>()
(d as String).onString() (d as String).onString()
@@ -29,8 +29,8 @@ fun dynamic.onDynamic() {}
class C { class C {
fun test(d: dynamic) { fun test(d: dynamic) {
d.member() d.<!DEBUG_INFO_DYNAMIC!>member<!>()
d.memberExtension() d.<!DEBUG_INFO_DYNAMIC!>memberExtension<!>()
} }
fun member() {} fun member() {}
@@ -1,34 +1,34 @@
// !MARK_DYNAMIC_CALLS // !MARK_DYNAMIC_CALLS
fun test(d: dynamic) { fun test(d: dynamic) {
d.onDynamic() d.<!DEBUG_INFO_DYNAMIC!>onDynamic<!>()
d.onNullableDynamic() d.<!DEBUG_INFO_DYNAMIC!>onNullableDynamic<!>()
d.valOnDynamic d.<!DEBUG_INFO_DYNAMIC!>valOnDynamic<!>
d.valOnDynamic = 1 d.<!DEBUG_INFO_DYNAMIC!>valOnDynamic<!> = 1
d.varOnDynamic d.<!DEBUG_INFO_DYNAMIC!>varOnDynamic<!>
d.varOnDynamic = 1 d.<!DEBUG_INFO_DYNAMIC!>varOnDynamic<!> = 1
} }
fun dynamic.extTest() { fun dynamic.extTest() {
onDynamic() <!DEBUG_INFO_DYNAMIC!>onDynamic<!>()
onNullableDynamic() <!DEBUG_INFO_DYNAMIC!>onNullableDynamic<!>()
valOnDynamic <!DEBUG_INFO_DYNAMIC!>valOnDynamic<!>
valOnDynamic = 1 <!DEBUG_INFO_DYNAMIC!>valOnDynamic<!> = 1
varOnDynamic <!DEBUG_INFO_DYNAMIC!>varOnDynamic<!>
varOnDynamic = 1 <!DEBUG_INFO_DYNAMIC!>varOnDynamic<!> = 1
this.onDynamic() this.<!DEBUG_INFO_DYNAMIC!>onDynamic<!>()
this.onNullableDynamic() this.<!DEBUG_INFO_DYNAMIC!>onNullableDynamic<!>()
this.valOnDynamic this.<!DEBUG_INFO_DYNAMIC!>valOnDynamic<!>
this.valOnDynamic = 1 this.<!DEBUG_INFO_DYNAMIC!>valOnDynamic<!> = 1
this.varOnDynamic this.<!DEBUG_INFO_DYNAMIC!>varOnDynamic<!>
this.varOnDynamic = 1 this.<!DEBUG_INFO_DYNAMIC!>varOnDynamic<!> = 1
} }
@@ -44,11 +44,11 @@ var dynamic.varOnDynamic: Int
class ForMemberExtensions { class ForMemberExtensions {
fun test(d: dynamic) { fun test(d: dynamic) {
d.memberExtensionVar d.<!DEBUG_INFO_DYNAMIC!>memberExtensionVar<!>
d.memberExtensionVar = 1 d.<!DEBUG_INFO_DYNAMIC!>memberExtensionVar<!> = 1
d.memberExtensionVal d.<!DEBUG_INFO_DYNAMIC!>memberExtensionVal<!>
d.memberExtensionVal = 1 d.<!DEBUG_INFO_DYNAMIC!>memberExtensionVal<!> = 1
} }
val dynamic.memberExtensionVal: Int get() = 1 val dynamic.memberExtensionVal: Int get() = 1
@@ -1,26 +0,0 @@
// !MARK_DYNAMIC_CALLS
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun <T> foo(t1: T, t2: T): T = t1
interface Tr
class C: Tr
fun <T: Tr> foo1(t1: T, t2: T): T = t1
fun test(d: dynamic, b: Boolean, n: String?) {
foo(d, "").foo()
foo1(d, C()).foo()
val fromIf = if (b) d else ""
fromIf.doo()
val fromElvis = n ?: d
fromElvis.doo()
}
class In<in T>(t: T)
fun <T> contra(a: In<T>, b: In<T>): T = null!!
fun testContra(d: dynamic) {
contra(In(d), In("")).get(0) // not a dynamic call
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !MARK_DYNAMIC_CALLS // !MARK_DYNAMIC_CALLS
// !DIAGNOSTICS: -UNUSED_PARAMETER // !DIAGNOSTICS: -UNUSED_PARAMETER
@@ -23,4 +24,4 @@ fun <T> contra(a: In<T>, b: In<T>): T = null!!
fun testContra(d: dynamic) { fun testContra(d: dynamic) {
contra(In(d), In("")).get(0) // not a dynamic call contra(In(d), In("")).get(0) // not a dynamic call
} }
@@ -5,7 +5,7 @@ fun dynamic.foo(s: String, a: Any) {}
fun dynamic.foo(s: Any, a: String) {} fun dynamic.foo(s: Any, a: String) {}
fun test(d: dynamic) { fun test(d: dynamic) {
d.foo(1, "") d.<!DEBUG_INFO_DYNAMIC!>foo<!>(1, "")
d.foo("", "") d.<!DEBUG_INFO_DYNAMIC!>foo<!>("", "")
d.foo(1, 1) d.<!DEBUG_INFO_DYNAMIC!>foo<!>(1, 1)
} }
@@ -1,12 +0,0 @@
// !MARK_DYNAMIC_CALLS
fun foo(d: dynamic) {
if (d is Foo) {
d.bar() // resolved statically
d.baz()
}
}
class Foo {
fun bar() {}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !MARK_DYNAMIC_CALLS // !MARK_DYNAMIC_CALLS
fun foo(d: dynamic) { fun foo(d: dynamic) {
@@ -2,26 +2,26 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER // !DIAGNOSTICS: -UNUSED_PARAMETER
fun dynamic.test() { fun dynamic.test() {
foo() <!DEBUG_INFO_DYNAMIC!>foo<!>()
ext() <!DEBUG_INFO_DYNAMIC!>ext<!>()
bar() bar()
this.bar() this.<!DEBUG_INFO_DYNAMIC!>bar<!>()
baz = 2 baz = 2
this.baz = 2 this.<!DEBUG_INFO_DYNAMIC!>baz<!> = 2
"".ext() "".ext()
ext() <!DEBUG_INFO_DYNAMIC!>ext<!>()
"".extValFun() "".extValFun()
extValFun() <!DEBUG_INFO_DYNAMIC!>extValFun<!>()
"".extVal "".extVal
extVal <!DEBUG_INFO_DYNAMIC!>extVal<!>
baz.extExtVal() baz.extExtVal()
extExtVal() <!DEBUG_INFO_DYNAMIC!>extExtVal<!>()
""() ""()
this() this()
@@ -29,7 +29,7 @@ fun dynamic.test() {
C() + C() C() + C()
<!UNRESOLVED_REFERENCE!>+<!>C() <!UNRESOLVED_REFERENCE!>+<!>C()
this + C() this <!DEBUG_INFO_DYNAMIC!>+<!> C()
0.<!UNRESOLVED_REFERENCE!>missing<!>() 0.<!UNRESOLVED_REFERENCE!>missing<!>()
} }
@@ -6,7 +6,7 @@ fun Any?.staticExtension() = 1
val Any?.staticProperty get() = 2 val Any?.staticProperty get() = 2
fun test(d: dynamic, staticParameter: Any?.() -> Unit) { fun test(d: dynamic, staticParameter: Any?.() -> Unit) {
d.staticExtension() d.<!DEBUG_INFO_DYNAMIC!>staticExtension<!>()
d.staticProperty d.<!DEBUG_INFO_DYNAMIC!>staticProperty<!>
d.staticParameter d.<!DEBUG_INFO_DYNAMIC!>staticParameter<!>
} }
@@ -1,8 +0,0 @@
// !MARK_DYNAMIC_CALLS
fun foo(d: dynamic) {
Foo(d).p.bar()
}
class Foo<T>(val p: T)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !MARK_DYNAMIC_CALLS // !MARK_DYNAMIC_CALLS
fun foo(d: dynamic) { fun foo(d: dynamic) {
@@ -8,6 +8,6 @@ fun test() {
fun v(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> d: dynamic) { fun v(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> d: dynamic) {
for (dd in d) { for (dd in d) {
dd.foo() dd.<!DEBUG_INFO_DYNAMIC!>foo<!>()
} }
} }
@@ -1,25 +0,0 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
open class A {
fun get(index: Int): Char = '*'
}
abstract class B : A(), CharSequence
interface I {
fun nextChar(): Char
}
abstract class C : CharIterator(), I {
override fun nextChar(): Char = '*'
}
class CC(val s: CharSequence) : CharSequence by s, MyCharSequence {}
interface MyCharSequence {
val length: Int
operator fun get(index: Int): Char
fun subSequence(startIndex: Int, endIndex: Int): CharSequence
}
@@ -72,8 +72,13 @@ class FirDiagnosticCodeMetaRenderConfiguration(
params.add("severity='${diagnostic.severity}'") params.add("severity='${diagnostic.severity}'")
params.add(getAdditionalParams(codeMetaInfo)) params.add(getAdditionalParams(codeMetaInfo))
val nonEmptyParams = params.filter { it.isNotEmpty() }
return "(\"${params.filter { it.isNotEmpty() }.joinToString("; ")}\")" return if (nonEmptyParams.isNotEmpty()) {
"(\"${params.filter { it.isNotEmpty() }.joinToString("; ")}\")"
} else {
""
}
} }
fun getTag(codeMetaInfo: FirDiagnosticCodeMetaInfo): String { fun getTag(codeMetaInfo: FirDiagnosticCodeMetaInfo): String {
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.test.frontend.fir.handlers
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.* import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory0
import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1 import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1
import org.jetbrains.kotlin.checkers.utils.TypeOfCall import org.jetbrains.kotlin.checkers.utils.TypeOfCall
import org.jetbrains.kotlin.diagnostics.* import org.jetbrains.kotlin.diagnostics.*
@@ -15,21 +16,22 @@ import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.builder.FirSyntaxErrors import org.jetbrains.kotlin.fir.builder.FirSyntaxErrors
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirFunction import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirNamedReference import org.jetbrains.kotlin.fir.references.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.renderForDebugInfo
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid
import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtQualifiedExpression import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives
@@ -91,7 +93,7 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
} }
globalMetadataInfoHandler.addMetadataInfosForFile(file, diagnosticsMetadataInfos) globalMetadataInfoHandler.addMetadataInfosForFile(file, diagnosticsMetadataInfos)
collectSyntaxDiagnostics(file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled) collectSyntaxDiagnostics(file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled)
collectDebugInfoDiagnostics(file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled) collectDebugInfoDiagnostics(module, file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled)
} }
} }
@@ -128,6 +130,7 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
} }
private fun collectDebugInfoDiagnostics( private fun collectDebugInfoDiagnostics(
module: TestModule,
testFile: TestFile, testFile: TestFile,
firFile: FirFile, firFile: FirFile,
lightTreeEnabled: Boolean, lightTreeEnabled: Boolean,
@@ -140,13 +143,16 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
.mapValues { (_, value) -> value.toSet() } .mapValues { (_, value) -> value.toSet() }
val consumer = DebugDiagnosticConsumer(result, diagnosedRangesToDiagnosticNames) val consumer = DebugDiagnosticConsumer(result, diagnosedRangesToDiagnosticNames)
val shouldRenderDynamic = DiagnosticsDirectives.MARK_DYNAMIC_CALLS in module.directives
object : FirDefaultVisitorVoid() { object : FirDefaultVisitorVoid() {
override fun visitElement(element: FirElement) { override fun visitElement(element: FirElement) {
if (element is FirExpression) { if (element is FirExpression) {
consumer.reportExpressionTypeDiagnostic(element) consumer.reportExpressionTypeDiagnostic(element)
} }
if (shouldRenderDynamic && element is FirResolvable) {
reportDynamic(element)
}
if (element is FirSmartCastExpression) { if (element is FirSmartCastExpression) {
element.originalExpression.acceptChildren(this) element.originalExpression.acceptChildren(this)
} else { } else {
@@ -154,6 +160,39 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
} }
} }
private fun reportDynamic(element: FirResolvable) {
val calleeDeclaration = element.calleeReference.toResolvedCallableSymbol() ?: return
val isInvokeCallWithDynamicReceiver = calleeDeclaration.name == OperatorNameConventions.INVOKE
&& element is FirQualifiedAccess
&& element.dispatchReceiver.typeRef.isFunctionTypeWithDynamicReceiver(firFile.moduleData.session)
if (calleeDeclaration.origin !is FirDeclarationOrigin.DynamicScope && !isInvokeCallWithDynamicReceiver) {
return
}
val source = element.calleeReference.source
// Unfortunately I had to repeat positioning strategy logic here
// (we need to check diagnostic range before applying it)
val target = when (calleeDeclaration.name) {
OperatorNameConventions.INVOKE -> when {
isInvokeCallWithDynamicReceiver -> source
else -> source?.parentAsSourceElement ?: source
}
in OperatorNameConventions.ALL_BINARY_OPERATION_NAMES,
in OperatorNameConventions.UNARY_OPERATION_NAMES,
in OperatorNameConventions.ASSIGNMENT_OPERATIONS,
OperatorNameConventions.GET, OperatorNameConventions.SET -> {
source?.operatorSignIfBinary ?: source
}
else -> {
source
}
}
consumer.reportDynamicDiagnostic(target)
}
override fun visitFunctionCall(functionCall: FirFunctionCall) { override fun visitFunctionCall(functionCall: FirFunctionCall) {
val reference = functionCall.calleeReference val reference = functionCall.calleeReference
consumer.reportCallDiagnostic(functionCall, reference) consumer.reportCallDiagnostic(functionCall, reference)
@@ -238,6 +277,10 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
} }
} }
private fun DebugDiagnosticConsumer.reportDynamicDiagnostic(sourceElement: KtSourceElement?) {
report(DebugInfoDiagnosticFactory0.DYNAMIC, sourceElement)
}
private fun getTypeOfCall( private fun getTypeOfCall(
reference: FirNamedReference, reference: FirNamedReference,
resolvedSymbol: FirBasedSymbol<*>? resolvedSymbol: FirBasedSymbol<*>?
@@ -274,6 +317,37 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {} override fun processAfterAllModules(someAssertionWasFailed: Boolean) {}
} }
private fun FirTypeRef.isFunctionTypeWithDynamicReceiver(session: FirSession) =
coneTypeSafe<ConeKotlinType>()?.isFunctionTypeWithDynamicReceiver(session) == true
private fun ConeKotlinType.isFunctionTypeWithDynamicReceiver(session: FirSession): Boolean {
val hasExplicitDynamicReceiver = receiverType(session) is ConeDynamicType
val hasImplicitDynamicReceiver = isExtensionFunctionType && this.typeArguments.firstOrNull()?.type is ConeDynamicType
return hasExplicitDynamicReceiver || hasImplicitDynamicReceiver
}
private val KtSourceElement.parentAsSourceElement: KtSourceElement?
get() = when (elementType) {
KtNodeTypes.REFERENCE_EXPRESSION -> when (this) {
is KtPsiSourceElement -> psi.parent.toKtPsiSourceElement(kind)
is KtLightSourceElement -> treeStructure.getParent(lighterASTNode)?.toKtLightSourceElement(treeStructure, kind)
else -> null
}
else -> null
}
private val KtSourceElement.operatorSignIfBinary: KtSourceElement?
get() = when (elementType) {
KtNodeTypes.BINARY_EXPRESSION -> when (this) {
is KtPsiSourceElement -> (psi as? KtBinaryExpression)?.operationReference?.toKtPsiSourceElement(kind)
is KtLightSourceElement -> treeStructure.getParent(lighterASTNode)
?.let { treeStructure.findChildByType(it, KtNodeTypes.OPERATION_REFERENCE) }
?.toKtLightSourceElement(treeStructure, kind)
else -> null
}
else -> null
}
private class DebugDiagnosticConsumer( private class DebugDiagnosticConsumer(
private val result: MutableList<KtDiagnostic>, private val result: MutableList<KtDiagnostic>,
private val diagnosedRangesToDiagnosticNames: Map<IntRange, Set<String>> private val diagnosedRangesToDiagnosticNames: Map<IntRange, Set<String>>
@@ -284,10 +358,51 @@ private class DebugDiagnosticConsumer(
KtFakeSourceElementKind.DesugaredCompoundAssignment, KtFakeSourceElementKind.DesugaredCompoundAssignment,
KtFakeSourceElementKind.ReferenceInAtomicQualifiedAccess, KtFakeSourceElementKind.ReferenceInAtomicQualifiedAccess,
KtFakeSourceElementKind.SmartCastExpression, KtFakeSourceElementKind.SmartCastExpression,
KtFakeSourceElementKind.DelegatingConstructorCall KtFakeSourceElementKind.DelegatingConstructorCall,
KtFakeSourceElementKind.ArrayAccessNameReference,
KtFakeSourceElementKind.ArrayIndexExpressionReference,
KtFakeSourceElementKind.DesugaredPrefixNameReference,
KtFakeSourceElementKind.DesugaredPostfixNameReference,
) )
} }
fun report(debugFactory: DebugInfoDiagnosticFactory0, sourceElement: KtSourceElement?) {
if (sourceElement == null || sourceElement.kind !in allowedKindsForDebugInfo) return
// Lambda argument is always (?) duplicated by function literal
// Block expression is always (?) duplicated by single block expression
if (sourceElement.elementType == KtNodeTypes.LAMBDA_ARGUMENT || sourceElement.elementType == KtNodeTypes.BLOCK) return
val availableDiagnostics = diagnosedRangesToDiagnosticNames[sourceElement.startOffset..sourceElement.endOffset]
if (availableDiagnostics == null || debugFactory.name !in availableDiagnostics) {
return
}
val factory = KtDiagnosticFactory0(
name = debugFactory.name,
severity = debugFactory.severity,
defaultPositioningStrategy = AbstractSourceElementPositioningStrategy.DEFAULT,
psiType = PsiElement::class
)
val diagnostic = when (sourceElement) {
is KtPsiSourceElement -> KtPsiSimpleDiagnostic(
sourceElement,
debugFactory.severity,
factory,
factory.defaultPositioningStrategy
)
is KtLightSourceElement -> KtLightSimpleDiagnostic(
sourceElement,
debugFactory.severity,
factory,
factory.defaultPositioningStrategy
)
}
result.add(diagnostic)
}
fun report(debugFactory: DebugInfoDiagnosticFactory1, element: FirElement, argumentFactory: () -> String) { fun report(debugFactory: DebugInfoDiagnosticFactory1, element: FirElement, argumentFactory: () -> String) {
val sourceElement = element.source?.takeIf { it.kind in allowedKindsForDebugInfo } ?: return val sourceElement = element.source?.takeIf { it.kind in allowedKindsForDebugInfo } ?: return
@@ -81,6 +81,12 @@ object OperatorNameConventions {
@JvmField @JvmField
val BINARY_OPERATION_NAMES = setOf(TIMES, PLUS, MINUS, DIV, MOD, REM, RANGE_TO, RANGE_UNTIL) val BINARY_OPERATION_NAMES = setOf(TIMES, PLUS, MINUS, DIV, MOD, REM, RANGE_TO, RANGE_UNTIL)
@JvmField
val BITWISE_OPERATION_NAMES = setOf(AND, OR, XOR, INV, SHL, SHR, USHR)
@JvmField
val ALL_BINARY_OPERATION_NAMES = BINARY_OPERATION_NAMES + BITWISE_OPERATION_NAMES + setOf(EQUALS, CONTAINS, COMPARE_TO)
@JvmField @JvmField
val ASSIGNMENT_OPERATIONS = setOf(TIMES_ASSIGN, DIV_ASSIGN, MOD_ASSIGN, REM_ASSIGN, PLUS_ASSIGN, MINUS_ASSIGN) val ASSIGNMENT_OPERATIONS = setOf(TIMES_ASSIGN, DIV_ASSIGN, MOD_ASSIGN, REM_ASSIGN, PLUS_ASSIGN, MINUS_ASSIGN)