Android extensions: Do not cache ViewStub widgets, do not cast it automatically
This commit is contained in:
+2
-2
@@ -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"
|
||||
|
||||
+26
-20
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-4
@@ -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
|
||||
|
||||
Vendored
+10
@@ -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>
|
||||
+18
@@ -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
|
||||
+11
@@ -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!
|
||||
+6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user