diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidConst.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidConst.kt index cb12e17d6fc..42224c865e7 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidConst.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidConst.kt @@ -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" diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidExpressionCodegenExtension.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidExpressionCodegenExtension.kt index 084397b523d..c6450a7f82a 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidExpressionCodegenExtension.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidExpressionCodegenExtension.kt @@ -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") } } diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/descriptors/AndroidSyntheticPackageFragmentDescriptor.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/descriptors/AndroidSyntheticPackageFragmentDescriptor.kt index a5bcd230ded..5c7e9e6272c 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/descriptors/AndroidSyntheticPackageFragmentDescriptor.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/descriptors/AndroidSyntheticPackageFragmentDescriptor.kt @@ -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) } } } diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/syntheticDescriptorGeneration.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/syntheticDescriptorGeneration.kt index 7681dc26bf3..6d4586e3b6e 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/syntheticDescriptorGeneration.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/syntheticDescriptorGeneration.kt @@ -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(), @@ -131,6 +138,7 @@ interface AndroidSyntheticFunction interface AndroidSyntheticProperty { val errorType: String? + val alwaysCastToView: Boolean val isErrorType: Boolean get() = errorType != null diff --git a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/viewStub/res/layout/layout.xml b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/viewStub/res/layout/layout.xml new file mode 100644 index 00000000000..3c3a4156aee --- /dev/null +++ b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/viewStub/res/layout/layout.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/viewStub/viewStub.kt b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/viewStub/viewStub.kt new file mode 100644 index 00000000000..40f701398b9 --- /dev/null +++ b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/viewStub/viewStub.kt @@ -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 \ No newline at end of file diff --git a/plugins/android-compiler-plugin/testData/descriptors/viewStub/res/layout/test.xml b/plugins/android-compiler-plugin/testData/descriptors/viewStub/res/layout/test.xml new file mode 100644 index 00000000000..154044a762f --- /dev/null +++ b/plugins/android-compiler-plugin/testData/descriptors/viewStub/res/layout/test.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/android-compiler-plugin/testData/descriptors/viewStub/result.txt b/plugins/android-compiler-plugin/testData/descriptors/viewStub/result.txt new file mode 100644 index 00000000000..6ea70ff0386 --- /dev/null +++ b/plugins/android-compiler-plugin/testData/descriptors/viewStub/result.txt @@ -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! \ No newline at end of file diff --git a/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/AndroidBytecodeShapeTestGenerated.java b/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/AndroidBytecodeShapeTestGenerated.java index 13910f33f90..f6e082ad9f6 100644 --- a/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/AndroidBytecodeShapeTestGenerated.java +++ b/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/AndroidBytecodeShapeTestGenerated.java @@ -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); + } } diff --git a/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/AndroidSyntheticPropertyDescriptorTestGenerated.java b/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/AndroidSyntheticPropertyDescriptorTestGenerated.java index a3cec6ca044..0a16b77aaca 100644 --- a/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/AndroidSyntheticPropertyDescriptorTestGenerated.java +++ b/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/AndroidSyntheticPropertyDescriptorTestGenerated.java @@ -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); + } }