From 6e3d3831c2729683484a24d050c01f744d4e2d12 Mon Sep 17 00:00:00 2001 From: Svyatoslav Kuzmich Date: Thu, 29 Aug 2019 18:25:09 +0300 Subject: [PATCH] [JS] JsExport diagnostics and legacy support Account for JsExport in legacy backend namer. It means we catch overloaded exported function conflicts for free! Add error diagnostics: * NESTED_JS_EXPORT (Fixes KT-36798) * WRONG_EXPORTED_DECLARATION (Part of the fix for KT-37752) * NON_EXPORTABLE_TYPE (Fixes KT-37771) --- .../export/extendingNonExportedType.kt | 26 +++ .../export/extendingNonExportedType.txt | 58 ++++++ .../export/jsExportOnNestedDeclarations.kt | 22 +++ .../export/jsExportOnNestedDeclarations.txt | 21 +++ .../secondaryConstructorWithoutJsName.kt | 15 ++ .../secondaryConstructorWithoutJsName.txt | 22 +++ .../export/unexportableTypesInSignature.kt | 56 ++++++ .../export/unexportableTypesInSignature.txt | 31 ++++ .../unexportableTypesInTypeParameters.kt | 17 ++ .../unexportableTypesInTypeParameters.txt | 31 ++++ .../export/wrongExportedDeclaration.kt | 20 ++ .../export/wrongExportedDeclaration.txt | 33 ++++ .../wrongExportedDeclarationInExportedFile.kt | 17 ++ ...wrongExportedDeclarationInExportedFile.txt | 33 ++++ .../DiagnosticsTestWithJsStdLibGenerated.java | 48 +++++ .../kotlin/js/naming/NameSuggestion.kt | 11 +- .../js/resolve/JsPlatformConfigurator.kt | 13 +- .../diagnostics/DefaultErrorMessagesJs.kt | 4 + .../js/resolve/diagnostics/ErrorsJs.java | 4 + .../diagnostics/JsBuiltinNameClashChecker.kt | 3 +- .../diagnostics/JsExportAnnotationChecker.kt | 28 +++ .../diagnostics/JsExportDeclarationChecker.kt | 173 ++++++++++++++++++ .../resolve/diagnostics/JsNameCharsChecker.kt | 15 +- .../resolve/diagnostics/JsNameClashChecker.kt | 4 +- .../js/translate/utils/AnnotationsUtils.java | 30 ++- .../kotlin/js/inline/FunctionReader.kt | 8 +- .../jetbrains/kotlin/js/inline/JsInliner.kt | 2 + .../context/FunctionDefinitionLoader.kt | 6 +- .../generators/tests/GenerateJsTests.kt | 5 + .../AbstractLegacyJsTypeScriptExportTest.kt | 14 ++ ...LegacyJsTypeScriptExportTestGenerated.java | 139 ++++++++++++++ .../kotlin/js/facade/K2JSTranslator.kt | 1 + .../kotlin/js/translate/context/Namer.java | 26 +-- .../js/translate/context/StaticContext.java | 5 +- .../translate/context/TranslationContext.java | 4 +- .../js/translate/context/UsageTracker.kt | 6 +- .../js/translate/expression/InlineMetadata.kt | 2 +- .../testData/box/char/topLevelCallables.kt | 7 +- .../testData/box/coercion/charValParameter.kt | 1 - .../testData/box/coercion/defaultAccessors.kt | 1 - .../box/coercion/propertyBridgeChar.kt | 1 - .../box/inline/inlinedObjectLiteralIsCheck.kt | 1 + .../box/propertyAccess/accessorsWithJsName.kt | 1 - .../declarations/declarations.kt | 3 +- .../inheritance/inheritance.kt | 3 +- .../namespaces/namespaces.kt | 3 +- .../primitives/primitives.d.ts | 1 - .../primitives/primitives.kt | 5 +- .../selectiveExport/selectiveExport.kt | 3 +- .../visibility/visibility.kt | 3 +- .../common/src/kotlin/JsAnnotationsH.kt | 29 ++- libraries/stdlib/js/src/kotlin/annotations.kt | 29 ++- 52 files changed, 972 insertions(+), 72 deletions(-) create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.txt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.txt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.txt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.txt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.txt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.txt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportAnnotationChecker.kt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/AbstractLegacyJsTypeScriptExportTest.kt create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LegacyJsTypeScriptExportTestGenerated.java diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt new file mode 100644 index 00000000000..02c05ae0ad2 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt @@ -0,0 +1,26 @@ +// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport +// !RENDER_DIAGNOSTICS_MESSAGES + +package foo + +open class NonExportedClass + +@JsExport +class ExportedClass : NonExportedClass() + +interface NonExportedInterface + +@JsExport +class ExportedClass2 : NonExportedInterface + +@JsExport +open class ExportedGenericClass + +@JsExport +class ")!>ExportedClass3 : ExportedGenericClass() + +@JsExport +interface ExportedGenericInterface + +@JsExport +class ")!>ExportedClass4 : ExportedGenericInterface \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.txt new file mode 100644 index 00000000000..773f74f1aa0 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.txt @@ -0,0 +1,58 @@ +package + +package foo { + + @kotlin.js.JsExport public final class ExportedClass : foo.NonExportedClass { + public constructor ExportedClass() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsExport public final class ExportedClass2 : foo.NonExportedInterface { + public constructor ExportedClass2() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsExport public final class ExportedClass3 : foo.ExportedGenericClass { + public constructor ExportedClass3() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsExport public final class ExportedClass4 : foo.ExportedGenericInterface { + public constructor ExportedClass4() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsExport public open class ExportedGenericClass { + public constructor ExportedGenericClass() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsExport public interface ExportedGenericInterface { + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public open class NonExportedClass { + public constructor NonExportedClass() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public interface NonExportedInterface { + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.kt new file mode 100644 index 00000000000..76bb54b083e --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.kt @@ -0,0 +1,22 @@ +// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport + +package foo + +class C1 { + @JsExport + fun f1() {} + + @JsExport + val p: Int = 10 + + @JsExport + object O +} + +fun f2() { + @JsExport + fun f3() {} + + @JsExport + class C2 +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.txt new file mode 100644 index 00000000000..df992d77393 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.txt @@ -0,0 +1,21 @@ +package + +package foo { + public fun f2(): kotlin.Unit + + public final class C1 { + public constructor C1() + @kotlin.js.JsExport public final val p: kotlin.Int = 10 + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsExport public final fun f1(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + @kotlin.js.JsExport public object O { + private constructor O() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.kt new file mode 100644 index 00000000000..8f7dcf80cb0 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.kt @@ -0,0 +1,15 @@ +// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport +// !RENDER_DIAGNOSTICS_MESSAGES + +package foo + +@JsExport +class C(val x: String) { + constructor(x: Int): this(x.toString()) +} + +@JsExport +class C2(val x: String) { + @JsName("JsNameProvided") + constructor(x: Int): this(x.toString()) +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.txt new file mode 100644 index 00000000000..8620cc983ec --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.txt @@ -0,0 +1,22 @@ +package + +package foo { + + @kotlin.js.JsExport public final class C { + public constructor C(/*0*/ x: kotlin.Int) + public constructor C(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsExport public final class C2 { + @kotlin.js.JsName(name = "JsNameProvided") public constructor C2(/*0*/ x: kotlin.Int) + public constructor C2(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt new file mode 100644 index 00000000000..43040c58f74 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt @@ -0,0 +1,56 @@ +// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport +// !DIAGNOSTICS: -UNUSED_PARAMETER +// !RENDER_DIAGNOSTICS_MESSAGES + +package foo + +class C + +@JsExport +fun foo(x: C) { +} + +@JsExport +fun bar() = C() + +@JsExport +val x: C = C() + +@JsExport +var x2: C + get() = C() + set(value) { } + +@JsExport +class A( + val x: C, + y: C +) { + fun foo(x: C) = x + + val x2: C = C() + + var x3: C + get() = C() + set(value) { } +} + +@JsExport +fun foo2() { +} + +@JsExport +fun foo3(x: Unit) { +} + +@JsExport +fun foo4(x: () -> Unit) { +} + +@JsExport +fun foo5( Unit")!>x: (Unit) -> Unit) { +} + +@JsExport +fun foo6(x: (A) -> A) { +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.txt new file mode 100644 index 00000000000..b6454f8e59d --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.txt @@ -0,0 +1,31 @@ +package + +package foo { + @kotlin.js.JsExport public val x: foo.C + @kotlin.js.JsExport public var x2: foo.C + @kotlin.js.JsExport public fun bar(): foo.C + @kotlin.js.JsExport public fun foo(/*0*/ x: foo.C): kotlin.Unit + @kotlin.js.JsExport public fun foo2(): kotlin.Unit + @kotlin.js.JsExport public fun foo3(/*0*/ x: kotlin.Unit): kotlin.Unit + @kotlin.js.JsExport public fun foo4(/*0*/ x: () -> kotlin.Unit): kotlin.Unit + @kotlin.js.JsExport public fun foo5(/*0*/ x: (kotlin.Unit) -> kotlin.Unit): kotlin.Unit + @kotlin.js.JsExport public fun foo6(/*0*/ x: (foo.A) -> foo.A): kotlin.Unit + + @kotlin.js.JsExport public final class A { + public constructor A(/*0*/ x: foo.C, /*1*/ y: foo.C) + public final val x: foo.C + public final val x2: foo.C + public final var x3: foo.C + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(/*0*/ x: foo.C): foo.C + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class C { + public constructor C() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt new file mode 100644 index 00000000000..3c61dd81c6d --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt @@ -0,0 +1,17 @@ +// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport +// !DIAGNOSTICS: -UNUSED_PARAMETER +// !RENDER_DIAGNOSTICS_MESSAGES + +package foo + +abstract class C +interface I + +@JsExport +fun <T : C>foo() { } + +@JsExport +class A<T : C, S: I> + +@JsExport +interface I2<T> where T : C, T : I \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.txt new file mode 100644 index 00000000000..618c24e34d9 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.txt @@ -0,0 +1,31 @@ +package + +package foo { + @kotlin.js.JsExport public fun foo(): kotlin.Unit + + @kotlin.js.JsExport public final class A { + public constructor A() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public abstract class C { + public constructor C() + 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 open override /*1*/ /*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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsExport public interface I2 where T : foo.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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt new file mode 100644 index 00000000000..09806d4c83b --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt @@ -0,0 +1,20 @@ +// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport +// !RENDER_DIAGNOSTICS_MESSAGES + +package foo + +@JsExport +inline fun inlineReifiedFun(x: Any) = x is T + +@JsExport +suspend fun suspendFun() { } + +@JsExport +val String.extensionProperty + get() = this.length + +@JsExport +enum class EnumClass { ENTRY1, ENTRY2 } + +@JsExport +annotation class AnnotationClass diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt new file mode 100644 index 00000000000..f7459275d85 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt @@ -0,0 +1,33 @@ +package + +package foo { + @kotlin.js.JsExport public val kotlin.String.extensionProperty: kotlin.Int + @kotlin.js.JsExport public inline fun inlineReifiedFun(/*0*/ x: kotlin.Any): kotlin.Boolean + @kotlin.js.JsExport public suspend fun suspendFun(): kotlin.Unit + + @kotlin.js.JsExport public final annotation class AnnotationClass : kotlin.Annotation { + public constructor AnnotationClass() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsExport public final enum class EnumClass : kotlin.Enum { + enum entry ENTRY1 + + enum entry ENTRY2 + + private constructor EnumClass() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: foo.EnumClass): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): foo.EnumClass + public final /*synthesized*/ fun values(): kotlin.Array + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.kt new file mode 100644 index 00000000000..f34249c8b56 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.kt @@ -0,0 +1,17 @@ +// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport +// !RENDER_DIAGNOSTICS_MESSAGES + +@file:JsExport + +package foo + +inline fun inlineReifiedFun(x: Any) = x is T + +suspend fun suspendFun() { } + +val String.extensionProperty + get() = this.length + +enum class EnumClass { ENTRY1, ENTRY2 } + +annotation class AnnotationClass diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.txt new file mode 100644 index 00000000000..47eced78efd --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.txt @@ -0,0 +1,33 @@ +package + +package foo { + public val kotlin.String.extensionProperty: kotlin.Int + public inline fun inlineReifiedFun(/*0*/ x: kotlin.Any): kotlin.Boolean + public suspend fun suspendFun(): kotlin.Unit + + public final annotation class AnnotationClass : kotlin.Annotation { + public constructor AnnotationClass() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final enum class EnumClass : kotlin.Enum { + enum entry ENTRY1 + + enum entry ENTRY2 + + private constructor EnumClass() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: foo.EnumClass): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): foo.EnumClass + public final /*synthesized*/ fun values(): kotlin.Array + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java index e0b4ed9960d..ffae5cf73d2 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -334,6 +334,54 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes } } + @TestMetadata("compiler/testData/diagnostics/testsWithJsStdLib/export") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Export extends AbstractDiagnosticsTestWithJsStdLib { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInExport() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithJsStdLib/export"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("extendingNonExportedType.kt") + public void testExtendingNonExportedType() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt"); + } + + @TestMetadata("jsExportOnNestedDeclarations.kt") + public void testJsExportOnNestedDeclarations() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.kt"); + } + + @TestMetadata("secondaryConstructorWithoutJsName.kt") + public void testSecondaryConstructorWithoutJsName() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/secondaryConstructorWithoutJsName.kt"); + } + + @TestMetadata("unexportableTypesInSignature.kt") + public void testUnexportableTypesInSignature() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInSignature.kt"); + } + + @TestMetadata("unexportableTypesInTypeParameters.kt") + public void testUnexportableTypesInTypeParameters() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt"); + } + + @TestMetadata("wrongExportedDeclaration.kt") + public void testWrongExportedDeclaration() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt"); + } + + @TestMetadata("wrongExportedDeclarationInExportedFile.kt") + public void testWrongExportedDeclarationInExportedFile() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclarationInExportedFile.kt"); + } + } + @TestMetadata("compiler/testData/diagnostics/testsWithJsStdLib/inline") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt index 60ef0e21aa6..0d863821b06 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.getNameForAnnotatedObject import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils.isCompanionObject import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic @@ -40,7 +41,7 @@ import kotlin.math.abs * A new instance of this class can be created for each request, however, it's recommended to use stable instance, since * [NameSuggestion] supports caching. */ -class NameSuggestion { +class NameSuggestion(val bindingContext: BindingContext) { private val cache: MutableMap = Collections.synchronizedMap(WeakHashMap()) /** @@ -112,7 +113,7 @@ class NameSuggestion { // Local functions and variables are always private with their own names as suggested names is CallableDescriptor -> if (DescriptorUtils.isDescriptorWithLocalVisibility(descriptor)) { - val ownName = getNameForAnnotatedObject(descriptor) ?: getSuggestedName(descriptor) + val ownName = getNameForAnnotatedObject(descriptor, bindingContext) ?: getSuggestedName(descriptor) var name = ownName var scope = descriptor.containingDeclaration @@ -195,7 +196,7 @@ class NameSuggestion { parts.reverse() val unmangledName = parts.joinToString("$") - val (id, stable) = mangleNameIfNecessary(unmangledName, fixedDescriptor) + val (id, stable) = mangleNameIfNecessary(unmangledName, fixedDescriptor, bindingContext) return SuggestedName(listOf(id), stable, fixedDescriptor, current) } @@ -217,7 +218,7 @@ class NameSuggestion { } companion object { - private fun mangleNameIfNecessary(baseName: String, descriptor: DeclarationDescriptor): NameAndStability { + private fun mangleNameIfNecessary(baseName: String, descriptor: DeclarationDescriptor, bindingContext: BindingContext): NameAndStability { // If we have a callable descriptor (property or method) it can override method in a parent class. // Traverse to the topmost overridden method. // It does not matter which path to choose during traversal, since front-end must ensure @@ -230,7 +231,7 @@ class NameSuggestion { } // If declaration is marked with either external, @native, @library or @JsName, return its stable name as is. - val nativeName = getNameForAnnotatedObject(overriddenDescriptor) + val nativeName = getNameForAnnotatedObject(overriddenDescriptor, bindingContext) if (nativeName != null) return NameAndStability(nativeName, true) if (overriddenDescriptor is FunctionDescriptor) { 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 59b8ebe7a4e..0d349ace3d8 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 @@ -31,11 +31,13 @@ import org.jetbrains.kotlin.types.DynamicTypesAllowed object JsPlatformConfigurator : PlatformConfiguratorBase( DynamicTypesAllowed(), additionalDeclarationCheckers = listOf( - NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(), - JsNameChecker, JsModuleChecker, JsExternalFileChecker, - JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker, - JsRuntimeAnnotationChecker, - JsDynamicDeclarationChecker + NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(), + JsNameChecker, JsModuleChecker, JsExternalFileChecker, + JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker, + JsRuntimeAnnotationChecker, + JsDynamicDeclarationChecker, + JsExportAnnotationChecker, + JsExportDeclarationChecker ), additionalCallCheckers = listOf( JsModuleCallChecker, @@ -46,7 +48,6 @@ object JsPlatformConfigurator : PlatformConfiguratorBase( identifierChecker = JsIdentifierChecker ) { override fun configureModuleComponents(container: StorageComponentContainer) { - container.useInstance(NameSuggestion()) container.useImpl() container.useImpl() container.useImpl() 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 536e70028c3..6df39216bcd 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 @@ -100,6 +100,10 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { "Can't apply multiple inheritance here, since it's impossible to generate bridge for system function {0}", Renderers.DECLARATION_NAME_WITH_KIND) + put(ErrorsJs.NESTED_JS_EXPORT, "@JsExport is only allowed on files and top-level declarations") + put(ErrorsJs.WRONG_EXPORTED_DECLARATION, "Declaration of such kind ({0}) can't be exported to JS", Renderers.STRING) + put(ErrorsJs.NON_EXPORTABLE_TYPE, "Exported declaration uses non-exportable {0} type: {1}", Renderers.STRING, RENDER_TYPE) + 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 29b37f8e2cb..c50945f0801 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 @@ -105,6 +105,10 @@ public interface ErrorsJs { DiagnosticFactory1 WRONG_MULTIPLE_INHERITANCE = DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory0 NESTED_JS_EXPORT = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 WRONG_EXPORTED_DECLARATION = DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory2 NON_EXPORTABLE_TYPE = DiagnosticFactory2.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/JsBuiltinNameClashChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsBuiltinNameClashChecker.kt index 5120032e4f4..74d5d5dd7ed 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsBuiltinNameClashChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsBuiltinNameClashChecker.kt @@ -25,8 +25,9 @@ import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker -class JsBuiltinNameClashChecker(private val nameSuggestion: NameSuggestion) : DeclarationChecker { +class JsBuiltinNameClashChecker : DeclarationChecker { override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + val nameSuggestion = NameSuggestion(context.trace.bindingContext) if (AnnotationsUtils.isNativeObject(descriptor)) return if (descriptor.containingDeclaration !is ClassDescriptor) return diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportAnnotationChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportAnnotationChecker.kt new file mode 100644 index 00000000000..c1fae3f5f6c --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportAnnotationChecker.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2019 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.js.resolve.diagnostics + +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor +import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.source.getPsi + +object JsExportAnnotationChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + val trace = context.trace + + val jsExport = AnnotationsUtils.getJsExportAnnotation(descriptor) ?: return + val jsExportPsi = jsExport.source.getPsi() ?: declaration + + if (descriptor !is PackageFragmentDescriptor && !DescriptorUtils.isTopLevelDeclaration(descriptor)) { + trace.report(ErrorsJs.NESTED_JS_EXPORT.on(jsExportPsi)) + } + } +} 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 new file mode 100644 index 00000000000..b5d9d93d498 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt @@ -0,0 +1,173 @@ +/* + * Copyright 2010-2019 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.js.resolve.diagnostics + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.isFunctionType +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.ClassKind.* +import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +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.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.* + +object JsExportDeclarationChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + val trace = context.trace + val bindingContext = trace.bindingContext + + fun checkTypeParameter(descriptor: TypeParameterDescriptor) { + for (upperBound in descriptor.upperBounds) { + if (!isTypeExportable(upperBound, bindingContext)) { + val typeParameterDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor)!! + trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(typeParameterDeclaration, "upper bound", upperBound)) + } + } + } + + fun checkValueParameter(descriptor: ValueParameterDescriptor) { + if (!isTypeExportable(descriptor.type, bindingContext)) { + val valueParameterDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor)!! + trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(valueParameterDeclaration, "parameter", descriptor.type)) + } + } + + if (!AnnotationsUtils.isExportedObject(descriptor, bindingContext)) return + if (descriptor !is MemberDescriptor) + return + + val hasJsName = AnnotationsUtils.getJsNameAnnotation(descriptor) != null + + fun reportWrongExportedDeclaration(kind: String) { + trace.report(ErrorsJs.WRONG_EXPORTED_DECLARATION.on(declaration, kind)) + } + + if (descriptor.isExpect) { + reportWrongExportedDeclaration("expect") + } + + when (descriptor) { + is FunctionDescriptor -> { + for (typeParameter in descriptor.typeParameters) { + checkTypeParameter(typeParameter) + } + + if (descriptor.isInlineWithReified()) { + reportWrongExportedDeclaration("inline function with reified type parameters") + return + } + + if (descriptor.isSuspend) { + reportWrongExportedDeclaration("suspend function") + return + } + + if (descriptor is ConstructorDescriptor) { + if (!descriptor.isPrimary && !hasJsName) + reportWrongExportedDeclaration("secondary constructor without @JsName") + } + + // Properties are checked instead of property accessors + if (descriptor !is PropertyAccessorDescriptor) { + for (parameter in descriptor.valueParameters) { + checkValueParameter(parameter) + } + + descriptor.returnType?.let { returnType -> + if (!isTypeExportable(returnType, bindingContext, true)) { + trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "return", returnType)) + } + } + } + } + + is PropertyDescriptor -> { + if (descriptor.isExtensionProperty) { + reportWrongExportedDeclaration("extension property") + return + } + if (!isTypeExportable(descriptor.type, bindingContext)) { + trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "property", descriptor.type)) + } + } + + is ClassDescriptor -> { + for (typeParameter in descriptor.declaredTypeParameters) { + checkTypeParameter(typeParameter) + } + + if (descriptor.kind == ENUM_CLASS) { + reportWrongExportedDeclaration("enum class") + return + } + if (descriptor.kind == ANNOTATION_CLASS) { + reportWrongExportedDeclaration("annotation class") + return + } + if (descriptor.kind == ENUM_ENTRY) { + // Covered by ENUM_CLASS + return + } + + for (superType in descriptor.defaultType.supertypes()) { + if (!isTypeExportable(superType, bindingContext)) { + trace.report(ErrorsJs.NON_EXPORTABLE_TYPE.on(declaration, "super", superType)) + } + } + } + } + } + + + private fun isTypeExportable(type: KotlinType, bindingContext: BindingContext, isReturnType: Boolean = false): Boolean { + if (isReturnType && type.isUnit()) + 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)) + return false + } + if (!isTypeExportable(arguments.last().type, bindingContext, isReturnType = true)) + return false + + return true + } + + for (argument: TypeProjection in type.arguments) { + if (!isTypeExportable(argument.type, bindingContext)) + return false + } + + val nonNullable = type.makeNotNullable() + + // 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 descriptor: ClassifierDescriptor = type.constructor.declarationDescriptor ?: return false + return descriptor.isEffectivelyExternal() || AnnotationsUtils.isExportedObject(descriptor, bindingContext) + } +} diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt index 1d76f0f9522..761637c5daa 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt @@ -5,18 +5,29 @@ package org.jetbrains.kotlin.js.resolve.diagnostics +import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor import org.jetbrains.kotlin.js.naming.NameSuggestion import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext -class JsNameCharsChecker(private val suggestion: NameSuggestion) : DeclarationChecker { +class JsNameCharsChecker : DeclarationChecker { override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + val bindingContext = context.trace.bindingContext + if (descriptor is PropertyAccessorDescriptor && AnnotationsUtils.getJsName(descriptor) == null) return + // This case will be reported as WRONG_EXPORTED_DECLARATION for + // secondary constructor with missing JsName. Skipping it here to simplify further logic. + if (descriptor is ConstructorDescriptor && + AnnotationsUtils.getJsName(descriptor) == null && + AnnotationsUtils.isExportedObject(descriptor, bindingContext) + ) return + + val suggestion = NameSuggestion(bindingContext) val suggestedName = suggestion.suggest(descriptor) ?: return if (suggestedName.stable && suggestedName.names.any { NameSuggestion.sanitizeName(it) != it }) { context.trace.report(ErrorsJs.NAME_CONTAINS_ILLEGAL_CHARS.on(declaration)) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt index 59bdf2c3fb7..2137a94e51c 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt @@ -35,7 +35,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty import org.jetbrains.kotlin.resolve.scopes.MemberScope class JsNameClashChecker( - private val nameSuggestion: NameSuggestion, private val languageVersionSettings: LanguageVersionSettings ) : DeclarationChecker { companion object { @@ -45,6 +44,7 @@ class JsNameClashChecker( Errors.PACKAGE_OR_CLASSIFIER_REDECLARATION) } + lateinit var nameSuggestion: NameSuggestion private val scopes = mutableMapOf>() private val clashedFakeOverrides = mutableMapOf>() private val clashedDescriptors = mutableSetOf>() @@ -62,6 +62,8 @@ class JsNameClashChecker( diagnosticHolder: DiagnosticSink, bindingContext: BindingContext ) { if (descriptor is ConstructorDescriptor && descriptor.isPrimary) return + if (!this::nameSuggestion.isInitialized || nameSuggestion.bindingContext !== bindingContext) + nameSuggestion = NameSuggestion(bindingContext) for (suggested in nameSuggestion.suggestAllPossibleNames(descriptor)) { if (suggested.stable && suggested.scope is ClassOrPackageFragmentDescriptor && presentsInGeneratedCode(suggested.descriptor)) { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java b/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java index f23e06d1942..dc9fb5df71e 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java @@ -40,6 +40,7 @@ import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt.isEf public final class AnnotationsUtils { private static final String JS_NAME = "kotlin.js.JsName"; + private static final FqName JS_EXPORT = new FqName("kotlin.js.JsExport"); public static final FqName JS_MODULE_ANNOTATION = new FqName("kotlin.js.JsModule"); private static final FqName JS_NON_MODULE_ANNOTATION = new FqName("kotlin.js.JsNonModule"); public static final FqName JS_QUALIFIER_ANNOTATION = new FqName("kotlin.js.JsQualifier"); @@ -83,7 +84,10 @@ public final class AnnotationsUtils { } @Nullable - public static String getNameForAnnotatedObject(@NotNull DeclarationDescriptor descriptor) { + public static String getNameForAnnotatedObject( + @NotNull DeclarationDescriptor descriptor, + @NotNull BindingContext bindingContext + ) { String defaultJsName = getJsName(descriptor); for (PredefinedAnnotation annotation : PredefinedAnnotation.Companion.getWITH_CUSTOM_NAME()) { @@ -97,7 +101,7 @@ public final class AnnotationsUtils { return name != null ? name : descriptor.getName().asString(); } - if (defaultJsName == null && isEffectivelyExternalMember(descriptor)) { + if (defaultJsName == null && (isEffectivelyExternalMember(descriptor) || isExportedObject(descriptor, bindingContext))) { return descriptor.getName().asString(); } @@ -112,6 +116,23 @@ public final class AnnotationsUtils { return descriptor.getAnnotations().findAnnotation(annotation.getFqName()); } + public static boolean isExportedObject(@NotNull DeclarationDescriptor descriptor, @NotNull BindingContext bindingContext) { + if (descriptor instanceof MemberDescriptor) { + MemberDescriptor memberDescriptor = (MemberDescriptor) descriptor; + if (memberDescriptor.getVisibility() != Visibilities.PUBLIC) return false; + } + + if (hasAnnotationOrInsideAnnotatedClass(descriptor, JS_EXPORT)) return true; + + if (CollectionsKt.any(getContainingFileAnnotations(bindingContext, descriptor), annotation -> + JS_EXPORT.equals(annotation.getFqName()) + )) { + return true; + } + + return false; + } + public static boolean isNativeObject(@NotNull DeclarationDescriptor descriptor) { if (hasAnnotationOrInsideAnnotatedClass(descriptor, PredefinedAnnotation.NATIVE) || isEffectivelyExternalMember(descriptor)) return true; @@ -154,6 +175,11 @@ public final class AnnotationsUtils { return descriptor.getAnnotations().findAnnotation(new FqName(JS_NAME)); } + @Nullable + public static AnnotationDescriptor getJsExportAnnotation(@NotNull DeclarationDescriptor descriptor) { + return descriptor.getAnnotations().findAnnotation(JS_EXPORT); + } + public static boolean isPredefinedObject(@NotNull DeclarationDescriptor descriptor) { if (descriptor instanceof MemberDescriptor && ((MemberDescriptor) descriptor).isExpect()) return true; if (isEffectivelyExternalMember(descriptor)) return true; diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt index c982356fecb..6fce51360b4 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.expression.InlineMetadata import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.utils.JsLibraryUtils import java.io.File @@ -55,7 +56,8 @@ private val SPECIAL_FUNCTION_PATTERN = Regex("var\\s+($JS_IDENTIFIER)\\s*=\\s*($ class FunctionReader( private val reporter: JsConfig.Reporter, - private val config: JsConfig + private val config: JsConfig, + private val bindingContext: BindingContext ) { /** * fileContent: .js file content, that contains this module definition. @@ -205,7 +207,7 @@ class FunctionReader( info: ModuleInfo, fragment: JsProgramFragment ): FunctionWithWrapper { - val tag = Namer.getFunctionTag(descriptor, config) + val tag = Namer.getFunctionTag(descriptor, config, bindingContext) val moduleReference = fragment.inlineModuleMap[tag]?.deepCopy() ?: fragment.scope.declareName("_").makeRef() val allDefinedNames = collectDefinedNamesInAllScopes(fn.function) val replacements = hashMapOf( @@ -234,7 +236,7 @@ class FunctionReader( private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): FunctionWithWrapper? { val source = info.fileContent - var tag = Namer.getFunctionTag(descriptor, config) + var tag = Namer.getFunctionTag(descriptor, config, bindingContext) var index = source.indexOf(tag) // Hack for compatibility with old versions of stdlib diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.kt index 8ad10f804e7..2035f86c898 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.kt @@ -13,11 +13,13 @@ import org.jetbrains.kotlin.js.inline.context.FunctionDefinitionLoader import org.jetbrains.kotlin.js.inline.context.InliningContext import org.jetbrains.kotlin.js.translate.general.AstGenerationResult +import org.jetbrains.kotlin.resolve.BindingContext class JsInliner( val reporter: JsConfig.Reporter, val config: JsConfig, val trace: DiagnosticSink, + val bindingContext: BindingContext, val translationResult: AstGenerationResult ) { diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/FunctionDefinitionLoader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/FunctionDefinitionLoader.kt index 196f7962c73..b13bfb28cdc 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/FunctionDefinitionLoader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/FunctionDefinitionLoader.kt @@ -75,7 +75,7 @@ class FunctionDefinitionLoader( return lookUpFunctionDirect(call, scope) ?: lookUpFunctionIndirect(call, scope) ?: lookUpFunctionExternal(call, scope.fragment) } - private val functionReader = FunctionReader(inliner.reporter, inliner.config) + private val functionReader = FunctionReader(inliner.reporter, inliner.config, inliner.bindingContext) private data class FragmentInfo( val functions: Map, @@ -160,7 +160,7 @@ class FunctionDefinitionLoader( private fun lookUpFunctionDirect(call: JsInvocation, callsiteScope: InliningScope): InlineFunctionDefinition? { val descriptor = call.descriptor ?: return null - val tag = Namer.getFunctionTag(descriptor, inliner.config) + val tag = Namer.getFunctionTag(descriptor, inliner.config, inliner.bindingContext) val definitionFragment = fragmentByTag(tag) ?: return null @@ -178,7 +178,7 @@ class FunctionDefinitionLoader( private fun lookUpFunctionExternal(call: JsInvocation, fragment: JsProgramFragment): InlineFunctionDefinition? = call.descriptor?.let { descriptor -> functionReader[descriptor, fragment]?.let { - InlineFunctionDefinition(it, Namer.getFunctionTag(descriptor, inliner.config)) + InlineFunctionDefinition(it, Namer.getFunctionTag(descriptor, inliner.config, inliner.bindingContext)) } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt b/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt index 6b57a2f4872..6c58a9928a2 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt @@ -32,6 +32,11 @@ fun main(args: Array) { model("typescript-export/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS_IR) } + testClass { + model("typescript-export/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS) + } + + testClass { model("sourcemap/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/AbstractLegacyJsTypeScriptExportTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/AbstractLegacyJsTypeScriptExportTest.kt new file mode 100644 index 00000000000..62d95585995 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/AbstractLegacyJsTypeScriptExportTest.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2010-2020 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.js.test.semantics + +import org.jetbrains.kotlin.js.test.BasicBoxTest + +abstract class AbstractLegacyJsTypeScriptExportTest : BasicBoxTest( + pathToTestDir = TEST_DATA_DIR_PATH + "typescript-export/", + testGroupOutputDirPrefix = "legacy-typescript-export/", + pathToRootOutputDir = TEST_DATA_DIR_PATH +) \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LegacyJsTypeScriptExportTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LegacyJsTypeScriptExportTestGenerated.java new file mode 100644 index 00000000000..8f9cc730252 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LegacyJsTypeScriptExportTestGenerated.java @@ -0,0 +1,139 @@ +/* + * Copyright 2010-2020 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.js.test.semantics; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("js/js.translator/testData/typescript-export") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class LegacyJsTypeScriptExportTestGenerated extends AbstractLegacyJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInTypescript_export() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("js/js.translator/testData/typescript-export/declarations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Declarations extends AbstractLegacyJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInDeclarations() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/declarations"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("declarations.kt") + public void testDeclarations() throws Exception { + runTest("js/js.translator/testData/typescript-export/declarations/declarations.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/inheritance") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Inheritance extends AbstractLegacyJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInInheritance() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/inheritance"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("inheritance.kt") + public void testInheritance() throws Exception { + runTest("js/js.translator/testData/typescript-export/inheritance/inheritance.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/namespaces") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Namespaces extends AbstractLegacyJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInNamespaces() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/namespaces"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("namespaces.kt") + public void testNamespaces() throws Exception { + runTest("js/js.translator/testData/typescript-export/namespaces/namespaces.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/primitives") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Primitives extends AbstractLegacyJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInPrimitives() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/primitives"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("primitives.kt") + public void testPrimitives() throws Exception { + runTest("js/js.translator/testData/typescript-export/primitives/primitives.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/selectiveExport") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class SelectiveExport extends AbstractLegacyJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInSelectiveExport() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/selectiveExport"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("selectiveExport.kt") + public void testSelectiveExport() throws Exception { + runTest("js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/visibility") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Visibility extends AbstractLegacyJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInVisibility() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/visibility"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("visibility.kt") + public void testVisibility() throws Exception { + runTest("js/js.translator/testData/typescript-export/visibility/visibility.kt"); + } + } +} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.kt index 7873cbb63d5..a5e64f6c357 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.kt @@ -167,6 +167,7 @@ class K2JSTranslator @JvmOverloads constructor( reporter, config, analysisResult.bindingTrace, + bindingTrace.bindingContext, translationResult ).process() if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index 036dffbb291..020a5e0cb92 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -34,12 +34,12 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.TypeCheck; import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.naming.NameSuggestion; import org.jetbrains.kotlin.js.naming.SuggestedName; -import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices; import org.jetbrains.kotlin.js.translate.intrinsic.functions.factories.ArrayFIF; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils; import org.jetbrains.kotlin.name.FqNameUnsafe; import org.jetbrains.kotlin.name.Name; +import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.DescriptorUtils; import java.util.Arrays; @@ -57,8 +57,8 @@ public final class Namer { public static final String KOTLIN_NAME = KotlinLanguage.NAME; public static final String KOTLIN_LOWER_NAME = KOTLIN_NAME.toLowerCase(); - public static final String EQUALS_METHOD_NAME = getStableMangledNameForDescriptor(JsPlatformAnalyzerServices.INSTANCE.getBuiltIns().getAny(), "equals"); - public static final String COMPARE_TO_METHOD_NAME = getStableMangledNameForDescriptor(JsPlatformAnalyzerServices.INSTANCE.getBuiltIns().getComparable(), "compareTo"); + public static final String EQUALS_METHOD_NAME = "equals"; + public static final String COMPARE_TO_METHOD_NAME = "compareTo_11rb$"; public static final String LONG_FROM_NUMBER = "fromNumber"; public static final String LONG_TO_NUMBER = "toNumber"; public static final String LONG_FROM_INT = "fromInt"; @@ -134,7 +134,11 @@ public final class Namer { public static final String SAM_FIELD_NAME = "function$"; @NotNull - public static String getFunctionTag(@NotNull CallableDescriptor functionDescriptor, @NotNull JsConfig config) { + public static String getFunctionTag( + @NotNull CallableDescriptor functionDescriptor, + @NotNull JsConfig config, + @NotNull BindingContext bindingContext + ) { String intrinsicTag = ArrayFIF.INSTANCE.getTag(functionDescriptor, config); if (intrinsicTag != null) return intrinsicTag; @@ -147,7 +151,7 @@ public final class Namer { qualifier = fqNameParent.asString(); } - SuggestedName suggestedName = new NameSuggestion().suggest(functionDescriptor); + SuggestedName suggestedName = new NameSuggestion(bindingContext).suggest(functionDescriptor); assert suggestedName != null : "Suggested name can be null only for module descriptors: " + functionDescriptor; String mangledName = suggestedName.getNames().get(0); return StringUtil.join(Arrays.asList(moduleName, qualifier, mangledName), "."); @@ -225,18 +229,6 @@ public final class Namer { callSetProperty = kotlin("callSetter"); } - // TODO: get rid of this function - @NotNull - private static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) { - Collection functions = descriptor.getDefaultType().getMemberScope().getContributedFunctions( - Name.identifier(functionName), NoLookupLocation.FROM_BACKEND - ); - assert functions.size() == 1 : "Can't select a single function: " + functionName + " in " + descriptor; - SuggestedName suggested = new NameSuggestion().suggest(functions.iterator().next()); - assert suggested != null : "Suggested name for class members is always non-null: " + functions.iterator().next(); - return suggested.getNames().get(0); - } - @NotNull public static JsNameRef kotlin(@NotNull JsName name) { return pureFqn(name, kotlinObject()); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java index ed1956fc759..41fc0e87dd3 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java @@ -111,7 +111,7 @@ public final class StaticContext { private final JsImportedModule currentModuleAsImported; @NotNull - private final NameSuggestion nameSuggestion = new NameSuggestion(); + private final NameSuggestion nameSuggestion; @NotNull private final Map nameCache = new HashMap<>(); @@ -169,6 +169,7 @@ public final class StaticContext { fragment = new JsProgramFragment(rootFunction.getScope(), packageFqn); this.bindingTrace = bindingTrace; + this.nameSuggestion = new NameSuggestion(bindingTrace.getBindingContext()); this.namer = Namer.newInstance(program.getRootScope()); this.intrinsics = new Intrinsics(); this.rootScope = fragment.getScope(); @@ -801,7 +802,7 @@ public final class StaticContext { public void addInlineCall(@NotNull CallableDescriptor descriptor) { descriptor = (CallableDescriptor) JsDescriptorUtils.findRealInlineDeclaration(descriptor); - String tag = Namer.getFunctionTag(descriptor, config); + String tag = Namer.getFunctionTag(descriptor, config, getBindingContext()); JsExpression moduleExpression = exportModuleForInline(DescriptorUtils.getContainingModule(descriptor)); if (moduleExpression == null) { moduleExpression = getModuleExpressionFor(descriptor); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java index 02fc3958115..6e3eece2918 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java @@ -179,13 +179,13 @@ public class TranslationContext { @NotNull public TranslationContext newFunctionBodyWithUsageTracker(@NotNull JsFunction fun, @NotNull MemberDescriptor descriptor) { DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody()); - UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor); + UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, bindingContext()); return new TranslationContext(this, this.staticContext, dynamicContext, this.aliasingContext.inner(), usageTracker, descriptor); } @NotNull public TranslationContext innerWithUsageTracker(@NotNull MemberDescriptor descriptor) { - UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor); + UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, bindingContext()); return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, descriptor); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt index 35804b6cc59..6fe041d5b05 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/UsageTracker.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.js.backend.ast.JsScope import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor import org.jetbrains.kotlin.js.descriptorUtils.isCoroutineLambda import org.jetbrains.kotlin.js.naming.NameSuggestion +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils.* import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject @@ -30,7 +31,8 @@ private val CAPTURED_RECEIVER_NAME_PREFIX : String = "this$" class UsageTracker( private val parent: UsageTracker?, - val containingDescriptor: MemberDescriptor + val containingDescriptor: MemberDescriptor, + val bindingContext: BindingContext ) { private val captured = linkedMapOf() @@ -175,7 +177,7 @@ class UsageTracker( // Append 'closure$' prefix to avoid name clash between closure and member fields in case of local classes else -> { - val mangled = NameSuggestion.sanitizeName(NameSuggestion().suggest(this)!!.names.last()) + val mangled = NameSuggestion.sanitizeName(NameSuggestion(bindingContext).suggest(this)!!.names.last()) "closure\$$mangled" } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt index d082fda3ced..52befef85e0 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt @@ -30,7 +30,7 @@ class InlineMetadata(val tag: JsStringLiteral, val function: FunctionWithWrapper companion object { @JvmStatic fun compose(function: JsFunction, descriptor: CallableDescriptor, context: TranslationContext): InlineMetadata { - val tag = JsStringLiteral(Namer.getFunctionTag(descriptor, context.config)) + val tag = JsStringLiteral(Namer.getFunctionTag(descriptor, context.config, context.bindingContext())) val inliningContext = context.inlineFunctionContext!! val block = JsBlock(inliningContext.importBlock.statements + inliningContext.prototypeBlock.statements + inliningContext.declarationsBlock.statements + JsReturn(function)) diff --git a/js/js.translator/testData/box/char/topLevelCallables.kt b/js/js.translator/testData/box/char/topLevelCallables.kt index 6c25996fd62..a2369968943 100644 --- a/js/js.translator/testData/box/char/topLevelCallables.kt +++ b/js/js.translator/testData/box/char/topLevelCallables.kt @@ -1,16 +1,15 @@ // SKIP_MINIFICATION + +// Exported declaration uses non-exportable return type: Char // IGNORE_BACKEND: JS_IR + @JsName("foo") -@JsExport fun foo(): Char = '1' -@JsExport val p1: Char = '2' -@JsExport var p2: Char = '3' -@JsExport var p3: Char = '4' get() = field + 1 set(value) { diff --git a/js/js.translator/testData/box/coercion/charValParameter.kt b/js/js.translator/testData/box/coercion/charValParameter.kt index f77e02aca9b..fa2a2f16c81 100644 --- a/js/js.translator/testData/box/coercion/charValParameter.kt +++ b/js/js.translator/testData/box/coercion/charValParameter.kt @@ -1,7 +1,6 @@ // EXPECTED_REACHABLE_NODES: 1276 // IGNORE_BACKEND: JS_IR -@JsExport class A(val x: Char) fun typeOf(x: dynamic): String = js("typeof x") diff --git a/js/js.translator/testData/box/coercion/defaultAccessors.kt b/js/js.translator/testData/box/coercion/defaultAccessors.kt index 72850576db0..d470bde747d 100644 --- a/js/js.translator/testData/box/coercion/defaultAccessors.kt +++ b/js/js.translator/testData/box/coercion/defaultAccessors.kt @@ -4,7 +4,6 @@ interface I { val a: Char } -@JsExport object X : I { override var a = '#' } diff --git a/js/js.translator/testData/box/coercion/propertyBridgeChar.kt b/js/js.translator/testData/box/coercion/propertyBridgeChar.kt index 7e80d964c7b..d1a024ff043 100644 --- a/js/js.translator/testData/box/coercion/propertyBridgeChar.kt +++ b/js/js.translator/testData/box/coercion/propertyBridgeChar.kt @@ -1,7 +1,6 @@ // EXPECTED_REACHABLE_NODES: 1289 // IGNORE_BACKEND: JS_IR -@JsExport open class A { val foo: Char get() = 'X' diff --git a/js/js.translator/testData/box/inline/inlinedObjectLiteralIsCheck.kt b/js/js.translator/testData/box/inline/inlinedObjectLiteralIsCheck.kt index 8eece0a7b85..8eea3699096 100644 --- a/js/js.translator/testData/box/inline/inlinedObjectLiteralIsCheck.kt +++ b/js/js.translator/testData/box/inline/inlinedObjectLiteralIsCheck.kt @@ -1,5 +1,6 @@ // EXPECTED_REACHABLE_NODES: 1282 +@JsExport interface I { fun ok(): String } diff --git a/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt b/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt index ccd559bdbc4..a502547de97 100644 --- a/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt +++ b/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt @@ -12,7 +12,6 @@ class A { } } -@JsExport val A.z: Int @JsName("getZ_") get() = 42 diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.kt b/js/js.translator/testData/typescript-export/declarations/declarations.kt index d503a98e7b8..dea6987939a 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations.kt +++ b/js/js.translator/testData/typescript-export/declarations/declarations.kt @@ -1,6 +1,7 @@ -// TARGET_BACKEND: JS_IR // CHECK_TYPESCRIPT_DECLARATIONS // RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS @file:JsExport diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance.kt b/js/js.translator/testData/typescript-export/inheritance/inheritance.kt index 0745f74d3e4..14d546b86ee 100644 --- a/js/js.translator/testData/typescript-export/inheritance/inheritance.kt +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance.kt @@ -1,6 +1,7 @@ -// TARGET_BACKEND: JS_IR // CHECK_TYPESCRIPT_DECLARATIONS // RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS @file:JsExport diff --git a/js/js.translator/testData/typescript-export/namespaces/namespaces.kt b/js/js.translator/testData/typescript-export/namespaces/namespaces.kt index 3567e2e5bcd..976e1413a04 100644 --- a/js/js.translator/testData/typescript-export/namespaces/namespaces.kt +++ b/js/js.translator/testData/typescript-export/namespaces/namespaces.kt @@ -1,6 +1,7 @@ -// TARGET_BACKEND: JS_IR // CHECK_TYPESCRIPT_DECLARATIONS // RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS // FILE: file1.kt diff --git a/js/js.translator/testData/typescript-export/primitives/primitives.d.ts b/js/js.translator/testData/typescript-export/primitives/primitives.d.ts index 0de767fb004..24023e38507 100644 --- a/js/js.translator/testData/typescript-export/primitives/primitives.d.ts +++ b/js/js.translator/testData/typescript-export/primitives/primitives.d.ts @@ -2,7 +2,6 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined namespace foo { const _any: any; - const _unit: void; function _nothing(): never const _throwable: Error; const _string: string; diff --git a/js/js.translator/testData/typescript-export/primitives/primitives.kt b/js/js.translator/testData/typescript-export/primitives/primitives.kt index db7b0faabce..ec11c3d064e 100644 --- a/js/js.translator/testData/typescript-export/primitives/primitives.kt +++ b/js/js.translator/testData/typescript-export/primitives/primitives.kt @@ -1,6 +1,7 @@ -// TARGET_BACKEND: JS_IR // CHECK_TYPESCRIPT_DECLARATIONS // RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS @file:JsExport @@ -8,8 +9,6 @@ package foo val _any: Any = Any() -val _unit: Unit = Unit - fun _nothing(): Nothing { throw Throwable() } val _throwable: Throwable = Throwable() diff --git a/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt index 4ede8cb5196..03523339752 100644 --- a/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt +++ b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt @@ -1,6 +1,7 @@ -// TARGET_BACKEND: JS_IR // CHECK_TYPESCRIPT_DECLARATIONS // RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS // FILE: file1.kt package foo diff --git a/js/js.translator/testData/typescript-export/visibility/visibility.kt b/js/js.translator/testData/typescript-export/visibility/visibility.kt index 6d63b345a2c..4078c404758 100644 --- a/js/js.translator/testData/typescript-export/visibility/visibility.kt +++ b/js/js.translator/testData/typescript-export/visibility/visibility.kt @@ -1,6 +1,7 @@ -// TARGET_BACKEND: JS_IR // CHECK_TYPESCRIPT_DECLARATIONS // RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS // TODO fix statics export in DCE-driven mode // SKIP_DCE_DRIVEN diff --git a/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt b/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt index bdc34850524..e312180bc49 100644 --- a/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt +++ b/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt @@ -32,12 +32,33 @@ public expect annotation class JsName(val name: String) public annotation class ExperimentalJsExport /** - * Exports top-level declaration. + * Exports top-level declaration on JS platform. * - * Compiler exports from the module those top-level declarations that are marked with this annotation. - * There is no effect if this annotation is applied to a non-top-level declaration. + * Compiled module exposes declarations that are marked with this annotation without name mangling. * - * This annotation has effect only on top-level declarations and only in IR-based JS backend. + * This annotation can be applied to either files or top-level declarations. + * + * It is currently prohibited to export the following kinds of declarations: + * + * * `expect` declarations + * * inline functions with reified type parameters + * * suspend functions + * * secondary constructors without `@JsName` + * * extension properties + * * enum classes + * * annotation classes + * + * Signatures of exported declarations must only contain "exportable" types: + * + * * `dynamic`, `Any`, `String`, `Boolean`, `Byte`, `Short`, `Int`, `Float`, `Double` + * * `BooleanArray`, `ByteArray`, `ShortArray`, `IntArray`, `FloatArray`, `DoubleArray` + * * `Array` + * * Function types with exportable parameters and return types + * * `external` or `@JsExport` classes and interfaces + * * Nullable counterparts of types above + * * Unit return type. Must not be nullable + * + * This annotation is experimental, meaning that restrictions mentioned above are subject to change. */ @ExperimentalJsExport @Retention(AnnotationRetention.BINARY) diff --git a/libraries/stdlib/js/src/kotlin/annotations.kt b/libraries/stdlib/js/src/kotlin/annotations.kt index 512f4466d05..edfd52fff14 100644 --- a/libraries/stdlib/js/src/kotlin/annotations.kt +++ b/libraries/stdlib/js/src/kotlin/annotations.kt @@ -162,12 +162,33 @@ public annotation class JsNonModule public annotation class JsQualifier(val value: String) /** - * Exports top-level declaration. + * Exports top-level declaration on JS platform. * - * Compiler exports from the module those top-level declarations that are marked with this annotation. - * There is no effect if this annotation is applied to a non-top-level declaration. + * Compiled module exposes declarations that are marked with this annotation without name mangling. * - * This annotation has effect only on top-level declarations and only in IR-based JS backend. + * This annotation can be applied to either files or top-level declarations. + * + * It is currently prohibited to export the following kinds of declarations: + * + * * `expect` declarations + * * inline functions with reified type parameters + * * suspend functions + * * secondary constructors without `@JsName` + * * extension properties + * * enum classes + * * annotation classes + * + * Signatures of exported declarations must only contain "exportable" types: + * + * * `dynamic`, `Any`, `String`, `Boolean`, `Byte`, `Short`, `Int`, `Float`, `Double` + * * `BooleanArray`, `ByteArray`, `ShortArray`, `IntArray`, `FloatArray`, `DoubleArray` + * * `Array` + * * Function types with exportable parameters and return types + * * `external` or `@JsExport` classes and interfaces + * * Nullable counterparts of types above + * * Unit return type. Must not be nullable + * + * This annotation is experimental, meaning that restrictions mentioned above are subject to change. */ @ExperimentalJsExport @Retention(AnnotationRetention.BINARY)