[Wasm] Port WasmImport/WasmExport checker to K2 (KT-56849)

This commit is contained in:
Svyatoslav Kuzmich
2023-11-14 12:24:10 +00:00
committed by Space Team
parent 28895a2613
commit 1c230c8f27
14 changed files with 205 additions and 128 deletions
@@ -28,4 +28,20 @@ object WASM_DIAGNOSTICS_LIST : DiagnosticList("FirWasmErrors") {
parameter<ConeKotlinType>("type")
}
}
val WASM_INTEROP by object : DiagnosticGroup("Wasm interop") {
val NESTED_WASM_EXPORT by error<KtElement>()
val WASM_EXPORT_ON_EXTERNAL_DECLARATION by error<KtElement>()
val JS_AND_WASM_EXPORTS_ON_SAME_DECLARATION by error<KtElement>()
val NESTED_WASM_IMPORT by error<KtElement>()
val WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION by error<KtElement>()
val WASM_IMPORT_EXPORT_PARAMETER_DEFAULT_VALUE by error<KtElement>()
val WASM_IMPORT_EXPORT_VARARG_PARAMETER by error<KtElement>()
val WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) {
parameter<ConeKotlinType>("type")
}
val WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) {
parameter<ConeKotlinType>("type")
}
}
}
@@ -23,6 +23,17 @@ object FirWasmErrors {
val CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION by error0<PsiElement>()
val WRONG_JS_INTEROP_TYPE by error2<KtElement, String, ConeKotlinType>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
// Wasm interop
val NESTED_WASM_EXPORT by error0<KtElement>()
val WASM_EXPORT_ON_EXTERNAL_DECLARATION by error0<KtElement>()
val JS_AND_WASM_EXPORTS_ON_SAME_DECLARATION by error0<KtElement>()
val NESTED_WASM_IMPORT by error0<KtElement>()
val WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION by error0<KtElement>()
val WASM_IMPORT_EXPORT_PARAMETER_DEFAULT_VALUE by error0<KtElement>()
val WASM_IMPORT_EXPORT_VARARG_PARAMETER by error0<KtElement>()
val WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE by error1<KtElement, ConeKotlinType>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE by error1<KtElement, ConeKotlinType>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
init {
RootDiagnosticRendererFactory.registerFactory(FirWasmErrorsDefaultMessages)
}
@@ -12,7 +12,16 @@ 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.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
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.NON_EXTERNAL_TYPE_EXTENDS_EXTERNAL_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.WASM_EXPORT_ON_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.WASM_IMPORT_EXPORT_PARAMETER_DEFAULT_VALUE
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.WASM_IMPORT_EXPORT_VARARG_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors.WRONG_JS_INTEROP_TYPE
@Suppress("unused")
@@ -34,5 +43,23 @@ object FirWasmErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"Type ''{0}'' cannot be used in {1}. Only external, primitive, string and function types are supported in Kotlin/Wasm JS interop.",
TO_STRING, FirDiagnosticRenderers.RENDER_TYPE,
)
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.")
map.put(NESTED_WASM_IMPORT, "Only top-level functions can be imported with '@WasmImport'.")
map.put(WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION, "Functions annotated with '@WasmImport' must be external.")
map.put(WASM_IMPORT_EXPORT_PARAMETER_DEFAULT_VALUE, "Default parameter values are not supported with '@WasmImport' and '@WasmExport'.")
map.put(WASM_IMPORT_EXPORT_VARARG_PARAMETER, "Vararg parameters are not supported with '@WasmImport' and '@WasmExport'.")
map.put(
WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE,
"Unsupported ''@WasmImport'' and ''@WasmExport'' parameter type ''{0}''.",
FirDiagnosticRenderers.RENDER_TYPE
)
map.put(
WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE,
"Unsupported ''@WasmImport'' and ''@WasmExport'' return type ''{0}''.",
FirDiagnosticRenderers.RENDER_TYPE
)
}
}
@@ -17,5 +17,7 @@ object WasmDeclarationCheckers : DeclarationCheckers() {
override val basicDeclarationCheckers: Set<FirBasicDeclarationChecker>
get() = setOf(
FirWasmJsInteropTypesChecker,
FirWasmImportAnnotationChecker,
FirWasmExportAnnotationChecker,
)
}
@@ -0,0 +1,44 @@
/*
* 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.declaration
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.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.isTopLevel
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors
import org.jetbrains.kotlin.fir.analysis.wasm.checkers.hasValidJsCodeBody
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.isEffectivelyExternal
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.name.WasmStandardClassIds
object FirWasmExportAnnotationChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val annotation: FirAnnotation =
declaration.annotations.getAnnotationByClassId(WasmStandardClassIds.Annotations.WasmExport, context.session) ?: return
if (!context.isTopLevel) {
reporter.reportOn(annotation.source, FirWasmErrors.NESTED_WASM_EXPORT, context)
}
if (declaration.annotations.hasAnnotation(WasmStandardClassIds.Annotations.JsExport, context.session)) {
reporter.reportOn(declaration.source, FirWasmErrors.JS_AND_WASM_EXPORTS_ON_SAME_DECLARATION, context)
}
if (declaration is FirSimpleFunction) {
if (declaration.symbol.isEffectivelyExternal(context.session) || declaration.hasValidJsCodeBody()) {
reporter.reportOn(annotation.source, FirWasmErrors.WASM_EXPORT_ON_EXTERNAL_DECLARATION, context)
}
checkWasmInteropSignature(declaration, context, reporter)
}
}
}
@@ -0,0 +1,76 @@
/*
* 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.declaration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.isTopLevel
import org.jetbrains.kotlin.fir.analysis.diagnostics.wasm.FirWasmErrors
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.declarations.utils.isEffectivelyExternal
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.WasmStandardClassIds
object FirWasmImportAnnotationChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val annotation: FirAnnotation =
declaration.annotations.getAnnotationByClassId(WasmStandardClassIds.Annotations.WasmImport, context.session) ?: return
if (!context.isTopLevel) {
reporter.reportOn(annotation.source, FirWasmErrors.NESTED_WASM_IMPORT, context)
}
if (!declaration.symbol.isEffectivelyExternal(context.session)) {
reporter.reportOn(annotation.source, FirWasmErrors.WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION, context)
}
if (declaration is FirFunction) {
checkWasmInteropSignature(declaration, context, reporter)
}
}
}
fun checkWasmInteropSignature(declaration: FirFunction, context: CheckerContext, reporter: DiagnosticReporter) {
for (parameter in declaration.valueParameters) {
val type = parameter.returnTypeRef.coneType
if (parameter.defaultValue != null) {
reporter.reportOn(parameter.source, FirWasmErrors.WASM_IMPORT_EXPORT_PARAMETER_DEFAULT_VALUE, context)
}
if (parameter.isVararg) {
reporter.reportOn(parameter.source, FirWasmErrors.WASM_IMPORT_EXPORT_VARARG_PARAMETER, context)
}
if (!isTypeSupportedInWasmInterop(type, false, context.session)) {
reporter.reportOn(parameter.source, FirWasmErrors.WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE, type, context)
}
}
val returnType = declaration.returnTypeRef.coneType
if (!isTypeSupportedInWasmInterop(returnType, true, context.session)) {
reporter.reportOn(declaration.source, FirWasmErrors.WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE, returnType, context)
}
}
private fun isTypeSupportedInWasmInterop(
unexpandedType: ConeKotlinType,
isInFunctionReturnPosition: Boolean,
session: FirSession,
): Boolean {
val type = unexpandedType.fullyExpandedType(session)
if (type.isUnit) {
return isInFunctionReturnPosition
}
// Primitive numbers and Boolean are supported
return type.isPrimitive && !type.isChar
}
@@ -1,58 +0,0 @@
import kotlin.wasm.WasmExport
@WasmExport("a")
external fun foo0(): Unit
@WasmExport("a")
fun foo1(): Int = js("42")
class C() {
@WasmExport("a")
fun foo2(): Int = 42
}
@WasmExport("a")
fun foo3(): Int = 42
@WasmExport()
fun foo4(): Int = 42
@OptIn(kotlin.js.ExperimentalJsExport::class)
@JsExport()
@WasmExport()
fun foo6(): Int = 42
val p1 = (@WasmExport("a") fun () {})
@WasmExport("a")
fun foo7(
p0: Unit,
p1: String,
p2: Any,
p3: Int?,
p4: Boolean?
): Unit {
p0.toString()
p1.toString()
p2.toString()
p3.toString()
p4.toString()
}
@WasmExport("a")
fun returnNullableUnit(): Unit? { return null }
@WasmExport("a")
fun returnNullableBoolean(): Boolean? { return null }
@WasmExport("a")
fun returnNullableAny(): Any? { return null }
@WasmExport("a")
fun <T> fooGeneric(x: T): T { return x }
@WasmExport("a")
fun fooDeafultAndVararg(
a: Int = <!CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION!>definedExternally<!>,
vararg b: Int
): Unit { b.toString() }
@@ -1,14 +1,16 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_PARAMETER
import kotlin.wasm.WasmExport
<!WASM_EXPORT_ON_EXTERNAL_DECLARATION!>@WasmExport("a")
external fun foo0(): Unit<!>
<!WASM_EXPORT_ON_EXTERNAL_DECLARATION!>@WasmExport("a")<!>
external fun foo0(): Unit
<!WASM_EXPORT_ON_EXTERNAL_DECLARATION!>@WasmExport("a")
fun foo1(): Int = js("42")<!>
<!WASM_EXPORT_ON_EXTERNAL_DECLARATION!>@WasmExport("a")<!>
fun foo1(): Int = js("42")
class C() {
<!NESTED_WASM_EXPORT!>@WasmExport("a")
fun foo2(): Int = 42<!>
<!NESTED_WASM_EXPORT!>@WasmExport("a")<!>
fun foo2(): Int = 42
}
@WasmExport("a")
@@ -22,7 +24,7 @@ fun foo4(): Int = 42
@WasmExport()
fun foo6(): Int = 42<!>
val p1 = (<!NESTED_WASM_EXPORT!>@WasmExport("a") fun () {}<!>)
val p1 = (<!NESTED_WASM_EXPORT!>@WasmExport("a")<!> fun () {})
@WasmExport("a")
fun foo7(
@@ -40,19 +42,19 @@ fun foo7(
}
<!WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE!>@WasmExport("a")
fun returnNullableUnit(): Unit? { return null }<!>
fun returnNullableUnit(): Unit?<!> { return null }
<!WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE!>@WasmExport("a")
fun returnNullableBoolean(): Boolean? { return null }<!>
fun returnNullableBoolean(): Boolean?<!> { return null }
<!WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE!>@WasmExport("a")
fun returnNullableAny(): Any? { return null }<!>
fun returnNullableAny(): Any?<!> { return null }
<!WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE!>@WasmExport("a")
fun <T> fooGeneric(<!WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE!>x: T<!>): T { return x }<!>
fun <T> fooGeneric(<!WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE!>x: T<!>): T<!> { return x }
@WasmExport("a")
fun fooDeafultAndVararg(
<!WASM_IMPORT_EXPORT_PARAMETER_DEFAULT_VALUE!><!UNUSED_PARAMETER!>a<!>: Int = <!CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION!>definedExternally<!><!>,
<!WASM_IMPORT_EXPORT_PARAMETER_DEFAULT_VALUE!>a: Int = <!CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION!>definedExternally<!><!>,
<!WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE, WASM_IMPORT_EXPORT_VARARG_PARAMETER!>vararg b: Int<!>
): Unit { b.toString() }
@@ -1,49 +0,0 @@
import kotlin.wasm.WasmImport
@WasmImport("a", "b")
external fun foo0(): Unit
@WasmImport("a", "b")
fun foo1() {
}
external class C {
@WasmImport("a", "b")
fun memberFunction()
}
fun foo2() {
@WasmImport("a", "b")
fun localFun() {
}
}
val p1 = (@WasmImport("a", "b") fun () {})
@WasmImport("a", "b")
external fun foo3(
p0: Unit,
p1: String,
p2: Any,
p3: Int?,
p4: Boolean?
): Unit
@WasmImport("a", "b")
external fun returnNullableUnit(): Unit?
@WasmImport("a", "b")
external fun returnNullableBoolean(): Boolean?
@WasmImport("a", "b")
external fun returnNullableAny(): Any?
@WasmImport("a", "b")
external fun <T> fooGeneric(x: T): T
@WasmImport("a", "b")
external fun fooDeafultAndVararg(
a: Int = definedExternally,
vararg b: Int
): Unit
@@ -1,3 +1,5 @@
// FIR_IDENTICAL
import kotlin.wasm.WasmImport
@WasmImport("a", "b")
@@ -26,6 +26,9 @@ object WasmStandardClassIds {
@JvmField
val WasmImport = "WasmImport".wasmId()
@JvmField
val WasmExport = "WasmExport".wasmId()
}
object Callables {
@@ -30,8 +30,10 @@ public interface ErrorsWasm {
DiagnosticFactory0<PsiElement> WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> WASM_IMPORT_EXPORT_PARAMETER_DEFAULT_VALUE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> WASM_IMPORT_EXPORT_VARARG_PARAMETER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, KotlinType> WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, KotlinType> WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, KotlinType> WASM_IMPORT_EXPORT_UNSUPPORTED_PARAMETER_TYPE =
DiagnosticFactory1.create(ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory1<PsiElement, KotlinType> WASM_IMPORT_EXPORT_UNSUPPORTED_RETURN_TYPE =
DiagnosticFactory1.create(ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory0<PsiElement> WRONG_JS_FUN_TARGET = DiagnosticFactory0.create(ERROR);
@@ -14,16 +14,18 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.wasm.util.hasValidJsCodeBody
// TODO: Implement in K2: KT-56849
abstract class WasmExportAnnotationChecker(val checkJsInterop: Boolean) : DeclarationChecker {
private val wasmExportFqName = FqName("kotlin.wasm.WasmExport")
private val jsExportFqName = FqName("kotlin.js.JsExport")
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (descriptor !is FunctionDescriptor) return
if (!descriptor.annotations.hasAnnotation(wasmExportFqName)) return
val wasmExport = descriptor.annotations.findAnnotation(wasmExportFqName) ?: return
val wasmExportPsi = wasmExport.source.getPsi() ?: declaration
val trace = context.trace
val bindingContext = trace.bindingContext
@@ -36,13 +38,11 @@ abstract class WasmExportAnnotationChecker(val checkJsInterop: Boolean) : Declar
}
if (descriptor.isEffectivelyExternal() || (checkJsInterop && descriptor.hasValidJsCodeBody(bindingContext))) {
val reportOn = descriptor.findPsi() ?: declaration
trace.report(ErrorsWasm.WASM_EXPORT_ON_EXTERNAL_DECLARATION.on(reportOn))
trace.report(ErrorsWasm.WASM_EXPORT_ON_EXTERNAL_DECLARATION.on(wasmExportPsi))
}
if (!DescriptorUtils.isTopLevelDeclaration(descriptor)) {
val reportOn = descriptor.findPsi() ?: declaration
trace.report(ErrorsWasm.NESTED_WASM_EXPORT.on(reportOn))
trace.report(ErrorsWasm.NESTED_WASM_EXPORT.on(wasmExportPsi))
}
WasmImportAnnotationChecker.checkSignatureIsPrimitive(descriptor, trace, declaration)
@@ -32,7 +32,6 @@ import org.jetbrains.kotlin.types.typeUtil.isBoolean
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType
import org.jetbrains.kotlin.types.typeUtil.isUnit
// TODO: Implement in K2: KT-56849
object WasmImportAnnotationChecker : DeclarationChecker {
private val wasmImportFqName = FqName("kotlin.wasm.WasmImport")