[FIR JS] Support 2 JS diagnostics

The change in `FirDiagnosticsHandler` ensures
`DEBUG_INFO_DYNAMIC` is still reported in
`FirPsiJsOldFrontendDiagnosticsTestGenerated.testConventions`.

Support `SPREAD_OPERATOR_IN_DYNAMIC_CALL` and
`WRONG_OPERATION_WITH_DYNAMIC`
This commit is contained in:
Nikolay Lunyak
2023-04-03 15:12:18 +03:00
committed by Space Team
parent 57568770b4
commit 03e2152957
15 changed files with 181 additions and 23 deletions
@@ -5063,6 +5063,19 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token, token,
) )
} }
add(FirJsErrors.SPREAD_OPERATOR_IN_DYNAMIC_CALL) { firDiagnostic ->
SpreadOperatorInDynamicCallImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.WRONG_OPERATION_WITH_DYNAMIC) { firDiagnostic ->
WrongOperationWithDynamicImpl(
firDiagnostic.a,
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirSyntaxErrors.SYNTAX) { firDiagnostic -> add(FirSyntaxErrors.SYNTAX) { firDiagnostic ->
SyntaxImpl( SyntaxImpl(
firDiagnostic as KtPsiDiagnostic, firDiagnostic as KtPsiDiagnostic,
@@ -3523,6 +3523,15 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = DelegationByDynamic::class override val diagnosticClass get() = DelegationByDynamic::class
} }
abstract class SpreadOperatorInDynamicCall : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = SpreadOperatorInDynamicCall::class
}
abstract class WrongOperationWithDynamic : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = WrongOperationWithDynamic::class
abstract val operation: String
}
abstract class Syntax : KtFirDiagnostic<PsiElement>() { abstract class Syntax : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = Syntax::class override val diagnosticClass get() = Syntax::class
} }
@@ -4262,6 +4262,17 @@ internal class DelegationByDynamicImpl(
override val token: KtLifetimeToken, override val token: KtLifetimeToken,
) : KtFirDiagnostic.DelegationByDynamic(), KtAbstractFirDiagnostic<KtElement> ) : KtFirDiagnostic.DelegationByDynamic(), KtAbstractFirDiagnostic<KtElement>
internal class SpreadOperatorInDynamicCallImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.SpreadOperatorInDynamicCall(), KtAbstractFirDiagnostic<KtElement>
internal class WrongOperationWithDynamicImpl(
override val operation: String,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.WrongOperationWithDynamic(), KtAbstractFirDiagnostic<KtElement>
internal class SyntaxImpl( internal class SyntaxImpl(
override val firDiagnostic: KtPsiDiagnostic, override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken, override val token: KtLifetimeToken,
@@ -117,5 +117,9 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
val DYNAMICS by object : DiagnosticGroup("Dynamics") { val DYNAMICS by object : DiagnosticGroup("Dynamics") {
val DELEGATION_BY_DYNAMIC by error<KtElement>() val DELEGATION_BY_DYNAMIC by error<KtElement>()
val SPREAD_OPERATOR_IN_DYNAMIC_CALL by error<KtElement>(PositioningStrategy.SPREAD_OPERATOR)
val WRONG_OPERATION_WITH_DYNAMIC by error<KtElement> {
parameter<String>("operation")
}
} }
} }
@@ -89,6 +89,8 @@ object FirJsErrors {
// Dynamics // Dynamics
val DELEGATION_BY_DYNAMIC by error0<KtElement>() val DELEGATION_BY_DYNAMIC by error0<KtElement>()
val SPREAD_OPERATOR_IN_DYNAMIC_CALL by error0<KtElement>(SourceElementPositioningStrategies.SPREAD_OPERATOR)
val WRONG_OPERATION_WITH_DYNAMIC by error1<KtElement, String>()
init { init {
RootDiagnosticRendererFactory.registerFactory(FirJsErrorsDefaultMessages) RootDiagnosticRendererFactory.registerFactory(FirJsErrorsDefaultMessages)
@@ -53,6 +53,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_E
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.RUNTIME_ANNOTATION_NOT_SUPPORTED import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.RUNTIME_ANNOTATION_NOT_SUPPORTED
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.SPREAD_OPERATOR_IN_DYNAMIC_CALL
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.UNCHECKED_CAST_TO_EXTERNAL_INTERFACE import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.UNCHECKED_CAST_TO_EXTERNAL_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_BODY_OF_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_BODY_OF_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_DEFAULT_VALUE_FOR_EXTERNAL_FUN_PARAMETER import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_DEFAULT_VALUE_FOR_EXTERNAL_FUN_PARAMETER
@@ -61,6 +62,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_EXTERN
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_INITIALIZER_OF_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_INITIALIZER_OF_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_JS_QUALIFIER import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_JS_QUALIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_MULTIPLE_INHERITANCE import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_MULTIPLE_INHERITANCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_OPERATION_WITH_DYNAMIC
@Suppress("unused") @Suppress("unused")
object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() { object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
@@ -78,6 +80,8 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
FirDiagnosticRenderers.SYMBOL FirDiagnosticRenderers.SYMBOL
) )
map.put(DELEGATION_BY_DYNAMIC, "Can't delegate to dynamic value") map.put(DELEGATION_BY_DYNAMIC, "Can't delegate to dynamic value")
map.put(SPREAD_OPERATOR_IN_DYNAMIC_CALL, "Can't apply spread operator in dynamic call")
map.put(WRONG_OPERATION_WITH_DYNAMIC, "Wrong operation with dynamic value: {0}", CommonRenderers.STRING)
map.put(IMPLEMENTING_FUNCTION_INTERFACE, "Implementing function interface is prohibited in JavaScript") map.put(IMPLEMENTING_FUNCTION_INTERFACE, "Implementing function interface is prohibited in JavaScript")
map.put(OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS, "Overriding `external` function with optional parameters") map.put(OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS, "Overriding `external` function with optional parameters")
map.put( map.put(
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.analysis.js.checkers package org.jetbrains.kotlin.fir.analysis.js.checkers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.* import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
import org.jetbrains.kotlin.fir.analysis.js.checkers.declaration.*
import org.jetbrains.kotlin.fir.analysis.js.checkers.expression.* import org.jetbrains.kotlin.fir.analysis.js.checkers.expression.*
object JsExpressionCheckers : ExpressionCheckers() { object JsExpressionCheckers : ExpressionCheckers() {
@@ -19,4 +20,9 @@ object JsExpressionCheckers : ExpressionCheckers() {
FirJsDefinedExternallyCallChecker, FirJsDefinedExternallyCallChecker,
FirJsNativeRttiChecker, FirJsNativeRttiChecker,
) )
override val functionCallCheckers: Set<FirFunctionCallChecker>
get() = setOf(
FirJsDynamicCallChecker,
)
} }
@@ -0,0 +1,107 @@
/*
* 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.fir.analysis.js.checkers.declaration
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirFunctionCallChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirNamedReference
import org.jetbrains.kotlin.fir.references.resolved
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.types.ConeDynamicType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.util.OperatorNameConventions
private val nameToOperator = mapOf(
OperatorNameConventions.CONTAINS to "in",
OperatorNameConventions.RANGE_TO to "..",
OperatorNameConventions.RANGE_UNTIL to "..<",
)
object FirJsDynamicCallChecker : FirFunctionCallChecker() {
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
val callee = expression.calleeReference.resolved ?: return
if (callee.resolvedSymbol.origin !is FirDeclarationOrigin.DynamicScope) {
return checkSpreadOperator(expression, context, reporter)
}
val symbol = callee.toResolvedCallableSymbol()
?: error("Resolved call callee without a callable symbol")
when {
expression.isArrayAccessWithMultipleIndices -> reporter.reportOn(
expression.source, FirJsErrors.WRONG_OPERATION_WITH_DYNAMIC, "indexed access with more than one index", context
)
expression.isInOperator -> reporter.reportOn(
expression.source, FirJsErrors.WRONG_OPERATION_WITH_DYNAMIC, "`in` operation", context
)
expression.isRangeOperator -> reporter.reportOn(
expression.source, FirJsErrors.WRONG_OPERATION_WITH_DYNAMIC, "`${nameToOperator[symbol.name]}` operation", context
)
expression is FirComponentCall -> reporter.reportOn(
expression.source, FirJsErrors.WRONG_OPERATION_WITH_DYNAMIC, "`destructuring declaration", context
)
}
forAllSpreadArgumentsOf(expression) {
reporter.reportOn(it.source, FirJsErrors.SPREAD_OPERATOR_IN_DYNAMIC_CALL, context)
}
}
private val FirCall.isArrayAccessWithMultipleIndices: Boolean
get() {
val callee = calleeReference as? FirNamedReference
?: return false
if (callee.source?.kind != KtFakeSourceElementKind.ArrayAccessNameReference) {
return false
}
val arguments = (arguments.singleOrNull() as? FirVarargArgumentsExpression)?.arguments
?: return false
return callee.name == OperatorNameConventions.GET && arguments.size >= 2
|| callee.name == OperatorNameConventions.SET && arguments.size >= 3
}
private val FirFunctionCall.isInOperator
get() = calleeReference.resolved?.name == OperatorNameConventions.CONTAINS && origin == FirFunctionCallOrigin.Operator
private val FirFunctionCall.isRangeOperator
get(): Boolean {
val name = calleeReference.resolved?.name
return (name == OperatorNameConventions.RANGE_TO || name == OperatorNameConventions.RANGE_UNTIL)
&& origin == FirFunctionCallOrigin.Operator
}
private fun checkSpreadOperator(expression: FirCall, context: CheckerContext, reporter: DiagnosticReporter) {
forAllSpreadArgumentsOf(expression) {
if (it.typeRef.coneType is ConeDynamicType) {
reporter.reportOn(it.source, FirJsErrors.WRONG_OPERATION_WITH_DYNAMIC, "spread operator", context)
}
}
}
private inline fun forAllSpreadArgumentsOf(call: FirCall, callback: (FirExpression) -> Unit) {
for (argument in call.argumentList.arguments) {
if (argument !is FirVarargArgumentsExpression) {
continue
}
for (it in argument.arguments) {
if (it is FirSpreadArgumentExpression) {
callback(it)
}
}
}
}
}
@@ -2399,11 +2399,13 @@ open class RawFirBuilder(
return leftArgument.generateLazyLogicalOperation(rightArgument, operationToken == ANDAND, source) return leftArgument.generateLazyLogicalOperation(rightArgument, operationToken == ANDAND, source)
in OperatorConventions.IN_OPERATIONS -> in OperatorConventions.IN_OPERATIONS ->
return rightArgument.generateContainsOperation( return rightArgument.generateContainsOperation(
leftArgument, operationToken == NOT_IN, source, expression.operationReference.toFirSourceElement(), leftArgument, operationToken == NOT_IN, source,
expression.operationReference.toFirSourceElement(),
) )
in OperatorConventions.COMPARISON_OPERATIONS -> in OperatorConventions.COMPARISON_OPERATIONS ->
return leftArgument.generateComparisonExpression( return leftArgument.generateComparisonExpression(
rightArgument, operationToken, source, expression.operationReference.toFirSourceElement(), rightArgument, operationToken, source,
expression.operationReference.toFirSourceElement(),
) )
} }
val conventionCallName = operationToken.toBinaryName() val conventionCallName = operationToken.toBinaryName()
@@ -1,17 +1,17 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER // !DIAGNOSTICS: -UNUSED_PARAMETER
fun foo() { fun foo() {
for ((x, y) in A()) { for ((<!WRONG_OPERATION_WITH_DYNAMIC!>x<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>y<!>) in A()) {
println(x + y) println(x + y)
} }
bar { (x, y) -> bar { (<!WRONG_OPERATION_WITH_DYNAMIC!>x<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>y<!>) ->
println(x + y) println(x + y)
} }
val x: dynamic = Any() val x: dynamic = Any()
val (y, z) = x val (<!WRONG_OPERATION_WITH_DYNAMIC!>y<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>z<!>) = x
println(y + z) println(y + z)
} }
@@ -1,12 +1,12 @@
fun foo() { fun foo() {
val a: dynamic = Any() val a: dynamic = Any()
println(a in setOf(1, 2)) println(a in setOf(1, 2))
println(1 in a) println(<!WRONG_OPERATION_WITH_DYNAMIC!>1 in a<!>)
println(1 !in a) println(<!WRONG_OPERATION_WITH_DYNAMIC!>1 !in a<!>)
when (2) { when (2) {
in a -> println("ok") <!WRONG_OPERATION_WITH_DYNAMIC!>in a<!> -> println("ok")
} }
when (3) { when (3) {
!in a -> println("ok") <!WRONG_OPERATION_WITH_DYNAMIC!>!in a<!> -> println("ok")
} }
} }
@@ -1,8 +1,8 @@
fun foo() { fun foo() {
val a: dynamic = Any() val a: dynamic = Any()
println(a[0]) println(a[0])
println(a[0, 1]) println(<!WRONG_OPERATION_WITH_DYNAMIC!>a[0, 1]<!>)
a[0] = 23 a[0] = 23
a[0, 1] = 42 <!WRONG_OPERATION_WITH_DYNAMIC!>a[0, 1] = 42<!>
} }
@@ -4,7 +4,7 @@ fun foo() {
val a: dynamic = Any() val a: dynamic = Any()
val b: dynamic = Any() val b: dynamic = Any()
val c = C() val c = C()
println(a..b) println(<!WRONG_OPERATION_WITH_DYNAMIC!>a..b<!>)
println(c..a) println(c..a)
println(a.rangeTo(b)) println(a.rangeTo(b))
} }
@@ -5,7 +5,7 @@ fun foo() {
val a: dynamic = Any() val a: dynamic = Any()
val b: dynamic = Any() val b: dynamic = Any()
val c = C() val c = C()
println(a..<b) println(<!WRONG_OPERATION_WITH_DYNAMIC!>a..<b<!>)
println(c..<a) println(c..<a)
println(a.rangeUntil(b)) println(a.rangeUntil(b))
} }
@@ -1,22 +1,22 @@
fun test(d: dynamic) { fun test(d: dynamic) {
val a = arrayOf(1, 2, 3) val a = arrayOf(1, 2, 3)
d.foo(*d) d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>d)
d.foo(*a) d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(1, "2", *a) d.foo(1, "2", <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(1, *a) <!VARARG_OUTSIDE_PARENTHESES!>{ }<!> d.foo(1, *a) <!VARARG_OUTSIDE_PARENTHESES!>{ }<!>
d.foo(*a) <!VARARG_OUTSIDE_PARENTHESES!>{ "" }<!> d.foo(*a) <!VARARG_OUTSIDE_PARENTHESES!>{ "" }<!>
d.foo(*a, *a) d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(*a, *a) <!VARARG_OUTSIDE_PARENTHESES!>{ "" }<!> d.foo(*a, *a) <!VARARG_OUTSIDE_PARENTHESES!>{ "" }<!>
d.foo(*a, 1, { "" }, *a) d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, 1, { "" }, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(*a, 1) d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, 1)
d.foo(*a, *a, { "" }) d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, { "" })
bar(d) bar(d)
bar(d, d) bar(d, d)
bar(*d) bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
bar(*d, *d) bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
bar(*d, 23, *d) bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>, 23, <!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
} }
fun bar(vararg x: Int): Unit = TODO("$x") fun bar(vararg x: Int): Unit = TODO("$x")