diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt index e8cc4ebd73e..9bcdcaa5ce5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt @@ -31,11 +31,13 @@ class ExportedFunction( val isMember: Boolean = false, val isStatic: Boolean = false, val isAbstract: Boolean = false, + val isProtected: Boolean, val ir: IrSimpleFunction ) : ExportedDeclaration() class ExportedConstructor( - val parameters: List + val parameters: List, + val isProtected: Boolean ) : ExportedDeclaration() class ExportedProperty( @@ -45,6 +47,7 @@ class ExportedProperty( val isMember: Boolean = false, val isStatic: Boolean = false, val isAbstract: Boolean, + val isProtected: Boolean, val irGetter: IrFunction?, val irSetter: IrFunction?, ) : ExportedDeclaration() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt index 114c32d9826..0ef8cd94142 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase.ES6_INIT_BOX_PARAMETER @@ -75,6 +76,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) { isMember = parent is IrClass, isStatic = function.isStaticMethodOfClass, isAbstract = parent is IrClass && !parent.isInterface && function.modality == Modality.ABSTRACT, + isProtected = function.visibility == DescriptorVisibilities.PROTECTED, ir = function ) } @@ -84,7 +86,10 @@ class ExportModelGenerator(val context: JsIrBackendContext) { if (!constructor.isPrimary) return null val allValueParameters = listOfNotNull(constructor.extensionReceiverParameter) + constructor.valueParameters.filterNot { it.origin === ES6_RESULT_TYPE_PARAMETER || it.origin === ES6_INIT_BOX_PARAMETER } - return ExportedConstructor(allValueParameters.map { exportParameter(it) }) + return ExportedConstructor( + parameters = allValueParameters.map { exportParameter(it) }, + isProtected = constructor.visibility == DescriptorVisibilities.PROTECTED + ) } private fun exportParameter(parameter: IrValueParameter): ExportedParameter { @@ -115,6 +120,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) { isMember = parentClass != null, isStatic = false, isAbstract = parentClass?.isInterface == false && property.modality == Modality.ABSTRACT, + isProtected = property.visibility == DescriptorVisibilities.PROTECTED, irGetter = property.getter, irSetter = property.setter ) @@ -217,6 +223,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) { isMember = klass.parent is IrClass, isStatic = true, isAbstract = false, + isProtected = klass.visibility == DescriptorVisibilities.PROTECTED, irGetter = context.mapping.objectToGetInstanceFunction[klass]!!, irSetter = null ) @@ -372,7 +379,7 @@ private fun getExportCandidate(declaration: IrDeclaration): IrDeclarationWithNam // Only actual public declarations with name can be exported if (declaration !is IrDeclarationWithVisibility || declaration !is IrDeclarationWithName || - declaration.visibility != DescriptorVisibilities.PUBLIC || + !declaration.visibility.isPublicAPI || declaration.isExpect ) { return null diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt index e69337df76e..450601ed98a 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -41,6 +41,8 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin "${prefix}namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}" is ExportedFunction -> { + val visibility = if (isProtected) "protected " else "" + val keyword: String = when { isMember -> when { isStatic -> "static " @@ -60,17 +62,21 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin val renderedReturnType = returnType.toTypeScript(indent) - "${prefix}$keyword$name$renderedTypeParameters($renderedParameters): $renderedReturnType;" + "${prefix}$visibility$keyword$name$renderedTypeParameters($renderedParameters): $renderedReturnType;" + } + is ExportedConstructor -> { + val visibility = if (isProtected) "protected " else "" + val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) } + "${visibility}constructor($renderedParameters);" } - is ExportedConstructor -> - "constructor(${parameters.joinToString(", ") { it.toTypeScript(indent) }});" is ExportedProperty -> { + val visibility = if (isProtected) "protected " else "" val keyword = when { isMember -> (if (isAbstract) "abstract " else "") + (if (!mutable) "readonly " else "") else -> if (mutable) "let " else "const " } - prefix + keyword + name + ": " + type.toTypeScript(indent) + ";" + "$prefix$visibility$keyword$name: ${type.toTypeScript(indent)};" } is ExportedClass -> { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt index 5ce9fa2c1c0..eb4cf81e7fa 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt @@ -101,7 +101,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo if (property.getter?.extensionReceiverParameter != null || property.setter?.extensionReceiverParameter != null) continue - if (property.visibility != DescriptorVisibilities.PUBLIC) + if (!property.visibility.isPublicAPI) continue if (property.isFakeOverride) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java index 3d002bc6980..d62cd1a9d5f 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java @@ -1632,6 +1632,11 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/export/exportNestedObject.kt"); } + @TestMetadata("exportProtectedMembers.kt") + public void testExportProtectedMembers() throws Exception { + runTest("js/js.translator/testData/box/export/exportProtectedMembers.kt"); + } + @TestMetadata("nonIndetifierModuleName.kt") public void testNonIndetifierModuleName() throws Exception { runTest("js/js.translator/testData/box/export/nonIndetifierModuleName.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index 75748baa40b..0e142f243fd 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -1632,6 +1632,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/export/exportNestedObject.kt"); } + @TestMetadata("exportProtectedMembers.kt") + public void testExportProtectedMembers() throws Exception { + runTest("js/js.translator/testData/box/export/exportProtectedMembers.kt"); + } + @TestMetadata("nonIndetifierModuleName.kt") public void testNonIndetifierModuleName() throws Exception { runTest("js/js.translator/testData/box/export/nonIndetifierModuleName.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 8f9f7c7e708..24becf9bf84 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -1637,6 +1637,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { runTest("js/js.translator/testData/box/export/exportNestedObject.kt"); } + @TestMetadata("exportProtectedMembers.kt") + public void testExportProtectedMembers() throws Exception { + runTest("js/js.translator/testData/box/export/exportProtectedMembers.kt"); + } + @TestMetadata("nonIndetifierModuleName.kt") public void testNonIndetifierModuleName() throws Exception { runTest("js/js.translator/testData/box/export/nonIndetifierModuleName.kt"); diff --git a/js/js.translator/testData/box/export/exportProtectedMembers.kt b/js/js.translator/testData/box/export/exportProtectedMembers.kt new file mode 100644 index 00000000000..d11d1194a32 --- /dev/null +++ b/js/js.translator/testData/box/export/exportProtectedMembers.kt @@ -0,0 +1,53 @@ +// EXPECTED_REACHABLE_NODES: 1265 +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// SKIP_MINIFICATION +// SKIP_NODE_JS +// SKIP_DCE_DRIVEN + +// MODULE: exportProtectedMembers +// FILE: lib.kt + +@JsExport +open class Foo protected constructor() { + protected fun bar(): String = "protected method" + + private var _baz: String = "baz" + + protected var baz: String + get() = _baz + set(value) { + _baz = value + } + + protected class NestedClass { + val prop: String = "nested class property" + } + protected object NestedObject { + val prop: String = "nested object property" + } + + protected companion object { + val prop: String = "companion object property" + } +} + +// FILE: test.js +function box() { + foo = new exportProtectedMembers.Foo(); + + if (foo.bar() != 'protected method') return 'failed to call protected method'; + if (foo.baz != 'baz') return 'failed to read protected property'; + foo.baz = 'beer'; + if (foo.baz != 'beer') return 'failed to write protected property'; + + nestedClass = new exportProtectedMembers.Foo.NestedClass() + if (nestedClass.prop != 'nested class property') + return 'failed to read protected class property' + if (exportProtectedMembers.Foo.NestedObject.prop != 'nested object property') + return 'failed to read protected nested object property' + if (exportProtectedMembers.Foo.Companion.prop != 'companion object property') + return 'failed to read protected companion object property' + + return 'OK'; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/visibility/visibility.d.ts b/js/js.translator/testData/typescript-export/visibility/visibility.d.ts index fc58155515a..a7a43989d72 100644 --- a/js/js.translator/testData/typescript-export/visibility/visibility.d.ts +++ b/js/js.translator/testData/typescript-export/visibility/visibility.d.ts @@ -9,10 +9,24 @@ declare namespace JS_TESTS { } class Class { constructor(); + protected readonly protectedVal: number; + protected protectedFun(): number; + protected readonly protectedNestedObject: { + }; + protected readonly Companion: { + readonly companionObjectProp: number; + }; readonly publicVal: number; publicFun(): number; } namespace Class { + class protectedClass { + constructor(); + } + class classWithProtectedConstructors { + protected constructor(); + protected static createWithString(arg: string): Class.classWithProtectedConstructors; + } class publicClass { constructor(); } diff --git a/js/js.translator/testData/typescript-export/visibility/visibility.kt b/js/js.translator/testData/typescript-export/visibility/visibility.kt index 8d1302babb5..8b573d6a6d3 100644 --- a/js/js.translator/testData/typescript-export/visibility/visibility.kt +++ b/js/js.translator/testData/typescript-export/visibility/visibility.kt @@ -34,7 +34,17 @@ open class Class { protected val protectedVal = 10 protected fun protectedFun() = 10 - protected class protectedClass + protected class protectedClass {} + protected object protectedNestedObject {} + protected companion object { + val companionObjectProp = 10 + } + + public class classWithProtectedConstructors protected constructor() { + + @JsName("createWithString") + protected constructor(arg: String): this() + } public val publicVal = 10 @JsName("publicFun") // TODO: Should work without JsName