From 03e215295754cc63dd7b7b09b7b850953d8f7330 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Mon, 3 Apr 2023 15:12:18 +0300 Subject: [PATCH] [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` --- .../diagnostics/KtFirDataClassConverters.kt | 13 +++ .../api/fir/diagnostics/KtFirDiagnostics.kt | 9 ++ .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 11 ++ .../diagnostics/FirJsDiagnosticsList.kt | 4 + .../analysis/diagnostics/js/FirJsErrors.kt | 2 + .../js/FirJsErrorsDefaultMessages.kt | 4 + .../js/checkers/JsExpressionCheckers.kt | 6 + .../declaration/FirJsDynamicCallChecker.kt | 107 ++++++++++++++++++ .../kotlin/fir/builder/RawFirBuilder.kt | 6 +- .../dynamicTypes/destructuring.fir.kt | 6 +- .../dynamicTypes/inExpression.fir.kt | 8 +- .../dynamicTypes/indexedAccess.fir.kt | 4 +- .../dynamicTypes/rangeExpression.fir.kt | 2 +- .../dynamicTypes/rangeUntilExpression.fir.kt | 2 +- .../dynamicTypes/spreadOperator.fir.kt | 20 ++-- 15 files changed, 181 insertions(+), 23 deletions(-) create mode 100644 compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsDynamicCallChecker.kt diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index ebe5bfb8022..847e05c125e 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -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, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 0562838d62e..82b47730e00 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -3523,6 +3523,15 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = DelegationByDynamic::class } + abstract class SpreadOperatorInDynamicCall : KtFirDiagnostic() { + override val diagnosticClass get() = SpreadOperatorInDynamicCall::class + } + + abstract class WrongOperationWithDynamic : KtFirDiagnostic() { + override val diagnosticClass get() = WrongOperationWithDynamic::class + abstract val operation: String + } + abstract class Syntax : KtFirDiagnostic() { override val diagnosticClass get() = Syntax::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 75305fae011..c636a30b091 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -4262,6 +4262,17 @@ internal class DelegationByDynamicImpl( override val token: KtLifetimeToken, ) : KtFirDiagnostic.DelegationByDynamic(), KtAbstractFirDiagnostic +internal class SpreadOperatorInDynamicCallImpl( + override val firDiagnostic: KtPsiDiagnostic, + override val token: KtLifetimeToken, +) : KtFirDiagnostic.SpreadOperatorInDynamicCall(), KtAbstractFirDiagnostic + +internal class WrongOperationWithDynamicImpl( + override val operation: String, + override val firDiagnostic: KtPsiDiagnostic, + override val token: KtLifetimeToken, +) : KtFirDiagnostic.WrongOperationWithDynamic(), KtAbstractFirDiagnostic + internal class SyntaxImpl( override val firDiagnostic: KtPsiDiagnostic, override val token: KtLifetimeToken, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt index 2ce2f8d70db..e436a99c848 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt @@ -117,5 +117,9 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") { val DYNAMICS by object : DiagnosticGroup("Dynamics") { val DELEGATION_BY_DYNAMIC by error() + val SPREAD_OPERATOR_IN_DYNAMIC_CALL by error(PositioningStrategy.SPREAD_OPERATOR) + val WRONG_OPERATION_WITH_DYNAMIC by error { + parameter("operation") + } } } diff --git a/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt b/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt index 2ea8eb982ff..2eee4bb2b72 100644 --- a/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt +++ b/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt @@ -89,6 +89,8 @@ object FirJsErrors { // Dynamics val DELEGATION_BY_DYNAMIC by error0() + val SPREAD_OPERATOR_IN_DYNAMIC_CALL by error0(SourceElementPositioningStrategies.SPREAD_OPERATOR) + val WRONG_OPERATION_WITH_DYNAMIC by error1() init { RootDiagnosticRendererFactory.registerFactory(FirJsErrorsDefaultMessages) diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt index 743fe632c37..56df8d43985 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt @@ -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( diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsExpressionCheckers.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsExpressionCheckers.kt index 575219f3e59..c4a4364e981 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsExpressionCheckers.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsExpressionCheckers.kt @@ -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 + get() = setOf( + FirJsDynamicCallChecker, + ) } diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsDynamicCallChecker.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsDynamicCallChecker.kt new file mode 100644 index 00000000000..8c0b4c45c6c --- /dev/null +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsDynamicCallChecker.kt @@ -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) + } + } + } + } +} \ No newline at end of file diff --git a/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt b/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt index 20f1ad0ead8..d9a0c912825 100644 --- a/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt +++ b/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt @@ -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() diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.fir.kt index 0378cfa6e77..28a41663551 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.fir.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.fir.kt @@ -1,17 +1,17 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER fun foo() { - for ((x, y) in A()) { + for ((x, y) in A()) { println(x + y) } - bar { (x, y) -> + bar { (x, y) -> println(x + y) } val x: dynamic = Any() - val (y, z) = x + val (y, z) = x println(y + z) } diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/inExpression.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/inExpression.fir.kt index e0206322b08..00bd7e9b1de 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/inExpression.fir.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/inExpression.fir.kt @@ -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(1 in a) + println(1 !in a) when (2) { - in a -> println("ok") + in a -> println("ok") } when (3) { - !in a -> println("ok") + !in a -> println("ok") } } diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/indexedAccess.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/indexedAccess.fir.kt index 6f38b4a8006..7cefb43d167 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/indexedAccess.fir.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/indexedAccess.fir.kt @@ -1,8 +1,8 @@ fun foo() { val a: dynamic = Any() println(a[0]) - println(a[0, 1]) + println(a[0, 1]) a[0] = 23 - a[0, 1] = 42 + a[0, 1] = 42 } diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/rangeExpression.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/rangeExpression.fir.kt index 6829a4d8806..4188ec76575 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/rangeExpression.fir.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/rangeExpression.fir.kt @@ -4,7 +4,7 @@ fun foo() { val a: dynamic = Any() val b: dynamic = Any() val c = C() - println(a..b) + println(a..b) println(c..a) println(a.rangeTo(b)) } diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/rangeUntilExpression.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/rangeUntilExpression.fir.kt index bc704083732..8058b6e9e3c 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/rangeUntilExpression.fir.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/rangeUntilExpression.fir.kt @@ -5,7 +5,7 @@ fun foo() { val a: dynamic = Any() val b: dynamic = Any() val c = C() - println(a..a..) println(c..*d) + d.foo(*a) + d.foo(1, "2", *a) d.foo(1, *a) { } d.foo(*a) { "" } - d.foo(*a, *a) + d.foo(*a, *a) d.foo(*a, *a) { "" } - d.foo(*a, 1, { "" }, *a) - d.foo(*a, 1) - d.foo(*a, *a, { "" }) + d.foo(*a, 1, { "" }, *a) + d.foo(*a, 1) + d.foo(*a, *a, { "" }) bar(d) bar(d, d) - bar(*d) - bar(*d, *d) - bar(*d, 23, *d) + bar(*d) + bar(*d, *d) + bar(*d, 23, *d) } fun bar(vararg x: Int): Unit = TODO("$x")