[Wasm] Port js(code) calls checker to K2 (KT-56849)

^KT-62725 Fixed
^KT-62726 Fixed
^KT-62727 Fixed
This commit is contained in:
Svyatoslav Kuzmich
2023-11-16 17:59:39 +01:00
committed by Space Team
parent 260db2fa65
commit 43345bbc34
8 changed files with 145 additions and 22 deletions
@@ -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
@@ -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>()
@@ -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.")
@@ -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,
)
}
@@ -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)
}
}
}
}
@@ -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);")
}