Android/IR: handle nulls from getView/getContainerView

Turns out it's possible to create an IR builder without a backend
context
This commit is contained in:
pyos
2020-01-31 12:34:46 +01:00
committed by Yan Zhulanow
parent 390d062721
commit 9b68e4fe56
@@ -11,13 +11,17 @@ import org.jetbrains.kotlin.android.synthetic.descriptors.AndroidSyntheticPackag
import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticFunction import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticFunction
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlock
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.* import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
@@ -28,14 +32,10 @@ import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.isClassWithFqName
import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isClass import org.jetbrains.kotlin.ir.util.isClass
import org.jetbrains.kotlin.ir.util.isObject import org.jetbrains.kotlin.ir.util.isObject
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
@@ -50,7 +50,9 @@ abstract class AndroidIrExtension : IrGenerationExtension {
} }
} }
private class AndroidIrTransformer(val extension: AndroidIrExtension, val pluginContext: IrPluginContext) : IrElementTransformerVoid() { private class AndroidIrTransformer(val extension: AndroidIrExtension, val pluginContext: IrPluginContext) :
IrElementTransformerVoidWithContext() {
private val cachedPackages = mutableMapOf<FqName, IrPackageFragment>() private val cachedPackages = mutableMapOf<FqName, IrPackageFragment>()
private val cachedClasses = mutableMapOf<FqName, IrClass>() private val cachedClasses = mutableMapOf<FqName, IrClass>()
private val cachedMethods = mutableMapOf<FqName, IrSimpleFunction>() private val cachedMethods = mutableMapOf<FqName, IrSimpleFunction>()
@@ -93,14 +95,14 @@ private class AndroidIrTransformer(val extension: AndroidIrExtension, val plugin
createClass(fqName.parent()).addField(fqName.shortName(), type, Visibilities.PUBLIC) createClass(fqName.parent()).addField(fqName.shortName(), type, Visibilities.PUBLIC)
} }
override fun visitClass(declaration: IrClass): IrStatement { override fun visitClassNew(declaration: IrClass): IrStatement {
if (!declaration.isClass && !declaration.isObject) if (!declaration.isClass && !declaration.isObject)
return super.visitClass(declaration) return super.visitClassNew(declaration)
val containerOptions = ContainerOptionsProxy.create(declaration.descriptor) val containerOptions = ContainerOptionsProxy.create(declaration.descriptor)
if ((containerOptions.cache ?: extension.getGlobalCacheImpl(declaration)) == CacheImplementation.NO_CACHE) if ((containerOptions.cache ?: extension.getGlobalCacheImpl(declaration)) == CacheImplementation.NO_CACHE)
return super.visitClass(declaration) return super.visitClassNew(declaration)
if (containerOptions.containerType == AndroidContainerType.LAYOUT_CONTAINER && !extension.isExperimental(declaration)) if (containerOptions.containerType == AndroidContainerType.LAYOUT_CONTAINER && !extension.isExperimental(declaration))
return super.visitClass(declaration) return super.visitClassNew(declaration)
// TODO 1. actually generate the cache; // TODO 1. actually generate the cache;
// 2. clear it in the function added below; // 2. clear it in the function added below;
@@ -113,7 +115,7 @@ private class AndroidIrTransformer(val extension: AndroidIrExtension, val plugin
addDispatchReceiver { type = declaration.defaultType } addDispatchReceiver { type = declaration.defaultType }
body = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET) body = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET)
} }
return super.visitClass(declaration) return super.visitClassNew(declaration)
} }
override fun visitCall(expression: IrCall): IrExpression { override fun visitCall(expression: IrCall): IrExpression {
@@ -130,6 +132,10 @@ private class AndroidIrTransformer(val extension: AndroidIrExtension, val plugin
?: return super.visitFunctionAccess(expression) ?: return super.visitFunctionAccess(expression)
val receiver = expression.extensionReceiver!!.transform(this, null) val receiver = expression.extensionReceiver!!.transform(this, null)
val packageFqName = FqName(packageFragment.packageData.moduleData.module.applicationPackage)
val field = createField(packageFqName.child("R\$id").child(resource.name), pluginContext.irBuiltIns.intType)
val resourceId = IrGetFieldImpl(expression.startOffset, expression.endOffset, field.symbol, field.type)
val containerType = ContainerOptionsProxy.create(receiverClass.descriptor).containerType val containerType = ContainerOptionsProxy.create(receiverClass.descriptor).containerType
val result = if (expression.type.classifierOrNull?.isFragment == true) { val result = if (expression.type.classifierOrNull?.isFragment == true) {
// this.get[Support]FragmentManager().findFragmentById(R$id.<name>) // this.get[Support]FragmentManager().findFragmentById(R$id.<name>)
@@ -150,38 +156,45 @@ private class AndroidIrTransformer(val extension: AndroidIrExtension, val plugin
val fragment = appPackageFqName.child("Fragment") val fragment = appPackageFqName.child("Fragment")
val fragmentManager = appPackageFqName.child("FragmentManager") val fragmentManager = appPackageFqName.child("FragmentManager")
val getFragmentManager = createMethod(getFragmentManagerFqName, createClass(fragmentManager).defaultType) val getFragmentManager = createMethod(getFragmentManagerFqName, createClass(fragmentManager).defaultType)
createMethod(fragmentManager.child("findFragmentById"), createClass(fragment).defaultType) { createMethod(fragmentManager.child("findFragmentById"), createClass(fragment).defaultType.makeNullable()) {
addValueParameter("id", pluginContext.irBuiltIns.intType) addValueParameter("id", pluginContext.irBuiltIns.intType)
}.callWithRanges(expression).apply { }.callWithRanges(expression).apply {
dispatchReceiver = getFragmentManager.callWithRanges(expression).apply { dispatchReceiver = getFragmentManager.callWithRanges(expression).apply {
dispatchReceiver = receiver dispatchReceiver = receiver
} }
putValueArgument(0, resourceId)
} }
} else { } else {
// this[.getView()|.getContainerView()].findViewById(R$id.<name>) // this[.getView()?|.getContainerView()?].findViewById(R$id.<name>)
val viewClass = createClass(FqName(AndroidConst.VIEW_FQNAME)) val viewClass = createClass(FqName(AndroidConst.VIEW_FQNAME))
val getView = when (containerType) { val getView = when (containerType) {
AndroidContainerType.ACTIVITY, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.VIEW, AndroidContainerType.DIALOG -> AndroidContainerType.ACTIVITY, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.VIEW, AndroidContainerType.DIALOG ->
null null
AndroidContainerType.FRAGMENT, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT, AndroidContainerType.SUPPORT_FRAGMENT -> AndroidContainerType.FRAGMENT, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT, AndroidContainerType.SUPPORT_FRAGMENT ->
createMethod(containerType.fqName.child("getView"), viewClass.defaultType) createMethod(containerType.fqName.child("getView"), viewClass.defaultType.makeNullable())
AndroidContainerType.LAYOUT_CONTAINER -> AndroidContainerType.LAYOUT_CONTAINER ->
createMethod(containerType.fqName.child("getContainerView"), viewClass.defaultType, true) createMethod(containerType.fqName.child("getContainerView"), viewClass.defaultType.makeNullable(), true)
else -> throw IllegalStateException("Invalid Android class type: $containerType") // Should never occur else -> throw IllegalStateException("Invalid Android class type: $containerType") // Should never occur
} }
val findViewByIdParent = if (getView == null) containerType.fqName else FqName(AndroidConst.VIEW_FQNAME) val findViewByIdParent = if (getView == null) containerType.fqName else FqName(AndroidConst.VIEW_FQNAME)
createMethod(findViewByIdParent.child("findViewById"), viewClass.defaultType) { val findViewById = createMethod(findViewByIdParent.child("findViewById"), viewClass.defaultType.makeNullable()) {
addValueParameter("id", pluginContext.irBuiltIns.intType) addValueParameter("id", pluginContext.irBuiltIns.intType)
}.callWithRanges(expression).apply { }.callWithRanges(expression).apply {
// TODO return null if getView returns null putValueArgument(0, resourceId)
dispatchReceiver = getView?.callWithRanges(expression)?.apply { }
dispatchReceiver = receiver if (getView == null) {
} ?: receiver findViewById.apply { dispatchReceiver = receiver }
} else {
val scope = currentScope!!.scope.scopeOwnerSymbol
DeclarationIrBuilder(IrGeneratorContextBase(pluginContext.irBuiltIns), scope).irBlock(expression) {
val variable = irTemporary(getView.callWithRanges(expression).apply {
dispatchReceiver = receiver
})
+irIfNull(findViewById.type, irGet(variable), irNull(), findViewById.apply {
dispatchReceiver = irGet(variable)
})
}
} }
}.apply {
val packageFqName = FqName(packageFragment.packageData.moduleData.module.applicationPackage)
val field = createField(packageFqName.child("R\$id").child(resource.name), pluginContext.irBuiltIns.intType)
putValueArgument(0, IrGetFieldImpl(expression.startOffset, expression.endOffset, field.symbol, field.type))
} }
return with(expression) { IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.CAST, type, result) } return with(expression) { IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.CAST, type, result) }
} }