aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2019-10-25 23:27:17 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2019-10-25 23:27:17 +0000
commitfd4cbc0d20d1ec32b5cf01e7e26fb5d303883e2f (patch)
tree2c51919200909ed80915ca3f45136094a5fa7db8
parent152be197d1ad68e43b81a3b8feca5f932955f317 (diff)
parent5e8938586a65bfb98cc4a3ee46dc33461a7d7023 (diff)
downloadsupport-fd4cbc0d20d1ec32b5cf01e7e26fb5d303883e2f.tar.gz
Merge "Re-enable startRestartGroup/endRestartGroup for functions" into androidx-master-dev
-rw-r--r--compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt98
-rw-r--r--compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedRestartCalls.kt25
-rw-r--r--compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt7
-rw-r--r--compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt356
-rw-r--r--compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/ViewComposerTests.kt25
-rw-r--r--compose/compose-runtime/src/commonMain/kotlin/androidx/compose/SlotTable.kt2
6 files changed, 285 insertions, 228 deletions
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
index 96811f0b386..fa66b06a294 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality
+import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.extensions.internal.CallResolutionInterceptorExtension
import org.jetbrains.kotlin.incremental.components.LookupLocation
@@ -112,10 +113,10 @@ open class ComposeCallResolutionInterceptorExtension : CallResolutionInterceptor
// to do any work at all, since it will never be anything we intercept
if (!needToLookupComposer) return candidates
- val temporaryTraceForKtxCall =
+ val temporaryTraceForComposeableCall =
TemporaryTraceAndCache.create(
resolutionContext,
- "trace to resolve ktx call", element
+ "trace to resolve composable call", element
)
val composableAnnotationChecker =
@@ -125,7 +126,7 @@ open class ComposeCallResolutionInterceptorExtension : CallResolutionInterceptor
// TODO(lmr): there ought to be a better way to do this
var walker: PsiElement? = call.callElement
var isComposableContext = false
-// var composableDescriptor: SimpleFunctionDescriptor? = null
+ var composableDescriptor: SimpleFunctionDescriptor? = null
while (walker != null) {
val descriptor = try {
resolutionContext.trace[BindingContext.FUNCTION, walker]
@@ -134,12 +135,12 @@ open class ComposeCallResolutionInterceptorExtension : CallResolutionInterceptor
}
if (descriptor != null) {
val composability = composableAnnotationChecker.analyze(
- temporaryTraceForKtxCall.trace,
+ temporaryTraceForComposeableCall.trace,
descriptor
)
isComposableContext =
composability != ComposableAnnotationChecker.Composability.NOT_COMPOSABLE
-// if (isComposableContext) composableDescriptor = descriptor
+ if (isComposableContext) composableDescriptor = descriptor
// If the descriptor is for an inlined lambda, infer composability from the
// outer scope
@@ -197,6 +198,42 @@ open class ComposeCallResolutionInterceptorExtension : CallResolutionInterceptor
return nonComposablesNonConstructors + constructors
}
+ val context = ExpressionTypingContext.newContext(
+ resolutionContext.trace,
+ resolutionContext.scope,
+ resolutionContext.dataFlowInfo,
+ resolutionContext.expectedType,
+ resolutionContext.languageVersionSettings,
+ resolutionContext.dataFlowValueFactory
+ )
+
+ val temporaryForComposableCall = context.replaceTraceAndCache(
+ temporaryTraceForComposeableCall
+ )
+
+ // Once we know we have a valid composer record the composer on the function descriptor
+ // for the restart transform
+ val trace = temporaryTraceForComposeableCall.trace
+ if (composableDescriptor != null && trace.get(
+ ComposeWritableSlices.RESTART_COMPOSER_NEEDED,
+ composableDescriptor
+ ) != false) {
+ val recordingContext = TemporaryTraceAndCache.create(
+ context,
+ "trace to resolve composable restart group methods",
+ element
+ )
+ val recordingTrace = recordingContext.trace
+ recordingTrace.record(
+ ComposeWritableSlices.RESTART_COMPOSER_NEEDED,
+ composableDescriptor,
+ false
+ )
+ recordingTrace.record(
+ ComposeWritableSlices.RESTART_COMPOSER, composableDescriptor, composerCall)
+ recordingTrace.commit()
+ }
+
// If there are no constructors, then all of the candidates are either composables or
// non-composable functions, and we follow normal resolution rules.
if (constructors.isEmpty()) {
@@ -227,63 +264,14 @@ open class ComposeCallResolutionInterceptorExtension : CallResolutionInterceptor
composerMetadata
)
- val context = ExpressionTypingContext.newContext(
- resolutionContext.trace,
- resolutionContext.scope,
- resolutionContext.dataFlowInfo,
- resolutionContext.expectedType,
- resolutionContext.languageVersionSettings,
- resolutionContext.dataFlowValueFactory
- )
-
- val temporaryForKtxCall = context.replaceTraceAndCache(temporaryTraceForKtxCall)
-
val emitCandidates = emitResolver.resolveCandidates(
call,
emittables,
composerCall,
name,
- temporaryForKtxCall
+ temporaryForComposableCall
)
- // TODO(lmr): deal with the RESTART_CALLS_NEEDED stuff for restartGroups
- // Once we know we have a valid binding to a composable function call see if the scope need
- // the startRestartGroup and endRestartGroup information
-// val trace = temporaryTraceForKtxCall.trace
-// if (trace.get(
-// ComposeWritableSlices.RESTART_CALLS_NEEDED,
-// composableDescriptor!!
-// ) != false) {
-// val recordingContext = TemporaryTraceAndCache.create(
-// context,
-// "trace to resolve ktx call", element
-// )
-// val recordingTrace = recordingContext.trace
-// recordingTrace.record(
-// ComposeWritableSlices.RESTART_CALLS_NEEDED,
-// composableDescriptor,
-// false
-// )
-//
-// val startRestartGroup = ktxCallResolver.resolveStartRestartGroup(
-// call,
-// temporaryForKtxCall
-// )
-// val endRestartGroup = ktxCallResolver.resolveEndRestartGroup(call, temporaryForKtxCall)
-// val composerCall = ktxCallResolver.resolveComposerCall()
-// if (startRestartGroup != null && endRestartGroup != null && composerCall != null) {
-// recordingTrace.record(
-// ComposeWritableSlices.RESTART_CALLS, composableDescriptor,
-// ResolvedRestartCalls(
-// startRestartGroup = startRestartGroup,
-// endRestartGroup = endRestartGroup,
-// composer = composerCall
-// )
-// )
-// }
-// recordingTrace.commit()
-// }
-
return nonComposablesNonConstructors +
composables.map {
ComposableFunctionDescriptor(it, composerCall, composerMetadata)
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedRestartCalls.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedRestartCalls.kt
deleted file mode 100644
index 9bf1eaacd60..00000000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedRestartCalls.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.plugins.kotlin
-
-import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
-
-class ResolvedRestartCalls(
- val composer: ResolvedCall<*>,
- val startRestartGroup: ResolvedCall<*>,
- val endRestartGroup: ResolvedCall<*>
-) \ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
index b2df5aaf290..dc98a4cf8bb 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
@@ -2,15 +2,12 @@ package androidx.compose.plugins.kotlin.analysis
import androidx.compose.plugins.kotlin.ComposableAnnotationChecker
import androidx.compose.plugins.kotlin.ComposerMetadata
-import androidx.compose.plugins.kotlin.ResolvedRestartCalls
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.psi.Call
import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.util.slicedMap.BasicWritableSlice
import org.jetbrains.kotlin.util.slicedMap.RewritePolicy
@@ -28,9 +25,9 @@ object ComposeWritableSlices {
BasicWritableSlice(RewritePolicy.DO_NOTHING)
val STABLE_TYPE: WritableSlice<KotlinType, Boolean?> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
- val RESTART_CALLS_NEEDED: WritableSlice<SimpleFunctionDescriptor, Boolean> =
+ val RESTART_COMPOSER_NEEDED: WritableSlice<SimpleFunctionDescriptor, Boolean> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
- val RESTART_CALLS: WritableSlice<SimpleFunctionDescriptor, ResolvedRestartCalls> =
+ val RESTART_COMPOSER: WritableSlice<SimpleFunctionDescriptor, ResolvedCall<*>> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
val COMPOSER_METADATA: WritableSlice<VariableDescriptor, ComposerMetadata> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt
index 16979001663..73aa339020f 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt
@@ -48,6 +48,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFunctionLiteral
import androidx.compose.plugins.kotlin.ComposableAnnotationChecker
import androidx.compose.plugins.kotlin.ComposeUtils.generateComposePackageName
+import androidx.compose.plugins.kotlin.KtxNameConventions
import androidx.compose.plugins.kotlin.KtxNameConventions.UPDATE_SCOPE
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrReturn
@@ -60,6 +61,7 @@ import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
+import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.irBlock
import org.jetbrains.kotlin.ir.builders.irEqeqeq
import org.jetbrains.kotlin.ir.builders.irGet
@@ -68,17 +70,21 @@ import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.builders.irTemporary
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
+import org.jetbrains.kotlin.ir.expressions.IrBreak
import org.jetbrains.kotlin.ir.expressions.IrCall
+import org.jetbrains.kotlin.ir.expressions.IrContinue
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTryImpl
import org.jetbrains.kotlin.ir.util.referenceFunction
+import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.inline.InlineUtil
+import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isUnit
@@ -137,30 +143,41 @@ class ComposeObservePatcher(val context: JvmBackendContext) :
}
}
- // Do not insert an observe scope if the funciton has a return result
+ // Do not insert an observe scope if the function has a return result
if (descriptor.returnType.let { it == null || !it.isUnit() })
return declaration
// Check if the descriptor has restart scope calls resolved
val bindingContext = context.state.bindingContext
- if (descriptor is SimpleFunctionDescriptor) {
- val restartCalls = bindingContext.get(ComposeWritableSlices.RESTART_CALLS, descriptor)
+ if (descriptor is SimpleFunctionDescriptor &&
+ // TODO(chuckj): Support lambdas
+ declaration.origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA &&
+ declaration.origin != IrDeclarationOrigin.LOCAL_FUNCTION_NO_CLOSURE) {
+ val composerResolvedCall =
+ bindingContext.get(ComposeWritableSlices.RESTART_COMPOSER, descriptor)
val oldBody = declaration.body
- // TODO(b/142286425): Re-enable restart groups
- if (restartCalls != null && oldBody != null /* disabled */ && descriptor.isInline) {
- val composerResolvedCall = restartCalls.composer
- val symbols = context.ir.symbols
- val symbolTable = symbols.externalSymbolTable
-
- // Create call to get the composer
- val unitType = context.irBuiltIns.unitType
- val composer = irGetProperty(composerResolvedCall)
-
- // Create call to startRestartGroup
- val startRestartGroup = irMethodCall(composer, restartCalls.startRestartGroup)
- .apply {
- putValueArgument(0,
+ if (composerResolvedCall != null && oldBody != null) {
+ val composerDescriptor =
+ composerResolvedCall.resultingDescriptor as PropertyDescriptor
+ val startRestartGroupDescriptor = getStartRestartGroup(composerDescriptor)
+ val endRestartGroupDescriptor = getEndRestartGroup(composerDescriptor)
+
+ if (startRestartGroupDescriptor != null && endRestartGroupDescriptor != null) {
+ val symbols = context.ir.symbols
+ val symbolTable = symbols.externalSymbolTable
+
+ // Create call to get the composer
+ val unitType = context.irBuiltIns.unitType
+ fun composer() = irGetProperty(composerResolvedCall)
+
+ // Create call to startRestartGroup
+ val startRestartGroup = irMethodCall(
+ composer(),
+ startRestartGroupDescriptor
+ ).apply {
+ putValueArgument(
+ 0,
keyExpression(
descriptor,
declaration.startOffset,
@@ -169,139 +186,148 @@ class ComposeObservePatcher(val context: JvmBackendContext) :
)
}
- // Create call to endRestartGroup
- val endRestartGroup = irMethodCall(composer, restartCalls.endRestartGroup)
-
- // Create self-invoke lambda
- val endRestartGroupResolvedCall = restartCalls.endRestartGroup
- val endRestartGroupDescriptor = (
- endRestartGroupResolvedCall.resultingDescriptor as? FunctionDescriptor
- ) ?: error("Expected function descriptor")
- val updateScopeDescriptor =
- endRestartGroupDescriptor.returnType?.memberScope?.getContributedFunctions(
- UPDATE_SCOPE,
- NoLookupLocation.FROM_BACKEND
- )?.singleOrNull()
- ?: error("updateScope not found in result type of endRestartGroup")
- val blockParameterDescriptor = updateScopeDescriptor.valueParameters.singleOrNull()
- ?: error("expected a single block parameter for updateScope")
- val blockParameterType = blockParameterDescriptor.type
- val selfSymbol = declaration.symbol
-
- val lambdaDescriptor = AnonymousFunctionDescriptor(
- declaration.descriptor,
- Annotations.EMPTY,
- CallableMemberDescriptor.Kind.DECLARATION,
- SourceElement.NO_SOURCE,
- false
- ).apply {
- initialize(
- null,
- null,
- emptyList(),
- emptyList(),
- blockParameterType,
- Modality.FINAL,
- Visibilities.LOCAL
- )
- }
+ // Create call to endRestartGroup
+ val endRestartGroup = irMethodCall(composer(), endRestartGroupDescriptor)
+
+ // Create self-invoke lambda
+ val updateScopeDescriptor =
+ endRestartGroupDescriptor.returnType?.memberScope?.getContributedFunctions(
+ UPDATE_SCOPE,
+ NoLookupLocation.FROM_BACKEND
+ )?.singleOrNull()
+ ?: error("updateScope not found in result type of endRestartGroup")
+ val blockParameterDescriptor =
+ updateScopeDescriptor.valueParameters.singleOrNull()
+ ?: error("expected a single block parameter for updateScope")
+ val blockParameterType = blockParameterDescriptor.type
+ val selfSymbol = declaration.symbol
+
+ val lambdaDescriptor = AnonymousFunctionDescriptor(
+ declaration.descriptor,
+ Annotations.EMPTY,
+ CallableMemberDescriptor.Kind.DECLARATION,
+ SourceElement.NO_SOURCE,
+ false
+ ).apply {
+ initialize(
+ null,
+ null,
+ emptyList(),
+ emptyList(),
+ blockParameterType,
+ Modality.FINAL,
+ Visibilities.LOCAL
+ )
+ }
- val fn = IrFunctionImpl(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA,
- IrSimpleFunctionSymbolImpl(lambdaDescriptor),
- context.irBuiltIns.unitType
- ).also {
- val irBuilder = context.createIrBuilder(it.symbol)
- it.body = irBuilder.irBlockBody {
- // Call the function again with the same parameters
- +irReturn(irCall(selfSymbol).apply {
- descriptor.valueParameters.forEachIndexed { index, valueParameter ->
- val value = declaration.valueParameters[index].symbol
- putValueArgument(
- index, IrGetValueImpl(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- valueParameter.type.toIrType(),
- value
+ val fn = IrFunctionImpl(
+ UNDEFINED_OFFSET, UNDEFINED_OFFSET,
+ IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA,
+ IrSimpleFunctionSymbolImpl(lambdaDescriptor),
+ context.irBuiltIns.unitType
+ ).also {
+ it.parent = declaration
+ val irBuilder = context.createIrBuilder(it.symbol)
+ it.body = irBuilder.irBlockBody {
+ // Call the function again with the same parameters
+ +irReturn(irCall(selfSymbol).apply {
+ descriptor.valueParameters.forEachIndexed { index, valueParameter ->
+ val value = declaration.valueParameters[index].symbol
+ putValueArgument(
+ index, IrGetValueImpl(
+ UNDEFINED_OFFSET,
+ UNDEFINED_OFFSET,
+ valueParameter.type.toIrType(),
+ value
+ )
)
+ }
+ descriptor.dispatchReceiverParameter?.let { receiverDescriptor ->
+ dispatchReceiver = irGet(
+ receiverDescriptor.type.toIrType(),
+ declaration.dispatchReceiverParameter?.symbol
+ ?: error("Expected dispatch receiver on declaration")
+ )
+ }
+ descriptor.extensionReceiverParameter?.let { receiverDescriptor ->
+ extensionReceiver = irGet(
+ receiverDescriptor.type.toIrType(),
+ declaration.extensionReceiverParameter?.symbol
+ ?: error("Expected extension receiver on declaration")
+ )
+ }
+ descriptor.typeParameters.forEachIndexed { index, descriptor ->
+ putTypeArgument(index, descriptor.defaultType.toIrType())
+ }
+ })
+ }
+ }
+
+ val irBuilder = context.createIrBuilder(declaration.symbol)
+ val endRestartGroupCallBlock = irBuilder.irBlock(
+ UNDEFINED_OFFSET,
+ UNDEFINED_OFFSET
+ ) {
+ val result = irTemporary(endRestartGroup)
+ val updateScopeSymbol =
+ symbolTable.referenceSimpleFunction(updateScopeDescriptor)
+ +irIfThen(irNot(irEqeqeq(irGet(result.type, result.symbol), irNull())),
+ irCall(updateScopeSymbol).apply {
+ dispatchReceiver = irGet(result.type, result.symbol)
+ putValueArgument(0,
+ irBlock(origin = IrStatementOrigin.LAMBDA) {
+ +fn
+ +IrFunctionReferenceImpl(
+ UNDEFINED_OFFSET,
+ UNDEFINED_OFFSET,
+ blockParameterType.toIrType(),
+ fn.symbol,
+ fn.descriptor,
+ 0,
+ IrStatementOrigin.LAMBDA
+ )
+ }
)
}
- descriptor.dispatchReceiverParameter?.let { receiverDescriptor ->
- dispatchReceiver = irGet(receiverDescriptor.type.toIrType(),
- declaration.dispatchReceiverParameter?.symbol
- ?: error("Expected dispatch receiver on declaration")
- )
- }
- descriptor.extensionReceiverParameter?.let { receiverDescriptor ->
- extensionReceiver = irGet(receiverDescriptor.type.toIrType(),
- declaration.extensionReceiverParameter?.symbol
- ?: error("Expected extension receiver on declaration")
- )
- }
- descriptor.typeParameters.forEachIndexed { index, descriptor ->
- putTypeArgument(index, descriptor.defaultType.toIrType())
- }
- })
+ )
}
- }
- val irBuilder = context.createIrBuilder(declaration.symbol)
- val endRestartGroupCallBlock = irBuilder.irBlock(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET
- ) {
- val result = irTemporary(endRestartGroup)
- val updateScopeSymbol =
- symbolTable.referenceSimpleFunction(updateScopeDescriptor)
- +fn
- +irIfThen(irNot(irEqeqeq(irGet(result.type, result.symbol), irNull())),
- irCall(updateScopeSymbol).apply {
- dispatchReceiver = irGet(result.type, result.symbol)
- putValueArgument(0,
- IrFunctionReferenceImpl(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- blockParameterType.toIrType(),
- fn.symbol,
- fn.descriptor,
- 0,
- IrStatementOrigin.LAMBDA
- )
- )
+ when (oldBody) {
+ is IrBlockBody -> {
+ if (containsPotentialEarly(oldBody)) {
+ // Transform the block into
+ // composer.startRestartGroup()
+ // try {
+ // ... old statements ...
+ // } finally {
+ // composer.endRestartGroup()
+ // }
+ declaration.body = irBuilder.irBlockBody {
+ +IrTryImpl(
+ oldBody.startOffset, oldBody.endOffset, unitType,
+ IrBlockImpl(
+ UNDEFINED_OFFSET,
+ UNDEFINED_OFFSET,
+ unitType
+ ).apply {
+ statements.add(startRestartGroup)
+ statements.addAll(oldBody.statements)
+ },
+ catches = emptyList(),
+ finallyExpression = endRestartGroupCallBlock
+ )
+ }
+ } else {
+ // Insert the start and end calls into the block
+ oldBody.statements.add(0, startRestartGroup)
+ oldBody.statements.add(endRestartGroupCallBlock)
+ }
+ return declaration
}
- )
- }
-
- when (oldBody) {
- is IrBlockBody -> {
- // Transform the block into
- // composer.startRestartGroup()
- // try {
- // ... old statements ...
- // } finally {
- // composer.endRestartGroup()
- // }
- declaration.body = irBuilder.irBlockBody {
- +IrTryImpl(
- oldBody.startOffset, oldBody.endOffset, unitType,
- IrBlockImpl(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- unitType
- ).apply {
- statements.add(startRestartGroup)
- statements.addAll(oldBody.statements)
- },
- catches = emptyList(),
- finallyExpression = endRestartGroupCallBlock
- )
+ else -> {
+ // Composable function do not use IrExpressionBody as they are converted
+ // by the call lowering to IrBlockBody to introduce the call temporaries.
}
- return declaration
- }
- else -> {
- // Composable function do not use IrExpressionBody as they are converted
- // by the call lowering to IrBlockBody to introduce the call temporaries.
}
}
}
@@ -427,11 +453,30 @@ class ComposeObservePatcher(val context: JvmBackendContext) :
fun irMethodCall(target: IrExpression, resolvedCall: ResolvedCall<*>): IrCall {
val descriptor = (resolvedCall.resultingDescriptor as?
FunctionDescriptor) ?: error("Expected function descriptor")
+ return irMethodCall(target, descriptor)
+ }
+
+ fun irMethodCall(target: IrExpression, descriptor: FunctionDescriptor): IrCall {
return irCall(descriptor).apply {
dispatchReceiver = target
}
}
+ fun MemberScope.firstFunctionOrNull(name: Name) =
+ getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).firstOrNull()
+
+ fun getStartRestartGroup(descriptor: PropertyDescriptor): FunctionDescriptor? =
+ descriptor
+ .type
+ .memberScope
+ .firstFunctionOrNull(KtxNameConventions.STARTRESTARTGROUP)
+
+ fun getEndRestartGroup(descriptor: PropertyDescriptor): FunctionDescriptor? =
+ descriptor
+ .type
+ .memberScope
+ .firstFunctionOrNull(KtxNameConventions.ENDRESTARTGROUP)
+
private fun isComposable(declaration: IrFunction): Boolean {
val tmpTrace =
DelegatingBindingTrace(
@@ -464,3 +509,30 @@ class ComposeObservePatcher(val context: JvmBackendContext) :
)
}
}
+
+private fun containsPotentialEarly(block: IrBlockBody): Boolean {
+ var result = false
+ block.accept(object : IrElementVisitor<Unit, Unit> {
+ override fun visitElement(element: IrElement, data: Unit) {
+ if (!result)
+ element.acceptChildren(this, Unit)
+ }
+
+ override fun visitBreak(jump: IrBreak, data: Unit) {
+ result = true
+ }
+
+ override fun visitContinue(jump: IrContinue, data: Unit) {
+ result = true
+ }
+
+ override fun visitReturn(expression: IrReturn, data: Unit) {
+ result = true
+ }
+
+ override fun visitDeclaration(declaration: IrDeclaration, data: Unit) {
+ // Skip bodies of declarations
+ }
+ }, Unit)
+ return result
+} \ No newline at end of file
diff --git a/compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/ViewComposerTests.kt b/compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/ViewComposerTests.kt
index eb08c5f3d36..faa8f26369f 100644
--- a/compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/ViewComposerTests.kt
+++ b/compose/compose-runtime/src/androidAndroidTest/kotlin/androidx/compose/ViewComposerTests.kt
@@ -32,6 +32,8 @@ import junit.framework.TestCase.assertEquals
import org.junit.Rule
import org.junit.runner.RunWith
import org.junit.Test
+import java.lang.AssertionError
+import java.lang.IllegalStateException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.test.assertTrue
@@ -1025,11 +1027,32 @@ class DisposeTests {
internal val Activity.root get() = findViewById(ComposerComposeTestCase.ROOT_ID) as ViewGroup
internal fun Activity.uiThread(block: () -> Unit) {
+ val latch = CountDownLatch(1)
+ var throwable: Throwable? = null
runOnUiThread(object : Runnable {
override fun run() {
- block()
+ try {
+ block()
+ } catch (e: Throwable) {
+ throwable = e
+ } finally {
+ latch.countDown()
+ }
}
})
+
+ val completed = latch.await(5, TimeUnit.SECONDS)
+ if (!completed) error("UI thread work did not complete within 5 seconds")
+ throwable?.let {
+ throw when (it) {
+ is AssertionError -> AssertionError(it.localizedMessage, it)
+ else ->
+ IllegalStateException(
+ "UI thread threw an exception: ${it.localizedMessage}",
+ it
+ )
+ }
+ }
}
internal fun Activity.disposeTestComposition() {
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/SlotTable.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/SlotTable.kt
index 4e75e4c95c8..092ed335b3e 100644
--- a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/SlotTable.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/SlotTable.kt
@@ -671,6 +671,8 @@ class SlotTable(internal var slots: Array<Any?> = arrayOf()) {
return SlotWriter(this)
}
+ val size: Int get() = slots.size - gapLen
+
internal fun close(reader: SlotReader) {
require(reader.table === this && readers > 0) { "Unexpected reader close()" }
readers--