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
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
public object AndroidConst {
val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic"
@@ -44,6 +42,8 @@ public object AndroidConst {
//TODO FqName / ClassId
val VIEW_FQNAME = "android.view.View"
val VIEWSTUB_FQNAME = "android.view.ViewStub"
val ACTIVITY_FQNAME = "android.app.Activity"
val FRAGMENT_FQNAME = "android.app.Fragment"
val SUPPORT_V4_PACKAGE = "android.support.v4"
@@ -93,7 +93,13 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache"
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(
@@ -123,21 +129,21 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
receiver: StackValue,
resolvedCall: ResolvedCall<*>,
c: ExpressionCodegenExtension.Context,
descriptor: FunctionDescriptor
functionDescriptor: FunctionDescriptor
): StackValue? {
if (descriptor !is AndroidSyntheticFunction) return null
if (descriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return null
if (functionDescriptor !is AndroidSyntheticFunction) return null
if (functionDescriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return null
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return null
if (!isCacheSupported(declarationDescriptor)) return StackValue.functionCall(Type.VOID_TYPE) {}
val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
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
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)
}
}
@@ -151,31 +157,31 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
if (descriptor !is AndroidSyntheticProperty) return null
val packageFragment = descriptor.containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor ?: return null
val androidPackage = packageFragment.packageData.moduleData.module.applicationPackage
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return null
val androidClassType = AndroidClassType.getClassType(declarationDescriptor)
val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
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(
val receiver: StackValue,
val typeMapper: JetTypeMapper,
val descriptor: PropertyDescriptor,
val declarationDescriptor: ClassifierDescriptor,
val propertyDescriptor: PropertyDescriptor,
val receiverDescriptor: ClassDescriptor,
val androidClassType: AndroidClassType,
val androidPackage: String
) : StackValue(typeMapper.mapType(descriptor.returnType!!)) {
) : StackValue(typeMapper.mapType(propertyDescriptor.returnType!!)) {
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) {
return putSelectorForFragment(v)
}
if (androidClassType.supportsCache && isCacheSupported(declarationDescriptor)) {
val declarationDescriptorType = typeMapper.mapType(declarationDescriptor)
if (androidClassType.supportsCache && isCacheSupported(receiverDescriptor, propertyDescriptor)) {
val declarationDescriptorType = typeMapper.mapType(receiverDescriptor)
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)
}
else {
@@ -224,7 +230,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
}
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) {
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.name.Name
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.types.*
import org.jetbrains.kotlin.types.typeUtil.makeNullable
@@ -65,17 +66,18 @@ internal fun genPropertyForWidget(
defaultType.constructor.parameters.map { StarProjectionImpl(it) })
} ?: 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(
packageFragmentDescriptor: PackageFragmentDescriptor,
receiverType: KotlinType,
type: KotlinType,
fragment: AndroidResource.Fragment
fragment: AndroidResource.Fragment,
context: SyntheticElementResolveContext
): PropertyDescriptor {
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(
@@ -84,8 +86,11 @@ private fun genProperty(
type: KotlinType,
containingDeclaration: DeclarationDescriptor,
sourceElement: SourceElement,
context: SyntheticElementResolveContext,
errorType: String?
): PropertyDescriptor {
val alwaysCastToView = type.constructor.declarationDescriptor?.fqNameUnsafe?.asString() == AndroidConst.VIEWSTUB_FQNAME
val property = object : AndroidSyntheticProperty, PropertyDescriptorImpl(
containingDeclaration,
null,
@@ -99,9 +104,11 @@ private fun genProperty(
false,
false) {
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(
flexibleType,
emptyList<TypeParameterDescriptor>(),
@@ -131,6 +138,7 @@ interface AndroidSyntheticFunction
interface AndroidSyntheticProperty {
val errorType: String?
val alwaysCastToView: Boolean
val isErrorType: Boolean
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/");
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/");
doTest(fileName);
}
@TestMetadata("viewStub")
public void testViewStub() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/descriptors/viewStub/");
doTest(fileName);
}
}