Android extensions: Do not cache ViewStub widgets, do not cast it automatically

This commit is contained in:
Yan Zhulanow
2015-11-12 17:59:18 +03:00
parent 3f97b384ef
commit 6caaa305bc
10 changed files with 127 additions and 27 deletions
@@ -16,8 +16,6 @@
package org.jetbrains.kotlin.android.synthetic package org.jetbrains.kotlin.android.synthetic
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
public object AndroidConst { public object AndroidConst {
val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic" val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic"
@@ -44,6 +42,8 @@ public object AndroidConst {
//TODO FqName / ClassId //TODO FqName / ClassId
val VIEW_FQNAME = "android.view.View" val VIEW_FQNAME = "android.view.View"
val VIEWSTUB_FQNAME = "android.view.ViewStub"
val ACTIVITY_FQNAME = "android.app.Activity" val ACTIVITY_FQNAME = "android.app.Activity"
val FRAGMENT_FQNAME = "android.app.Fragment" val FRAGMENT_FQNAME = "android.app.Fragment"
val SUPPORT_V4_PACKAGE = "android.support.v4" val SUPPORT_V4_PACKAGE = "android.support.v4"
@@ -93,7 +93,13 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache" val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache"
val ON_DESTROY_METHOD_NAME = "onDestroyView" val ON_DESTROY_METHOD_NAME = "onDestroyView"
fun isCacheSupported(descriptor: ClassifierDescriptor) = descriptor.source is KotlinSourceElement fun isCacheSupported(receiverDescriptor: ClassDescriptor, descriptor: PropertyDescriptor? = null): Boolean {
val receiverIsKotlinClass = receiverDescriptor.source is KotlinSourceElement
return receiverIsKotlinClass && when (descriptor) {
is AndroidSyntheticProperty -> !descriptor.alwaysCastToView
else -> true
}
}
} }
private class SyntheticPartsGenerateContext( private class SyntheticPartsGenerateContext(
@@ -123,21 +129,21 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
receiver: StackValue, receiver: StackValue,
resolvedCall: ResolvedCall<*>, resolvedCall: ResolvedCall<*>,
c: ExpressionCodegenExtension.Context, c: ExpressionCodegenExtension.Context,
descriptor: FunctionDescriptor functionDescriptor: FunctionDescriptor
): StackValue? { ): StackValue? {
if (descriptor !is AndroidSyntheticFunction) return null if (functionDescriptor !is AndroidSyntheticFunction) return null
if (descriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return null if (functionDescriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return null
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return null val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
if (!isCacheSupported(declarationDescriptor)) return StackValue.functionCall(Type.VOID_TYPE) {} if (!isCacheSupported(receiverDescriptor)) return StackValue.functionCall(Type.VOID_TYPE) {}
val androidClassType = AndroidClassType.getClassType(declarationDescriptor) val androidClassType = AndroidClassType.getClassType(receiverDescriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return null if (androidClassType == AndroidClassType.UNKNOWN) return null
return StackValue.functionCall(Type.VOID_TYPE) { return StackValue.functionCall(Type.VOID_TYPE) {
val bytecodeClassName = c.typeMapper.mapType(declarationDescriptor).internalName val bytecodeClassName = c.typeMapper.mapType(receiverDescriptor).internalName
receiver.put(c.typeMapper.mapType(declarationDescriptor), it) receiver.put(c.typeMapper.mapType(receiverDescriptor), it)
it.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false) it.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
} }
} }
@@ -151,31 +157,31 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
if (descriptor !is AndroidSyntheticProperty) return null if (descriptor !is AndroidSyntheticProperty) return null
val packageFragment = descriptor.containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor ?: return null val packageFragment = descriptor.containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor ?: return null
val androidPackage = packageFragment.packageData.moduleData.module.applicationPackage val androidPackage = packageFragment.packageData.moduleData.module.applicationPackage
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return null val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
val androidClassType = AndroidClassType.getClassType(declarationDescriptor) val androidClassType = AndroidClassType.getClassType(receiverDescriptor)
return SyntheticProperty(receiver, c.typeMapper, descriptor, declarationDescriptor, androidClassType, androidPackage) return SyntheticProperty(receiver, c.typeMapper, descriptor, receiverDescriptor, androidClassType, androidPackage)
} }
private class SyntheticProperty( private class SyntheticProperty(
val receiver: StackValue, val receiver: StackValue,
val typeMapper: JetTypeMapper, val typeMapper: JetTypeMapper,
val descriptor: PropertyDescriptor, val propertyDescriptor: PropertyDescriptor,
val declarationDescriptor: ClassifierDescriptor, val receiverDescriptor: ClassDescriptor,
val androidClassType: AndroidClassType, val androidClassType: AndroidClassType,
val androidPackage: String val androidPackage: String
) : StackValue(typeMapper.mapType(descriptor.returnType!!)) { ) : StackValue(typeMapper.mapType(propertyDescriptor.returnType!!)) {
override fun putSelector(type: Type, v: InstructionAdapter) { override fun putSelector(type: Type, v: InstructionAdapter) {
val returnTypeString = typeMapper.mapType(descriptor.type.lowerIfFlexible()).className val returnTypeString = typeMapper.mapType(propertyDescriptor.type.lowerIfFlexible()).className
if (AndroidConst.FRAGMENT_FQNAME == returnTypeString || AndroidConst.SUPPORT_FRAGMENT_FQNAME == returnTypeString) { if (AndroidConst.FRAGMENT_FQNAME == returnTypeString || AndroidConst.SUPPORT_FRAGMENT_FQNAME == returnTypeString) {
return putSelectorForFragment(v) return putSelectorForFragment(v)
} }
if (androidClassType.supportsCache && isCacheSupported(declarationDescriptor)) { if (androidClassType.supportsCache && isCacheSupported(receiverDescriptor, propertyDescriptor)) {
val declarationDescriptorType = typeMapper.mapType(declarationDescriptor) val declarationDescriptorType = typeMapper.mapType(receiverDescriptor)
receiver.put(declarationDescriptorType, v) receiver.put(declarationDescriptorType, v)
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.name.asString(), "I") v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.name.asString(), "I")
v.invokevirtual(declarationDescriptorType.internalName, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false) v.invokevirtual(declarationDescriptorType.internalName, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false)
} }
else { else {
@@ -224,7 +230,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
} }
fun getResourceId(v: InstructionAdapter) { fun getResourceId(v: InstructionAdapter) {
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.name.asString(), "I") v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.name.asString(), "I")
} }
} }
@@ -66,7 +66,7 @@ class AndroidSyntheticPackageFragmentDescriptor(
} }
is AndroidResource.Fragment -> if (!packageData.forView) { is AndroidResource.Fragment -> if (!packageData.forView) {
for ((receiverType, type) in fragmentTypes) { for ((receiverType, type) in fragmentTypes) {
properties += genPropertyForFragment(packageFragmentDescriptor, receiverType, type, resource) properties += genPropertyForFragment(packageFragmentDescriptor, receiverType, type, resource, context)
} }
} }
} }
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.makeNullable import org.jetbrains.kotlin.types.typeUtil.makeNullable
@@ -65,17 +66,18 @@ internal fun genPropertyForWidget(
defaultType.constructor.parameters.map { StarProjectionImpl(it) }) defaultType.constructor.parameters.map { StarProjectionImpl(it) })
} ?: context.viewType } ?: context.viewType
return genProperty(resolvedWidget.widget.id, receiverType, type, packageFragmentDescriptor, sourceEl, resolvedWidget.errorType) return genProperty(resolvedWidget.widget.id, receiverType, type, packageFragmentDescriptor, sourceEl, context, resolvedWidget.errorType)
} }
internal fun genPropertyForFragment( internal fun genPropertyForFragment(
packageFragmentDescriptor: PackageFragmentDescriptor, packageFragmentDescriptor: PackageFragmentDescriptor,
receiverType: KotlinType, receiverType: KotlinType,
type: KotlinType, type: KotlinType,
fragment: AndroidResource.Fragment fragment: AndroidResource.Fragment,
context: SyntheticElementResolveContext
): PropertyDescriptor { ): PropertyDescriptor {
val sourceElement = fragment.sourceElement?.let { XmlSourceElement(it) } ?: SourceElement.NO_SOURCE val sourceElement = fragment.sourceElement?.let { XmlSourceElement(it) } ?: SourceElement.NO_SOURCE
return genProperty(fragment.id, receiverType, type, packageFragmentDescriptor, sourceElement, null) return genProperty(fragment.id, receiverType, type, packageFragmentDescriptor, sourceElement, context, null)
} }
private fun genProperty( private fun genProperty(
@@ -84,8 +86,11 @@ private fun genProperty(
type: KotlinType, type: KotlinType,
containingDeclaration: DeclarationDescriptor, containingDeclaration: DeclarationDescriptor,
sourceElement: SourceElement, sourceElement: SourceElement,
context: SyntheticElementResolveContext,
errorType: String? errorType: String?
): PropertyDescriptor { ): PropertyDescriptor {
val alwaysCastToView = type.constructor.declarationDescriptor?.fqNameUnsafe?.asString() == AndroidConst.VIEWSTUB_FQNAME
val property = object : AndroidSyntheticProperty, PropertyDescriptorImpl( val property = object : AndroidSyntheticProperty, PropertyDescriptorImpl(
containingDeclaration, containingDeclaration,
null, null,
@@ -99,9 +104,11 @@ private fun genProperty(
false, false,
false) { false) {
override val errorType = errorType override val errorType = errorType
override val alwaysCastToView = alwaysCastToView
} }
val flexibleType = DelegatingFlexibleType.create(type, type.makeNullable(), FlexibleTypeCapabilities.NONE) val actualType = if (alwaysCastToView) context.viewType else type
val flexibleType = DelegatingFlexibleType.create(actualType, actualType.makeNullable(), FlexibleTypeCapabilities.NONE)
property.setType( property.setType(
flexibleType, flexibleType,
emptyList<TypeParameterDescriptor>(), emptyList<TypeParameterDescriptor>(),
@@ -131,6 +138,7 @@ interface AndroidSyntheticFunction
interface AndroidSyntheticProperty { interface AndroidSyntheticProperty {
val errorType: String? val errorType: String?
val alwaysCastToView: Boolean
val isErrorType: Boolean val isErrorType: Boolean
get() = errorType != null get() = errorType != null
@@ -0,0 +1,10 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ViewStub
android:id="@+id/stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
@@ -0,0 +1,18 @@
package test
import android.app.Activity
import android.os.Bundle
import java.io.File
import kotlinx.android.synthetic.main.layout.*
public class MyActivity : Activity() {
fun test() {
stub
}
}
// 1 GETSTATIC test/R\$id\.stub
// 0 INVOKEVIRTUAL test/MyActivity\._\$_findCachedViewById
// 1 INVOKEVIRTUAL android/app/Activity\.findViewById
// 0 CHECKCAST android/view/ViewStub
// 2 CHECKCAST android/view/View
@@ -0,0 +1,11 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ViewStub
android:id="@+id/stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
@@ -0,0 +1,35 @@
kotlinx
kotlinx.android
kotlinx.android.synthetic
public fun android.app.Activity.clearFindViewByIdCache(): kotlin.Unit
public fun android.app.Fragment.clearFindViewByIdCache(): kotlin.Unit
kotlinx.android.synthetic.main
kotlinx.android.synthetic.main.test
public val android.app.Activity.stub: android.view.View!
public val android.app.Fragment.stub: android.view.View!
kotlinx.android.synthetic.main.test.view
public val android.view.View.stub: android.view.View!
kotlinx.android.synthetic.test
public val android.app.Activity.stub: android.view.View!
public val android.app.Fragment.stub: android.view.View!
kotlinx.android.synthetic.test.view
public val android.view.View.stub: android.view.View!
@@ -160,4 +160,10 @@ public class AndroidBytecodeShapeTestGenerated extends AbstractAndroidBytecodeSh
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/supportSimpleFragmentProperty/"); String fileName = KotlinTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/supportSimpleFragmentProperty/");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("viewStub")
public void testViewStub() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/viewStub/");
doTest(fileName);
}
} }
@@ -118,4 +118,10 @@ public class AndroidSyntheticPropertyDescriptorTestGenerated extends AbstractAnd
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/descriptors/unresolvedWidget/"); String fileName = KotlinTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/descriptors/unresolvedWidget/");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("viewStub")
public void testViewStub() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/descriptors/viewStub/");
doTest(fileName);
}
} }