[Wasm] Restrict types allowed in JS interop
- Prohibit Any, Array and other unsupported non-external types in JS interop context - Add K1 diagnostic - Update BE testdata ^KT-57136 Fixed
This commit is contained in:
committed by
Space Team
parent
bb05c8528f
commit
1e91fe155b
@@ -53,7 +53,7 @@ external class C {
|
||||
val x1: Int
|
||||
val x2: Int
|
||||
fun foo(x3: Int = definedExternally, x4: Int = definedExternally): String
|
||||
fun bar(x5: C = definedExternally, x6: Any = definedExternally) : String
|
||||
fun bar(x5: C = definedExternally, x6: C = definedExternally) : String
|
||||
}
|
||||
|
||||
open external class Writable: WritableStream {
|
||||
|
||||
+15
-13
@@ -88,7 +88,7 @@ function createJsLambda() {
|
||||
|
||||
// FILE: externals.kt
|
||||
|
||||
external fun createJsLambda(): (Boolean, Byte, Short, Char, Int, Long, Float, Double, String, EI, DC, (DC) -> Int) -> Int
|
||||
external fun createJsLambda(): (Boolean, Byte, Short, Char, Int, Long, Float, Double, String, EI, JSDC, (JSDC) -> Int) -> Int
|
||||
|
||||
external fun apply7(f: (String) -> ((String) -> ((String) -> ((String) -> ((String) -> ((String) -> ((String) -> String))))))): String
|
||||
|
||||
@@ -98,10 +98,11 @@ external fun is123Array(x: EI): Boolean
|
||||
external fun create123Array(): EI
|
||||
|
||||
data class DC(val x: Int, val y: Int)
|
||||
typealias JSDC = JsHandle<DC>
|
||||
|
||||
external fun extenalWithLambda(
|
||||
x: (Boolean, Byte, Short, Char, Int, Long, Float, Double, String, EI, DC) -> Unit,
|
||||
dc: DC,
|
||||
x: (Boolean, Byte, Short, Char, Int, Long, Float, Double, String, EI, JSDC) -> Unit,
|
||||
dc: JSDC,
|
||||
)
|
||||
|
||||
external fun externalWithLambdas2(
|
||||
@@ -115,8 +116,8 @@ external fun externalWithLambdas2(
|
||||
double: () -> Double,
|
||||
string: () -> String,
|
||||
ei: () -> EI,
|
||||
dc: () -> DC,
|
||||
dcGetY: (DC) -> Int,
|
||||
dc: () -> JSDC,
|
||||
dcGetY: (JSDC) -> Int,
|
||||
): Int
|
||||
|
||||
@JsExport
|
||||
@@ -165,7 +166,8 @@ fun box(): String {
|
||||
double: Double,
|
||||
string: String,
|
||||
ei: EI,
|
||||
dc: DC ->
|
||||
jsdc: JSDC ->
|
||||
val dc = jsdc.get()
|
||||
test(bool == true)
|
||||
test(byte == 1.toByte())
|
||||
test(short == 2.toShort())
|
||||
@@ -177,7 +179,7 @@ fun box(): String {
|
||||
test(string == "S")
|
||||
test(is123Array(ei))
|
||||
test(dc.x == 100 && dc.y == 200)
|
||||
}, DC(100, 200))
|
||||
}, DC(100, 200).toJsHandle())
|
||||
|
||||
|
||||
if (extenalWithLambdasCount != 11) return "Fail 1"
|
||||
@@ -193,8 +195,8 @@ fun box(): String {
|
||||
double = { 600.5 },
|
||||
string = { "700" },
|
||||
ei = { create123Array() },
|
||||
dc = { DC(800, 800) },
|
||||
dcGetY = { it.y }
|
||||
dc = { DC(800, 800).toJsHandle() },
|
||||
dcGetY = { it.get().y }
|
||||
)
|
||||
if (externalWithLambdas2Count != 11) return "Fail externalWithLambdas2"
|
||||
|
||||
@@ -210,8 +212,8 @@ fun box(): String {
|
||||
{ 600.5 },
|
||||
{ "700" },
|
||||
{ create123Array() },
|
||||
{ DC(800, 800) },
|
||||
{ it.y }
|
||||
{ DC(800, 800).toJsHandle() },
|
||||
{ it.get().y }
|
||||
)
|
||||
if (externalWithLambdas2RefCount != 11) return "Fail externalWithLambdas2"
|
||||
|
||||
@@ -228,8 +230,8 @@ fun box(): String {
|
||||
600.5,
|
||||
"700",
|
||||
create123Array(),
|
||||
DC(800, 800),
|
||||
{ it.y }
|
||||
DC(800, 800).toJsHandle(),
|
||||
{ it.get().y }
|
||||
)
|
||||
if (jsLambdaCount != 11)
|
||||
return "Fail 3"
|
||||
|
||||
+5
-4
@@ -1,13 +1,14 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
// MODULE: main
|
||||
// FILE: externals.kt
|
||||
|
||||
class C(val x: Int)
|
||||
|
||||
@JsExport
|
||||
fun makeC(x: Int): C = C(x)
|
||||
fun makeC(x: Int): JsHandle<C> = C(x).toJsHandle()
|
||||
|
||||
@JsExport
|
||||
fun getX(c: C): Int = c.x
|
||||
fun getX(c: JsHandle<C>): Int = c.get().x
|
||||
|
||||
@JsExport
|
||||
fun getString(s: String): String = "Test string $s";
|
||||
@@ -18,10 +19,10 @@ fun isEven(x: Int): Boolean = x % 2 == 0
|
||||
external interface EI
|
||||
|
||||
@JsExport
|
||||
fun eiAsAny(ei: EI): Any = ei
|
||||
fun eiAsAny(ei: EI): JsHandle<Any> = ei.toJsHandle()
|
||||
|
||||
@JsExport
|
||||
fun anyAsEI(any: Any): EI = any as EI
|
||||
fun anyAsEI(any: JsHandle<Any>): EI = any.get() as EI
|
||||
|
||||
fun box(): String = "OK"
|
||||
|
||||
|
||||
@@ -43,7 +43,8 @@ fun testExterRef() {
|
||||
check(null2ExternRef() == null)
|
||||
}
|
||||
|
||||
class DataRef
|
||||
class DataRefImpl
|
||||
typealias DataRef = JsHandle<DataRefImpl>
|
||||
|
||||
fun notNullDataRef(x: DataRef): DataRef = js("x")
|
||||
|
||||
@@ -54,7 +55,7 @@ fun nullDataRef(x: DataRef): DataRef? = js("x")
|
||||
fun null2DataRef(x: DataRef): DataRef? = js("null")
|
||||
|
||||
fun testDataRef() {
|
||||
val dataRef = DataRef()
|
||||
val dataRef = DataRefImpl().toJsHandle()
|
||||
check(notNullDataRef(dataRef) == dataRef)
|
||||
checkNPE { notNull2DataRef(dataRef) }
|
||||
check (nullDataRef(dataRef) == dataRef)
|
||||
@@ -121,22 +122,6 @@ fun testFloat() {
|
||||
check(null2Float() == null)
|
||||
}
|
||||
|
||||
|
||||
fun notNullNumber(): Number = js("123.5")
|
||||
|
||||
fun notNull2Number(): Number = js("null")
|
||||
|
||||
fun nullNumber(): Number? = js("123.5")
|
||||
|
||||
fun null2Number(): Number? = js("null")
|
||||
|
||||
fun testNumber() {
|
||||
check(notNullNumber() == 123.5)
|
||||
check(notNull2Number() == 0.0)
|
||||
check(nullNumber() == 123.5)
|
||||
check(null2Number() == null)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
testString()
|
||||
testExterRef()
|
||||
@@ -145,6 +130,5 @@ fun box(): String {
|
||||
testBoolean()
|
||||
testShort()
|
||||
testFloat()
|
||||
testNumber()
|
||||
return "OK"
|
||||
}
|
||||
|
||||
@@ -40,27 +40,6 @@ fun testExterRef() {
|
||||
null2ExternRef(null)
|
||||
}
|
||||
|
||||
class DataRef
|
||||
|
||||
fun notNullDataRef(x: DataRef) {
|
||||
js("if (x === null) throw 'error'")
|
||||
}
|
||||
|
||||
fun nullDataRef(x: DataRef?) {
|
||||
js("if (x === null) throw 'error'")
|
||||
}
|
||||
|
||||
fun null2DataRef(x: DataRef?) {
|
||||
js("if (x !== null) throw 'error'")
|
||||
}
|
||||
|
||||
fun testDataRef() {
|
||||
val dataRef = DataRef()
|
||||
notNullDataRef(dataRef)
|
||||
nullDataRef(dataRef)
|
||||
null2DataRef(null)
|
||||
}
|
||||
|
||||
fun notNullInt(x: Int) {
|
||||
js("if (x !== 123) throw 'error'")
|
||||
}
|
||||
@@ -133,47 +112,12 @@ fun testFloat() {
|
||||
null2Float(null)
|
||||
}
|
||||
|
||||
fun notNullNumber(x: Number) {
|
||||
js("if (x !== 123.5) throw 'error'")
|
||||
}
|
||||
|
||||
fun nullNumber(x: Number?) {
|
||||
js("if (x !== 123.5) throw 'error'")
|
||||
}
|
||||
|
||||
fun null2Number(x: Number?) {
|
||||
js("if (x !== null) throw 'error'")
|
||||
}
|
||||
|
||||
fun byte2Number(x: Number) {
|
||||
js("if (x !== 123) throw 'error'")
|
||||
}
|
||||
|
||||
fun notNullByte2Number(x: Number?) {
|
||||
js("if (x !== 123) throw 'error'")
|
||||
}
|
||||
|
||||
fun nullByte2Number(x: Number?) {
|
||||
js("if (x !== null) throw 'error'")
|
||||
}
|
||||
|
||||
fun testNumber() {
|
||||
notNullNumber(123.5)
|
||||
nullNumber(123.5)
|
||||
null2Number(null)
|
||||
byte2Number(123)
|
||||
notNullByte2Number(123)
|
||||
nullByte2Number(null)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
testString()
|
||||
testExterRef()
|
||||
testDataRef()
|
||||
testInt()
|
||||
testBoolean()
|
||||
testShort()
|
||||
testFloat()
|
||||
testNumber()
|
||||
return "OK"
|
||||
}
|
||||
@@ -72,10 +72,8 @@ external fun getFalseBoolean(): Boolean
|
||||
|
||||
external interface EI
|
||||
|
||||
external fun createJsObjectAsAny(): Any
|
||||
external fun createJsObjectAsExternalInterface(): EI
|
||||
external fun getObjectValueEI(x: EI): String
|
||||
external fun getObjectValueAny(x: Any): String
|
||||
|
||||
fun box(): String {
|
||||
// Strings
|
||||
@@ -98,15 +96,6 @@ fun box(): String {
|
||||
val objAsEI: EI = createJsObjectAsExternalInterface()
|
||||
if (getObjectValueEI(objAsEI) != "object created by createJsObjectAsExternalInterface")
|
||||
return "Fail createJsObjectAsExternalInterface + getObjectValueEI"
|
||||
if (getObjectValueAny(objAsEI) != "object created by createJsObjectAsExternalInterface")
|
||||
return "Fail createJsObjectAsExternalInterface + getObjectValueAny"
|
||||
|
||||
// Any
|
||||
val objAsAny: Any = createJsObjectAsAny()
|
||||
if (getObjectValueAny(objAsAny) != "object created by createJsObjectAsAny")
|
||||
return "Fail createJsObjectAsAny + getObjectValueAny"
|
||||
if (getObjectValueEI(objAsAny as EI) != "object created by createJsObjectAsAny")
|
||||
return "Fail createJsObjectAsAny + getObjectValueEI"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -9,7 +9,7 @@ fun funBlockBody(x: Int): Int {
|
||||
}
|
||||
|
||||
fun <!IMPLICIT_NOTHING_RETURN_TYPE!>returnTypeNotSepcified<!>() = js("1")
|
||||
val <!IMPLICIT_NOTHING_PROPERTY_TYPE!>valTypeNotSepcified<!> = js("1")
|
||||
<!WRONG_JS_INTEROP_TYPE!>val <!IMPLICIT_NOTHING_PROPERTY_TYPE!>valTypeNotSepcified<!><!> = js("1")
|
||||
|
||||
val a = "1"
|
||||
fun nonConst(): String = "1"
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// !OPT_IN: kotlin.js.ExperimentalJsExport
|
||||
|
||||
external interface EI
|
||||
external open class EC
|
||||
external object EO
|
||||
|
||||
external fun supportedTypes(
|
||||
boolean: Boolean,
|
||||
|
||||
byte: Byte,
|
||||
short: Short,
|
||||
int: Int,
|
||||
long: Long,
|
||||
|
||||
float: Float,
|
||||
double: Double,
|
||||
|
||||
char: Char,
|
||||
string: String,
|
||||
|
||||
ei: EI,
|
||||
ec: EC,
|
||||
eo: EO,
|
||||
|
||||
f1: (Boolean, Byte, Short, Int, Long, Float, Double, Char, String, EI, EC, EO) -> Unit,
|
||||
|
||||
f2: (Boolean) -> ((Int) -> ((Float) -> ((String) -> EI))),
|
||||
|
||||
f3: ((((Boolean) -> Int) -> Float) -> String) -> EI,
|
||||
): Unit
|
||||
|
||||
external fun supportedNullableTypes(
|
||||
boolean: Boolean?,
|
||||
|
||||
byte: Byte?,
|
||||
short: Short?,
|
||||
int: Int?,
|
||||
long: Long?,
|
||||
|
||||
float: Float?,
|
||||
double: Double?,
|
||||
|
||||
char: Char?,
|
||||
string: String?,
|
||||
|
||||
ei: EI?,
|
||||
ec: EC?,
|
||||
eo: EO?,
|
||||
|
||||
f1: ((Boolean?, Byte?, Short?, Int?, Long?, Float?, Double?, Char?, String?, EI?, EC?, EO?) -> Unit)?,
|
||||
|
||||
f2: ((Boolean?) -> ((Int?) -> ((Float?) -> ((String?) -> EI)?)?)?)?,
|
||||
|
||||
f3: ((((((((Boolean?) -> Int?)?) -> Float?)?) -> String?)?) -> EI?)?,
|
||||
): Unit
|
||||
|
||||
external fun supportedReturnTypeUnit(): Unit
|
||||
external fun supportedReturnTypeNothing(): Nothing
|
||||
external fun supportedReturnTypeBoolean(): Boolean
|
||||
external fun supportedReturnTypeNullableInt(): Int?
|
||||
external fun supportedReturnTypeEI(): EI
|
||||
external fun supportedReturnTypeNullableEC(): EC?
|
||||
|
||||
external fun <
|
||||
T1 : EI,
|
||||
T2 : EC?,
|
||||
T3 : T1?
|
||||
> supportedTypeParamtersUpperBounds(p1: T1, p2: T2): T3
|
||||
|
||||
|
||||
external fun wrongExternalTypes(
|
||||
<!WRONG_JS_INTEROP_TYPE!>any: Any<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>nany: Any?<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>unit: Unit<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>nunit: Unit?<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>nothing: Nothing<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>nnothing: Nothing?<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>charSequence: CharSequence<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>list: List<Int><!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>array: Array<Int><!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>intArray: IntArray<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>pair: Pair<Int, Int><!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>number: Number<!>,
|
||||
)
|
||||
|
||||
external fun <<!WRONG_JS_INTEROP_TYPE!>T<!>> supportedTypeParamtersUpperBounds(p: T): T where T : EI, T : Any
|
||||
|
||||
external fun <
|
||||
<!WRONG_JS_INTEROP_TYPE!>T1<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>T2 : Number<!>,
|
||||
<!WRONG_JS_INTEROP_TYPE!>T3: List<Int><!>,
|
||||
> supportedTypeParamtersUpperBounds(
|
||||
p1: T1,
|
||||
p2: T2
|
||||
): T3
|
||||
|
||||
<!WRONG_JS_INTEROP_TYPE!>fun jsCode1(<!WRONG_JS_INTEROP_TYPE!>x: Any<!>): Any<!> = js("x")
|
||||
<!WRONG_JS_INTEROP_TYPE!>fun jsCode2(<!WRONG_JS_INTEROP_TYPE!>x: Any<!>): Any<!> {
|
||||
js("return x;")
|
||||
}
|
||||
<!WRONG_JS_INTEROP_TYPE!>val jsProp: Any<!> = js("1")
|
||||
|
||||
<!WRONG_JS_INTEROP_TYPE!>@JsExport
|
||||
fun exported(<!WRONG_JS_INTEROP_TYPE!>x: Any<!>): Any<!> = x
|
||||
Generated
-6
@@ -55,12 +55,6 @@ public class FirJsCodegenWasmJsInteropTestGenerated extends AbstractFirJsCodegen
|
||||
runTest("compiler/testData/codegen/boxWasmJsInterop/jsCode.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("jsExport.kt")
|
||||
public void testJsExport() throws Exception {
|
||||
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("jsModule.kt")
|
||||
public void testJsModule() throws Exception {
|
||||
|
||||
Generated
-6
@@ -55,12 +55,6 @@ public class IrCodegenWasmJsInteropJsTestGenerated extends AbstractIrCodegenWasm
|
||||
runTest("compiler/testData/codegen/boxWasmJsInterop/jsCode.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("jsExport.kt")
|
||||
public void testJsExport() throws Exception {
|
||||
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("jsModule.kt")
|
||||
public void testJsModule() throws Exception {
|
||||
|
||||
@@ -29,6 +29,7 @@ object WasmPlatformConfigurator : PlatformConfiguratorBase(
|
||||
WasmExternalDeclarationChecker,
|
||||
WasmImportAnnotationChecker,
|
||||
WasmJsFunAnnotationChecker,
|
||||
WasmJsInteropTypesChecker,
|
||||
),
|
||||
additionalCallCheckers = listOf(
|
||||
JsModuleCallChecker,
|
||||
|
||||
+7
@@ -18,6 +18,13 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
|
||||
Renderers.RENDER_TYPE
|
||||
)
|
||||
|
||||
put(
|
||||
ErrorsWasm.WRONG_JS_INTEROP_TYPE, "Type {1} cannot be used in {0}. " +
|
||||
"Only external, primitive, string and function types are supported in Kotlin/Wasm JS interop.",
|
||||
CommonRenderers.STRING,
|
||||
Renderers.RENDER_TYPE
|
||||
)
|
||||
|
||||
put(ErrorsWasm.NESTED_WASM_IMPORT, "Only top-level functions can be imported with @WasmImport")
|
||||
put(ErrorsWasm.WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION, "Functions annotated with @WasmImport must be external")
|
||||
put(ErrorsWasm.WASM_IMPORT_PARAMETER_DEFAULT_VALUE, "Default parameter values are not supported with @WasmImport")
|
||||
|
||||
+4
-4
@@ -6,10 +6,7 @@
|
||||
package org.jetbrains.kotlin.wasm.resolve.diagnostics;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
|
||||
import org.jetbrains.kotlin.diagnostics.Errors;
|
||||
import org.jetbrains.kotlin.diagnostics.PositioningStrategies;
|
||||
import org.jetbrains.kotlin.diagnostics.*;
|
||||
import org.jetbrains.kotlin.psi.KtElement;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
|
||||
@@ -19,6 +16,9 @@ public interface ErrorsWasm {
|
||||
DiagnosticFactory1<KtElement, KotlinType> NON_EXTERNAL_TYPE_EXTENDS_EXTERNAL_TYPE =
|
||||
DiagnosticFactory1.create(ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
|
||||
DiagnosticFactory2<PsiElement, String, KotlinType>
|
||||
WRONG_JS_INTEROP_TYPE = DiagnosticFactory2.create(ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
|
||||
DiagnosticFactory0<PsiElement> NESTED_WASM_IMPORT = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> WASM_IMPORT_PARAMETER_DEFAULT_VALUE = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.wasm.resolve.diagnostics
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
||||
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
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.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.isNothing
|
||||
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||
import org.jetbrains.kotlin.wasm.util.hasValidJsCodeBody
|
||||
|
||||
// TODO: Implement in K2: KT-56849
|
||||
object WasmJsInteropTypesChecker : DeclarationChecker {
|
||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
if (descriptor !is MemberDescriptor)
|
||||
return
|
||||
|
||||
val trace = context.trace
|
||||
val bindingContext = trace.bindingContext
|
||||
|
||||
fun isExternalJsInteropDeclaration() =
|
||||
descriptor.isEffectivelyExternal() &&
|
||||
!descriptor.annotations.hasAnnotation(FqName("kotlin.wasm.WasmImport"))
|
||||
|
||||
fun isJsCodeDeclaration() =
|
||||
(descriptor is FunctionDescriptor && descriptor.hasValidJsCodeBody(bindingContext) ||
|
||||
descriptor is PropertyDescriptor && descriptor.hasValidJsCodeBody(bindingContext))
|
||||
|
||||
fun isJsExportDeclaration() =
|
||||
AnnotationsUtils.isExportedObject(descriptor, bindingContext)
|
||||
|
||||
if (
|
||||
!isExternalJsInteropDeclaration() &&
|
||||
!isJsCodeDeclaration() &&
|
||||
!isJsExportDeclaration()
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
fun KotlinType.checkJsInteropType(
|
||||
typePositionDescription: String,
|
||||
reportOn: PsiElement,
|
||||
isInFunctionReturnPosition: Boolean = false,
|
||||
) {
|
||||
if (!isTypeSupportedInJsInterop(this, isInFunctionReturnPosition)) {
|
||||
trace.report(ErrorsWasm.WRONG_JS_INTEROP_TYPE.on(reportOn, typePositionDescription, this))
|
||||
}
|
||||
}
|
||||
|
||||
fun TypeParameterDescriptor.checkJsInteropTypeParameter() {
|
||||
for (upperBound in this.upperBounds) {
|
||||
if (!isTypeSupportedInJsInterop(upperBound, isInFunctionReturnPosition = false)) {
|
||||
val reportOn = this.findPsi() ?: declaration
|
||||
trace.report(ErrorsWasm.WRONG_JS_INTEROP_TYPE.on(reportOn, "type parameter upper bound", upperBound))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (descriptor) {
|
||||
is ClassDescriptor -> {
|
||||
for (typeParameter in descriptor.declaredTypeParameters) {
|
||||
typeParameter.checkJsInteropTypeParameter()
|
||||
}
|
||||
}
|
||||
is PropertyDescriptor -> {
|
||||
for (typeParameter in descriptor.typeParameters) {
|
||||
typeParameter.checkJsInteropTypeParameter()
|
||||
}
|
||||
descriptor.type.checkJsInteropType("external property", declaration)
|
||||
}
|
||||
is FunctionDescriptor -> {
|
||||
for (typeParameter in descriptor.typeParameters) {
|
||||
typeParameter.checkJsInteropTypeParameter()
|
||||
}
|
||||
for (parameter in descriptor.valueParameters) {
|
||||
val typeToCheck = parameter.varargElementType ?: parameter.type
|
||||
typeToCheck.checkJsInteropType(
|
||||
"external function parameter",
|
||||
reportOn = parameter.findPsi() ?: declaration
|
||||
)
|
||||
}
|
||||
descriptor.returnType?.checkJsInteropType(
|
||||
"external function return",
|
||||
reportOn = declaration,
|
||||
isInFunctionReturnPosition = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTypeSupportedInJsInterop(
|
||||
type: KotlinType,
|
||||
isInFunctionReturnPosition: Boolean,
|
||||
): Boolean {
|
||||
if (type.isUnit() || type.isNothing()) {
|
||||
return isInFunctionReturnPosition
|
||||
}
|
||||
|
||||
val nonNullable = type.makeNotNullable()
|
||||
if (
|
||||
KotlinBuiltIns.isPrimitiveType(nonNullable) ||
|
||||
KotlinBuiltIns.isString(nonNullable)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Interop type parameters upper bounds should are checked
|
||||
// on declaration site separately
|
||||
if (nonNullable.isTypeParameter()) {
|
||||
return true
|
||||
}
|
||||
|
||||
val classifierDescriptor = nonNullable.constructor.declarationDescriptor
|
||||
if (classifierDescriptor is MemberDescriptor && classifierDescriptor.isEffectivelyExternal()) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (type.isFunctionType) {
|
||||
val arguments = type.arguments
|
||||
for (i in 0 until arguments.lastIndex) {
|
||||
val isArgumentSupported = isTypeSupportedInJsInterop(
|
||||
arguments[i].type,
|
||||
isInFunctionReturnPosition = false,
|
||||
)
|
||||
|
||||
if (!isArgumentSupported) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return isTypeSupportedInJsInterop(
|
||||
arguments.last().type, // Function type result type
|
||||
isInFunctionReturnPosition = true,
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
+6
@@ -68,6 +68,12 @@ public class DiagnosticsWasmTestGenerated extends AbstractDiagnosticsWasmTest {
|
||||
public void testJsFun() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/wasmTests/jsInterop/jsFun.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("types.kt")
|
||||
public void testTypes() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/wasmTests/jsInterop/types.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
Reference in New Issue
Block a user