[JS IR BE] Initial version of member namer

This commit is contained in:
Svyatoslav Kuzmich
2019-06-17 16:37:33 +03:00
parent 3e10de9cd1
commit 0b19a4a32b
14 changed files with 222 additions and 125 deletions
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.asString
import org.jetbrains.kotlin.ir.backend.js.utils.functionSignature
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
@@ -28,7 +28,6 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.*
// Constructs bridges for inherited generic functions
@@ -114,7 +113,7 @@ class BridgesConstruction(val context: JsIrBackendContext) : ClassLoweringPass {
): IrFunction {
val origin =
if (bridge.isEffectivelyExternal())
if (bridge.isEffectivelyExternal() || bridge.getJsName() != null)
JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION
else
IrDeclarationOrigin.BRIDGE
@@ -200,26 +199,7 @@ class FunctionAndSignature(val function: IrSimpleFunction) {
// TODO: Use type-upper-bound-based signature instead of Strings
// Currently strings are used for compatibility with a hack-based name generator
private data class Signature(
val name: String,
val extensionReceiverType: String? = null,
val valueParameters: List<String?> = emptyList(),
val returnType: String? = null
)
private val jsName = function.getJsName()
private val signature = when {
jsName != null -> Signature(jsName)
function.isEffectivelyExternal() -> Signature(function.name.asString())
else -> Signature(
function.name.asString(),
function.extensionReceiverParameter?.type?.asString(),
function.valueParameters.map { it.type.asString() },
// Return type used in signature for inline classes and Unit because
// they are binary incompatible with supertypes and require bridges.
function.returnType.run { if (isInlined() || isUnit()) asString() else null }
)
}
private val signature = functionSignature(function)
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -48,7 +48,7 @@ class PrimitiveCompanionLowering(val context: JsIrBackendContext) : FileLowering
val actualFunction =
actualCompanion.declarations
.filterIsInstance<IrSimpleFunction>()
.find { it.name == function.name }
.single { it.name == function.name }
return actualFunction!!
}
@@ -69,8 +69,7 @@ class PrimitiveCompanionLowering(val context: JsIrBackendContext) : FileLowering
override fun visitCall(expression: IrCall): IrExpression {
val newCall = super.visitCall(expression) as IrCall
val function = expression.symbol.owner as? IrSimpleFunction
?: return newCall
val function = expression.symbol.owner as IrSimpleFunction
val actualFunction = getActualPrimitiveCompanionPropertyAccessor(function)
?: return newCall
@@ -135,7 +135,6 @@ class IrModuleToJsTransformer(
val program = JsProgram()
val nameGenerator = IrNamerImpl(
memberNameGenerator = LegacyMemberNameGenerator(program.rootScope),
newNameTables = namer,
rootScope = program.rootScope
)
@@ -81,12 +81,23 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
if (property.origin == IrDeclarationOrigin.FAKE_OVERRIDE)
continue
fun IrSimpleFunction.accessorRef(): JsNameRef? =
when (visibility) {
Visibilities.PRIVATE -> null
else -> JsNameRef(
context.getNameForMemberFunction(this),
classPrototypeRef
)
}
val getterRef = property.getter?.accessorRef()
val setterRef = property.setter?.accessorRef()
classBlock.statements += JsExpressionStatement(
defineProperty(
classPrototypeRef,
context.getNameForProperty(property).ident,
getter = property.getter?.let { JsNameRef(context.getNameForMemberFunction(it), classPrototypeRef) },
setter = property.setter?.let { JsNameRef(context.getNameForMemberFunction(it), classPrototypeRef) }
getter = getterRef,
setter = setterRef
)
)
}
@@ -121,9 +132,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
private fun generateMemberFunction(declaration: IrSimpleFunction): JsStatement? {
val translatedFunction = declaration.run { if (isReal) accept(IrFunctionToJsTransformer(), context) else null }
if (declaration.isStaticMethodOfClass) {
return translatedFunction?.makeStmt()
}
assert(!declaration.isStaticMethodOfClass)
val memberName = context.getNameForMemberFunction(declaration.realOverrideTarget)
val memberRef = JsNameRef(memberName, classPrototypeRef)
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.js.backend.ast.JsNameRef
import org.jetbrains.kotlin.js.backend.ast.JsRootScope
class IrNamerImpl(
private val memberNameGenerator: LegacyMemberNameGenerator,
private val newNameTables: NameTables,
private val rootScope: JsRootScope // TODO: Don't use scopes
) : IrNamer {
@@ -32,12 +31,12 @@ class IrNamerImpl(
override fun getNameForMemberFunction(function: IrSimpleFunction): JsName {
require(function.dispatchReceiverParameter != null)
return memberNameGenerator.getNameForMemberFunction(function)
return rootScope.declareName(newNameTables.getNameForMemberFunction(function))
}
override fun getNameForMemberField(field: IrField): JsName {
require(!field.isStatic)
return memberNameGenerator.getNameForMemberField(field)
return rootScope.declareName(newNameTables.getNameForMemberField(field))
}
override fun getNameForField(field: IrField): JsName {
@@ -14,12 +14,11 @@ import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.isEnumClass
import org.jetbrains.kotlin.ir.util.isInlined
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.backend.ast.JsName
import org.jetbrains.kotlin.js.backend.ast.JsScope
import org.jetbrains.kotlin.js.naming.isES5IdentifierPart
import org.jetbrains.kotlin.js.naming.isES5IdentifierStart
import org.jetbrains.kotlin.name.FqName
@@ -73,10 +72,72 @@ fun NameTable<IrDeclaration>.dump(): String =
"--- $declRef => $name"
}
sealed class Signature
data class StableNameSignature(val name: String) : Signature()
data class BackingFieldSignature(val field: IrField) : Signature()
data class ParameterTypeBasedSignature(val mangledName: String, val suggestedName: String) : Signature()
fun fieldSignature(field: IrField): Signature {
if (field.isEffectivelyExternal()) {
return StableNameSignature(field.name.identifier)
}
return BackingFieldSignature(field)
}
fun functionSignature(declaration: IrFunction): Signature {
require(!declaration.isStaticMethodOfClass)
require(declaration.dispatchReceiverParameter != null)
val declarationName = declaration.getJsNameOrKotlinName().asString()
val stableName = StableNameSignature(declarationName)
if (declaration.origin == JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION) {
return stableName
}
if (declaration.isEffectivelyExternal()) {
return stableName
}
if (declaration.getJsName() != null) {
return stableName
}
// Handle names for special functions
if (declaration is IrSimpleFunction && declaration.isMethodOfAny()) {
return stableName
}
val nameBuilder = StringBuilder()
nameBuilder.append(declarationName)
// TODO should we skip type parameters and use upper bound of type parameter when print type of value parameters?
declaration.typeParameters.ifNotEmpty {
nameBuilder.append("_\$t")
joinTo(nameBuilder, "") { "_${it.name.asString()}" }
}
declaration.extensionReceiverParameter?.let {
nameBuilder.append("_r$${it.type.asString()}")
}
declaration.valueParameters.ifNotEmpty {
joinTo(nameBuilder, "") { "_${it.type.asString()}" }
}
declaration.returnType.let {
// Return type is only used in signature for inline class and Unit types because
// they are binary incompatible with supertypes.
if (it.isInlined() || it.isUnit()) {
nameBuilder.append("_ret$${it.asString()}")
}
}
val signature = nameBuilder.toString()
// TODO: Check reserved names
return ParameterTypeBasedSignature(signature, declarationName)
}
class NameTables(packages: List<IrPackageFragment>) {
private val globalNames: NameTable<IrDeclaration>
private val memberNames: NameTable<IrDeclaration>
private val memberNames: NameTable<Signature>
private val localNames = mutableMapOf<IrDeclaration, NameTable<IrDeclaration>>()
private val loopNames = mutableMapOf<IrLoop, String>()
@@ -97,15 +158,40 @@ class NameTables(packages: List<IrPackageFragment>) {
for (p in packages) {
for (declaration in p.declarations) {
if (declaration.isEffectivelyExternal())
continue
val localNameGenerator = LocalNameGenerator(declaration)
if (declaration is IrClass) {
declaration.thisReceiver!!.acceptVoid(localNameGenerator)
for (memberDecl in declaration.declarations) {
memberDecl.acceptChildrenVoid(LocalNameGenerator(memberDecl))
if (declaration.isEffectivelyExternal()) {
declaration.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
val parent = declaration.parent
if (parent is IrClass && !parent.isEnumClass) {
generateNameForMemberFunction(declaration)
}
}
override fun visitField(declaration: IrField) {
val parent = declaration.parent
if (parent is IrClass && !parent.isEnumClass) {
generateNameForMemberField(declaration)
}
}
})
} else {
declaration.thisReceiver!!.acceptVoid(localNameGenerator)
for (memberDecl in declaration.declarations) {
memberDecl.acceptChildrenVoid(LocalNameGenerator(memberDecl))
when (memberDecl) {
is IrSimpleFunction ->
generateNameForMemberFunction(memberDecl)
is IrField ->
generateNameForMemberField(memberDecl)
}
}
}
} else {
declaration.acceptChildrenVoid(localNameGenerator)
@@ -114,6 +200,33 @@ class NameTables(packages: List<IrPackageFragment>) {
}
}
private fun generateNameForMemberField(field: IrField) {
require(!field.isTopLevel)
require(!field.isStatic)
val signature = fieldSignature(field)
if (field.isEffectivelyExternal()) {
memberNames.declareStableName(signature, field.name.identifier)
}
memberNames.declareFreshName(signature, "_" + sanitizeName(field.name.asString()))
}
private fun generateNameForMemberFunction(declaration: IrSimpleFunction) {
when (val signature = functionSignature(declaration)) {
is StableNameSignature -> memberNames.declareStableName(signature, signature.name)
is ParameterTypeBasedSignature -> {
// TODO: Fix hack: Coroutines runtime currently relies on stable names
// of `invoke` functions in FunctionN interfaces
if (declaration.name.asString().startsWith("invoke")) {
memberNames.declareStableName(signature, sanitizeName(signature.mangledName))
} else {
memberNames.declareFreshName(signature, signature.suggestedName)
}
}
}
}
@Suppress("unused")
fun dump(): String {
val local = localNames.toList().joinToString("\n") { (decl, table) ->
@@ -121,7 +234,7 @@ class NameTables(packages: List<IrPackageFragment>) {
"\nLocal names for $declRef:\n${table.dump()}\n"
}
return "Global names:\n${globalNames.dump()}" +
"\nMember names:\n${memberNames.dump()}" +
// "\nMember names:\n${memberNames.dump()}" +
"\nLocal names:\n$local\n"
}
@@ -148,6 +261,28 @@ class NameTables(packages: List<IrPackageFragment>) {
error("Can't find name for declaration ${declaration.fqNameWhenAvailable}")
}
fun getNameForMemberField(field: IrField): String {
val signature = fieldSignature(field)
val name = memberNames.names[signature]
require(name != null) {
"Can't find name for member field $field"
}
return name
}
fun getNameForMemberFunction(function: IrSimpleFunction): String {
val signature = functionSignature(function)
val name = memberNames.names[signature]
// TODO: Fix hack: Coroutines runtime currently relies on stable names
// of `invoke` functions in FunctionN interfaces
if (name == null && signature is ParameterTypeBasedSignature && signature.suggestedName.startsWith("invoke"))
return signature.suggestedName
require(name != null) {
"Can't find name for member function $function"
}
return name
}
private fun generateNamesForTopLevelDecl(declaration: IrDeclaration) {
when {
@@ -208,85 +343,6 @@ class NameTables(packages: List<IrPackageFragment>) {
loopNames[loop]!!
}
// TODO: implement without JsScope
class LegacyMemberNameGenerator(val scope: JsScope) {
private val fieldCache = mutableMapOf<IrField, JsName>()
private val functionCache = mutableMapOf<IrFunction, JsName>()
fun getNameForMemberField(field: IrField): JsName {
return fieldCache.getOrPut(field) { getNewNameForField(field) }
}
fun getNameForMemberFunction(function: IrSimpleFunction): JsName {
return functionCache.getOrPut(function) { getNewNameForFunction(function) }
}
private fun getNewNameForField(f: IrField): JsName {
require(!f.isTopLevel)
require(!f.isStatic)
if (f.isEffectivelyExternal()) {
return scope.declareName(f.name.identifier)
}
val parentName = (f.parent as IrDeclarationWithName).name.asString()
val name = "${f.name.asString()}_$parentName"
return scope.declareFreshName(sanitizeName(name))
}
private fun getNewNameForFunction(declaration: IrSimpleFunction): JsName {
require(!declaration.isStaticMethodOfClass)
require(declaration.dispatchReceiverParameter != null)
val declarationName = declaration.getJsNameOrKotlinName().asString()
if (declaration.origin == JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION) {
return scope.declareName(declarationName)
}
if (declaration.isEffectivelyExternal()) {
return scope.declareName(declarationName)
}
declaration.getJsName()?.let { jsName ->
return scope.declareName(jsName)
}
val nameBuilder = StringBuilder()
// Handle names for special functions
if (declaration.isMethodOfAny()) {
return scope.declareName(declarationName)
}
nameBuilder.append(declarationName)
// TODO should we skip type parameters and use upper bound of type parameter when print type of value parameters?
declaration.typeParameters.ifNotEmpty {
nameBuilder.append("_\$t")
joinTo(nameBuilder, "") { "_${it.name.asString()}" }
}
declaration.extensionReceiverParameter?.let {
nameBuilder.append("_r$${it.type.asString()}")
}
declaration.valueParameters.ifNotEmpty {
joinTo(nameBuilder, "") { "_${it.type.asString()}" }
}
declaration.returnType.let {
// Return type is only used in signature for inline class and Unit types because
// they are binary incompatible with supertypes.
if (it.isInlined() || it.isUnit()) {
nameBuilder.append("_ret$${it.asString()}")
}
}
// TODO: Check reserved names
return scope.declareName(sanitizeName(nameBuilder.toString()))
}
}
fun sanitizeName(name: String): String {
if (name.isEmpty()) return "_"
@@ -103,7 +103,7 @@ val RESERVED_IDENTIFIERS = setOf(
// global identifiers usually declared in a typical JS interpreter
"NaN", "isNaN", "Infinity", "undefined",
"Error", "Object", "Number",
"Error", "Object", "Number", "String",
"Math", "String", "Boolean", "Date", "Array", "RegExp", "JSON", "Map",
+3
View File
@@ -1,3 +1,6 @@
// Name clashes
// IGNORE_BACKEND: JS_IR
class A (val p: String, p1: String, p2: String) {
var cond1 :String = ""
+3
View File
@@ -1,3 +1,6 @@
// Name clashes
// IGNORE_BACKEND: JS_IR
class A (val p: String, p1: String, p2: String) {
var cond1: String = ""
@@ -6754,6 +6754,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
@TestMetadata("abstractCollectionToArray.kt")
public void testAbstractCollectionToArray() throws Exception {
runTest("js/js.translator/testData/box/regression/stdlibTestSnippets/abstractCollectionToArray.kt");
}
public void testAllFilesPresentInStdlibTestSnippets() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/regression/stdlibTestSnippets"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true);
}
@@ -6789,6 +6789,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
}
@TestMetadata("abstractCollectionToArray.kt")
public void testAbstractCollectionToArray() throws Exception {
runTest("js/js.translator/testData/box/regression/stdlibTestSnippets/abstractCollectionToArray.kt");
}
public void testAllFilesPresentInStdlibTestSnippets() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/regression/stdlibTestSnippets"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true);
}
@@ -1,3 +1,6 @@
// Name clashes
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1295
package foo
@@ -0,0 +1,33 @@
// EXPECTED_REACHABLE_NODES: 1750
// KJS_WITH_FULL_RUNTIME
fun abstractCollectionToArray() {
class TestCollection<out E>(val data: Collection<E>) : AbstractCollection<E>() {
val invocations = mutableListOf<String>()
override val size get() = data.size
override fun iterator() = data.iterator()
override fun toArray(): Array<Any?> {
invocations += "toArray1"
return data.toTypedArray()
}
public override fun <T> toArray(array: Array<T>): Array<T> {
invocations += "toArray2"
return super.toArray(array)
}
}
val data = listOf("abc", "def")
val coll = TestCollection(data)
val arr1 = coll.toTypedArray()
assertEquals(data, arr1.asList())
assertTrue("toArray1" in coll.invocations || "toArray2" in coll.invocations)
val arr2: Array<String> = coll.toArray(Array(coll.size + 1) { "" })
assertEquals(data + listOf(null), arr2.asList())
}
fun box(): String {
abstractCollectionToArray()
return "OK"
}
@@ -4,6 +4,8 @@
*/
package kotlin.collections
import kotlin.js.JsName
/**
* Provides a skeletal implementation of the read-only [Collection] interface.
*
@@ -28,6 +30,7 @@ public abstract class AbstractCollection<out E> protected constructor() : Collec
/**
* Returns new array of type `Array<Any?>` with the elements of this collection.
*/
@JsName("toArray")
protected open fun toArray(): Array<Any?> = copyToArrayImpl(this)
/**