[JS IR] Control an inheritance of non-external entity from external

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
This commit is contained in:
Alexander Korepanov
2023-03-21 11:45:04 +01:00
committed by Space Team
parent 6e7b078873
commit 4813b659ab
26 changed files with 677 additions and 1 deletions
@@ -22,6 +22,7 @@ object JsPlatformConfigurator : PlatformConfiguratorBase(
NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(),
JsNameChecker, JsModuleChecker, JsExternalFileChecker,
JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker,
JsExternalInheritorOnlyChecker,
JsRuntimeAnnotationChecker,
JsDynamicDeclarationChecker,
JsExportAnnotationChecker,
@@ -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
}
}
@@ -114,6 +114,9 @@ public interface ErrorsJs {
DiagnosticFactory1<PsiElement, String> NON_CONSUMABLE_EXPORTED_IDENTIFIER = DiagnosticFactory1.create(WARNING, DEFAULT);
DiagnosticFactory2<PsiElement, DeclarationDescriptor, DeclarationDescriptor> JS_EXTERNAL_INHERITORS_ONLY = DiagnosticFactory2.create(
ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{
@@ -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))
}
}
}
}
}
@@ -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;
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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"
}
@@ -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"
};