Disable intrinsic rmember in functions containing a vararg parameter
If a compoasble function contains a vararg parmaeter an implicit
group is added around the vararg parameter checking. This group should
have disabled intrinsic remember for the function block.
Fixes: 266116281
Test: ./gradlew :compose:compiler:c-h:i-t:tDUT
Change-Id: I9a32225f4f3a67e76a421156d6139b64ff59adfd
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
index 8fb61ca..4a60e10 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
@@ -1688,4 +1688,64 @@
fun Text(value: String) { }
"""
)
+
+ @Test
+ fun testVarargsIntrinsicRemember() = verifyComposeIrTransform(
+ source = """
+ import androidx.compose.runtime.*
+
+ @Composable
+ fun Test(vararg strings: String) {
+ val show = remember { mutableStateOf(false) }
+ if (show.value) {
+ Text("Showing")
+ }
+ }
+ """,
+ extra = """
+ import androidx.compose.runtime.*
+
+ @Composable
+ fun Text(value: String) { }
+ """,
+ expectedTransformed = """
+ @Composable
+ fun Test(strings: Array<out String>, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<rememb...>,<Text("...>:Test.kt")
+ val %dirty = %changed
+ %composer.startMovableGroup(<>, strings.size)
+ val tmp0_iterator = strings.iterator()
+ while (tmp0_iterator.hasNext()) {
+ val value = tmp0_iterator.next()
+ %dirty = %dirty or if (%composer.changed(value)) 0b0100 else 0
+ }
+ %composer.endMovableGroup()
+ if (%dirty and 0b1110 === 0) {
+ %dirty = %dirty or 0b0010
+ }
+ if (%dirty and 0b0001 !== 0 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val show = remember({
+ mutableStateOf(
+ value = false
+ )
+ }, %composer, 0)
+ if (show.value) {
+ Text("Showing", %composer, 0b0110)
+ }
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ Test(*strings, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ """
+ )
}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 6cd17ef..5ec690d 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -860,7 +860,10 @@
val emitTraceMarkers = traceEventMarkersEnabled && !scope.function.isInline
- scope.updateIntrinsiceRememberSafety(!mightUseDefaultGroup(false, scope, defaultParam))
+ scope.updateIntrinsiceRememberSafety(
+ !mightUseDefaultGroup(false, scope, defaultParam) &&
+ !mightUseVarArgsGroup(false, scope)
+ )
transformed = transformed.apply {
transformChildrenVoid()
@@ -1001,7 +1004,10 @@
val emitTraceMarkers = traceEventMarkersEnabled && !scope.isInlinedLambda
- scope.updateIntrinsiceRememberSafety(!mightUseDefaultGroup(canSkipExecution, scope, null))
+ scope.updateIntrinsiceRememberSafety(
+ !mightUseDefaultGroup(canSkipExecution, scope, null) &&
+ !mightUseVarArgsGroup(canSkipExecution, scope)
+ )
// we must transform the body first, since that will allow us to see whether or not we
// are using the dispatchReceiverParameter or the extensionReceiverParameter
@@ -1157,7 +1163,8 @@
val defaultScope = transformDefaults(scope)
scope.updateIntrinsiceRememberSafety(
- !mightUseDefaultGroup(true, scope, defaultParam)
+ !mightUseDefaultGroup(true, scope, defaultParam) &&
+ !mightUseVarArgsGroup(true, scope)
)
// we must transform the body first, since that will allow us to see whether or not we
@@ -1335,6 +1342,13 @@
return parameters.any { it.defaultValue?.expression?.isStatic() == false }
}
+ // Like mightUseDefaultGroup(), this is an intentionally conservative value that must be true
+ // when ever a varargs group could be generated but can be true when it is not.
+ private fun mightUseVarArgsGroup(
+ isSkippableDeclaration: Boolean,
+ scope: Scope.FunctionScope
+ ) = isSkippableDeclaration && scope.allTrackedParams.any { it.isVararg }
+
private fun buildPreambleStatementsAndReturnIfSkippingPossible(
sourceElement: IrElement,
skipPreamble: IrStatementContainer,
@@ -1535,6 +1549,7 @@
irGet(param),
param.type.classOrNull!!.getPropertyGetter("size")!!.owner
)
+
// TODO(lmr): verify this works with default vararg expressions!
skipPreamble.statements.add(
irStartMovableGroup(