[Wasm] Port js(code) calls checker to K2 (KT-56849)
^KT-62725 Fixed ^KT-62726 Fixed ^KT-62727 Fixed
This commit is contained in:
committed by
Space Team
parent
260db2fa65
commit
43345bbc34
+8
@@ -33,6 +33,14 @@ object WASM_DIAGNOSTICS_LIST : DiagnosticList("FirWasmErrors") {
|
||||
val WRONG_JS_FUN_TARGET by error<PsiElement>()
|
||||
}
|
||||
|
||||
val JSCODE by object : DiagnosticGroup("JsCode") {
|
||||
val JSCODE_WRONG_CONTEXT by error<KtElement>(PositioningStrategy.NAME_IDENTIFIER)
|
||||
val JSCODE_UNSUPPORTED_FUNCTION_KIND by error<KtElement>(PositioningStrategy.NAME_IDENTIFIER) {
|
||||
parameter<String>("place")
|
||||
}
|
||||
val JSCODE_INVALID_PARAMETER_NAME by error<KtElement>()
|
||||
}
|
||||
|
||||
val WASM_INTEROP by object : DiagnosticGroup("Wasm interop") {
|
||||
val NESTED_WASM_EXPORT by error<KtElement>()
|
||||
val WASM_EXPORT_ON_EXTERNAL_DECLARATION by error<KtElement>()
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
@@ -26,6 +26,11 @@ object FirWasmErrors {
|
||||
// JsFun
|
||||
val WRONG_JS_FUN_TARGET by error0<PsiElement>()
|
||||
|
||||
// JsCode
|
||||
val JSCODE_WRONG_CONTEXT by error0<KtElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
|
||||
val JSCODE_UNSUPPORTED_FUNCTION_KIND by error1<KtElement, String>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
|
||||
val JSCODE_INVALID_PARAMETER_NAME by error0<KtElement>()
|
||||
|
||||
// Wasm interop
|
||||
val NESTED_WASM_EXPORT by error0<KtElement>()
|
||||
val WASM_EXPORT_ON_EXTERNAL_DECLARATION by error0<KtElement>()
|
||||
|
||||
+17
@@ -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.")
|
||||
|
||||
+2
@@ -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<FirFunctionCallChecker>
|
||||
get() = setOf(
|
||||
FirJsCodeConstantArgumentChecker,
|
||||
FirWasmJsCodeCallChecker,
|
||||
)
|
||||
}
|
||||
|
||||
+84
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-18
@@ -1,3 +1,5 @@
|
||||
// !DIAGNOSTICS: -UNREACHABLE_CODE -UNUSED_PARAMETER
|
||||
|
||||
val prop: Int =
|
||||
js("1")
|
||||
|
||||
@@ -54,60 +56,60 @@ val p14: Int = js(<!JSCODE_ARGUMENT_NON_CONST_EXPRESSION!>delegatedVal<!>)
|
||||
|
||||
|
||||
fun foo0(b: Boolean): Int =
|
||||
if (b) js("1") else js("2")
|
||||
if (b) <!JSCODE_WRONG_CONTEXT!>js<!>("1") else <!JSCODE_WRONG_CONTEXT!>js<!>("2")
|
||||
|
||||
fun foo1(): Int {
|
||||
println()
|
||||
js("return x;")
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("return x;")
|
||||
}
|
||||
|
||||
fun foo11() {
|
||||
fun local1(): Int = js("1")
|
||||
fun local1(): Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")
|
||||
fun local2(): Int {
|
||||
js("return 1;")
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("return 1;")
|
||||
}
|
||||
fun local3(): Int {
|
||||
println()
|
||||
js("return 1;")
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("return 1;")
|
||||
}
|
||||
}
|
||||
|
||||
class C {
|
||||
fun memberFun1(): Int = js("1")
|
||||
fun memberFun1(): Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")
|
||||
fun memberFun2(): Int {
|
||||
js("return 1;")
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("return 1;")
|
||||
}
|
||||
|
||||
constructor() {
|
||||
js("1;")
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("1;")
|
||||
}
|
||||
|
||||
init {
|
||||
js("1")
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("1")
|
||||
}
|
||||
|
||||
val memberProperty: Int = js("1")
|
||||
val memberProperty: Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")
|
||||
}
|
||||
|
||||
fun withDefault(x: Int = js("1")) {
|
||||
fun withDefault(x: Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")) {
|
||||
println(x)
|
||||
}
|
||||
|
||||
suspend fun suspendFun(): Int = js("1")
|
||||
suspend fun suspendFun(): Int = <!JSCODE_UNSUPPORTED_FUNCTION_KIND!>js<!>("1")
|
||||
|
||||
inline fun inlineFun(f: () -> Int): Int = js("f()")
|
||||
inline fun inlineFun(f: () -> Int): Int = <!JSCODE_UNSUPPORTED_FUNCTION_KIND!>js<!>("f()")
|
||||
|
||||
fun Int.extensionFun(): Int = js("1")
|
||||
fun Int.extensionFun(): Int = <!JSCODE_UNSUPPORTED_FUNCTION_KIND!>js<!>("1")
|
||||
|
||||
var propertyWithAccessors: Int
|
||||
get(): Int = js("1")
|
||||
get(): Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")
|
||||
set(value: Int) {
|
||||
js("console.log(value);")
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("console.log(value);")
|
||||
}
|
||||
|
||||
|
||||
fun invalidNames(
|
||||
`a b`: Int,
|
||||
`1b`: Int,
|
||||
<!JSCODE_INVALID_PARAMETER_NAME!>`a b`: Int<!>,
|
||||
<!JSCODE_INVALID_PARAMETER_NAME!>`1b`: Int<!>,
|
||||
`ab$`: Int
|
||||
): Int = js("1")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// !DIAGNOSTICS: -UNREACHABLE_CODE -UNUSED_PARAMETER
|
||||
|
||||
val prop: Int =
|
||||
js("1")
|
||||
|
||||
@@ -78,15 +80,15 @@ class C {
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("return 1;")
|
||||
}
|
||||
|
||||
constructor() <!UNREACHABLE_CODE!>{
|
||||
constructor() {
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("1;")
|
||||
}<!>
|
||||
}
|
||||
|
||||
init {
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("1")
|
||||
}
|
||||
|
||||
<!UNREACHABLE_CODE!>val memberProperty: Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")<!>
|
||||
val memberProperty: Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")
|
||||
}
|
||||
|
||||
fun withDefault(x: Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")) {
|
||||
@@ -101,7 +103,7 @@ fun Int.extensionFun(): Int = <!JSCODE_UNSUPPORTED_FUNCTION_KIND!>js<!>("1")
|
||||
|
||||
var propertyWithAccessors: Int
|
||||
get(): Int = <!JSCODE_WRONG_CONTEXT!>js<!>("1")
|
||||
set(<!UNUSED_PARAMETER!>value<!>: Int) {
|
||||
set(value: Int) {
|
||||
<!JSCODE_WRONG_CONTEXT!>js<!>("console.log(value);")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user