Correctly transform @Composable getValue operators
Allows usage of @Composable annotation on getValue operator and marks generated getter for delegate as composable in IR to ensure it is correctly transformed later.
Fixes: 181718338
Test: Updated compiler tests
Change-Id: Ib40ff82e608811673f64d80d2accd5121e8a4b7c
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
index f70db8d..7aa9356 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
@@ -945,10 +945,10 @@
class StableDelegateProp {
var p1: StableDelegate = StableDelegate()
get() {
- return <this>.p1%delegate.getValue()
+ return <this>.p1%delegate.getValue(<this>, ::p1)
}
set(value) {
- return <this>.p1%delegate.setValue()
+ return <this>.p1%delegate.setValue(<this>, ::p1, <set-?>)
}
static val %stable: Int = 0
}
@@ -956,10 +956,10 @@
class UnstableDelegateProp {
var p1: UnstableDelegate = UnstableDelegate()
get() {
- return <this>.p1%delegate.getValue()
+ return <this>.p1%delegate.getValue(<this>, ::p1)
}
set(value) {
- return <this>.p1%delegate.setValue()
+ return <this>.p1%delegate.setValue(<this>, ::p1, <set-?>)
}
static val %stable: Int = 8
}
@@ -1166,10 +1166,10 @@
class StableDelegateProp {
var p1: StableDelegate = StableDelegate()
get() {
- return <this>.p1%delegate.getValue()
+ return <this>.p1%delegate.getValue(<this>, ::p1)
}
set(value) {
- return <this>.p1%delegate.setValue()
+ return <this>.p1%delegate.setValue(<this>, ::p1, <set-?>)
}
static val %stable: Int = 0
}
@@ -1177,10 +1177,10 @@
class UnstableDelegateProp {
var p1: UnstableDelegate = UnstableDelegate()
get() {
- return <this>.p1%delegate.getValue()
+ return <this>.p1%delegate.getValue(<this>, ::p1)
}
set(value) {
- return <this>.p1%delegate.setValue()
+ return <this>.p1%delegate.setValue(<this>, ::p1, <set-?>)
}
static val %stable: Int = UnstableDelegate.%stable
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
index 8e53da5..bbe9866 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
@@ -1668,4 +1668,23 @@
}
"""
)
+
+ @Test
+ fun testComposableInlineFieldDelegate_noPropertyRefInit() = validateBytecode(
+ """
+ import kotlin.reflect.KProperty
+
+ class FooInline
+
+ @Composable
+ inline operator fun FooInline.getValue(thisRef: Any?, property: KProperty<*>) = 0
+
+ @Composable fun Test(foo: FooInline): Int {
+ val value by foo
+ return value
+ }
+ """,
+ ) {
+ assertFalse(it.contains("INVOKESTATIC kotlin/jvm/internal/Reflection.property0 (Lkotlin/jvm/internal/PropertyReference0;)Lkotlin/reflect/KProperty0;"))
+ }
}
\ No newline at end of file
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
index 1dc65de..e3ca786 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
@@ -88,7 +88,7 @@
if (isTraceInProgress()) {
traceEventStart(<>, %changed, -1, <>)
}
- bar
+ <get-bar>(%composer, 0)
if (isTraceInProgress()) {
traceEventEnd()
}
@@ -680,4 +680,287 @@
""".trimIndent()
)
}
+
+ @Test
+ fun testDelegateCall() {
+ composerParam(
+ """
+ import kotlin.reflect.KProperty
+
+ class Foo
+ @Composable
+ operator fun Foo.getValue(thisObj: Any?, property: KProperty<*>): Foo = this
+
+ class FooDelegate {
+ @Composable
+ operator fun getValue(thisObj: Any?, property: KProperty<*>): FooDelegate = this
+ }
+
+ class Bar {
+ val foo by Foo()
+ }
+
+ @Composable
+ fun test() {
+ val foo by Foo()
+ val fooDelegate by FooDelegate()
+ val bar = Bar()
+ println(foo)
+ println(fooDelegate)
+ println(bar.foo)
+ }
+ """,
+ """
+ @StabilityInferred(parameters = 0)
+ class Foo {
+ static val %stable: Int = 0
+ }
+ @Composable
+ fun Foo.getValue(thisObj: Any?, property: KProperty<*>, %composer: Composer?, %changed: Int): Foo {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(getValue)P(1):Test.kt#2487m")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val tmp0 = <this>
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ %composer.endReplaceableGroup()
+ return tmp0
+ }
+ @StabilityInferred(parameters = 0)
+ class FooDelegate {
+ @Composable
+ fun getValue(thisObj: Any?, property: KProperty<*>, %composer: Composer?, %changed: Int): FooDelegate {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(getValue)P(1):Test.kt#2487m")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val tmp0 = <this>
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ %composer.endReplaceableGroup()
+ return tmp0
+ }
+ static val %stable: Int = 0
+ }
+ @StabilityInferred(parameters = 0)
+ class Bar {
+ val foo: Foo = Foo()
+ @Composable @JvmName(name = "getFoo")
+ get() {
+ sourceInformationMarkerStart(%composer, <>, "C<Foo()>:Test.kt#2487m")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val tmp0 = <this>.foo%delegate.getValue(<this>, ::foo, %composer, 0b001000000000 or 0b01110000 and %changed shl 0b0011)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ static val %stable: Int = 0
+ }
+ @Composable
+ fun test(%composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(test)*<foo>,<fooDel...>,<foo>:Test.kt#2487m")
+ if (%changed !== 0 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val foo by {
+ val foo%delegate = Foo()
+ @Composable
+ get(%composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "C<Foo()>:Test.kt#2487m")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val tmp0 = foo%delegate.getValue(null, ::foo%delegate, %composer, 0b00110000)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ }
+ val fooDelegate by {
+ val fooDelegate%delegate = FooDelegate()
+ @Composable
+ get(%composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "C<FooDel...>:Test.kt#2487m")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val tmp0 = fooDelegate%delegate.getValue(null, ::fooDelegate%delegate, %composer, 0b0110)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ }
+ val bar = Bar()
+ println(<get-foo>(%composer, 0))
+ println(<get-fooDelegate>(%composer, 0))
+ println(bar.<get-foo>(%composer, 0))
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ test(%composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ """,
+ )
+ }
+
+ @Test
+ fun testUnstableDelegateCall() = composerParam(
+ """
+ import kotlin.reflect.KProperty
+
+ class Foo {
+ var unstableField: Int = 0
+ }
+
+ @Composable
+ inline operator fun Foo.getValue(thisObj: Any?, property: KProperty<*>): Foo = this
+
+ @Composable
+ fun test() {
+ val foo by Foo()
+ println(foo)
+ }
+ """,
+ """
+ @StabilityInferred(parameters = 0)
+ class Foo {
+ var unstableField: Int = 0
+ static val %stable: Int = 8
+ }
+ @Composable
+ fun Foo.getValue(thisObj: Any?, property: KProperty<*>, %composer: Composer?, %changed: Int): Foo {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "CC(getValue)P(1):Test.kt#2487m")
+ val tmp0 = <this>
+ %composer.endReplaceableGroup()
+ return tmp0
+ }
+ @Composable
+ fun test(%composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(test)*<foo>:Test.kt#2487m")
+ if (%changed !== 0 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val foo by {
+ val foo%delegate = Foo()
+ @Composable
+ get(%composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "C<Foo()>:Test.kt#2487m")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val tmp0 = foo%delegate.getValue(null, ::foo%delegate, %composer, 0b00111000)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ }
+ println(<get-foo>(%composer, 0))
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ test(%composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ """
+ )
+
+ @Test
+ fun testStableDelegateCall() = composerParam(
+ """
+ import kotlin.reflect.KProperty
+
+ class Foo
+
+ @Composable
+ inline operator fun Foo.getValue(thisObj: Any?, property: KProperty<*>): Foo = this
+
+ @Composable
+ fun test(foo: Foo) {
+ val delegated by foo
+ used(delegated)
+ }
+ """,
+ """
+ @StabilityInferred(parameters = 0)
+ class Foo {
+ static val %stable: Int = 0
+ }
+ @Composable
+ fun Foo.getValue(thisObj: Any?, property: KProperty<*>, %composer: Composer?, %changed: Int): Foo {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "CC(getValue)P(1):Test.kt#2487m")
+ val tmp0 = <this>
+ %composer.endReplaceableGroup()
+ return tmp0
+ }
+ @Composable
+ fun test(foo: Foo, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(test)<delega...>:Test.kt#2487m")
+ val %dirty = %changed
+ if (%changed and 0b1110 === 0) {
+ %dirty = %dirty or if (%composer.changed(foo)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %dirty, -1, <>)
+ }
+ val delegated by {
+ val delegated%delegate = foo
+ @Composable
+ get(%composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "C<foo>:Test.kt#2487m")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ val tmp0 = delegated%delegate.getValue(null, ::delegated%delegate, %composer, 0b00110000 or 0b1110 and %dirty)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ }
+ used(<get-delegated>(%composer, 0))
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ test(foo, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+ }
+ """
+ )
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index 82dcf95..48ceddc 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -5488,7 +5488,7 @@
if (isTraceInProgress()) {
traceEventStart(<>, %changed, -1, "Test (Test.kt:16)")
}
- val c = current
+ val c = <get-current>(%composer, 0)
val cl = calculateSometing(%composer, 0)
Layout({ %composer: Composer?, %changed: Int ->
sourceInformationMarkerStart(%composer, <>, "C<Text("...>:Test.kt")
@@ -5579,7 +5579,7 @@
if (isTraceInProgress()) {
traceEventStart(<>, %changed, -1, "HolderHolder.<get-current> (Test.kt:16)")
}
- val tmp0 = _currentHolder.current
+ val tmp0 = _currentHolder.<get-current>(%composer, 0)
if (isTraceInProgress()) {
traceEventEnd()
}
@@ -5611,7 +5611,7 @@
if (isTraceInProgress()) {
traceEventStart(<>, %changed, -1, "Test (Test.kt:28)")
}
- val c = holderHolder.current
+ val c = holderHolder.<get-current>(%composer, 0b0110)
val cl = calculateSomething(%composer, 0)
Layout({ %composer: Composer?, %changed: Int ->
sourceInformationMarkerStart(%composer, <>, "C<Text("...>:Test.kt")
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
index edd8cf3..80eb3c3 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
@@ -3405,7 +3405,7 @@
wontChange = 123
}
if (%default and 0b0010 !== 0) {
- mightChange = LocalColor.current
+ mightChange = LocalColor.<get-current>(%composer, 0b0110)
%dirty = %dirty and 0b01110000.inv()
}
} else {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
index 87e5fde..f2fa489 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
@@ -373,7 +373,14 @@
if (isTraceInProgress()) {
traceEventStart(<>, %changed, -1, <>)
}
- <<LOCALDELPROP>>
+ val x by {
+ val x%delegate = mutableStateOf(
+ value = 123
+ )
+ get() {
+ return x%delegate.getValue(null, ::x%delegate)
+ }
+ }
B(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
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 59d3c97..2cd3d0b 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
@@ -501,7 +501,7 @@
if (isTraceInProgress()) {
traceEventStart(<>, %changed, -1, <>)
}
- val bar = compositionLocalBar.current
+ val bar = compositionLocalBar.<get-current>(%composer, 0b0110)
val foo = remember(bar, {
Foo()
}, %composer, 0)
@@ -542,7 +542,7 @@
if (isTraceInProgress()) {
traceEventStart(<>, %changed, -1, <>)
}
- val foo = remember(compositionLocalBar.current, {
+ val foo = remember(compositionLocalBar.<get-current>(%composer, 0b0110), {
Foo()
}, %composer, 0)
if (isTraceInProgress()) {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
index b7d9f1b..9731b2f 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
@@ -1264,7 +1264,12 @@
if (isTraceInProgress()) {
traceEventStart(<>, %dirty, -1, <>)
}
- <<LOCALDELPROP>>
+ val updatedContent by {
+ val updatedContent%delegate = rememberUpdatedState(content, %composer, 0b1110 and %dirty)
+ get() {
+ return updatedContent%delegate.getValue(null, ::updatedContent%delegate)
+ }
+ }
Defer(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
index 5dcbf04..9211731 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
@@ -1318,4 +1318,54 @@
}
}
""")
+
+ @Test
+ fun testComposableValueOperator() {
+ check(
+ """
+ import androidx.compose.runtime.Composable
+ import kotlin.reflect.KProperty
+
+ class Foo
+ class FooDelegate {
+ @Composable
+ operator fun getValue(thisObj: Any?, property: KProperty<*>) {}
+ @Composable
+ operator fun <!COMPOSE_INVALID_DELEGATE!>setValue<!>(thisObj: Any?, property: KProperty<*>, value: Any) {}
+ }
+ @Composable operator fun Foo.getValue(thisObj: Any?, property: KProperty<*>) {}
+ @Composable operator fun Foo.<!COMPOSE_INVALID_DELEGATE!>setValue<!>(thisObj: Any?, property: KProperty<*>, value: Any) {}
+
+ fun <!COMPOSABLE_EXPECTED!>nonComposable<!>() {
+ val fooValue = Foo()
+ val foo by fooValue
+ val fooDelegate by FooDelegate()
+ var mutableFoo by <!COMPOSE_INVALID_DELEGATE!>fooValue<!>
+ val bar = Bar()
+
+ println(<!COMPOSABLE_INVOCATION!>foo<!>)
+ println(<!COMPOSABLE_INVOCATION!>fooDelegate<!>)
+ println(bar.<!COMPOSABLE_INVOCATION!>foo<!>)
+
+ <!COMPOSABLE_INVOCATION!>mutableFoo<!> = Unit
+ }
+
+ @Composable
+ fun TestComposable() {
+ val fooValue = Foo()
+ val foo by fooValue
+ val fooDelegate by FooDelegate()
+ val bar = Bar()
+
+ println(foo)
+ println(fooDelegate)
+ println(bar.foo)
+ }
+
+ class Bar {
+ val foo by Foo()
+ }
+ """
+ )
+ }
}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
index 4b18ff3..848bf03 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
@@ -28,6 +28,7 @@
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.PropertyGetterDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
+import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.synthetic.FunctionInterfaceConstructorDescriptor
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
@@ -48,6 +49,7 @@
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.KtTryExpression
import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.getValueArgumentForExpression
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
@@ -131,11 +133,15 @@
reportOn: PsiElement,
context: CallCheckerContext
) {
- if (!resolvedCall.isComposableInvocation()) {
+ val bindingContext = context.trace.bindingContext
+ if (
+ !resolvedCall.isComposableDelegateReference(bindingContext) &&
+ !resolvedCall.isComposableInvocation()
+ ) {
checkInlineLambdaCall(resolvedCall, reportOn, context)
return
}
- val bindingContext = context.trace.bindingContext
+
var node: PsiElement? = reportOn
loop@while (node != null) {
when (node) {
@@ -224,6 +230,23 @@
// KtPropertyAccessor, the ONLY time we make it into this branch is when the
// call was done in the initializer of the property/variable.
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, node]
+
+ if (resolvedCall.isComposableDelegateOperator()) {
+ // The call is initializer for fields like `val foo by composableDelegate()`.
+ // Creating the property doesn't have any requirements from Compose side,
+ // we will recheck on the property call site instead.
+ if (
+ descriptor is VariableDescriptorWithAccessors &&
+ descriptor.isDelegated
+ ) {
+ if (descriptor.isVar) {
+ // setValue delegate is not allowed for now.
+ illegalComposableDelegate(context, reportOn)
+ }
+ return
+ }
+ }
+
if (
descriptor !is LocalVariableDescriptor &&
node.annotationEntries.hasComposableAnnotation(bindingContext)
@@ -313,6 +336,13 @@
context.trace.report(ComposeErrors.COMPOSABLE_FUNCTION_REFERENCE.on(refExpr))
}
+ private fun illegalComposableDelegate(
+ context: CallCheckerContext,
+ reportOn: PsiElement
+ ) {
+ context.trace.report(ComposeErrors.COMPOSE_INVALID_DELEGATE.on(reportOn))
+ }
+
override fun checkType(
expression: KtExpression,
expressionType: KotlinType,
@@ -416,6 +446,23 @@
}
}
+fun ResolvedCall<*>.isComposableDelegateReference(bindingContext: BindingContext): Boolean {
+ val descriptor = candidateDescriptor
+ if (descriptor is VariableDescriptorWithAccessors) {
+ val delegateInitCall = bindingContext[DELEGATED_PROPERTY_RESOLVED_CALL, descriptor.getter]
+ return delegateInitCall?.candidateDescriptor?.isMarkedAsComposable() == true
+ } else {
+ return false
+ }
+}
+
+fun ResolvedCall<*>.isComposableDelegateOperator(): Boolean {
+ val descriptor = candidateDescriptor
+ return descriptor is FunctionDescriptor &&
+ descriptor.isOperator &&
+ descriptor.name in OperatorNameConventions.DELEGATED_PROPERTY_OPERATORS
+}
+
fun ResolvedCall<*>.isComposableInvocation(): Boolean {
if (this is VariableAsFunctionResolvedCall) {
if (variableCall.candidateDescriptor.type.hasComposableAnnotation())
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt
index 6f2e72c..c0aad2e 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt
@@ -41,6 +41,7 @@
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.util.OperatorNameConventions
class ComposableDeclarationChecker : DeclarationChecker, StorageComponentContainerContributor {
override fun registerModuleComponents(
@@ -145,6 +146,18 @@
COMPOSABLE_FUN_MAIN.on(declaration.nameIdentifier ?: declaration)
)
}
+
+ if (
+ hasComposableAnnotation &&
+ descriptor.isOperator &&
+ descriptor.name == OperatorNameConventions.SET_VALUE
+ ) {
+ context.trace.report(
+ ComposeErrors.COMPOSE_INVALID_DELEGATE.on(
+ declaration.nameIdentifier ?: declaration
+ )
+ )
+ }
}
private fun checkType(
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrorMessages.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrorMessages.kt
index 0398542..b000674 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrorMessages.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrorMessages.kt
@@ -124,5 +124,9 @@
ComposeErrors.COMPOSE_APPLIER_DECLARATION_MISMATCH,
"The composition target of an override must match the ancestor target"
)
+ MAP.put(
+ ComposeErrors.COMPOSE_INVALID_DELEGATE,
+ "Composable setValue operator is not currently supported."
+ )
}
}
\ No newline at end of file
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrors.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrors.kt
index 3b3f96dd..2d7e47a 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrors.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrors.kt
@@ -154,6 +154,12 @@
Severity.WARNING
)
+ @JvmField
+ val COMPOSE_INVALID_DELEGATE =
+ DiagnosticFactory0.create<PsiElement>(
+ Severity.ERROR
+ )
+
init {
Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages(
ComposeErrors::class.java,
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/Stability.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/Stability.kt
index fd3dc96..1fabebc 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/Stability.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/Stability.kt
@@ -32,6 +32,7 @@
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
+import org.jetbrains.kotlin.ir.expressions.IrLocalDelegatedPropertyReference
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.IrDynamicType
@@ -438,6 +439,7 @@
stability
}
}
+ is IrLocalDelegatedPropertyReference -> Stability.Stable
// some default parameters and consts can be wrapped in composite
is IrComposite -> {
if (expr.statements.all { it is IrExpression && stabilityOf(it).knownStable() }) {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
index d3e97ae..667a9c9 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
@@ -67,6 +67,7 @@
import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
+import org.jetbrains.kotlin.ir.expressions.IrReturn
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
@@ -117,6 +118,7 @@
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.ir.util.primaryConstructor
+import org.jetbrains.kotlin.ir.util.statements
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.load.kotlin.computeJvmDescriptor
import org.jetbrains.kotlin.name.CallableId
@@ -140,12 +142,19 @@
context.referenceClass(ComposeClassIds.Composer)?.owner
?: error("Cannot find the Composer class in the classpath")
+ private val _composableIrClass =
+ context.referenceClass(ComposeClassIds.Composable)?.owner
+ ?: error("Cannot find the Composable annotation class in the classpath")
+
// this ensures that composer always references up-to-date composer class symbol
// otherwise, after remapping of symbols in DeepCopyTransformer, it results in duplicated
// references
protected val composerIrClass: IrClass
get() = symbolRemapper.getReferencedClass(_composerIrClass.symbol).owner
+ protected val composableIrClass: IrClass
+ get() = symbolRemapper.getReferencedClass(_composableIrClass.symbol).owner
+
fun referenceFunction(symbol: IrFunctionSymbol): IrFunctionSymbol {
return symbolRemapper.getReferencedFunction(symbol)
}
@@ -1087,6 +1096,20 @@
val stringKey = "$name$signature"
return stringKey.hashCode()
}
+
+ /*
+ * Delegated accessors are generated with IrReturn(IrCall(<delegated function>)) structure.
+ * To verify the delegated function is composable, this function is unpacking it and
+ * checks annotation on the symbol owner of the call.
+ */
+ fun IrFunction.isComposableDelegatedAccessor(): Boolean =
+ origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR &&
+ body?.let {
+ val returnStatement = it.statements.singleOrNull() as? IrReturn
+ val callStatement = returnStatement?.value as? IrCall
+ val target = callStatement?.symbol?.owner
+ target?.hasComposableAnnotation()
+ } == true
}
private val unsafeSymbolsRegex = "[ <>]".toRegex()
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 0dd6696..e4f2e8a3 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
@@ -813,6 +813,9 @@
if (!returnType.isUnit())
return false
+ if (isComposableDelegatedAccessor())
+ return false
+
// Do not insert an observe scope if the function hasn't been transformed by the
// ComposerParamTransformer and has a synthetic "composer param" as its last parameter
if (composerParam() == null) return false
@@ -847,7 +850,9 @@
val body = declaration.body!!
val hasExplicitGroups = declaration.hasExplicitGroups
- val elideGroups = hasExplicitGroups || declaration.hasReadOnlyAnnotation
+ val elideGroups = hasExplicitGroups ||
+ declaration.hasReadOnlyAnnotation ||
+ declaration.isComposableDelegatedAccessor()
val skipPreamble = mutableStatementContainer()
val bodyPreamble = mutableStatementContainer()
@@ -2860,13 +2865,14 @@
numChanged = changedParamCount(numRealValueParams, ownerFn.thisParamCount)
}
- require(
- numContextParams +
+ val expectedNumParams = numContextParams +
numRealValueParams +
- 1 + // composer param
- numChanged +
- numDefaults == numValueParams
- )
+ 1 + // composer param
+ numChanged +
+ numDefaults
+ require(numValueParams == expectedNumParams) {
+ "Expected $expectedNumParams params for ${ownerFn.name}, but got $numValueParams"
+ }
val composerIndex = numContextParams + numRealValueParams
val changedArgIndex = composerIndex + 1
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt
index 7548c42..006279c 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt
@@ -32,7 +32,9 @@
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFunction
+import org.jetbrains.kotlin.ir.declarations.IrLocalDelegatedProperty
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
+import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.declarations.copyAttributes
@@ -40,6 +42,7 @@
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
+import org.jetbrains.kotlin.ir.expressions.IrLocalDelegatedPropertyReference
import org.jetbrains.kotlin.ir.expressions.IrReturn
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.copyTypeArgumentsFrom
@@ -48,6 +51,7 @@
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
+import org.jetbrains.kotlin.ir.expressions.impl.IrLocalDelegatedPropertyReferenceImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.types.IrSimpleType
@@ -58,6 +62,7 @@
import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper
+import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.copyTo
import org.jetbrains.kotlin.ir.util.copyTypeParametersFrom
@@ -69,6 +74,7 @@
import org.jetbrains.kotlin.ir.util.isGetter
import org.jetbrains.kotlin.ir.util.isVararg
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
+import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.util.remapTypeParameters
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
@@ -138,8 +144,67 @@
override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement =
super.visitSimpleFunction(declaration.withComposerParamIfNeeded())
+ override fun visitLocalDelegatedPropertyReference(
+ expression: IrLocalDelegatedPropertyReference
+ ): IrExpression {
+ val transformedGetter = expression.getter.owner.withComposerParamIfNeeded()
+ return super.visitLocalDelegatedPropertyReference(
+ IrLocalDelegatedPropertyReferenceImpl(
+ expression.startOffset,
+ expression.endOffset,
+ expression.type,
+ expression.symbol,
+ expression.delegate,
+ transformedGetter.symbol,
+ expression.setter,
+ expression.origin
+ )
+ )
+ }
+
+ override fun visitLocalDelegatedProperty(declaration: IrLocalDelegatedProperty): IrStatement {
+ if (declaration.getter.isComposableDelegatedAccessor()) {
+ declaration.getter.annotations += createComposableAnnotation()
+ }
+
+ if (declaration.setter?.isComposableDelegatedAccessor() == true) {
+ declaration.setter!!.annotations += createComposableAnnotation()
+ }
+
+ return super.visitLocalDelegatedProperty(declaration)
+ }
+
+ override fun visitProperty(declaration: IrProperty): IrStatement {
+ if (declaration.getter?.isComposableDelegatedAccessor() == true) {
+ declaration.getter!!.annotations += createComposableAnnotation()
+ }
+
+ if (declaration.setter?.isComposableDelegatedAccessor() == true) {
+ declaration.setter!!.annotations += createComposableAnnotation()
+ }
+
+ return super.visitProperty(declaration)
+ }
+
+ private fun createComposableAnnotation() =
+ IrConstructorCallImpl(
+ startOffset = SYNTHETIC_OFFSET,
+ endOffset = SYNTHETIC_OFFSET,
+ type = composableIrClass.defaultType,
+ symbol = composableIrClass.primaryConstructor!!.symbol,
+ typeArgumentsCount = 0,
+ constructorTypeArgumentsCount = 0,
+ valueArgumentsCount = 0
+ )
+
fun IrCall.withComposerParamIfNeeded(composerParam: IrValueParameter): IrCall {
val ownerFn = when {
+ symbol.owner.isComposableDelegatedAccessor() -> {
+ if (!symbol.owner.hasComposableAnnotation()) {
+ symbol.owner.annotations += createComposableAnnotation()
+ }
+ symbol.owner.withComposerParamIfNeeded()
+ }
symbol.owner.hasComposableAnnotation() ->
symbol.owner.withComposerParamIfNeeded()
isComposableLambdaInvoke() ->
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
index 4782eb0..01fc0be 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
@@ -17,6 +17,7 @@
package androidx.compose.compiler.plugins.kotlin.lower
import androidx.compose.compiler.plugins.kotlin.KtxNameConventions
+import androidx.compose.compiler.plugins.kotlin.hasComposableAnnotation
import java.util.Locale
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
@@ -107,11 +108,13 @@
import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.isObject
+import org.jetbrains.kotlin.ir.util.isSetter
import org.jetbrains.kotlin.ir.util.kotlinFqName
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.util.statements
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
+import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.utils.Printer
@@ -384,7 +387,12 @@
expression.getValueArgument(0)?.print()
print(opSymbol)
}
- // invoke
+ "getValue", "setValue" -> {
+ (expression.dispatchReceiver ?: expression.extensionReceiver)?.print()
+ print(".")
+ print(opSymbol)
+ expression.printArgumentList()
+ }
"invoke" -> {
(expression.dispatchReceiver ?: expression.extensionReceiver)?.print()
expression.printArgumentList()
@@ -414,7 +422,7 @@
print(" $opSymbol ")
expression.getValueArgument(1)?.print()
}
- "iterator", "hasNext", "next", "getValue", "setValue",
+ "iterator", "hasNext", "next",
"noWhenBranchMatchedException" -> {
(expression.dispatchReceiver ?: expression.extensionReceiver)?.print()
print(".")
@@ -456,7 +464,7 @@
val prop = (function as? IrSimpleFunction)?.correspondingPropertySymbol?.owner
- if (prop != null) {
+ if (prop != null && !function.hasComposableAnnotation()) {
val propName = prop.name.asString()
print(propName)
if (function == prop.setter) {
@@ -1283,13 +1291,37 @@
}
override fun visitLocalDelegatedProperty(declaration: IrLocalDelegatedProperty) {
- print("<<LOCALDELPROP>>")
+ declaration.printAnnotations(onePerLine = true)
+ print(if (declaration.isVar) "var " else "val ")
+ print(declaration.name)
+ print(" by ")
+ bracedBlock {
+ declaration.delegate.acceptVoid(this)
+ declaration.getter.scoped { it.printPropertyAccessor() }
+ declaration.setter?.scoped { it.printPropertyAccessor(isSetter = true) }
+ }
+ }
+
+ private fun IrFunction.printPropertyAccessor(isSetter: Boolean = this.isSetter) {
+ if (origin != IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) {
+ println()
+ printAnnotations()
+ println()
+ print(if (isSetter) "set" else "get")
+ print("(")
+ valueParameters.printJoin(", ")
+ print(") ")
+ bracedBlock {
+ body?.acceptVoid(this@IrSourcePrinterVisitor)
+ }
+ }
}
override fun visitLocalDelegatedPropertyReference(
expression: IrLocalDelegatedPropertyReference
) {
- print("<<LOCALDELPROPREF>>")
+ print("::")
+ print(expression.delegate.owner.name)
}
override fun visitLoop(loop: IrLoop) {
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
index 9c514dc..3f627b5 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -46,6 +46,7 @@
import androidx.compose.runtime.snapshots.Snapshot
import kotlin.coroutines.CoroutineContext
import kotlin.random.Random
+import kotlin.reflect.KProperty
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@@ -3714,6 +3715,23 @@
expectChanges()
revalidate()
}
+
+ @Test
+ fun composableDelegates() = compositionTest {
+ val local = compositionLocalOf { "Default" }
+ val delegatedLocal by local
+ compose {
+ Text(delegatedLocal)
+
+ CompositionLocalProvider(local provides "Scoped") {
+ Text(delegatedLocal)
+ }
+ }
+ validate {
+ Text("Default")
+ Text("Scoped")
+ }
+ }
}
class SomeUnstableClass(val a: Any = "abc")
@@ -3965,4 +3983,7 @@
@Composable
private inline fun InlineSubcomposition(
crossinline content: @Composable () -> Unit
-) = TestSubcomposition { content() }
\ No newline at end of file
+) = TestSubcomposition { content() }
+
+@Composable
+operator fun <T> CompositionLocal<T>.getValue(thisRef: Any?, property: KProperty<*>) = current
\ No newline at end of file