[JS] Don't fail on recursive upper bounds while resolving JsExports

Fixes https://youtrack.jetbrains.com/issue/KT-42112
This commit is contained in:
Shagen Ogandzhanian
2020-11-23 17:59:03 +01:00
parent f9a032dbfa
commit 3282e092d8
6 changed files with 141 additions and 33 deletions
@@ -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)
}
}
@@ -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)
@@ -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)
@@ -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()
}