[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)
This commit is contained in:
Svyatoslav Kuzmich
2019-08-29 18:25:09 +03:00
parent 4076bf40a9
commit 6e3d3831c2
52 changed files with 972 additions and 72 deletions
@@ -0,0 +1,26 @@
// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
package foo
open class NonExportedClass
@JsExport
class <!NON_EXPORTABLE_TYPE("super", "NonExportedClass")!>ExportedClass<!> : NonExportedClass()
interface NonExportedInterface
@JsExport
class <!NON_EXPORTABLE_TYPE("super", "NonExportedInterface")!>ExportedClass2<!> : NonExportedInterface
@JsExport
open class ExportedGenericClass<T>
@JsExport
class <!NON_EXPORTABLE_TYPE("super", "ExportedGenericClass<NonExportedClass>")!>ExportedClass3<!> : ExportedGenericClass<NonExportedClass>()
@JsExport
interface ExportedGenericInterface<T>
@JsExport
class <!NON_EXPORTABLE_TYPE("super", "ExportedGenericInterface<NonExportedClass>")!>ExportedClass4<!> : ExportedGenericInterface<NonExportedClass>
@@ -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<foo.NonExportedClass> {
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<foo.NonExportedClass> {
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</*0*/ T> {
public constructor ExportedGenericClass</*0*/ T>()
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</*0*/ T> {
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
}
}
@@ -0,0 +1,22 @@
// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport
package foo
class C1 {
<!NESTED_JS_EXPORT!>@JsExport<!>
fun f1() {}
<!NESTED_JS_EXPORT!>@JsExport<!>
val p: Int = 10
<!NESTED_JS_EXPORT!>@JsExport<!>
object O
}
fun f2() {
<!NESTED_JS_EXPORT!>@JsExport<!>
fun f3() {}
<!NESTED_JS_EXPORT!>@JsExport<!>
class C2
}
@@ -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
}
}
}
@@ -0,0 +1,15 @@
// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
package foo
@JsExport
class C(val x: String) {
<!WRONG_EXPORTED_DECLARATION("secondary constructor without @JsName")!>constructor(x: Int)<!>: this(x.toString())
}
@JsExport
class C2(val x: String) {
@JsName("JsNameProvided")
constructor(x: Int): this(x.toString())
}
@@ -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
}
}
@@ -0,0 +1,56 @@
// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport
// !DIAGNOSTICS: -UNUSED_PARAMETER
// !RENDER_DIAGNOSTICS_MESSAGES
package foo
class C
@JsExport
fun foo(<!NON_EXPORTABLE_TYPE("parameter", "C")!>x: C<!>) {
}
<!NON_EXPORTABLE_TYPE("return", "C")!>@JsExport
fun bar()<!> = C()
<!NON_EXPORTABLE_TYPE("property", "C")!>@JsExport
val x: C<!> = C()
<!NON_EXPORTABLE_TYPE("property", "C")!>@JsExport
var x2: C<!>
get() = C()
set(value) { }
@JsExport
class A(
<!NON_EXPORTABLE_TYPE("parameter", "C"), NON_EXPORTABLE_TYPE("property", "C")!>val x: C<!>,
<!NON_EXPORTABLE_TYPE("parameter", "C")!>y: C<!>
) {
<!NON_EXPORTABLE_TYPE("return", "C")!>fun foo(<!NON_EXPORTABLE_TYPE("parameter", "C")!>x: C<!>)<!> = x
<!NON_EXPORTABLE_TYPE("property", "C")!>val x2: C<!> = C()
<!NON_EXPORTABLE_TYPE("property", "C")!>var x3: C<!>
get() = C()
set(value) { }
}
@JsExport
fun foo2() {
}
@JsExport
fun foo3(<!NON_EXPORTABLE_TYPE("parameter", "Unit")!>x: Unit<!>) {
}
@JsExport
fun foo4(x: () -> Unit) {
}
@JsExport
fun foo5(<!NON_EXPORTABLE_TYPE("parameter", "(Unit) -> Unit")!>x: (Unit) -> Unit<!>) {
}
@JsExport
fun foo6(x: (A) -> A) {
}
@@ -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
}
}
@@ -0,0 +1,17 @@
// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport
// !DIAGNOSTICS: -UNUSED_PARAMETER
// !RENDER_DIAGNOSTICS_MESSAGES
package foo
abstract class C
interface I
@JsExport
fun <<!NON_EXPORTABLE_TYPE("upper bound", "C")!>T : C<!>>foo() { }
@JsExport
class A<<!NON_EXPORTABLE_TYPE("upper bound", "C")!>T : C<!>, <!NON_EXPORTABLE_TYPE("upper bound", "I")!>S: I<!>>
@JsExport
interface I2<<!NON_EXPORTABLE_TYPE("upper bound", "C"), NON_EXPORTABLE_TYPE("upper bound", "I")!>T<!>> where T : C, T : I
@@ -0,0 +1,31 @@
package
package foo {
@kotlin.js.JsExport public fun </*0*/ T : foo.C> foo(): kotlin.Unit
@kotlin.js.JsExport public final class A</*0*/ T : foo.C, /*1*/ S : foo.I> {
public constructor A</*0*/ T : foo.C, /*1*/ S : 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
}
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</*0*/ T : foo.C> 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
}
}
@@ -0,0 +1,20 @@
// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
package foo
<!WRONG_EXPORTED_DECLARATION("inline function with reified type parameters")!>@JsExport
inline fun <reified T> inlineReifiedFun(x: Any)<!> = x is T
<!WRONG_EXPORTED_DECLARATION("suspend function")!>@JsExport
suspend fun suspendFun()<!> { }
<!WRONG_EXPORTED_DECLARATION("extension property")!>@JsExport
val String.extensionProperty<!>
get() = this.length
@JsExport
enum class <!WRONG_EXPORTED_DECLARATION("enum class")!>EnumClass<!> { ENTRY1, ENTRY2 }
@JsExport
annotation class <!WRONG_EXPORTED_DECLARATION("annotation class")!>AnnotationClass<!>
@@ -0,0 +1,33 @@
package
package foo {
@kotlin.js.JsExport public val kotlin.String.extensionProperty: kotlin.Int
@kotlin.js.JsExport public inline fun </*0*/ reified T> 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<foo.EnumClass> {
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<foo.EnumClass>
}
}
@@ -0,0 +1,17 @@
// !USE_EXPERIMENTAL: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
@file:JsExport
package foo
<!WRONG_EXPORTED_DECLARATION("inline function with reified type parameters")!>inline fun <reified T> inlineReifiedFun(x: Any)<!> = x is T
<!WRONG_EXPORTED_DECLARATION("suspend function")!>suspend fun suspendFun()<!> { }
<!WRONG_EXPORTED_DECLARATION("extension property")!>val String.extensionProperty<!>
get() = this.length
enum class <!WRONG_EXPORTED_DECLARATION("enum class")!>EnumClass<!> { ENTRY1, ENTRY2 }
annotation class <!WRONG_EXPORTED_DECLARATION("annotation class")!>AnnotationClass<!>
@@ -0,0 +1,33 @@
package
package foo {
public val kotlin.String.extensionProperty: kotlin.Int
public inline fun </*0*/ reified T> 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<foo.EnumClass> {
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<foo.EnumClass>
}
}
@@ -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)
@@ -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<DeclarationDescriptor, SuggestedName?> = 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) {
@@ -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<JsCallChecker>()
container.useImpl<JsTypeSpecificityComparator>()
container.useImpl<JsNameClashChecker>()
@@ -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
}
}
@@ -105,6 +105,10 @@ public interface ErrorsJs {
DiagnosticFactory1<PsiElement, CallableMemberDescriptor> WRONG_MULTIPLE_INHERITANCE =
DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory0<PsiElement> NESTED_JS_EXPORT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<KtExpression, String> WRONG_EXPORTED_DECLARATION = DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory2<PsiElement, String, KotlinType> NON_EXPORTABLE_TYPE = DiagnosticFactory2.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{
@@ -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
@@ -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))
}
}
}
@@ -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)
}
}
@@ -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))
@@ -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<DeclarationDescriptor, MutableMap<String, DeclarationDescriptor>>()
private val clashedFakeOverrides = mutableMapOf<DeclarationDescriptor, Pair<DeclarationDescriptor, DeclarationDescriptor>>()
private val clashedDescriptors = mutableSetOf<Pair<DeclarationDescriptor, String>>()
@@ -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)) {
@@ -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;
@@ -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
@@ -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
) {
@@ -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<JsName, FunctionWithWrapper>,
@@ -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))
}
}
@@ -32,6 +32,11 @@ fun main(args: Array<String>) {
model("typescript-export/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS_IR)
}
testClass<AbstractLegacyJsTypeScriptExportTest> {
model("typescript-export/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS)
}
testClass<AbstractSourceMapGenerationSmokeTest> {
model("sourcemap/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS)
}
@@ -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
)
@@ -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");
}
}
}
@@ -167,6 +167,7 @@ class K2JSTranslator @JvmOverloads constructor(
reporter,
config,
analysisResult.bindingTrace,
bindingTrace.bindingContext,
translationResult
).process()
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
@@ -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<? extends SimpleFunctionDescriptor> 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());
@@ -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<DeclarationDescriptor, JsName> 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);
@@ -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);
}
@@ -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<DeclarationDescriptor, JsName>()
@@ -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"
}
}
@@ -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))
+3 -4
View File
@@ -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) {
@@ -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")
@@ -4,7 +4,6 @@ interface I {
val a: Char
}
@JsExport
object X : I {
override var a = '#'
}
@@ -1,7 +1,6 @@
// EXPECTED_REACHABLE_NODES: 1289
// IGNORE_BACKEND: JS_IR
@JsExport
open class A {
val foo: Char
get() = 'X'
@@ -1,5 +1,6 @@
// EXPECTED_REACHABLE_NODES: 1282
@JsExport
interface I {
fun ok(): String
}
@@ -12,7 +12,6 @@ class A {
}
}
@JsExport
val A.z: Int
@JsName("getZ_") get() = 42
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
@file:JsExport
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
@file:JsExport
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
// FILE: file1.kt
@@ -2,7 +2,6 @@ declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined
namespace foo {
const _any: any;
const _unit: void;
function _nothing(): never
const _throwable: Error;
const _string: string;
@@ -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()
@@ -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
@@ -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
@@ -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<exportable-type>`
* * 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)
+25 -4
View File
@@ -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<exportable-type>`
* * 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)