From 46526db8f0cfbdbf0f42132a3a2245a1388433f7 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 26 Sep 2017 12:55:53 +0300 Subject: [PATCH] JS: fix behaviour of char-returning functions with multiple inheritance See KT-19772 --- .../wrongMultipleInheritance.kt | 25 +++++++ .../wrongMultipleInheritance.txt | 56 +++++++++++++++ .../DiagnosticsTestWithJsStdLibGenerated.java | 6 ++ .../js/resolve/JsPlatformConfigurator.kt | 2 +- .../diagnostics/DefaultErrorMessagesJs.kt | 4 ++ .../js/resolve/diagnostics/ErrorsJs.java | 4 ++ .../JsMultipleInheritanceChecker.kt | 49 +++++++++++++ .../js/test/semantics/BoxJsTestGenerated.java | 24 +++++++ .../callTranslator/CallTranslator.kt | 4 +- .../initializer/InitializerVisitor.java | 4 ++ .../reference/ReferenceTranslator.java | 5 ++ .../js/translate/utils/TranslationUtils.java | 59 +++++++++++++--- .../testData/box/char/topLevelCallables.kt | 32 +++++++++ .../testData/box/char/unboxedCharSpecials.kt | 24 +++++++ .../testData/box/coercion/bridgeChar.kt | 41 +++++++++++ .../box/coercion/propertyBridgeChar.kt | 70 +++++++++++++++++++ .../box/expression/stringClass/subSequence.kt | 9 +-- 17 files changed, 401 insertions(+), 17 deletions(-) create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.txt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsMultipleInheritanceChecker.kt create mode 100644 js/js.translator/testData/box/char/topLevelCallables.kt create mode 100644 js/js.translator/testData/box/char/unboxedCharSpecials.kt create mode 100644 js/js.translator/testData/box/coercion/bridgeChar.kt create mode 100644 js/js.translator/testData/box/coercion/propertyBridgeChar.kt diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.kt b/compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.kt new file mode 100644 index 00000000000..5f79668bc00 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.kt @@ -0,0 +1,25 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +open class A { + fun get(index: Int): Char = '*' +} + +abstract class B : A(), CharSequence + +interface I { + fun nextChar(): Char +} + +abstract class C : CharIterator(), I { + override fun nextChar(): Char = '*' +} + +class CC(val s: CharSequence) : CharSequence by s, MyCharSequence {} + +interface MyCharSequence { + val length: Int + + operator fun get(index: Int): Char + + fun subSequence(startIndex: Int, endIndex: Int): CharSequence +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.txt b/compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.txt new file mode 100644 index 00000000000..30c56ea4cea --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.txt @@ -0,0 +1,56 @@ +package + +public open class A { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun get(/*0*/ index: kotlin.Int): kotlin.Char + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public abstract class B : A, kotlin.CharSequence { + public constructor B() + public abstract override /*1*/ /*fake_override*/ val length: kotlin.Int + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*2*/ /*fake_override*/ fun get(/*0*/ index: kotlin.Int): kotlin.Char + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public abstract override /*1*/ /*fake_override*/ fun subSequence(/*0*/ startIndex: kotlin.Int, /*1*/ endIndex: kotlin.Int): kotlin.CharSequence + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public abstract class C : kotlin.collections.CharIterator, I { + public constructor C() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract override /*1*/ /*fake_override*/ fun hasNext(): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun next(): kotlin.Char + public open override /*2*/ fun nextChar(): kotlin.Char + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class CC : kotlin.CharSequence, MyCharSequence { + public constructor CC(/*0*/ s: kotlin.CharSequence) + public open override /*2*/ /*delegation*/ val length: kotlin.Int + public final val s: kotlin.CharSequence + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*delegation*/ fun get(/*0*/ index: kotlin.Int): kotlin.Char + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*delegation*/ fun subSequence(/*0*/ startIndex: kotlin.Int, /*1*/ endIndex: kotlin.Int): kotlin.CharSequence + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface I { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public abstract fun nextChar(): kotlin.Char + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface MyCharSequence { + public abstract val length: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract operator fun get(/*0*/ index: kotlin.Int): kotlin.Char + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public abstract fun subSequence(/*0*/ startIndex: kotlin.Int, /*1*/ endIndex: kotlin.Int): kotlin.CharSequence + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java index dd2b81ba1da..3e3634c2df1 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -66,6 +66,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes doTest(fileName); } + @TestMetadata("wrongMultipleInheritance.kt") + public void testWrongMultipleInheritance() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/wrongMultipleInheritance.kt"); + doTest(fileName); + } + @TestMetadata("compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt index f5d4dbfae10..a2b4c6f6621 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt @@ -35,7 +35,7 @@ object JsPlatformConfigurator : PlatformConfigurator( additionalDeclarationCheckers = listOf( NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(), JsNameChecker, JsModuleChecker, JsExternalFileChecker, - JsExternalChecker, JsInheritanceChecker, + JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker, JsRuntimeAnnotationChecker, JsDynamicDeclarationChecker, ExpectedActualDeclarationChecker diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt index fcad2c56f7b..2c91181b1a0 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt @@ -104,6 +104,10 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { put(ErrorsJs.EXTERNAL_CLASS_CONSTRUCTOR_PROPERTY_PARAMETER, "External class constructor cannot have a property parameter") put(ErrorsJs.CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION, "This property can only be used from external declarations") + put(ErrorsJs.WRONG_MULTIPLE_INHERITANCE, + "Can't apply multiple inheritance here, since it's impossible to generate bridge for system function {0}", + Renderers.DECLARATION_NAME_WITH_KIND) + this } } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java index 43dd6903a02..cc5cf0bff08 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics; import com.intellij.psi.PsiElement; +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.FunctionDescriptor; import org.jetbrains.kotlin.diagnostics.*; @@ -109,6 +110,9 @@ public interface ErrorsJs { DiagnosticFactory0 EXTERNAL_CLASS_CONSTRUCTOR_PROPERTY_PARAMETER = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 WRONG_MULTIPLE_INHERITANCE = + DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); + @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsMultipleInheritanceChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsMultipleInheritanceChecker.kt new file mode 100644 index 00000000000..5270b6c582b --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsMultipleInheritanceChecker.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.resolve.diagnostics + +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.name.FqNameUnsafe +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe + +object JsMultipleInheritanceChecker : SimpleDeclarationChecker { + private val fqNames = listOf( + FqNameUnsafe("kotlin.CharSequence.get"), + FqNameUnsafe("kotlin.collections.CharIterator.nextChar") + ) + private val simpleNames = fqNames.mapTo(mutableSetOf()) { it.shortName() } + + override fun check( + declaration: KtDeclaration, descriptor: DeclarationDescriptor, + diagnosticHolder: DiagnosticSink, bindingContext: BindingContext + ) { + if (descriptor !is ClassDescriptor) return + + for (callable in descriptor.unsubstitutedMemberScope.getContributedDescriptors { it in simpleNames } + .filterIsInstance()) { + if (callable.overriddenDescriptors.size > 1 && callable.overriddenDescriptors.any { it.fqNameUnsafe in fqNames }) { + diagnosticHolder.report(ErrorsJs.WRONG_MULTIPLE_INHERITANCE.on(declaration, callable)) + } + } + } +} \ No newline at end of file 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 21e087ae36a..f5a3f143cf7 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 @@ -445,6 +445,18 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/char/charUnaryOperations.kt"); doTest(fileName); } + + @TestMetadata("topLevelCallables.kt") + public void testTopLevelCallables() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/char/topLevelCallables.kt"); + doTest(fileName); + } + + @TestMetadata("unboxedCharSpecials.kt") + public void testUnboxedCharSpecials() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/char/unboxedCharSpecials.kt"); + doTest(fileName); + } } @TestMetadata("js/js.translator/testData/box/classObject") @@ -803,6 +815,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/coercion"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true); } + @TestMetadata("bridgeChar.kt") + public void testBridgeChar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/coercion/bridgeChar.kt"); + doTest(fileName); + } + @TestMetadata("classProperty.kt") public void testClassProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/coercion/classProperty.kt"); @@ -857,6 +875,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("propertyBridgeChar.kt") + public void testPropertyBridgeChar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/coercion/propertyBridgeChar.kt"); + doTest(fileName); + } + @TestMetadata("receiverSmartCast.kt") public void testReceiverSmartCast() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/coercion/receiverSmartCast.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt index 87921331733..79f37af594c 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt @@ -65,7 +65,9 @@ object CallTranslator { value: JsExpression, extensionOrDispatchReceiver: JsExpression? = null ): JsExpression { - val variableAccessInfo = VariableAccessInfo(context.getCallInfo(resolvedCall, extensionOrDispatchReceiver), value) + val type = TranslationUtils.getReturnTypeForCoercion(resolvedCall.resultingDescriptor) + val coerceValue = TranslationUtils.coerce(context, value, type) + val variableAccessInfo = VariableAccessInfo(context.getCallInfo(resolvedCall, extensionOrDispatchReceiver), coerceValue) val result = variableAccessInfo.translateVariableAccess().source(resolvedCall.call.callElement) result.type = context.currentModule.builtIns.unitType return result diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/InitializerVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/InitializerVisitor.java index 898fd42a937..4cf5950219d 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/InitializerVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/InitializerVisitor.java @@ -51,6 +51,10 @@ public final class InitializerVisitor extends TranslatorVisitor { KtExpression delegate = property.getDelegateExpression(); if (initializer != null) { assert value != null; + KotlinType type = TranslationUtils.isReferenceToSyntheticBackingField(descriptor) ? + descriptor.getType() : + TranslationUtils.getReturnTypeForCoercion(descriptor); + value = TranslationUtils.coerce(context, value, type); statement = generateInitializerForProperty(context, descriptor, value); } else if (delegate != null) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/ReferenceTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/ReferenceTranslator.java index 8f11061a2d6..fa64778cf16 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/ReferenceTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/ReferenceTranslator.java @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils; import org.jetbrains.kotlin.name.FqNameUnsafe; import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.KtQualifiedExpression; @@ -84,6 +85,10 @@ public final class ReferenceTranslator { if (parameter.getContainingDeclaration() instanceof AnonymousFunctionDescriptor) { return DescriptorUtils.getContainingModule(descriptor).getBuiltIns().getAnyType(); } + if (parameter.getContainingDeclaration() instanceof PropertySetterDescriptor) { + PropertySetterDescriptor setter = (PropertySetterDescriptor) parameter.getContainingDeclaration(); + return TranslationUtils.getReturnTypeForCoercion(setter.getCorrespondingProperty(), false); + } } return ((CallableDescriptor) descriptor).getReturnType(); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java index f8723abd65e..17205d39813 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java @@ -39,6 +39,7 @@ import org.jetbrains.kotlin.js.translate.general.Translation; import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator; import org.jetbrains.kotlin.name.ClassId; import org.jetbrains.kotlin.name.FqName; +import org.jetbrains.kotlin.name.FqNameUnsafe; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext; @@ -58,6 +59,13 @@ import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDe import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*; public final class TranslationUtils { + private static final Set CLASSES_WITH_NON_BOXED_CHARS = new HashSet<>(Arrays.asList( + new FqNameUnsafe("kotlin.collections.CharIterator"), + new FqNameUnsafe("kotlin.ranges.CharProgression"), + new FqNameUnsafe("kotlin.js.internal.CharCompanionObject"), + new FqNameUnsafe("kotlin.Char.Companion"), + KotlinBuiltIns.FQ_NAMES.charSequence, KotlinBuiltIns.FQ_NAMES.number + )); private TranslationUtils() { } @@ -172,17 +180,21 @@ public final class TranslationUtils { @NotNull public static JsName getNameForBackingField(@NotNull TranslationContext context, @NotNull PropertyDescriptor descriptor) { - DeclarationDescriptor containingDescriptor = descriptor.getContainingDeclaration(); - - if (!JsDescriptorUtils.isSimpleFinalProperty(descriptor) && !(containingDescriptor instanceof PackageFragmentDescriptor)) { + if (isReferenceToSyntheticBackingField(descriptor)) { return context.getNameForBackingField(descriptor); } + DeclarationDescriptor containingDescriptor = descriptor.getContainingDeclaration(); return containingDescriptor instanceof PackageFragmentDescriptor ? context.getInnerNameForDescriptor(descriptor) : context.getNameForDescriptor(descriptor); } + public static boolean isReferenceToSyntheticBackingField(@NotNull PropertyDescriptor descriptor) { + DeclarationDescriptor containingDescriptor = descriptor.getContainingDeclaration(); + return !JsDescriptorUtils.isSimpleFinalProperty(descriptor) && !(containingDescriptor instanceof PackageFragmentDescriptor); + } + @NotNull public static JsNameRef backingFieldReference(@NotNull TranslationContext context, @NotNull PropertyDescriptor descriptor) { DeclarationDescriptor containingDescriptor = descriptor.getContainingDeclaration(); @@ -196,7 +208,7 @@ public final class TranslationUtils { } JsNameRef result = new JsNameRef(getNameForBackingField(context, descriptor), receiver); - MetadataProperties.setType(result, getReturnTypeForCoercion(descriptor)); + MetadataProperties.setType(result, getReturnTypeForCoercion(descriptor, true)); return result; } @@ -396,25 +408,50 @@ public final class TranslationUtils { @NotNull public static KotlinType getReturnTypeForCoercion(@NotNull CallableDescriptor descriptor) { + return getReturnTypeForCoercion(descriptor, false); + } + + @NotNull + public static KotlinType getReturnTypeForCoercion(@NotNull CallableDescriptor descriptor, boolean forcePrivate) { descriptor = descriptor.getOriginal(); if (FunctionTypesKt.getFunctionalClassKind(descriptor) != null || descriptor instanceof AnonymousFunctionDescriptor) { - return DescriptorUtils.getContainingModule(descriptor).getBuiltIns().getAnyType(); + return getAnyTypeFromSameModule(descriptor); } Collection overridden = descriptor.getOverriddenDescriptors(); if (overridden.isEmpty()) { - return descriptor.getReturnType() != null ? - descriptor.getReturnType() : - DescriptorUtils.getContainingModule(descriptor).getBuiltIns().getAnyType(); + KotlinType returnType = descriptor.getReturnType(); + if (returnType == null) { + return getAnyTypeFromSameModule(descriptor); + } + + DeclarationDescriptor container = descriptor.getContainingDeclaration(); + boolean isPublic = descriptor.getVisibility().effectiveVisibility(descriptor, true).getPublicApi() && !forcePrivate; + if (KotlinBuiltIns.isCharOrNullableChar(returnType) && container instanceof ClassDescriptor && isPublic) { + ClassDescriptor containingClass = (ClassDescriptor) container; + FqNameUnsafe containingClassName = DescriptorUtilsKt.getFqNameUnsafe(containingClass); + if (!CLASSES_WITH_NON_BOXED_CHARS.contains(containingClassName) && + !KotlinBuiltIns.isPrimitiveType(containingClass.getDefaultType()) && + !KotlinBuiltIns.isPrimitiveArray(containingClassName) + ) { + return getAnyTypeFromSameModule(descriptor); + } + } + return returnType; } Set typesFromOverriddenCallables = overridden.stream() - .map(TranslationUtils::getReturnTypeForCoercion) + .map(o -> getReturnTypeForCoercion(o, forcePrivate)) .collect(Collectors.toSet()); return typesFromOverriddenCallables.size() == 1 ? typesFromOverriddenCallables.iterator().next() - : DescriptorUtils.getContainingModule(descriptor).getBuiltIns().getAnyType(); + : getAnyTypeFromSameModule(descriptor); + } + + @NotNull + private static KotlinType getAnyTypeFromSameModule(@NotNull DeclarationDescriptor descriptor) { + return DescriptorUtils.getContainingModule(descriptor).getBuiltIns().getAnyType(); } @NotNull @@ -435,7 +472,7 @@ public final class TranslationUtils { .collect(Collectors.toSet()); return typesFromOverriddenCallables.size() == 1 ? typesFromOverriddenCallables.iterator().next() - : DescriptorUtils.getContainingModule(descriptor).getBuiltIns().getAnyType(); + : getAnyTypeFromSameModule(descriptor); } @NotNull diff --git a/js/js.translator/testData/box/char/topLevelCallables.kt b/js/js.translator/testData/box/char/topLevelCallables.kt new file mode 100644 index 00000000000..32a99cb3512 --- /dev/null +++ b/js/js.translator/testData/box/char/topLevelCallables.kt @@ -0,0 +1,32 @@ +// SKIP_MINIFICATION +fun foo(): Char = '1' + +val p1: Char = '2' + +var p2: Char = '3' + +var p3: Char = '4' + get() = field + 1 + set(value) { + field = value + 1 + } + +fun box(): String { + var root = eval("_") + + var r = typeOf(root.foo()) + if (r !== "number") return "fail1: $r" + + r = typeOf(root.p1) + if (r !== "number") return "fail2: $r" + + r = typeOf(root.p2) + if (r !== "number") return "fail3: $r" + + r = typeOf(root.p3) + if (r !== "number") return "fail4: $r" + + return "OK" +} + +fun typeOf(x: dynamic): String = js("typeof x") \ No newline at end of file diff --git a/js/js.translator/testData/box/char/unboxedCharSpecials.kt b/js/js.translator/testData/box/char/unboxedCharSpecials.kt new file mode 100644 index 00000000000..15f8032ba4b --- /dev/null +++ b/js/js.translator/testData/box/char/unboxedCharSpecials.kt @@ -0,0 +1,24 @@ +// EXPECTED_REACHABLE_NODES: 1099 +private inline fun typeOf(x: dynamic): String = js("typeof x").unsafeCast() + +fun box(): String { + val arr = charArrayOf('A') + + var r = typeOf(arr.iterator().asDynamic().nextChar()) + if (r != "number") return "fail1: $r" + r = typeOf(arr.iterator().asDynamic().next()) + if (r != "object") return "fail1a: $r" + + var progression = 'A'..'Z' + r = typeOf(progression.asDynamic().first) + if (r != "number") return "fail2: $r" + r = typeOf(progression.asDynamic().last) + if (r != "number") return "fail3: $r" + + r = typeOf(Char.asDynamic().MIN_HIGH_SURROGATE) + if (r != "number") return "fail4: $r" + + return "OK" +} + +fun getInt() = 65 \ No newline at end of file diff --git a/js/js.translator/testData/box/coercion/bridgeChar.kt b/js/js.translator/testData/box/coercion/bridgeChar.kt new file mode 100644 index 00000000000..ff085fe95a7 --- /dev/null +++ b/js/js.translator/testData/box/coercion/bridgeChar.kt @@ -0,0 +1,41 @@ +// EXPECTED_REACHABLE_NODES: 1039 +open class A { + fun foo(): Char = 'X' +} + +interface I { + fun foo(): Any +} + +class B : A(), I + +fun typeOf(x: dynamic): String = js("typeof x") + +fun box(): String { + val b = B() + val i: I = B() + val a: A = B() + + val r1 = typeOf(b.asDynamic().foo()) + if (r1 != "object") return "fail1: $r1" + + val r2 = typeOf(i.asDynamic().foo()) + if (r2 != "object") return "fail2: $r2" + + val r3 = typeOf(a.asDynamic().foo()) + if (r3 != "object") return "fail3: $r3" + + val x4 = b.foo() + val r4 = typeOf(x4) + if (r4 != "number") return "fail4: $r4" + + val x5 = i.foo() + val r5 = typeOf(x5) + if (r5 != "object") return "fail5: $r5" + + val x6 = a.foo() + val r6 = typeOf(x6) + if (r6 != "number") return "fail6: $r6" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/coercion/propertyBridgeChar.kt b/js/js.translator/testData/box/coercion/propertyBridgeChar.kt new file mode 100644 index 00000000000..a648c8adafc --- /dev/null +++ b/js/js.translator/testData/box/coercion/propertyBridgeChar.kt @@ -0,0 +1,70 @@ +// EXPECTED_REACHABLE_NODES: 1039 +open class A { + val foo: Char + get() = 'X' + + var bar: Char = 'Y' + + val baz: Char = 'Q' + + var mutable: Char = 'W' + get() { + typeOfMutable += typeOf(field.asDynamic()) + ";" + return field + 1 + } + set(value) { + typeOfMutable += typeOf(js("value")) + ";" + typeOf(value) + field = value + } +} + +interface I { + val foo: Any + + val bar: Any + + val baz: Any + + val mutable: Any +} + +class B : A(), I + +fun typeOf(x: dynamic): String = js("typeof x") + +var typeOfMutable = "" + +fun box(): String { + val a = B() + val b: I = B() + + val r1 = typeOf(a.foo) + if (r1 != "number") return "fail1: $r1" + + val r2 = typeOf(b.foo) + if (r2 != "object") return "fail2: $r2" + + val r3 = typeOf(a.asDynamic().foo) + if (r3 != "object") return "fail3: $r3" + + val r4 = typeOf(a.asDynamic().bar) + if (r4 != "object") return "fail4: $r4" + + val r5 = typeOf(a.asDynamic().baz) + if (r5 != "object") return "fail5: $r5" + + a.bar++ + val r6 = typeOf(a.asDynamic().bar) + if (r6 != "object") return "fail6: $r6" + + val r7 = typeOf(a.asDynamic().mutable) + if (r7 != "object") return "fail7: $r7" + + a.mutable = 'E' + if (typeOfMutable != "number;object;number") return "fail8: $typeOfMutable" + + val r9 = typeOf(a.mutable) + if (r9 != "number") return "fail9: $r9" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/expression/stringClass/subSequence.kt b/js/js.translator/testData/box/expression/stringClass/subSequence.kt index be53d266739..283625047e7 100644 --- a/js/js.translator/testData/box/expression/stringClass/subSequence.kt +++ b/js/js.translator/testData/box/expression/stringClass/subSequence.kt @@ -6,7 +6,8 @@ class CC(val s: CharSequence) : CharSequence by s, MyCharSequence {} interface MyCharSequence { val length: Int - operator fun get(index: Int): Char + // TODO: uncomment when it's possible to implement bridges for get/charCodeAt + //operator fun get(index: Int): Char fun subSequence(startIndex: Int, endIndex: Int): CharSequence } @@ -25,17 +26,17 @@ fun box(): String { val cc: CharSequence = CC(kotlin) if (cc.length != 6) return "Fail 3: ${cc.length}" if (cc.subSequence(0, 3) != kot) return "Fail 4" - if (cc[2] != 't') return "Fail 5: ${cc[2]}" + //if (cc[2] != 't') return "Fail 5: ${cc[2]}" val mcc: MyCharSequence = CC(kotlin) if (mcc.length != 6) return "Fail 6: ${mcc.length}" if (mcc.subSequence(0, 3) != kot) return "Fail 7" - if (mcc[2] != 't') return "Fail 8: ${mcc[2]}" + //if (mcc[2] != 't') return "Fail 8: ${mcc[2]}" val ccc = CC(cc) if (ccc.length != 6) return "Fail 6: ${ccc.length}" if (ccc.subSequence(0, 3) != kot) return "Fail 7" - if (ccc[2] != 't') return "Fail 8: ${ccc[2]}" + //if (ccc[2] != 't') return "Fail 8: ${ccc[2]}" return "OK" }