diff --git a/plugins/android-extensions/android-extensions-compiler/build.gradle.kts b/plugins/android-extensions/android-extensions-compiler/build.gradle.kts index d40e989baa4..7151d138796 100644 --- a/plugins/android-extensions/android-extensions-compiler/build.gradle.kts +++ b/plugins/android-extensions/android-extensions-compiler/build.gradle.kts @@ -17,12 +17,14 @@ dependencies { compileOnly(project(":compiler:frontend")) compileOnly(project(":compiler:frontend.java")) compileOnly(project(":compiler:backend")) + compileOnly(project(":compiler:ir.backend.common")) compileOnly(project(":kotlin-android-extensions-runtime")) compileOnly(intellijCoreDep()) { includeJars("intellij-core") } compileOnly(intellijDep()) { includeJars("asm-all", rootProject = rootProject) } testCompile(project(":compiler:util")) testCompile(project(":compiler:backend")) + testCompile(project(":compiler:ir.backend.common")) testCompile(project(":compiler:cli")) testCompile(project(":kotlin-android-extensions-runtime")) testCompile(projectTests(":compiler:tests-common")) diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt index f0d2c95560f..bfa050c0863 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension import org.jetbrains.kotlin.android.parcel.ParcelableDeclarationChecker import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidExtensionsExpressionCodegenExtension +import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidIrExtension import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidOnDestroyClassBuilderInterceptorExtension import org.jetbrains.kotlin.android.synthetic.codegen.ParcelableClinitClassBuilderInterceptorExtension import org.jetbrains.kotlin.android.synthetic.diagnostic.AndroidExtensionPropertiesCallChecker @@ -31,6 +32,7 @@ import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager import org.jetbrains.kotlin.android.synthetic.res.AndroidVariant import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager import org.jetbrains.kotlin.android.synthetic.res.CliAndroidPackageFragmentProviderExtension +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension import org.jetbrains.kotlin.compiler.plugin.* @@ -130,6 +132,9 @@ class AndroidComponentRegistrar : ComponentRegistrar { ExpressionCodegenExtension.registerExtension(project, CliAndroidExtensionsExpressionCodegenExtension(isExperimental, globalCacheImpl)) + IrGenerationExtension.registerExtension(project, + CliAndroidIrExtension(isExperimental, globalCacheImpl)) + StorageComponentContainerContributor.registerExtension(project, AndroidExtensionPropertiesComponentContainerContributor()) diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidIrExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidIrExtension.kt new file mode 100644 index 00000000000..53915eb79cf --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidIrExtension.kt @@ -0,0 +1,201 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.android.synthetic.codegen + +import kotlinx.android.extensions.CacheImplementation +import org.jetbrains.kotlin.android.synthetic.AndroidConst +import org.jetbrains.kotlin.android.synthetic.descriptors.AndroidSyntheticPackageFragmentDescriptor +import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy +import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticFunction +import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.declarations.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrTypeOperator +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol +import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl +import org.jetbrains.kotlin.ir.types.IrType +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.isClass +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.FqNameUnsafe +import org.jetbrains.kotlin.name.Name + +abstract class AndroidIrExtension : IrGenerationExtension { + abstract fun isEnabled(declaration: IrClass): Boolean + abstract fun isExperimental(declaration: IrClass): Boolean + abstract fun getGlobalCacheImpl(declaration: IrClass): CacheImplementation + + override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { + moduleFragment.transform(AndroidIrTransformer(this, pluginContext), null) + } +} + +private class AndroidIrTransformer(val extension: AndroidIrExtension, val pluginContext: IrPluginContext) : IrElementTransformerVoid() { + private val cachedPackages = mutableMapOf() + private val cachedClasses = mutableMapOf() + private val cachedMethods = mutableMapOf() + private val cachedFields = mutableMapOf() + + private fun createPackage(fqName: FqName) = + cachedPackages.getOrPut(fqName) { + val descriptor = EmptyPackageFragmentDescriptor(pluginContext.moduleDescriptor, fqName) + IrExternalPackageFragmentImpl(IrExternalPackageFragmentSymbolImpl(descriptor)) + } + + private fun createClass(fqName: FqName, isInterface: Boolean = false) = + cachedClasses.getOrPut(fqName) { + buildClass { + name = fqName.shortName() + kind = if (isInterface) ClassKind.INTERFACE else ClassKind.CLASS + origin = IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB + }.apply { + parent = createPackage(fqName.parent()) + createImplicitParameterDeclarationWithWrappedDescriptor() + } + } + + private fun createMethod(fqName: FqName, type: IrType, inInterface: Boolean = false, f: IrFunctionImpl.() -> Unit = {}) = + cachedMethods.getOrPut(fqName) { + val parent = createClass(fqName.parent(), inInterface) + parent.addFunction { + name = fqName.shortName() + origin = IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB + modality = if (inInterface) Modality.ABSTRACT else Modality.FINAL + returnType = type + }.apply { + addDispatchReceiver { this.type = parent.defaultType } + f() + } + } + + private fun createField(fqName: FqName, type: IrType) = + cachedFields.getOrPut(fqName) { + createClass(fqName.parent()).addField(fqName.shortName(), type, Visibilities.PUBLIC) + } + + override fun visitClass(declaration: IrClass): IrStatement { + if (!declaration.isClass && !declaration.isObject) + return super.visitClass(declaration) + val containerOptions = ContainerOptionsProxy.create(declaration.descriptor) + if ((containerOptions.cache ?: extension.getGlobalCacheImpl(declaration)) == CacheImplementation.NO_CACHE) + return super.visitClass(declaration) + if (containerOptions.containerType == AndroidContainerType.LAYOUT_CONTAINER && !extension.isExperimental(declaration)) + return super.visitClass(declaration) + + // TODO 1. actually generate the cache; + // 2. clear it in the function added below; + // 3. if containerOptions.containerType.isFragment and there's no onDestroy, add one that clears the cache + declaration.addFunction { + name = Name.identifier(AbstractAndroidExtensionsExpressionCodegenExtension.CLEAR_CACHE_METHOD_NAME) + modality = Modality.OPEN + returnType = pluginContext.irBuiltIns.unitType + }.apply { + addDispatchReceiver { type = declaration.defaultType } + body = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET) + } + return super.visitClass(declaration) + } + + override fun visitCall(expression: IrCall): IrExpression { + if (expression.symbol.descriptor is AndroidSyntheticFunction) { + // TODO actually call the appropriate CLEAR_CACHE_METHOD_NAME-named function + return IrBlockImpl(expression.startOffset, expression.endOffset, pluginContext.irBuiltIns.unitType) + } + + val resource = (expression.symbol.descriptor as? PropertyGetterDescriptor)?.correspondingProperty as? AndroidSyntheticProperty + ?: return super.visitFunctionAccess(expression) + val packageFragment = (resource as PropertyDescriptor).containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor + ?: return super.visitFunctionAccess(expression) + val receiverClass = expression.extensionReceiver?.type?.classOrNull + ?: return super.visitFunctionAccess(expression) + val receiver = expression.extensionReceiver!!.transform(this, null) + + val containerType = ContainerOptionsProxy.create(receiverClass.descriptor).containerType + val result = if (expression.type.classifierOrNull?.isFragment == true) { + // this.get[Support]FragmentManager().findFragmentById(R$id.) + val appPackageFqName = when (containerType) { + AndroidContainerType.ACTIVITY, + AndroidContainerType.FRAGMENT -> FqName("android.app") + AndroidContainerType.SUPPORT_FRAGMENT, + AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY -> FqName("android.support.v4.app") + AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT, + AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT_ACTIVITY -> FqName("androidx.fragment.app") + else -> throw IllegalStateException("Invalid Android class type: $this") // Should never occur + } + val getFragmentManagerFqName = when (containerType) { + AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY, + AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT_ACTIVITY -> containerType.fqName.child("getSupportFragmentManager") + else -> containerType.fqName.child("getFragmentManager") + } + val fragment = appPackageFqName.child("Fragment") + val fragmentManager = appPackageFqName.child("FragmentManager") + val getFragmentManager = createMethod(getFragmentManagerFqName, createClass(fragmentManager).defaultType) + createMethod(fragmentManager.child("findFragmentById"), createClass(fragment).defaultType) { + addValueParameter("id", pluginContext.irBuiltIns.intType) + }.callWithRanges(expression).apply { + dispatchReceiver = getFragmentManager.callWithRanges(expression).apply { + dispatchReceiver = receiver + } + } + } else { + // this[.getView()|.getContainerView()].findViewById(R$id.) + val viewClass = createClass(FqName(AndroidConst.VIEW_FQNAME)) + val getView = when (containerType) { + AndroidContainerType.ACTIVITY, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.VIEW, AndroidContainerType.DIALOG -> + null + AndroidContainerType.FRAGMENT, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT, AndroidContainerType.SUPPORT_FRAGMENT -> + createMethod(containerType.fqName.child("getView"), viewClass.defaultType) + AndroidContainerType.LAYOUT_CONTAINER -> + createMethod(containerType.fqName.child("getContainerView"), viewClass.defaultType, true) + else -> throw IllegalStateException("Invalid Android class type: $containerType") // Should never occur + } + val findViewByIdParent = if (getView == null) containerType.fqName else FqName(AndroidConst.VIEW_FQNAME) + createMethod(findViewByIdParent.child("findViewById"), viewClass.defaultType) { + addValueParameter("id", pluginContext.irBuiltIns.intType) + }.callWithRanges(expression).apply { + // TODO return null if getView returns null + dispatchReceiver = getView?.callWithRanges(expression)?.apply { + dispatchReceiver = receiver + } ?: receiver + } + }.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) } + } +} + +private fun FqName.child(name: String) = child(Name.identifier(name)) + +private fun IrSimpleFunction.callWithRanges(source: IrExpression) = + IrCallImpl(source.startOffset, source.endOffset, returnType, symbol) + +private val AndroidContainerType.fqName: FqName + get() = FqName(internalClassName.replace("/", ".")) + +private val IrClassifierSymbol.isFragment: Boolean + get() = isClassWithFqName(FqNameUnsafe(AndroidConst.FRAGMENT_FQNAME)) || + isClassWithFqName(FqNameUnsafe(AndroidConst.SUPPORT_FRAGMENT_FQNAME)) || + isClassWithFqName(FqNameUnsafe(AndroidConst.ANDROIDX_SUPPORT_FRAGMENT_FQNAME)) diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidIrExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidIrExtension.kt new file mode 100644 index 00000000000..42d80436ce1 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidIrExtension.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.android.synthetic.codegen + +import kotlinx.android.extensions.CacheImplementation +import org.jetbrains.kotlin.ir.declarations.IrClass + +class CliAndroidIrExtension(val isExperimental: Boolean, private val globalCacheImpl: CacheImplementation) : AndroidIrExtension() { + override fun isEnabled(declaration: IrClass) = true + override fun isExperimental(declaration: IrClass) = isExperimental + override fun getGlobalCacheImpl(declaration: IrClass) = globalCacheImpl +}