blob: 05baa8cebbfc12aef4f65be9a41de357c69eed1e [file] [log] [blame]
/*
* Copyright 2021 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.compiler.plugins.kotlin
import org.junit.Test
@Suppress("SpellCheckingInspection") // Expected strings can have partial words
class TargetAnnotationsTransformTests : ComposeIrTransformTest() {
@Test
fun testInferUIFromCall() = verify(
"""
import androidx.compose.runtime.Composable
@Composable
fun Test() {
Text("Hello")
}
""",
"""
@Composable
@ComposableTarget(applier = "UI")
fun Test(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<Text("...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Text("Hello", %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(%composer, %changed or 0b0001)
}
}
"""
)
@Test
fun testInferVectorFromCall() = verify(
"""
import androidx.compose.runtime.Composable
@Composable
fun Test() {
Circle()
}
""",
"""
@Composable
@ComposableTarget(applier = "Vector")
fun Test(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<Circle...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Circle(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(%composer, %changed or 0b0001)
}
}
"""
)
// No annotations is the same as leaving the applier open.
@Test
fun testInferSimpleOpen() = verify(
"""
import androidx.compose.runtime.Composable
@Composable
fun Test() { }
""",
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test):Test.kt")
if (%changed !== 0 || !%composer.skipping) {
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(%composer, %changed or 0b0001)
}
}
"""
)
@Test
fun testInferUnifiedParameters() = verify(
"""
import androidx.compose.runtime.Composable
@Composable
fun Test(content: @Composable () -> Unit) {
content()
}
""",
"""
@Composable
@ComposableInferredTarget(scheme = "[0[0]]")
fun Test(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<conten...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
content(%composer, 0b1110 and %dirty)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(content, %composer, %changed or 0b0001)
}
}
"""
)
@Test
fun testInferLambdaParameter() = verify(
"""
import androidx.compose.runtime.Composable
@Composable
fun Test(content: @Composable () -> Unit) {
Row {
Text("test")
}
}
""",
"""
@Composable
@ComposableInferredTarget(scheme = "[UI[_]]")
fun Test(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<Row>:Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
Row(ComposableSingletons%TestKt.lambda-1, %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(content, %composer, %changed or 0b0001)
}
}
internal object ComposableSingletons%TestKt {
val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<Text("...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Text("test", %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
}
}
"""
)
@Test
fun testInferInlineLambdaParameter() = verify(
"""
import androidx.compose.runtime.Composable
@Composable
fun Test(content: @Composable () -> Unit) {
InlineRow {
Text("test")
}
}
""",
"""
@Composable
@ComposableInferredTarget(scheme = "[UI[_]]")
fun Test(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<Inline...>:Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
InlineRow({ %composer: Composer?, %changed: Int ->
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "C<Text("...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Text("test", %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
%composer.endReplaceableGroup()
}, %composer, 0)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(content, %composer, %changed or 0b0001)
}
}
"""
)
@Test
fun testCanInferWithGeneric() = verify(
"""
import androidx.compose.runtime.Composable
@Composable
fun Test() {
Wrapper {
Text("test")
}
}
""",
"""
@Composable
@ComposableTarget(applier = "UI")
fun Test(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<Wrappe...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Wrapper(ComposableSingletons%TestKt.lambda-1, %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(%composer, %changed or 0b0001)
}
}
internal object ComposableSingletons%TestKt {
val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<Text("...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Text("test", %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
}
}
"""
)
@Test
fun testCompositionLocalsProvider() = verify(
"""
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@Composable
fun Test() {
CompositionLocalProvider {
Text("test")
}
}
""",
"""
@Composable
@ComposableTarget(applier = "UI")
fun Test(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<Compos...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
CompositionLocalProvider(
content = ComposableSingletons%TestKt.lambda-1,
%composer = %composer,
%changed = 56
)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(%composer, %changed or 0b0001)
}
}
internal object ComposableSingletons%TestKt {
val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<Text("...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Text("test", %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
}
}
"""
)
@Test
fun testInferringFunInterfaceParameterAnnotations() = verify(
"""
import androidx.compose.runtime.Composable
fun interface CustomComposable {
@Composable
fun call()
}
@Composable
fun OpenCustom(content: CustomComposable) {
content.call()
}
@Composable
fun ClosedCustom(content: CustomComposable) {
Text("Test")
content.call()
}
@Composable
fun Test() {
OpenCustom {
Text("Test")
}
ClosedCustom {
Text("Test")
}
}
""",
"""
interface CustomComposable {
@Composable
abstract fun call(%composer: Composer?, %changed: Int)
}
@Composable
@ComposableInferredTarget(scheme = "[0[0]]")
fun OpenCustom(content: CustomComposable, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(OpenCustom)<call()>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
content.call(%composer, 0b1110 and %dirty)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
OpenCustom(content, %composer, %changed or 0b0001)
}
}
@Composable
@ComposableInferredTarget(scheme = "[UI[UI]]")
fun ClosedCustom(content: CustomComposable, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(ClosedCustom)<Text("...>,<call()>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
Text("Test", %composer, 0b0110)
content.call(%composer, 0b1110 and %dirty)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
ClosedCustom(content, %composer, %changed or 0b0001)
}
}
@Composable
@ComposableTarget(applier = "UI")
fun Test(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<OpenCu...>,<Closed...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
OpenCustom(class <no name provided> : CustomComposable {
@Composable
@ComposableTarget(applier = "UI")
override fun call(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(call)<Text("...>:Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
Text("Test", %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
val tmp0_rcvr = <this>
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
tmp0_rcvr.call(%composer, %changed or 0b0001)
}
}
}
<no name provided>(), %composer, 0)
ClosedCustom(class <no name provided> : CustomComposable {
@Composable
@ComposableTarget(applier = "UI")
override fun call(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(call)<Text("...>:Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
Text("Test", %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
val tmp0_rcvr = <this>
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
tmp0_rcvr.call(%composer, %changed or 0b0001)
}
}
}
<no name provided>(), %composer, 0)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(%composer, %changed or 0b0001)
}
}
"""
)
fun testLetIt() = verifyComposeIrTransform(
"""
import androidx.compose.runtime.*
@Composable
fun Test(content: (@Composable () -> Unit?)) {
content?.let { it() }
}
""",
"""
@Composable
@ComposableInferredTarget(scheme = "[0[0]]")
fun Test(content: Function2<Composer, Int, Unit?>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)*<it()>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
val tmp0_safe_receiver = content
val tmp1_group = when {
tmp0_safe_receiver == null -> {
null
}
else -> {
val tmp0_group = tmp0_safe_receiver.let { it: Function2<Composer, Int, Unit?> ->
val tmp0_return = it(%composer, 0)
tmp0_return
}
tmp0_group
}
}
tmp1_group
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(content, %composer, %changed or 0b0001)
}
}
"""
)
@Test
fun testOptionalParameters() = verifyComposeIrTransform(
"""
import androidx.compose.runtime.*
@Composable
@ComposableTarget("UI")
fun Leaf() { }
@Composable
fun Wrapper(content: @Composable () -> Unit) { content() }
// [0,[0],[0],[0],[0],[0],[0],[0]]
@Composable
fun Optional(
one: @Composable () -> Unit = { },
two: (@Composable () -> Unit)? = null,
three: (@Composable () -> Unit)? = null,
four: (@Composable () -> Unit)? = null,
five: (@Composable () -> Unit)? = null,
six: (@Composable () -> Unit)? = null,
content: @Composable () -> Unit
) {
one()
// Invoke through a ?.
two?.invoke()
// Invoke through a let
three?.let { it() }
// Invoke through a let test
four?.let { four() }
// Invoke through in an then block
if (five != null)
five()
six?.let { it -> Wrapper(it) }
content()
}
@Composable
fun UseOptional() {
Optional {
Leaf()
}
}
""",
"""
@Composable
@ComposableTarget(applier = "UI")
fun Leaf(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Leaf):Test.kt")
if (%changed !== 0 || !%composer.skipping) {
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Leaf(%composer, %changed or 0b0001)
}
}
@Composable
@ComposableInferredTarget(scheme = "[0[0]]")
fun Wrapper(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Wrapper)<conten...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
content(%composer, 0b1110 and %dirty)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Wrapper(content, %composer, %changed or 0b0001)
}
}
@Composable
@ComposableInferredTarget(scheme = "[0[0][0][0][0][0][0][0]]")
fun Optional(one: Function2<Composer, Int, Unit>?, two: Function2<Composer, Int, Unit>?, three: Function2<Composer, Int, Unit>?, four: Function2<Composer, Int, Unit>?, five: Function2<Composer, Int, Unit>?, six: Function2<Composer, Int, Unit>?, content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int, %default: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Optional)P(3,6,5,2,1,4)<one()>,<conten...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
} else if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(one)) 0b0100 else 0b0010
}
if (%default and 0b0010 !== 0) {
%dirty = %dirty or 0b00110000
} else if (%changed and 0b01110000 === 0) {
%dirty = %dirty or if (%composer.changed(two)) 0b00100000 else 0b00010000
}
if (%default and 0b0100 !== 0) {
%dirty = %dirty or 0b000110000000
} else if (%changed and 0b001110000000 === 0) {
%dirty = %dirty or if (%composer.changed(three)) 0b000100000000 else 0b10000000
}
if (%default and 0b1000 !== 0) {
%dirty = %dirty or 0b110000000000
} else if (%changed and 0b0001110000000000 === 0) {
%dirty = %dirty or if (%composer.changed(four)) 0b100000000000 else 0b010000000000
}
if (%default and 0b00010000 !== 0) {
%dirty = %dirty or 0b0110000000000000
} else if (%changed and 0b1110000000000000 === 0) {
%dirty = %dirty or if (%composer.changed(five)) 0b0100000000000000 else 0b0010000000000000
}
if (%default and 0b00100000 !== 0) {
%dirty = %dirty or 0b00110000000000000000
} else if (%changed and 0b01110000000000000000 === 0) {
%dirty = %dirty or if (%composer.changed(six)) 0b00100000000000000000 else 0b00010000000000000000
}
if (%default and 0b01000000 !== 0) {
%dirty = %dirty or 0b000110000000000000000000
} else if (%changed and 0b001110000000000000000000 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b000100000000000000000000 else 0b10000000000000000000
}
if (%dirty and 0b001011011011011011011011 !== 0b10010010010010010010 || !%composer.skipping) {
if (%default and 0b0001 !== 0) {
one = ComposableSingletons%TestKt.lambda-1
}
if (%default and 0b0010 !== 0) {
two = null
}
if (%default and 0b0100 !== 0) {
three = null
}
if (%default and 0b1000 !== 0) {
four = null
}
if (%default and 0b00010000 !== 0) {
five = null
}
if (%default and 0b00100000 !== 0) {
six = null
}
one(%composer, 0b1110 and %dirty)
two?.invoke(%composer, 0b1110 and %dirty shr 0b0011)
val tmp1_safe_receiver = three
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "*<it()>")
val tmp1_group = when {
tmp1_safe_receiver == null -> {
null
}
else -> {
tmp1_safe_receiver.let { it: Function2<Composer, Int, Unit> ->
it(%composer, 0)
}
}
}
%composer.endReplaceableGroup()
tmp1_group
val tmp2_safe_receiver = four
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "*<four()>")
val tmp2_group = when {
tmp2_safe_receiver == null -> {
null
}
else -> {
tmp2_safe_receiver.let { it: Function2<Composer, Int, Unit> ->
four(%composer, 0b1110 and %dirty shr 0b1001)
}
}
}
%composer.endReplaceableGroup()
tmp2_group
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "<five()>")
if (five != null) {
five(%composer, 0b1110 and %dirty shr 0b1100)
}
%composer.endReplaceableGroup()
val tmp3_safe_receiver = six
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "*<Wrappe...>")
val tmp3_group = when {
tmp3_safe_receiver == null -> {
null
}
else -> {
tmp3_safe_receiver.let { it: Function2<Composer, Int, Unit> ->
Wrapper(it, %composer, 0)
}
}
}
%composer.endReplaceableGroup()
tmp3_group
content(%composer, 0b1110 and %dirty shr 0b00010010)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Optional(one, two, three, four, five, six, content, %composer, %changed or 0b0001, %default)
}
}
@Composable
@ComposableTarget(applier = "UI")
fun UseOptional(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(UseOptional)<Option...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Optional(null, null, null, null, null, null, ComposableSingletons%TestKt.lambda-2, %composer, 0b000110000000000000000000, 0b00111111)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
UseOptional(%composer, %changed or 0b0001)
}
}
internal object ComposableSingletons%TestKt {
val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Unit
} else {
%composer.skipToGroupEnd()
}
}
val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<Leaf()>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Leaf(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
}
}
"""
)
@Test
fun testReceiverScope() = verifyComposeIrTransform(
"""
import androidx.compose.runtime.*
import androidx.compose.ui.layout.*
import androidx.compose.foundation.text.*
import androidx.compose.ui.text.*
import androidx.compose.ui.text.style.*
import androidx.compose.ui.*
@Immutable
interface LocalBoxScope {
@Stable
fun Modifier.align(alignment: Alignment): Modifier
}
object LocalBoxScopeInstance : LocalBoxScope {
override fun Modifier.align(alignment: Alignment): Modifier = Modifier
}
val localBoxMeasurePolicy = MeasurePolicy { _, constraints ->
layout(
constraints.minWidth,
constraints.minHeight
) {}
}
@Composable
inline fun LocalBox(
modifier: Modifier = Modifier,
content: @Composable LocalBoxScope.() -> Unit
) {
Layout(
modifier = modifier,
measurePolicy = localBoxMeasurePolicy,
content = { LocalBoxScopeInstance.content() }
)
}
""",
"""
@Immutable
interface LocalBoxScope {
@Stable
abstract fun Modifier.align(alignment: Alignment): Modifier
}
@StabilityInferred(parameters = 0)
object LocalBoxScopeInstance : LocalBoxScope {
override fun Modifier.align(alignment: Alignment): Modifier {
return Companion
}
static val %stable: Int = 0
}
val localBoxMeasurePolicy: MeasurePolicy = class <no name provided> : MeasurePolicy {
override fun measure(%this%MeasurePolicy: MeasureScope, <anonymous parameter 0>: List<Measurable>, constraints: Constraints): MeasureResult {
return %this%MeasurePolicy.layout(
width = constraints.minWidth,
height = constraints.minHeight
) {
}
}
}
<no name provided>()
@Composable
@ComposableInferredTarget(scheme = "[androidx.compose.ui.UiComposable[androidx.compose.ui.UiComposable]]")
fun LocalBox(modifier: Modifier?, content: @[ExtensionFunctionType] Function3<LocalBoxScope, Composer, Int, Unit>, %composer: Composer?, %changed: Int, %default: Int) {
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "C(LocalBox)P(1)<Layout...>:Test.kt")
if (%default and 0b0001 !== 0) {
modifier = Companion
}
val tmp0_measurePolicy = localBoxMeasurePolicy
Layout({ %composer: Composer?, %changed: Int ->
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "C<conten...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
content(LocalBoxScopeInstance, %composer, 0b0110 or 0b01110000 and %changed)
} else {
%composer.skipToGroupEnd()
}
%composer.endReplaceableGroup()
}, modifier, tmp0_measurePolicy, %composer, 0b000110000000 or 0b01110000 and %changed shl 0b0011, 0)
%composer.endReplaceableGroup()
}
"""
)
@Test
fun testCallingLayout() = verifyComposeIrTransform(
"""
import androidx.compose.runtime.*
import androidx.compose.ui.layout.*
import androidx.compose.foundation.text.*
import androidx.compose.ui.text.*
import androidx.compose.ui.text.style.*
@Composable
fun Test1() {
Layout(content = { }) { _, _ -> error("") }
}
@Composable
fun Test2(content: @Composable ()->Unit) {
Layout(content = content) { _, _ -> error("") }
}
@Composable
fun Test3() {
Test1()
}
@Composable
fun Test4() {
BasicText(text = AnnotatedString("Some text"))
}
val Local = compositionLocalOf { 0 }
@Composable
fun Test5(content: @Composable () -> Unit) {
CompositionLocalProvider(Local provides 5) {
Test1()
content()
}
}
@Composable
fun Test6(test: String) {
CompositionLocalProvider(Local provides 6) {
T(test)
Test1()
}
}
@Composable
fun T(value: String) { }
""",
"""
@Composable
@ComposableTarget(applier = "androidx.compose.ui.UiComposable")
fun Test1(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test1)<Layout...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Layout({ %composer: Composer?, %changed: Int ->
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Unit
} else {
%composer.skipToGroupEnd()
}
%composer.endReplaceableGroup()
}, null, class <no name provided> : MeasurePolicy {
override fun measure(%this%Layout: MeasureScope, <anonymous parameter 0>: List<Measurable>, <anonymous parameter 1>: Constraints): MeasureResult {
return error("")
}
}
<no name provided>(), %composer, 0, 0b0010)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test1(%composer, %changed or 0b0001)
}
}
@Composable
@ComposableInferredTarget(scheme = "[androidx.compose.ui.UiComposable[androidx.compose.ui.UiComposable]]")
fun Test2(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test2)<Layout...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
Layout(content, null, class <no name provided> : MeasurePolicy {
override fun measure(%this%Layout: MeasureScope, <anonymous parameter 0>: List<Measurable>, <anonymous parameter 1>: Constraints): MeasureResult {
return error("")
}
}
<no name provided>(), %composer, 0b1110 and %dirty, 0b0010)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test2(content, %composer, %changed or 0b0001)
}
}
@Composable
@ComposableTarget(applier = "androidx.compose.ui.UiComposable")
fun Test3(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test3)<Test1(...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Test1(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test3(%composer, %changed or 0b0001)
}
}
@Composable
@ComposableTarget(applier = "androidx.compose.ui.UiComposable")
fun Test4(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test4)<BasicT...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
BasicText(AnnotatedString(
text = "Some text"
), null, null, null, <unsafe-coerce>(0), false, 0, null, %composer, 0, 0b11111110)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test4(%composer, %changed or 0b0001)
}
}
val Local: ProvidableCompositionLocal<Int> = compositionLocalOf {
0
}
@Composable
@ComposableInferredTarget(scheme = "[androidx.compose.ui.UiComposable[androidx.compose.ui.UiComposable]]")
fun Test5(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test5)<Compos...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
CompositionLocalProvider(Local provides 5, composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<Test1(...>,<conten...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Test1(%composer, 0)
content(%composer, 0b1110 and %dirty)
} else {
%composer.skipToGroupEnd()
}
}, %composer, 0b00111000)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test5(content, %composer, %changed or 0b0001)
}
}
@Composable
@ComposableTarget(applier = "androidx.compose.ui.UiComposable")
fun Test6(test: String, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test6)<Compos...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(test)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
CompositionLocalProvider(Local provides 6, composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<T(test...>,<Test1(...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
T(test, %composer, 0b1110 and %dirty)
Test1(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
}, %composer, 0b00111000)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test6(test, %composer, %changed or 0b0001)
}
}
@Composable
fun T(value: String, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(T):Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
T(value, %composer, %changed or 0b0001)
}
}
"""
)
@Suppress("unused")
fun testCollectAsState() = verifyComposeIrTransform(
"""
import kotlin.coroutines.*
import kotlinx.coroutines.flow.*
import androidx.compose.runtime.*
@Composable
fun <T> StateFlow<T>.collectAsState(
context: CoroutineContext = EmptyCoroutineContext
): State<T> = collectAsState(value, context)
@Composable
fun <T : R, R> Flow<T>.collectAsState(
initial: R,
context: CoroutineContext = EmptyCoroutineContext
): State<R> = mutableStateOf(initial)
""",
"""
@Composable
fun <T> StateFlow<T>.collectAsState(context: CoroutineContext?, %composer: Composer?, %changed: Int, %default: Int): State<T> {
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "C(collectAsState)<collec...>:Test.kt")
if (%default and 0b0001 !== 0) {
context = EmptyCoroutineContext
}
val tmp0 = collectAsState(value, context, %composer, 0b001000001000, 0)
%composer.endReplaceableGroup()
return tmp0
}
@Composable
fun <T: R, R> Flow<T>.collectAsState(initial: R, context: CoroutineContext?, %composer: Composer?, %changed: Int, %default: Int): State<R> {
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "C(collectAsState)P(1):Test.kt")
if (%default and 0b0010 !== 0) {
context = EmptyCoroutineContext
}
val tmp0 = mutableStateOf(
value = initial
)
%composer.endReplaceableGroup()
return tmp0
}
"""
)
@Test
fun testRememberUpdatedState() = verifyComposeIrTransform(
source = """
import androidx.compose.runtime.*
@Composable
fun Test(content: @Composable () -> Unit) {
val updatedContent by rememberUpdatedState(content)
Defer {
UiContent {
updatedContent()
}
}
}
""",
extra = """
import androidx.compose.runtime.*
fun Defer(content: @Composable () -> Unit) { }
fun UiContent(content: @Composable @ComposableTarget("UI") () -> Unit) { }
""",
expectedTransformed = """
@Composable
@ComposableInferredTarget(scheme = "[UI[UI]]")
fun Test(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<rememb...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
<<LOCALDELPROP>>
Defer(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
UiContent(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<update...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
<get-updatedContent>()(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
}
)
} else {
%composer.skipToGroupEnd()
}
}
)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(content, %composer, %changed or 0b0001)
}
}
"""
)
@Test
fun testAddingComposablesToAList() = verifyComposeIrTransform(
"""
import androidx.compose.runtime.*
class Scope {
private val list = IntervalList<Scope.(Int) -> (@Composable () -> Unit)>()
fun item(content: @Composable Scope.() -> Unit) {
list.add(1) { @Composable { content() } }
}
}
""",
"""
@StabilityInferred(parameters = 0)
class Scope {
val list: IntervalList<@[ExtensionFunctionType] Function2<Scope, Int, Function2<Composer, Int, Unit>>> = IntervalList()
fun item(content: @[ExtensionFunctionType] Function3<Scope, Composer, Int, Unit>) {
list.add(1) { it: Int ->
composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<conten...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
content(%this%add, %composer, 0)
} else {
%composer.skipToGroupEnd()
}
}
}
}
static val %stable: Int = 0
}
""",
extra = """
class IntervalList<T> {
fun add(size: Int, content: T) { }
}
"""
)
@Test
fun testCallingNullableComposableWithNull() = verifyComposeIrTransform(
"""
import androidx.compose.runtime.*
@Composable
fun Test() {
Widget(null)
}
""",
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<Widget...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Widget(null, %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(%composer, %changed or 0b0001)
}
}
""",
extra = """
import androidx.compose.runtime.*
@Composable
fun Widget(content: (@Composable () -> Unit)?) {
if (content != null) content()
}
"""
)
@Test
fun testCallingComposableParameterWithComposableParameter() = verify(
"""
import androidx.compose.runtime.*
@Composable
fun Test(decorator: @Composable (content: @Composable () -> Unit) -> Unit) {
decorator {
Text("Some text")
}
}
""",
"""
@Composable
@ComposableInferredTarget(scheme = "[UI[UI[UI]]]")
fun Test(decorator: Function3<@[ParameterName(name = 'content')] Function2<Composer, Int, Unit>, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Test)<decora...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(decorator)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
decorator(ComposableSingletons%TestKt.lambda-1, %composer, 0b0110 or 0b01110000 and %dirty shl 0b0011)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(decorator, %composer, %changed or 0b0001)
}
}
internal object ComposableSingletons%TestKt {
val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C<Text("...>:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
Text("Some text", %composer, 0b0110)
} else {
%composer.skipToGroupEnd()
}
}
}
"""
)
@Test
fun testFileScoped() = verifyComposeIrTransform(
source = """
@file:NComposable
import androidx.compose.runtime.*
@Composable
fun NFromFile() {
Open()
}
@Composable
fun NFromInference() {
N()
}
""",
expectedTransformed = """
@Composable
fun NFromFile(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(NFromFile)<Open()>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Open(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
NFromFile(%composer, %changed or 0b0001)
}
}
@Composable
fun NFromInference(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(NFromInference)<N()>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
N(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
NFromInference(%composer, %changed or 0b0001)
}
}
""",
extra = """
import androidx.compose.runtime.*
@ComposableTargetMarker(description = "An N Composable")
@Target(
AnnotationTarget.FILE,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.TYPE,
AnnotationTarget.TYPE_PARAMETER,
)
annotation class NComposable()
@Composable @ComposableOpenTarget(0) fun Open() { }
@Composable @NComposable fun N() { }
""".trimIndent()
)
@Test
fun testCrossfileFileScope() = verifyComposeIrTransform(
source = """
import androidx.compose.runtime.*
@Composable
fun InferN() { N() }
""",
expectedTransformed = """
@Composable
@ComposableTarget(applier = "NComposable")
fun InferN(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(InferN)<N()>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
N(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
InferN(%composer, %changed or 0b0001)
}
}
""",
extra = """
@file:NComposable
import androidx.compose.runtime.*
@ComposableTargetMarker(description = "An N Composable")
@Target(
AnnotationTarget.FILE,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.TYPE,
AnnotationTarget.TYPE_PARAMETER,
)
annotation class NComposable()
@Composable fun N() { }
"""
)
@Test
fun testInferringTargetFromAncestorMethod() = verifyComposeIrTransform(
source = """
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposableTarget
import androidx.compose.runtime.ComposableOpenTarget
@Composable @ComposableOpenTarget(0) fun OpenTarget() { }
abstract class Base {
@Composable @ComposableTarget("N") abstract fun Compose()
}
class Valid : Base () {
@Composable override fun Compose() {
OpenTarget()
}
}
""",
expectedTransformed = """
@Composable
@ComposableOpenTarget(index = 0)
fun OpenTarget(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(OpenTarget):Test.kt")
if (%changed !== 0 || !%composer.skipping) {
} else {
%composer.skipToGroupEnd()
}
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
OpenTarget(%composer, %changed or 0b0001)
}
}
@StabilityInferred(parameters = 0)
abstract class Base {
@Composable
@ComposableTarget(applier = "N")
abstract fun Compose(%composer: Composer?, %changed: Int)
static val %stable: Int = 0
}
@StabilityInferred(parameters = 0)
class Valid : Base {
@Composable
override fun Compose(%composer: Composer?, %changed: Int) {
%composer = %composer.startRestartGroup(<>)
sourceInformation(%composer, "C(Compose)<OpenTa...>:Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
OpenTarget(%composer, 0)
} else {
%composer.skipToGroupEnd()
}
val tmp0_rcvr = <this>
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
tmp0_rcvr.Compose(%composer, %changed or 0b0001)
}
}
static val %stable: Int = 0
}
"""
)
private fun verify(source: String, expected: String) =
verifyComposeIrTransform(source, expected, baseDefinition)
private val baseDefinition = """
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposableTarget
import androidx.compose.runtime.ComposableOpenTarget
import androidx.compose.runtime.Applier
@Composable
@ComposableTarget("UI")
fun Layout() { }
@Composable
@ComposableTarget("UI")
fun Layout(content: @Composable @ComposableTarget("UI") () -> Unit) { }
@Composable
@ComposableTarget("UI")
inline fun InlineLayout(content: @Composable @ComposableTarget("UI") () -> Unit) { }
@Composable
fun Text(text: String) { Layout() }
@Composable
fun Row(content: @Composable () -> Unit) {
Layout(content)
}
@Composable
inline fun InlineRow(content: @Composable () -> Unit) {
InlineLayout(content)
}
@Composable
@ComposableTarget("Vector")
fun Vector() { }
@Composable
fun Circle() { Vector() }
@Composable
fun Square() { Vector() }
@Composable
@ComposableTarget("Vector")
fun Vector(content: @Composable @ComposableTarget("Vector") () -> Unit) { }
@Composable
fun Layer(content: @Composable () -> Unit) { Vector(content) }
@Composable
@ComposableTarget("UI")
fun Drawing(content: @Composable @ComposableTarget("Vector") () -> Unit) { }
@Composable
fun Wrapper(content: @Composable () -> Unit) { content() }
"""
}