From 4813b659abc6a358b5101c303b7fad63056410c6 Mon Sep 17 00:00:00 2001 From: Alexander Korepanov Date: Tue, 21 Mar 2023 11:45:04 +0100 Subject: [PATCH] [JS IR] Control an inheritance of non-external entity from external MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a special annotation @JsExternalInheritorsOnly for marking external interfaces and classes. The marked interface or class can’t be a parent for non external interfaces, classes or objects. ^KT-57423 Fixed --- .../diagnostics/KtFirDataClassConverters.kt | 8 + .../api/fir/diagnostics/KtFirDiagnostics.kt | 6 + .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 7 + .../diagnostics/FirJsDiagnosticsList.kt | 5 + .../analysis/diagnostics/js/FirJsErrors.kt | 2 + .../js/FirJsErrorsDefaultMessages.kt | 7 + .../js/checkers/JsDeclarationCheckers.kt | 1 + .../FirJsExternalInheritorOnlyChecker.kt | 53 ++++ .../jsExternalInheritorsOnly.kt | 135 ++++++++++ .../jsExternalInheritorsOnly.txt | 238 ++++++++++++++++++ .../DiagnosticsTestWithJsStdLibGenerated.java | 6 + .../kotlin/name/JsStandardClassIds.kt | 3 + .../js/resolve/JsPlatformConfigurator.kt | 1 + .../diagnostics/DefaultErrorMessagesJs.kt | 5 + .../js/resolve/diagnostics/ErrorsJs.java | 3 + .../JsExternalInheritorOnlyChecker.kt | 27 ++ .../js/translate/utils/AnnotationsUtils.java | 5 + .../js/test/fir/FirJsBoxTestGenerated.java | 6 + ...JsOldFrontendDiagnosticsTestGenerated.java | 6 + .../js/test/ir/IrBoxJsES6TestGenerated.java | 6 + .../js/test/ir/IrBoxJsTestGenerated.java | 6 + .../jsModule/jsExternalInheritorsOnly.kt | 63 +++++ .../jsModule/jsExternalInheritorsOnly.mjs | 21 ++ libraries/stdlib/api/js-v1/kotlin.js.kt | 8 + libraries/stdlib/api/js/kotlin.js.kt | 8 + libraries/stdlib/js/src/kotlin/annotations.kt | 42 +++- 26 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExternalInheritorOnlyChecker.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.txt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalInheritorOnlyChecker.kt create mode 100644 js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.kt create mode 100644 js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.mjs diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index de2453d0b08..ebe5bfb8022 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -5021,6 +5021,14 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirJsErrors.JS_EXTERNAL_INHERITORS_ONLY) { firDiagnostic -> + JsExternalInheritorsOnlyImpl( + firSymbolBuilder.classifierBuilder.buildClassLikeSymbol(firDiagnostic.a), + firSymbolBuilder.classifierBuilder.buildClassLikeSymbol(firDiagnostic.b), + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirJsErrors.NESTED_JS_EXPORT) { firDiagnostic -> NestedJsExportImpl( firDiagnostic as KtPsiDiagnostic, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 6fa02823dc2..0562838d62e 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -3493,6 +3493,12 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = ExternalInterfaceAsClassLiteral::class } + abstract class JsExternalInheritorsOnly : KtFirDiagnostic() { + override val diagnosticClass get() = JsExternalInheritorsOnly::class + abstract val parent: KtClassLikeSymbol + abstract val kid: KtClassLikeSymbol + } + abstract class NestedJsExport : KtFirDiagnostic() { override val diagnosticClass get() = NestedJsExport::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 5e9519c2487..75305fae011 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -4226,6 +4226,13 @@ internal class ExternalInterfaceAsClassLiteralImpl( override val token: KtLifetimeToken, ) : KtFirDiagnostic.ExternalInterfaceAsClassLiteral(), KtAbstractFirDiagnostic +internal class JsExternalInheritorsOnlyImpl( + override val parent: KtClassLikeSymbol, + override val kid: KtClassLikeSymbol, + override val firDiagnostic: KtPsiDiagnostic, + override val token: KtLifetimeToken, +) : KtFirDiagnostic.JsExternalInheritorsOnly(), KtAbstractFirDiagnostic + internal class NestedJsExportImpl( override val firDiagnostic: KtPsiDiagnostic, override val token: KtLifetimeToken, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt index eff7562ec44..2ce2f8d70db 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.fir.PrivateForInline import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.DiagnosticList import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.PositioningStrategy import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.psi.* @@ -94,6 +95,10 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") { parameter("targetType") } val EXTERNAL_INTERFACE_AS_CLASS_LITERAL by error() + val JS_EXTERNAL_INHERITORS_ONLY by error(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) { + parameter>("parent") + parameter>("kid") + } } val EXPORT by object : DiagnosticGroup("Export") { diff --git a/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt b/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt index 32390776759..2ea8eb982ff 100644 --- a/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt +++ b/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory import org.jetbrains.kotlin.fir.analysis.diagnostics.* import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.psi.KtAnonymousInitializer @@ -78,6 +79,7 @@ object FirJsErrors { val CANNOT_CHECK_FOR_EXTERNAL_INTERFACE by error1() val UNCHECKED_CAST_TO_EXTERNAL_INTERFACE by warning2() val EXTERNAL_INTERFACE_AS_CLASS_LITERAL by error0() + val JS_EXTERNAL_INHERITORS_ONLY by error2, FirClassLikeSymbol<*>>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT) // Export val NESTED_JS_EXPORT by error0() diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt index 82a7fd42ccd..9b5cb5f7dce 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.IMPLEMENTING import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.INLINE_CLASS_IN_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.INLINE_CLASS_IN_EXTERNAL_DECLARATION_WARNING import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.ENUM_CLASS_IN_EXTERNAL_DECLARATION_WARNING +import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_EXTERNAL_INHERITORS_ONLY import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.INLINE_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_NON_NATIVE import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_VAR @@ -153,6 +154,12 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() { "Can''t put non-external declarations in file marked with {0} annotation", FirDiagnosticRenderers.RENDER_TYPE ) + map.put( + JS_EXTERNAL_INHERITORS_ONLY, + "External {0} can''t be a parent of non-external {1}", + FirDiagnosticRenderers.RENDER_CLASS_OR_OBJECT_NAME, + FirDiagnosticRenderers.RENDER_CLASS_OR_OBJECT_NAME + ) map.put(JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY, "@JsName is prohibited for extension properties") map.put(JS_NAME_IS_NOT_ON_ALL_ACCESSORS, "@JsName should be on all the property accessors") map.put(JS_NAME_PROHIBITED_FOR_NAMED_NATIVE, "@JsName is prohibited for external declaration with explicit name") diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsDeclarationCheckers.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsDeclarationCheckers.kt index c835428b2f3..0e36b504a10 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsDeclarationCheckers.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsDeclarationCheckers.kt @@ -36,6 +36,7 @@ object JsDeclarationCheckers : DeclarationCheckers() { FirJsMultipleInheritanceChecker, FirJsDynamicDeclarationChecker, FirJsInheritanceClassChecker, + FirJsExternalInheritorOnlyChecker ) override val simpleFunctionCheckers: Set diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExternalInheritorOnlyChecker.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExternalInheritorOnlyChecker.kt new file mode 100644 index 00000000000..615cc831a11 --- /dev/null +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExternalInheritorOnlyChecker.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2023 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.fir.analysis.js.checkers.declaration + +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker +import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors +import org.jetbrains.kotlin.fir.analysis.js.checkers.isEffectivelyExternal +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.declarations.hasAnnotation +import org.jetbrains.kotlin.fir.declarations.utils.superConeTypes +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.types.ConeClassLikeType +import org.jetbrains.kotlin.fir.types.coneTypeSafe +import org.jetbrains.kotlin.fir.types.toRegularClassSymbol +import org.jetbrains.kotlin.name.JsStandardClassIds.Annotations.JsExternalInheritorsOnly +import org.jetbrains.kotlin.utils.addToStdlib.popLast + +object FirJsExternalInheritorOnlyChecker : FirClassChecker() { + private fun FirClass.forEachParents(context: CheckerContext, f: (FirRegularClassSymbol) -> Unit) { + val todo = superConeTypes.toMutableList() + val done = hashSetOf() + + while (todo.isNotEmpty()) { + val classSymbol = todo.popLast().toRegularClassSymbol(context.session) ?: continue + if (done.add(classSymbol)) { + f(classSymbol) + classSymbol.resolvedSuperTypeRefs.mapNotNullTo(todo) { it.type as? ConeClassLikeType } + } + } + } + + override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) { + if (!declaration.symbol.isEffectivelyExternal(context)) { + declaration.forEachParents(context) { parent -> + if (parent.hasAnnotation(JsExternalInheritorsOnly, context.session)) { + reporter.reportOn( + declaration.source, + FirJsErrors.JS_EXTERNAL_INHERITORS_ONLY, + parent, + declaration.symbol, + context + ) + } + } + } + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.kt b/compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.kt new file mode 100644 index 00000000000..2268026c3f5 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.kt @@ -0,0 +1,135 @@ +// FIR_IDENTICAL + +@OptIn(ExperimentalStdlibApi::class) +@JsExternalInheritorsOnly +external interface ExternalInterfaceX { + val x: String +} + +@OptIn(ExperimentalStdlibApi::class) +@JsExternalInheritorsOnly +external open class ExternalOpenClassX { + val x: String +} + +// check interfaces + +external interface ExternalInterfaceXY : ExternalInterfaceX { + val y: String +} + +interface InterfaceXY : ExternalInterfaceX { + val y: String +} + +interface InterfaceXYZ : ExternalInterfaceXY { + val z: String +} + +// check objects + +external object ExternalObjectXY : ExternalInterfaceX { + override val x: String + val y: String +} + +external object ExternalObjectXYZ : ExternalInterfaceXY { + override val x: String + override val y: String + val z: String +} + +external object ExternalObjectXZ : ExternalOpenClassX { + val z: String +} + +object ObjectXY : ExternalInterfaceX { + override val x: String = "X" + val y: String = "Y" +} + +object ObjectXYZ : ExternalInterfaceXY { + override val x: String = "X" + override val y: String = "Y" + val z: String = "Z" +} + +object ObjectXZ : ExternalOpenClassX() { + val z: String = "Z" +} + +// check classes + +external class ExternalClassXY : ExternalInterfaceX { + override val x: String + val y: String +} + +external class ExternalClassXYZ : ExternalInterfaceXY { + override val x: String + override val y: String + val z: String +} + +external open class ExternalOpenClassXZ : ExternalOpenClassX { + val z: String +} + +class ClassXY : ExternalInterfaceX { + override val x: String = "X" + val y: String = "Y" +} + +class ClassXYZ : ExternalInterfaceXY { + override val x: String = "X" + override val y: String = "Y" + val z: String = "Z" +} + +class ClassXZ : ExternalOpenClassX() { + val z: String = "Z" +} + +class ClassXZY : ExternalOpenClassXZ() { + val y: String = "Y" +} + +// check nested + +external class ExternalClassNameSpace { + @OptIn(ExperimentalStdlibApi::class) + @JsExternalInheritorsOnly + interface NestedInterfaceX { + val x: String + } + + interface NestedInterfaceXY : NestedInterfaceX { + val y: String + } +} + +external interface ExternalInterfaceXY2 : ExternalClassNameSpace.NestedInterfaceX { + val y: String +} + +external interface ExternalInterfaceXYZ2 : ExternalClassNameSpace.NestedInterfaceXY { + val z: String +} + +interface InterfaceXY2 : ExternalClassNameSpace.NestedInterfaceX { + val y: String +} + +interface InterfaceXYZ2 : ExternalClassNameSpace.NestedInterfaceXY { + val z: String +} + +// multiple inheritance + +external class ExternalClassXY2 : ExternalInterfaceX, ExternalOpenClassX { + val y: String +} + +class ClassXY2 : ExternalInterfaceX, ExternalOpenClassX() { + val y: String = "Y" +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.txt b/compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.txt new file mode 100644 index 00000000000..e38c293d561 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.txt @@ -0,0 +1,238 @@ +package + +public final class ClassXY : ExternalInterfaceX { + public constructor ClassXY() + public open override /*1*/ val x: kotlin.String = "X" + public final val y: kotlin.String = "Y" + 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 class ClassXY2 : ExternalInterfaceX, ExternalOpenClassX { + public constructor ClassXY2() + public final override /*2*/ /*fake_override*/ val x: kotlin.String + public final val y: kotlin.String = "Y" + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class ClassXYZ : ExternalInterfaceXY { + public constructor ClassXYZ() + public open override /*1*/ val x: kotlin.String = "X" + public open override /*1*/ val y: kotlin.String = "Y" + public final val z: kotlin.String = "Z" + 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 class ClassXZ : ExternalOpenClassX { + public constructor ClassXZ() + public final override /*1*/ /*fake_override*/ val x: kotlin.String + public final val z: kotlin.String = "Z" + 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 class ClassXZY : ExternalOpenClassXZ { + public constructor ClassXZY() + public final override /*1*/ /*fake_override*/ val x: kotlin.String + public final val y: kotlin.String = "Y" + public final override /*1*/ /*fake_override*/ val z: 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 +} + +public final external class ExternalClassNameSpace { + public constructor ExternalClassNameSpace() + 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.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) @kotlin.js.JsExternalInheritorsOnly public interface NestedInterfaceX { + public abstract 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 + } + + public interface NestedInterfaceXY : ExternalClassNameSpace.NestedInterfaceX { + public abstract override /*1*/ /*fake_override*/ val x: kotlin.String + public abstract val y: 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 + } +} + +public final external class ExternalClassXY : ExternalInterfaceX { + public constructor ExternalClassXY() + public open override /*1*/ val x: kotlin.String + public final val y: 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 +} + +public final external class ExternalClassXY2 : ExternalInterfaceX, ExternalOpenClassX { + public constructor ExternalClassXY2() + public final override /*2*/ /*fake_override*/ val x: kotlin.String + public final val y: kotlin.String + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final external class ExternalClassXYZ : ExternalInterfaceXY { + public constructor ExternalClassXYZ() + public open override /*1*/ val x: kotlin.String + public open override /*1*/ val y: kotlin.String + public final val z: 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.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) @kotlin.js.JsExternalInheritorsOnly public external interface ExternalInterfaceX { + public abstract 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 +} + +public external interface ExternalInterfaceXY : ExternalInterfaceX { + public abstract override /*1*/ /*fake_override*/ val x: kotlin.String + public abstract val y: 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 +} + +public external interface ExternalInterfaceXY2 : ExternalClassNameSpace.NestedInterfaceX { + public abstract override /*1*/ /*fake_override*/ val x: kotlin.String + public abstract val y: 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 +} + +public external interface ExternalInterfaceXYZ2 : ExternalClassNameSpace.NestedInterfaceXY { + public abstract override /*1*/ /*fake_override*/ val x: kotlin.String + public abstract override /*1*/ /*fake_override*/ val y: kotlin.String + public abstract val z: 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 +} + +public external object ExternalObjectXY : ExternalInterfaceX { + private constructor ExternalObjectXY() + public open override /*1*/ val x: kotlin.String + public final val y: 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 +} + +public external object ExternalObjectXYZ : ExternalInterfaceXY { + private constructor ExternalObjectXYZ() + public open override /*1*/ val x: kotlin.String + public open override /*1*/ val y: kotlin.String + public final val z: 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 +} + +public external object ExternalObjectXZ : ExternalOpenClassX { + private constructor ExternalObjectXZ() + public final override /*1*/ /*fake_override*/ val x: kotlin.String + public final val z: 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.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) @kotlin.js.JsExternalInheritorsOnly public open external class ExternalOpenClassX { + public constructor ExternalOpenClassX() + 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 +} + +public open external class ExternalOpenClassXZ : ExternalOpenClassX { + public constructor ExternalOpenClassXZ() + public final override /*1*/ /*fake_override*/ val x: kotlin.String + public final val z: 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 +} + +public interface InterfaceXY : ExternalInterfaceX { + public abstract override /*1*/ /*fake_override*/ val x: kotlin.String + public abstract val y: 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 +} + +public interface InterfaceXY2 : ExternalClassNameSpace.NestedInterfaceX { + public abstract override /*1*/ /*fake_override*/ val x: kotlin.String + public abstract val y: 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 +} + +public interface InterfaceXYZ : ExternalInterfaceXY { + public abstract override /*1*/ /*fake_override*/ val x: kotlin.String + public abstract override /*1*/ /*fake_override*/ val y: kotlin.String + public abstract val z: 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 +} + +public interface InterfaceXYZ2 : ExternalClassNameSpace.NestedInterfaceXY { + public abstract override /*1*/ /*fake_override*/ val x: kotlin.String + public abstract override /*1*/ /*fake_override*/ val y: kotlin.String + public abstract val z: 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 +} + +public object ObjectXY : ExternalInterfaceX { + private constructor ObjectXY() + public open override /*1*/ val x: kotlin.String = "X" + public final val y: kotlin.String = "Y" + 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 object ObjectXYZ : ExternalInterfaceXY { + private constructor ObjectXYZ() + public open override /*1*/ val x: kotlin.String = "X" + public open override /*1*/ val y: kotlin.String = "Y" + public final val z: kotlin.String = "Z" + 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 object ObjectXZ : ExternalOpenClassX { + private constructor ObjectXZ() + public final override /*1*/ /*fake_override*/ val x: kotlin.String + public final val z: kotlin.String = "Z" + 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/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJsStdLibGenerated.java index 762feb781f5..2c4155c8374 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJsStdLibGenerated.java @@ -36,6 +36,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes runTest("compiler/testData/diagnostics/testsWithJsStdLib/implementingFunction.kt"); } + @Test + @TestMetadata("jsExternalInheritorsOnly.kt") + public void testJsExternalInheritorsOnly() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.kt"); + } + @Test @TestMetadata("localClassMetadata.kt") public void testLocalClassMetadata() throws Exception { diff --git a/core/compiler.common.js/src/org/jetbrains/kotlin/name/JsStandardClassIds.kt b/core/compiler.common.js/src/org/jetbrains/kotlin/name/JsStandardClassIds.kt index 1d917f4cd79..f1bb191cf3d 100644 --- a/core/compiler.common.js/src/org/jetbrains/kotlin/name/JsStandardClassIds.kt +++ b/core/compiler.common.js/src/org/jetbrains/kotlin/name/JsStandardClassIds.kt @@ -41,6 +41,9 @@ object JsStandardClassIds { @JvmField val JsExport = "JsExport".jsId() + @JvmField + val JsExternalInheritorsOnly = "JsExternalInheritorsOnly".jsId() + @JvmField val JsExportIgnore = JsExport.createNestedClassId(Name.identifier("Ignore")) 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 42c0faedc3b..fbce0136f15 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 @@ -22,6 +22,7 @@ object JsPlatformConfigurator : PlatformConfiguratorBase( NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(), JsNameChecker, JsModuleChecker, JsExternalFileChecker, JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker, + JsExternalInheritorOnlyChecker, JsRuntimeAnnotationChecker, JsDynamicDeclarationChecker, JsExportAnnotationChecker, 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 35fc64c081c..b8fe119fb79 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 @@ -109,6 +109,11 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { put(ErrorsJs.NON_CONSUMABLE_EXPORTED_IDENTIFIER, "Exported declaration contains non-consumable identifier '${0}', that can't be represented inside TS definitions and ESM", STRING) + put(ErrorsJs.JS_EXTERNAL_INHERITORS_ONLY, + "External {0} can''t be a parent of non-external {1}", + Renderers.DECLARATION_NAME_WITH_KIND, + Renderers.DECLARATION_NAME_WITH_KIND) + this } } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java index 2791c2a7eb5..088a3ceaa7a 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 @@ -114,6 +114,9 @@ public interface ErrorsJs { DiagnosticFactory1 NON_CONSUMABLE_EXPORTED_IDENTIFIER = DiagnosticFactory1.create(WARNING, DEFAULT); + DiagnosticFactory2 JS_EXTERNAL_INHERITORS_ONLY = 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/JsExternalInheritorOnlyChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalInheritorOnlyChecker.kt new file mode 100644 index 00000000000..6839b5b6a4c --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalInheritorOnlyChecker.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2023 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.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers +import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal + +object JsExternalInheritorOnlyChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + if (descriptor is ClassDescriptor && !descriptor.isEffectivelyExternal()) { + descriptor.getAllSuperClassifiers().forEach { parent -> + if (parent is ClassDescriptor && AnnotationsUtils.isJsExternalInheritorsOnly(parent)) { + context.trace.report(ErrorsJs.JS_EXTERNAL_INHERITORS_ONLY.on(declaration, parent, 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 f4e35a304e5..08b9e866ebd 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 @@ -46,6 +46,7 @@ public final class AnnotationsUtils { private static final FqName JS_MODULE_ANNOTATION = Annotations.JsModule.asSingleFqName(); private static final FqName JS_NON_MODULE_ANNOTATION = Annotations.JsNonModule.asSingleFqName(); private static final FqName JS_QUALIFIER_ANNOTATION = Annotations.JsQualifier.asSingleFqName(); + private static final FqName JS_EXTERNAL_INHERITORS_ONLY = Annotations.JsExternalInheritorsOnly.asSingleFqName(); private AnnotationsUtils() { } @@ -258,6 +259,10 @@ public final class AnnotationsUtils { ); } + public static boolean isJsExternalInheritorsOnly(@NotNull ClassDescriptor declaration) { + return declaration.getAnnotations().hasAnnotation(JS_EXTERNAL_INHERITORS_ONLY); + } + @Nullable private static String extractSingleStringArgument(@NotNull AnnotationDescriptor annotation) { if (annotation.getAllValueArguments().isEmpty()) return null; diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index 068ae34732d..518278f4192 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -2457,6 +2457,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest { runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt"); } + @Test + @TestMetadata("jsExternalInheritorsOnly.kt") + public void testJsExternalInheritorsOnly() throws Exception { + runTest("js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.kt"); + } + @Test @TestMetadata("topLevelVarargFun.kt") public void testTopLevelVarargFun() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirPsiJsOldFrontendDiagnosticsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirPsiJsOldFrontendDiagnosticsTestGenerated.java index 40d057deeac..bfb037cac1c 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirPsiJsOldFrontendDiagnosticsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirPsiJsOldFrontendDiagnosticsTestGenerated.java @@ -37,6 +37,12 @@ public class FirPsiJsOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiJ runTest("compiler/testData/diagnostics/testsWithJsStdLib/implementingFunction.kt"); } + @Test + @TestMetadata("jsExternalInheritorsOnly.kt") + public void testJsExternalInheritorsOnly() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJsStdLib/jsExternalInheritorsOnly.kt"); + } + @Test @TestMetadata("localClassMetadata.kt") public void testLocalClassMetadata() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index 85b846a9253..a1f3897b596 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -2563,6 +2563,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt"); } + @Test + @TestMetadata("jsExternalInheritorsOnly.kt") + public void testJsExternalInheritorsOnly() throws Exception { + runTest("js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.kt"); + } + @Test @TestMetadata("topLevelVarargFun.kt") public void testTopLevelVarargFun() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index d115c39fe05..1b7a1f445e8 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -2457,6 +2457,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt"); } + @Test + @TestMetadata("jsExternalInheritorsOnly.kt") + public void testJsExternalInheritorsOnly() throws Exception { + runTest("js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.kt"); + } + @Test @TestMetadata("topLevelVarargFun.kt") public void testTopLevelVarargFun() throws Exception { diff --git a/js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.kt b/js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.kt new file mode 100644 index 00000000000..1076b5d5837 --- /dev/null +++ b/js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.kt @@ -0,0 +1,63 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// DONT_TARGET_EXACT_BACKEND: WASM +// ES_MODULES + +@OptIn(ExperimentalStdlibApi::class) +@JsExternalInheritorsOnly +external interface ExternalInterfaceX { + val x: String +} + +external interface ExternalInterfaceXY : ExternalInterfaceX { + val y: String +} + +external interface ExternalInterfaceXYZ : ExternalInterfaceXY { + val z: String +} + +external class ExternalXYZ() : ExternalInterfaceXYZ { + override val x: String + override val y: String + override val z: String +} + +external class ExternalClassNameSpace { + interface NestedInterfaceXYZ : ExternalInterfaceXYZ { + override val x: String + override val y: String + override val z: String + } +} + +@JsModule("./jsExternalInheritorsOnly.mjs") +external object Creator: ExternalInterfaceXYZ { + fun createX(): ExternalInterfaceX + fun createXY(): ExternalInterfaceXY + fun createXYZ(): ExternalInterfaceXYZ + fun createClassXYZ(): ExternalXYZ + + fun createNestedInterfaceXYZ(): ExternalClassNameSpace.NestedInterfaceXYZ + + override val x: String + override val y: String + override val z: String +} + +fun checkX(x: ExternalInterfaceX, id: Int) = x.x == "X$id" +fun checkXY(xy: ExternalInterfaceXY, id: Int) = checkX(xy, id) && xy.y == "Y$id" +fun checkXYZ(xyz: ExternalInterfaceXYZ, id: Int) = checkXY(xyz, id) && xyz.z == "Z$id" + +fun box(): String { + if (!checkX(Creator.createX(), 1)) return "Fail interface X" + if (!checkXY(Creator.createXY(), 2)) return "Fail interface XY" + if (!checkXYZ(Creator.createXYZ(), 3)) return "Fail interface XYZ" + + if (!checkXYZ(Creator.createClassXYZ(), 4)) return "Fail class XYZ" + + if (!checkXYZ(Creator.createNestedInterfaceXYZ(), 5)) return "Fail nested interface XYZ" + + if (!checkXYZ(Creator, 6)) return "Fail object XYZ" + + return "OK" +} diff --git a/js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.mjs b/js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.mjs new file mode 100644 index 00000000000..35ccf800f74 --- /dev/null +++ b/js/js.translator/testData/box/esModules/jsModule/jsExternalInheritorsOnly.mjs @@ -0,0 +1,21 @@ +export default { + createX: function () { + return {x: "X1"}; + }, + createXY: function () { + return {x: "X2", y: "Y2"}; + }, + createXYZ: function () { + return {x: "X3", y: "Y3", z: "Z3"}; + }, + createClassXYZ: function () { + return {x: "X4", y: "Y4", z: "Z4"}; + }, + createNestedInterfaceXYZ: function () { + return {x: "X5", y: "Y5", z: "Z5"}; + }, + + x: "X6", + y: "Y6", + z: "Z6" +}; diff --git a/libraries/stdlib/api/js-v1/kotlin.js.kt b/libraries/stdlib/api/js-v1/kotlin.js.kt index 4416386650b..b6a44ac99a5 100644 --- a/libraries/stdlib/api/js-v1/kotlin.js.kt +++ b/libraries/stdlib/api/js-v1/kotlin.js.kt @@ -250,6 +250,14 @@ public final annotation class JsExport : kotlin.Annotation { } } +@kotlin.ExperimentalStdlibApi +@kotlin.annotation.Retention(value = AnnotationRetention.BINARY) +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS}) +@kotlin.SinceKotlin(version = "1.9") +public final annotation class JsExternalInheritorsOnly : kotlin.Annotation { + public constructor JsExternalInheritorsOnly() +} + @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.FILE}) public final annotation class JsModule : kotlin.Annotation { diff --git a/libraries/stdlib/api/js/kotlin.js.kt b/libraries/stdlib/api/js/kotlin.js.kt index b3f07c8bd01..f241341995b 100644 --- a/libraries/stdlib/api/js/kotlin.js.kt +++ b/libraries/stdlib/api/js/kotlin.js.kt @@ -249,6 +249,14 @@ public final annotation class JsExport : kotlin.Annotation { } } +@kotlin.ExperimentalStdlibApi +@kotlin.annotation.Retention(value = AnnotationRetention.BINARY) +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS}) +@kotlin.SinceKotlin(version = "1.9") +public final annotation class JsExternalInheritorsOnly : kotlin.Annotation { + public constructor JsExternalInheritorsOnly() +} + @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.FILE}) public final annotation class JsModule : kotlin.Annotation { diff --git a/libraries/stdlib/js/src/kotlin/annotations.kt b/libraries/stdlib/js/src/kotlin/annotations.kt index dfddd9e0c45..517975a8f8d 100644 --- a/libraries/stdlib/js/src/kotlin/annotations.kt +++ b/libraries/stdlib/js/src/kotlin/annotations.kt @@ -211,4 +211,44 @@ public actual annotation class JsExport { @Target(AnnotationTarget.PROPERTY) @SinceKotlin("1.6") @Deprecated("This annotation is a temporal migration assistance and may be removed in the future releases, please consider filing an issue about the case where it is needed") -public annotation class EagerInitialization \ No newline at end of file +public annotation class EagerInitialization + + +/** + * When placed on an external interface or class, requires all its child + * interfaces, classes, and objects to be external as well. + * + * The compiler mangles identifiers of functions and properties from non-external interfaces and classes, + * and doesn't mangle from external ones. Requiring external interfaces and classes inheritors being external + * is necessary to avoid non-obvious bugs when identifier naming in an inheritor doesn't correspond to + * naming in the base class or interface. + * + * Example: + * + * ```kotlin + * // From kotlin-wrappers + * external interface Props { + * var key: Key? + * } + * + * // User code + * @OptIn(ExperimentalStdlibApi::class) + * @JsExternalInheritorsOnly + * external interface LayoutProps : Props { + * var layout: Layout? + * } + * + * external interface ComponentProps : LayoutProps // OK + * + * external interface ComponentA : ComponentProps // OK + * + * interface ComponentB : ComponentProps // Compilation error! + * ``` + * + * This annotation is experimental, meaning that the restrictions mentioned above are subject to change. + */ +@ExperimentalStdlibApi +@Retention(AnnotationRetention.BINARY) +@Target(CLASS) +@SinceKotlin("1.9") +public annotation class JsExternalInheritorsOnly