[JS IR] Export protected members too

#KT-47524 Fixed
#KT-47525 Fixed
This commit is contained in:
Sergej Jaskiewicz
2021-09-06 12:46:25 +00:00
committed by Space
parent 6abd3bf589
commit cb3d5f90e6
10 changed files with 117 additions and 9 deletions
@@ -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<ExportedParameter>
val parameters: List<ExportedParameter>,
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()
@@ -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
@@ -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 -> {
@@ -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)
@@ -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");
@@ -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");
@@ -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");
@@ -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';
}
@@ -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();
}
@@ -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