diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt index 2a9fafe6b5a..c9649eedc20 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt @@ -19,7 +19,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty import org.jetbrains.kotlin.resolve.inline.isInlineWithReified import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.TypeProjection import org.jetbrains.kotlin.types.isDynamic import org.jetbrains.kotlin.types.typeUtil.* @@ -30,7 +29,7 @@ object JsExportDeclarationChecker : DeclarationChecker { fun checkTypeParameter(descriptor: TypeParameterDescriptor) { for (upperBound in descriptor.upperBounds) { - if (!isTypeExportable(upperBound, bindingContext)) { + if (!upperBound.isExportable(bindingContext)) { val typeParameterDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor)!! trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(typeParameterDeclaration, "upper bound", upperBound)) } @@ -38,7 +37,7 @@ object JsExportDeclarationChecker : DeclarationChecker { } fun checkValueParameter(descriptor: ValueParameterDescriptor) { - if (!isTypeExportable(descriptor.type, bindingContext)) { + if (!descriptor.type.isExportable(bindingContext)) { val valueParameterDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor)!! trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(valueParameterDeclaration, "parameter", descriptor.type)) } @@ -86,7 +85,7 @@ object JsExportDeclarationChecker : DeclarationChecker { } descriptor.returnType?.let { returnType -> - if (!isTypeExportable(returnType, bindingContext, true)) { + if (!returnType.isExportableReturn(bindingContext)) { trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "return", returnType)) } } @@ -98,7 +97,7 @@ object JsExportDeclarationChecker : DeclarationChecker { reportWrongExportedDeclaration("extension property") return } - if (!isTypeExportable(descriptor.type, bindingContext)) { + if (!descriptor.type.isExportable(bindingContext)) { trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "property", descriptor.type)) } } @@ -122,7 +121,7 @@ object JsExportDeclarationChecker : DeclarationChecker { } for (superType in descriptor.defaultType.supertypes()) { - if (!isTypeExportable(superType, bindingContext)) { + if (!superType.isExportable(bindingContext)) { trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "super", superType)) } } @@ -130,45 +129,58 @@ object JsExportDeclarationChecker : DeclarationChecker { } } + private fun KotlinType.isExportableReturn(bindingContext: BindingContext, currentlyProcessed: MutableSet = mutableSetOf()) = + isUnit() || isExportable(bindingContext, currentlyProcessed) - private fun isTypeExportable(type: KotlinType, bindingContext: BindingContext, isReturnType: Boolean = false): Boolean { - if (isReturnType && type.isUnit()) + private fun KotlinType.isExportable( + bindingContext: BindingContext, + currentlyProcessed: MutableSet = mutableSetOf() + ): Boolean { + if (!currentlyProcessed.add(this)) { return true + } - if (type.isFunctionType) { - val arguments = type.arguments - val argumentsSize = type.arguments.size - 1 - for (i in 0 until argumentsSize) { - if (!isTypeExportable(arguments[i].type, bindingContext)) + currentlyProcessed.add(this) + + if (isFunctionType) { + for (i in 0 until arguments.lastIndex) { + if (!arguments[i].type.isExportable(bindingContext, currentlyProcessed)) { + currentlyProcessed.remove(this) return false + } } - if (!isTypeExportable(arguments.last().type, bindingContext, isReturnType = true)) - return false - return true + currentlyProcessed.remove(this) + return arguments.last().type.isExportableReturn(bindingContext, currentlyProcessed) } - for (argument: TypeProjection in type.arguments) { - if (!isTypeExportable(argument.type, bindingContext)) + for (argument in arguments) { + if (!argument.type.isExportable(bindingContext, currentlyProcessed)) { + currentlyProcessed.remove(this) return false + } } - val nonNullable = type.makeNotNullable() + currentlyProcessed.remove(this) - // Is primitive exportable type - if (nonNullable.isAnyOrNullableAny() || - nonNullable.isDynamic() || - nonNullable.isBoolean() || - KotlinBuiltIns.isThrowableOrNullableThrowable(nonNullable) || - KotlinBuiltIns.isString(nonNullable) || - (nonNullable.isPrimitiveNumberOrNullableType() && !nonNullable.isLong()) || - nonNullable.isNothingOrNullableNothing() || - (KotlinBuiltIns.isArray(type)) || - KotlinBuiltIns.isPrimitiveArray(type) - ) return true + val nonNullable = makeNotNullable() - val descriptor = type.constructor.declarationDescriptor ?: return false - return descriptor is MemberDescriptor && descriptor.isEffectivelyExternal() || - AnnotationsUtils.isExportedObject(descriptor, bindingContext) + val isPrimitiveExportableType = nonNullable.isAnyOrNullableAny() || + nonNullable.isDynamic() || + nonNullable.isBoolean() || + KotlinBuiltIns.isThrowableOrNullableThrowable(nonNullable) || + KotlinBuiltIns.isString(nonNullable) || + (nonNullable.isPrimitiveNumberOrNullableType() && !nonNullable.isLong()) || + nonNullable.isNothingOrNullableNothing() || + KotlinBuiltIns.isArray(this) || + KotlinBuiltIns.isPrimitiveArray(this) + + if (isPrimitiveExportableType) return true + + val descriptor = constructor.declarationDescriptor + + if (descriptor !is MemberDescriptor) return false + + return descriptor.isEffectivelyExternal() || AnnotationsUtils.isExportedObject(descriptor, bindingContext) } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java index 947af4adfc7..808d305870a 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java @@ -5155,6 +5155,24 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { } } + @TestMetadata("js/js.translator/testData/box/jsExport") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JsExport extends AbstractIrBoxJsES6Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath); + } + + public void testAllFilesPresentInJsExport() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsExport"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true); + } + + @TestMetadata("recursiveExport.kt") + public void testRecursiveExport() throws Exception { + runTest("js/js.translator/testData/box/jsExport/recursiveExport.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/jsModule") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index d5393fe7a60..14377b8240e 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -5155,6 +5155,24 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/jsExport") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JsExport extends AbstractIrBoxJsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInJsExport() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsExport"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @TestMetadata("recursiveExport.kt") + public void testRecursiveExport() throws Exception { + runTest("js/js.translator/testData/box/jsExport/recursiveExport.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/jsModule") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index b8f16ffd577..7ba15667bbe 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -5170,6 +5170,24 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/jsExport") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JsExport extends AbstractBoxJsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInJsExport() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsExport"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("recursiveExport.kt") + public void testRecursiveExport() throws Exception { + runTest("js/js.translator/testData/box/jsExport/recursiveExport.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/jsModule") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.translator/testData/box/jsExport/recursiveExport.js b/js/js.translator/testData/box/jsExport/recursiveExport.js new file mode 100644 index 00000000000..41ec29165c2 --- /dev/null +++ b/js/js.translator/testData/box/jsExport/recursiveExport.js @@ -0,0 +1,14 @@ +$kotlin_test_internal$.beginModule(); + +module.exports = function() { + var ping = require("JS_TESTS").api.ping; + var Something = require("JS_TESTS").api.Something; + + return { + "pingCall": function() { + return ping(new Something()) + }, + }; +}; + +$kotlin_test_internal$.endModule("lib"); diff --git a/js/js.translator/testData/box/jsExport/recursiveExport.kt b/js/js.translator/testData/box/jsExport/recursiveExport.kt new file mode 100644 index 00000000000..581390221ee --- /dev/null +++ b/js/js.translator/testData/box/jsExport/recursiveExport.kt @@ -0,0 +1,28 @@ +// MODULE_KIND: COMMON_JS +// SKIP_MINIFICATION + +// FILE: api.kt +package api +@JsExport +class Something> { + fun ping(): String { + return "OK" + } +} + +@JsExport +fun ping(s: Something<*>): String { + return s.ping() +} + +// FILE: main.kt +external interface JsResult { + val pingCall: () -> String +} + +@JsModule("lib") +external fun jsBox(): JsResult + +fun box(): String { + return jsBox().pingCall() +}