[JS] Don't fail on recursive upper bounds while resolving JsExports
Fixes https://youtrack.jetbrains.com/issue/KT-42112
This commit is contained in:
+45
-33
@@ -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<KotlinType> = 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<KotlinType> = 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)
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -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)
|
||||
|
||||
+18
@@ -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)
|
||||
|
||||
+18
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
@@ -0,0 +1,28 @@
|
||||
// MODULE_KIND: COMMON_JS
|
||||
// SKIP_MINIFICATION
|
||||
|
||||
// FILE: api.kt
|
||||
package api
|
||||
@JsExport
|
||||
class Something<T: Something<T>> {
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user