IR JS: support findAssociatedObject feature (KT-37418 fixed)

This commit is contained in:
Anton Bannykh
2020-04-16 17:33:44 +03:00
parent f4b6e1bff6
commit 442331acc9
14 changed files with 354 additions and 5 deletions
@@ -8,8 +8,10 @@ package org.jetbrains.kotlin.ir.backend.js
import org.jetbrains.kotlin.backend.common.ir.isMemberOfOpenClass
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.associatedObject
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.isAssociatedObjectAnnotatedAnnotation
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
@@ -78,8 +80,19 @@ private fun removeUselessDeclarations(module: IrModuleFragment, usefulDeclaratio
process(declaration)
}
private fun IrConstructorCall.shouldKeepAnnotation(): Boolean {
associatedObject()?.let { obj ->
if (obj !in usefulDeclarations) return false
}
return true
}
override fun visitClass(declaration: IrClass) {
process(declaration)
// Remove annotations for `findAssociatedObject` feature, which reference objects eliminated by the DCE.
// Otherwise `JsClassGenerator.generateAssociatedKeyProperties` will try to reference the object factory (which is removed).
// That will result in an error from the Namer. It cannot generate a name for an absent declaration.
declaration.annotations = declaration.annotations.filter { it.shouldKeepAnnotation() }
}
// TODO bring back the primary constructor fix
@@ -118,6 +131,10 @@ fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendConte
val contagiousReachableDeclarations = hashSetOf<IrOverridableDeclaration<*>>()
val constructedClasses = hashSetOf<IrClass>()
val classesWithObjectAssociations = hashSetOf<IrClass>()
val referencedJsClasses = hashSetOf<IrDeclaration>()
val referencedJsClassesFromExpressions = hashSetOf<IrClass>()
fun IrDeclaration.enqueue(
from: IrDeclaration?,
description: String?,
@@ -211,6 +228,14 @@ fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendConte
}
}
}
declaration.annotations.forEach {
val annotationClass = it.symbol.owner.constructedClass
if (annotationClass.isAssociatedObjectAnnotatedAnnotation) {
classesWithObjectAssociations += declaration
annotationClass.enqueue("@AssociatedObject annotated annotation class")
}
}
}
if (declaration is IrSimpleFunction && declaration.isFakeOverride) {
@@ -268,7 +293,13 @@ fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendConte
constructor.enqueue("intrinsic: jsBoxIntrinsic")
}
context.intrinsics.jsClass -> {
(expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration).enqueue("intrinsic: jsClass")
val ref = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration
ref.enqueue("intrinsic: jsClass")
referencedJsClasses += ref
}
context.intrinsics.jsGetKClassFromExpression -> {
val ref = expression.getTypeArgument(0)?.classOrNull ?: context.irBuiltIns.anyClass
referencedJsClassesFromExpressions += ref.owner
}
context.intrinsics.jsObjectCreate.symbol -> {
val classToCreate = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass
@@ -314,6 +345,21 @@ fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendConte
return null
}
// Handle objects, constructed via `findAssociatedObject` annotation
referencedJsClassesFromExpressions += constructedClasses.filterDescendantsOf(referencedJsClassesFromExpressions) // Grow the set of possible results of instance::class expression
for (klass in classesWithObjectAssociations) {
if (klass !in referencedJsClasses && klass !in referencedJsClassesFromExpressions) continue
for (annotation in klass.annotations) {
val annotationClass = annotation.symbol.owner.constructedClass
if (annotationClass !in referencedJsClasses) continue
annotation.associatedObject()?.let { obj ->
context.mapping.objectToGetInstanceFunction[obj]?.enqueue(klass, "associated object factory")
}
}
}
for (klass in constructedClasses) {
// TODO a better way to support inverse overrides.
for (declaration in ArrayList(klass.declarations)) {
@@ -369,3 +415,29 @@ fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendConte
return result
}
private fun Collection<IrClass>.filterDescendantsOf(bases: Collection<IrClass>): Collection<IrClass> {
val visited = hashSetOf<IrClass>()
val baseDescendants = hashSetOf<IrClass>()
baseDescendants += bases
fun overridesAnyBase(klass: IrClass): Boolean {
if (klass in baseDescendants) return true
if (klass in visited) return false
visited += klass
klass.superTypes.forEach {
(it.classifierOrNull as? IrClassSymbol)?.owner?.let {
if (overridesAnyBase(it)) {
baseDescendants += klass
return true
}
}
}
return false
}
return this.filter { overridesAnyBase(it) }
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.types.IrType
@@ -189,6 +190,8 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
metadataLiteral.propertyInitializers += generateSuperClasses()
metadataLiteral.propertyInitializers += generateAssociatedKeyProperties()
if (isCoroutineClass()) {
metadataLiteral.propertyInitializers += generateSuspendArity()
}
@@ -218,6 +221,31 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
)
)
}
private fun generateAssociatedKeyProperties(): List<JsPropertyInitializer> {
var result = emptyList<JsPropertyInitializer>()
context.getAssociatedObjectKey(irClass)?.let { key ->
result = result + JsPropertyInitializer(JsStringLiteral("associatedObjectKey"), JsIntLiteral(key))
}
val associatedObjects = irClass.annotations.mapNotNull { annotation ->
val annotationClass = annotation.symbol.owner.constructedClass
context.getAssociatedObjectKey(annotationClass)?.let { key ->
annotation.associatedObject()?.let { obj ->
context.staticContext.backendContext.mapping.objectToGetInstanceFunction[obj]?.let { factory ->
JsPropertyInitializer(JsIntLiteral(key), context.staticContext.getNameForStaticFunction(factory).makeRef())
}
}
}
}
if (associatedObjects.isNotEmpty()) {
result = result + JsPropertyInitializer(JsStringLiteral("associatedObjects"), JsObjectLiteral(associatedObjects))
}
return result
}
}
private val IrClassifierSymbol.isInterface get() = (owner as? IrClass)?.isInterface == true
@@ -7,10 +7,11 @@ package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.util.getAnnotation
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -45,4 +46,15 @@ fun IrDeclarationWithName.getJsNameOrKotlinName(): Name =
when (val jsName = getJsName()) {
null -> name
else -> Name.identifier(jsName)
}
}
private val associatedObjectKeyAnnotationFqName = FqName("kotlin.reflect.AssociatedObjectKey")
val IrClass.isAssociatedObjectAnnotatedAnnotation: Boolean
get() = isAnnotationClass && annotations.any { it.symbol.owner.constructedClass.fqNameWhenAvailable == associatedObjectKeyAnnotationFqName }
fun IrConstructorCall.associatedObject(): IrClass? {
if (!symbol.owner.constructedClass.isAssociatedObjectAnnotatedAnnotation) return null
val klass = ((getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol)?.owner ?: return null
return if (klass.isObject) klass else null
}
@@ -22,4 +22,5 @@ interface IrNamer {
fun getNameForProperty(property: IrProperty): JsName
fun getRefForExternalClass(klass: IrClass): JsNameRef
fun getNameForLoop(loop: IrLoop): JsName?
fun getAssociatedObjectKey(irClass: IrClass): Int?
}
@@ -73,4 +73,14 @@ class IrNamerImpl(private val newNameTables: NameTables) : IrNamer {
error("Unsupported external class parent $parent")
}
}
private val associatedObjectKeyMap = mutableMapOf<IrClass, Int>()
override fun getAssociatedObjectKey(irClass: IrClass): Int? {
if (irClass.isAssociatedObjectAnnotatedAnnotation) {
return associatedObjectKeyMap.getOrPut(irClass) { associatedObjectKeyMap.size }
}
return null
}
}