diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirWasmDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirWasmDiagnosticsList.kt index 937452a8d5d..cc65a26c243 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirWasmDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirWasmDiagnosticsList.kt @@ -33,6 +33,14 @@ object WASM_DIAGNOSTICS_LIST : DiagnosticList("FirWasmErrors") { val WRONG_JS_FUN_TARGET by error() } + val JSCODE by object : DiagnosticGroup("JsCode") { + val JSCODE_WRONG_CONTEXT by error(PositioningStrategy.NAME_IDENTIFIER) + val JSCODE_UNSUPPORTED_FUNCTION_KIND by error(PositioningStrategy.NAME_IDENTIFIER) { + parameter("place") + } + val JSCODE_INVALID_PARAMETER_NAME by error() + } + val WASM_INTEROP by object : DiagnosticGroup("Wasm interop") { val NESTED_WASM_EXPORT by error() val WASM_EXPORT_ON_EXTERNAL_DECLARATION by error() diff --git a/compiler/fir/checkers/checkers.wasm/build.gradle.kts b/compiler/fir/checkers/checkers.wasm/build.gradle.kts index 25a6d5f9810..9f3c44d3a0d 100644 --- a/compiler/fir/checkers/checkers.wasm/build.gradle.kts +++ b/compiler/fir/checkers/checkers.wasm/build.gradle.kts @@ -10,6 +10,9 @@ dependencies { api(project(":compiler:fir:checkers:checkers.web.common")) api(project(":core:compiler.common.wasm")) + // Needed for JS identifier utils + implementation(project(":js:js.ast")) + /* * We can't remove this dependency until we use * diagnostics framework from FE 1.0 diff --git a/compiler/fir/checkers/checkers.wasm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/wasm/FirWasmErrors.kt b/compiler/fir/checkers/checkers.wasm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/wasm/FirWasmErrors.kt index 5b9247542cd..b5d9000af04 100644 --- a/compiler/fir/checkers/checkers.wasm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/wasm/FirWasmErrors.kt +++ b/compiler/fir/checkers/checkers.wasm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/wasm/FirWasmErrors.kt @@ -26,6 +26,11 @@ object FirWasmErrors { // JsFun val WRONG_JS_FUN_TARGET by error0() + // JsCode + val JSCODE_WRONG_CONTEXT by error0(SourceElementPositioningStrategies.NAME_IDENTIFIER) + val JSCODE_UNSUPPORTED_FUNCTION_KIND by error1(SourceElementPositioningStrategies.NAME_IDENTIFIER) + val JSCODE_INVALID_PARAMETER_NAME by error0() + // Wasm interop val NESTED_WASM_EXPORT by error0() val WASM_EXPORT_ON_EXTERNAL_DECLARATION by error0() diff --git a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/wasm/FirWasmErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/wasm/FirWasmErrorsDefaultMessages.kt index e6ddedd0ece..0df00bdd16f 100644 --- a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/wasm/FirWasmErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/wasm/FirWasmErrorsDefaultMessages.kt @@ -11,6 +11,9 @@ import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.EXTERNAL_TYPE_EXTENDS_NON_EXTERNAL_TYPE +import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.JSCODE_INVALID_PARAMETER_NAME +import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.JSCODE_UNSUPPORTED_FUNCTION_KIND +import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.JSCODE_WRONG_CONTEXT import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.JS_AND_WASM_EXPORTS_ON_SAME_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.NESTED_WASM_EXPORT import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.NESTED_WASM_IMPORT @@ -46,6 +49,20 @@ object FirWasmErrorsDefaultMessages : BaseDiagnosticRendererFactory() { map.put(WRONG_JS_FUN_TARGET, "Only top-level external functions can be implemented using '@JsFun'.") + map.put( + JSCODE_WRONG_CONTEXT, + "Calls to 'js(code)' should be a single expression inside a top-level function body or a property initializer in Kotlin/Wasm." + ) + map.put( + JSCODE_UNSUPPORTED_FUNCTION_KIND, + "Calls to ''js(code)'' are not supported in {0} in Kotlin/Wasm.", + TO_STRING + ) + map.put( + JSCODE_INVALID_PARAMETER_NAME, + "Parameters passed to 'js(code)' should have a valid JavaScript name." + ) + map.put(NESTED_WASM_EXPORT, "Only top-level functions can be exported with '@WasmExport'.") map.put(WASM_EXPORT_ON_EXTERNAL_DECLARATION, "Functions annotated with '@WasmExport' must not be external.") map.put(JS_AND_WASM_EXPORTS_ON_SAME_DECLARATION, "Cannot use '@WasmExport' and '@JsExport' for same function.") diff --git a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/WasmExpressionCheckers.kt b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/WasmExpressionCheckers.kt index f3b0dc4ef85..2e121370e51 100644 --- a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/WasmExpressionCheckers.kt +++ b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/WasmExpressionCheckers.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.wasm.checkers import org.jetbrains.kotlin.fir.analysis.checkers.expression.* import org.jetbrains.kotlin.fir.analysis.wasm.checkers.expression.FirWasmDefinedExternallyCallChecker +import org.jetbrains.kotlin.fir.analysis.wasm.checkers.expression.FirWasmJsCodeCallChecker import org.jetbrains.kotlin.fir.analysis.web.common.checkers.expression.FirJsCodeConstantArgumentChecker object WasmExpressionCheckers : ExpressionCheckers() { @@ -18,5 +19,6 @@ object WasmExpressionCheckers : ExpressionCheckers() { override val functionCallCheckers: Set get() = setOf( FirJsCodeConstantArgumentChecker, + FirWasmJsCodeCallChecker, ) } diff --git a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/expression/FirWasmJsCodeCallChecker.kt b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/expression/FirWasmJsCodeCallChecker.kt new file mode 100644 index 00000000000..e60791ee4e9 --- /dev/null +++ b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/expression/FirWasmJsCodeCallChecker.kt @@ -0,0 +1,84 @@ +/* + * 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.wasm.checkers.expression + +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.wasm.FirWasmErrors +import org.jetbrains.kotlin.fir.analysis.wasm.checkers.hasValidJsCodeBody +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.isExtension +import org.jetbrains.kotlin.fir.declarations.utils.isInline +import org.jetbrains.kotlin.fir.declarations.utils.isSuspend +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol +import org.jetbrains.kotlin.js.common.isValidES5Identifier +import org.jetbrains.kotlin.name.WasmStandardClassIds + +object FirWasmJsCodeCallChecker : FirFunctionCallChecker() { + override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) { + val symbol = expression.calleeReference.toResolvedCallableSymbol() ?: return + + if (symbol.callableId != WasmStandardClassIds.Callables.Js) { + return + } + + val containingDeclarations = context.containingDeclarations + + val containingDeclaration: FirDeclaration = containingDeclarations.lastOrNull() ?: return + + val containingDeclarationOfContainingDeclaration = + containingDeclarations.getOrNull(containingDeclarations.size - 2) + + val isContainingDeclarationTopLevel = + containingDeclarationOfContainingDeclaration is FirFile || containingDeclarationOfContainingDeclaration is FirScript + + val source = expression.calleeReference.source + + if (!isContainingDeclarationTopLevel) { + reporter.reportOn(source, FirWasmErrors.JSCODE_WRONG_CONTEXT, context) + return + } + + when (containingDeclaration) { + is FirSimpleFunction -> { + if (!containingDeclaration.hasValidJsCodeBody()) { + reporter.reportOn(source, FirWasmErrors.JSCODE_WRONG_CONTEXT, context) + } else { + if (containingDeclaration.isSuspend) { + reporter.reportOn(source, FirWasmErrors.JSCODE_UNSUPPORTED_FUNCTION_KIND, "suspend function", context) + } + if (containingDeclaration.isInline) { + reporter.reportOn(source, FirWasmErrors.JSCODE_UNSUPPORTED_FUNCTION_KIND, "inline function", context) + } + if (containingDeclaration.isExtension) { + reporter.reportOn( + source, + FirWasmErrors.JSCODE_UNSUPPORTED_FUNCTION_KIND, + "function with extension receiver", + context + ) + } + for (parameter in containingDeclaration.valueParameters) { + if (parameter.name.identifierOrNullIfSpecial?.isValidES5Identifier() != true) { + reporter.reportOn(parameter.source, FirWasmErrors.JSCODE_INVALID_PARAMETER_NAME, context) + } + } + } + } + is FirProperty -> { + if (!containingDeclaration.hasValidJsCodeBody()) { + reporter.reportOn(source, FirWasmErrors.JSCODE_WRONG_CONTEXT, context) + } + } + else -> { + reporter.reportOn(source, FirWasmErrors.JSCODE_WRONG_CONTEXT, context) + } + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/wasmTests/jsInterop/jsCode.fir.kt b/compiler/testData/diagnostics/wasmTests/jsInterop/jsCode.fir.kt index 1888076d285..728bcfde9ca 100644 --- a/compiler/testData/diagnostics/wasmTests/jsInterop/jsCode.fir.kt +++ b/compiler/testData/diagnostics/wasmTests/jsInterop/jsCode.fir.kt @@ -1,3 +1,5 @@ +// !DIAGNOSTICS: -UNREACHABLE_CODE -UNUSED_PARAMETER + val prop: Int = js("1") @@ -54,60 +56,60 @@ val p14: Int = js(delegatedVal) fun foo0(b: Boolean): Int = - if (b) js("1") else js("2") + if (b) js("1") else js("2") fun foo1(): Int { println() - js("return x;") + js("return x;") } fun foo11() { - fun local1(): Int = js("1") + fun local1(): Int = js("1") fun local2(): Int { - js("return 1;") + js("return 1;") } fun local3(): Int { println() - js("return 1;") + js("return 1;") } } class C { - fun memberFun1(): Int = js("1") + fun memberFun1(): Int = js("1") fun memberFun2(): Int { - js("return 1;") + js("return 1;") } constructor() { - js("1;") + js("1;") } init { - js("1") + js("1") } - val memberProperty: Int = js("1") + val memberProperty: Int = js("1") } -fun withDefault(x: Int = js("1")) { +fun withDefault(x: Int = js("1")) { println(x) } -suspend fun suspendFun(): Int = js("1") +suspend fun suspendFun(): Int = js("1") -inline fun inlineFun(f: () -> Int): Int = js("f()") +inline fun inlineFun(f: () -> Int): Int = js("f()") -fun Int.extensionFun(): Int = js("1") +fun Int.extensionFun(): Int = js("1") var propertyWithAccessors: Int - get(): Int = js("1") + get(): Int = js("1") set(value: Int) { - js("console.log(value);") + js("console.log(value);") } fun invalidNames( - `a b`: Int, - `1b`: Int, + `a b`: Int, + `1b`: Int, `ab$`: Int ): Int = js("1") diff --git a/compiler/testData/diagnostics/wasmTests/jsInterop/jsCode.kt b/compiler/testData/diagnostics/wasmTests/jsInterop/jsCode.kt index 353f5925c95..6bb99d1a6eb 100644 --- a/compiler/testData/diagnostics/wasmTests/jsInterop/jsCode.kt +++ b/compiler/testData/diagnostics/wasmTests/jsInterop/jsCode.kt @@ -1,3 +1,5 @@ +// !DIAGNOSTICS: -UNREACHABLE_CODE -UNUSED_PARAMETER + val prop: Int = js("1") @@ -78,15 +80,15 @@ class C { js("return 1;") } - constructor() { + constructor() { js("1;") - } + } init { js("1") } - val memberProperty: Int = js("1") + val memberProperty: Int = js("1") } fun withDefault(x: Int = js("1")) { @@ -101,7 +103,7 @@ fun Int.extensionFun(): Int = js("1") var propertyWithAccessors: Int get(): Int = js("1") - set(value: Int) { + set(value: Int) { js("console.log(value);") }