[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,
)
}
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 ->
SyntaxImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -3523,6 +3523,15 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
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>() {
override val diagnosticClass get() = Syntax::class
}
@@ -4262,6 +4262,17 @@ internal class DelegationByDynamicImpl(
override val token: KtLifetimeToken,
) : 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(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
@@ -117,5 +117,9 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
val DYNAMICS by object : DiagnosticGroup("Dynamics") {
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
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 {
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.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.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.WRONG_BODY_OF_EXTERNAL_DECLARATION
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_JS_QUALIFIER
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")
object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
@@ -78,6 +80,8 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
FirDiagnosticRenderers.SYMBOL
)
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(OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS, "Overriding `external` function with optional parameters")
map.put(
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.analysis.js.checkers
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.*
object JsExpressionCheckers : ExpressionCheckers() {
@@ -19,4 +20,9 @@ object JsExpressionCheckers : ExpressionCheckers() {
FirJsDefinedExternallyCallChecker,
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)
in OperatorConventions.IN_OPERATIONS ->
return rightArgument.generateContainsOperation(
leftArgument, operationToken == NOT_IN, source, expression.operationReference.toFirSourceElement(),
leftArgument, operationToken == NOT_IN, source,
expression.operationReference.toFirSourceElement(),
)
in OperatorConventions.COMPARISON_OPERATIONS ->
return leftArgument.generateComparisonExpression(
rightArgument, operationToken, source, expression.operationReference.toFirSourceElement(),
rightArgument, operationToken, source,
expression.operationReference.toFirSourceElement(),
)
}
val conventionCallName = operationToken.toBinaryName()
@@ -1,17 +1,17 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun foo() {
for ((x, y) in A()) {
for ((<!WRONG_OPERATION_WITH_DYNAMIC!>x<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>y<!>) in A()) {
println(x + y)
}
bar { (x, y) ->
bar { (<!WRONG_OPERATION_WITH_DYNAMIC!>x<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>y<!>) ->
println(x + y)
}
val x: dynamic = Any()
val (y, z) = x
val (<!WRONG_OPERATION_WITH_DYNAMIC!>y<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>z<!>) = x
println(y + z)
}
@@ -1,12 +1,12 @@
fun foo() {
val a: dynamic = Any()
println(a in setOf(1, 2))
println(1 in a)
println(1 !in a)
println(<!WRONG_OPERATION_WITH_DYNAMIC!>1 in a<!>)
println(<!WRONG_OPERATION_WITH_DYNAMIC!>1 !in a<!>)
when (2) {
in a -> println("ok")
<!WRONG_OPERATION_WITH_DYNAMIC!>in a<!> -> println("ok")
}
when (3) {
!in a -> println("ok")
<!WRONG_OPERATION_WITH_DYNAMIC!>!in a<!> -> println("ok")
}
}
@@ -1,8 +1,8 @@
fun foo() {
val a: dynamic = Any()
println(a[0])
println(a[0, 1])
println(<!WRONG_OPERATION_WITH_DYNAMIC!>a[0, 1]<!>)
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 b: dynamic = Any()
val c = C()
println(a..b)
println(<!WRONG_OPERATION_WITH_DYNAMIC!>a..b<!>)
println(c..a)
println(a.rangeTo(b))
}
@@ -5,7 +5,7 @@ fun foo() {
val a: dynamic = Any()
val b: dynamic = Any()
val c = C()
println(a..<b)
println(<!WRONG_OPERATION_WITH_DYNAMIC!>a..<b<!>)
println(c..<a)
println(a.rangeUntil(b))
}
@@ -1,22 +1,22 @@
fun test(d: dynamic) {
val a = arrayOf(1, 2, 3)
d.foo(*d)
d.foo(*a)
d.foo(1, "2", *a)
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>d)
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(1, "2", <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(1, *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, 1, { "" }, *a)
d.foo(*a, 1)
d.foo(*a, *a, { "" })
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, 1, { "" }, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, 1)
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, { "" })
bar(d)
bar(d, d)
bar(*d)
bar(*d, *d)
bar(*d, 23, *d)
bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>, 23, <!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
}
fun bar(vararg x: Int): Unit = TODO("$x")