Merge "Stop transforming non-composable fun interfaces in Compose compiler" into androidx-main
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 5d649af..f476366 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
@@ -1471,15 +1471,7 @@
             public final class TestKt {
               public final static Example(LA;)V
               public final static Usage()V
-              final static INNERCLASS TestKt%Usage%1 null null
-            }
-            final class TestKt%Usage%1 implements A {
-              <init>()V
-              public final compute(I)V
-              static <clinit>()V
-              public final static LTestKt%Usage%1; INSTANCE
-              OUTERCLASS TestKt Usage ()V
-              final static INNERCLASS TestKt%Usage%1 null null
+              private final static Usage%lambda%0(I)V
             }
         """
     )
@@ -1524,6 +1516,93 @@
         """
     )
 
+    @Test
+    fun testFunInterfacesInComposableCall() = checkApi(
+        """
+            fun interface MeasurePolicy {
+                fun compute(value: Int): Unit
+            }
+
+            @NonRestartableComposable
+            @Composable fun Text() {
+                Layout { value ->
+                    println(value)
+                }
+            }
+
+            @Composable inline fun Layout(policy: MeasurePolicy) {
+                policy.compute(0)
+            }
+        """,
+        """
+            public abstract interface MeasurePolicy {
+              public abstract compute(I)V
+            }
+            public final class TestKt {
+              public final static Text(Landroidx/compose/runtime/Composer;I)V
+              public final static Layout(LMeasurePolicy;Landroidx/compose/runtime/Composer;I)V
+              private final static Text%lambda%0(I)V
+            }
+        """,
+    )
+
+    @Test
+        fun testComposableFunInterfacesInVariance() = checkApi(
+        """
+           import androidx.compose.runtime.*
+
+            fun interface Consumer<T> {
+                @Composable fun consume(t: T)
+            }
+
+            class Repro<T : Any>() {
+                fun test(consumer: Consumer<in T>) {}
+            }
+
+            fun test() {
+                Repro<String>().test { string ->
+                    println(string)
+                }
+            }
+        """,
+        """
+            public abstract interface Consumer {
+              public abstract consume(Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)V
+            }
+            public final class Repro {
+              public <init>()V
+              public final test(LConsumer;)V
+              static <clinit>()V
+              public final static I %stable
+            }
+            public final class TestKt {
+              public final static test()V
+              final static INNERCLASS TestKt%test%1 null null
+            }
+            final class TestKt%test%1 implements Consumer {
+              <init>()V
+              public final consume(Ljava/lang/String;Landroidx/compose/runtime/Composer;I)V
+              public synthetic bridge consume(Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)V
+              static <clinit>()V
+              public final static LTestKt%test%1; INSTANCE
+              OUTERCLASS TestKt test ()V
+              final static INNERCLASS TestKt%test%1 null null
+              final static INNERCLASS TestKt%test%1%consume%1 null null
+            }
+            final class TestKt%test%1%consume%1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function2 {
+              <init>(LTestKt%test%1;Ljava/lang/String;I)V
+              public final invoke(Landroidx/compose/runtime/Composer;I)V
+              public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+              final synthetic LTestKt%test%1; %tmp0_rcvr
+              final synthetic Ljava/lang/String; %string
+              final synthetic I %%changed
+              OUTERCLASS TestKt%test%1 consume (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)V
+              final static INNERCLASS TestKt%test%1 null null
+              final static INNERCLASS TestKt%test%1%consume%1 null null
+            }
+        """
+    )
+
     val hashCodeEqualsAndToString = if (useFir) {
         """
               public static equals-impl(ILjava/lang/Object;)Z
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceExtensionReceiverTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceExtensionReceiverTransformTests.kt
deleted file mode 100644
index 29e745d..0000000
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceExtensionReceiverTransformTests.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2023 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
-
-class FunctionalInterfaceExtensionReceiverTransformTests(
-    useFir: Boolean
-) : AbstractControlFlowTransformTests(useFir) {
-    @Test
-    fun testFunctionalInterfaceWithExtensionReceiverTransformation() {
-        verifyComposeIrTransform(
-            source = """
-                import androidx.compose.runtime.*
-                fun interface TestContent {
-                    @Composable
-                    fun String.Content()
-                }
-                @Composable
-                fun Test(content: TestContent) {
-                    with(content) {
-                        "".Content()
-                    }
-                }
-
-                @Composable
-                fun CallTest() {
-                    Test { this.length }
-                }
-            """.trimIndent(),
-            expectedTransformed = """
-            interface TestContent {
-              @Composable
-              abstract fun String.Content(%composer: Composer?, %changed: Int)
-            }
-            @Composable
-            @ComposableInferredTarget(scheme = "[0[0]]")
-            fun Test(content: TestContent, %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) {
-                if (isTraceInProgress()) {
-                  traceEventStart(<>, %changed, -1, <>)
-                }
-                with(content) {
-                  %this%with.Content(%composer, 0b0110)
-                }
-                if (isTraceInProgress()) {
-                  traceEventEnd()
-                }
-              } else {
-                %composer.skipToGroupEnd()
-              }
-              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-                Test(content, %composer, updateChangedFlags(%changed or 0b0001))
-              }
-            }
-            @Composable
-            fun CallTest(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>)
-              sourceInformation(%composer, "C(CallTest)<Test>:Test.kt")
-              if (%changed !== 0 || !%composer.skipping) {
-                if (isTraceInProgress()) {
-                  traceEventStart(<>, %changed, -1, <>)
-                }
-                Test(class <no name provided> : TestContent {
-                  @Composable
-                  override fun Content(%this%Test: String, %composer: Composer?, %changed: Int) {
-                    %composer = %composer.startRestartGroup(<>)
-                    sourceInformation(%composer, "C(Content):Test.kt")
-                    val %dirty = %changed
-                    if (%changed and 0b1110 === 0) {
-                      %dirty = %dirty or if (%composer.changed(%this%Test)) 0b0100 else 0b0010
-                    }
-                    if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
-                      if (isTraceInProgress()) {
-                        traceEventStart(<>, %changed, -1, <>)
-                      }
-                      %this%Test.length
-                      if (isTraceInProgress()) {
-                        traceEventEnd()
-                      }
-                    } else {
-                      %composer.skipToGroupEnd()
-                    }
-                    val tmp0_rcvr = <this>
-                    %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-                      tmp0_rcvr.Content(%this%Test, %composer, updateChangedFlags(%changed or 0b0001))
-                    }
-                  }
-                }
-                <no name provided>(), %composer, 0)
-                if (isTraceInProgress()) {
-                  traceEventEnd()
-                }
-              } else {
-                %composer.skipToGroupEnd()
-              }
-              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-                CallTest(%composer, updateChangedFlags(%changed or 0b0001))
-              }
-            }
-            """.trimIndent()
-        )
-    }
-}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt
new file mode 100644
index 0000000..606cc70
--- /dev/null
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionalInterfaceTransformTests.kt
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2023 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
+
+class FunctionalInterfaceTransformTests(
+    useFir: Boolean
+) : AbstractControlFlowTransformTests(useFir) {
+    @Test
+    fun testFunctionalInterfaceWithExtensionReceiverTransformation() {
+        verifyComposeIrTransform(
+            source = """
+                import androidx.compose.runtime.*
+                fun interface TestContent {
+                    @Composable
+                    fun String.Content()
+                }
+                @Composable
+                fun Test(content: TestContent) {
+                    with(content) {
+                        "".Content()
+                    }
+                }
+
+                @Composable
+                fun CallTest() {
+                    Test { this.length }
+                }
+            """.trimIndent(),
+            expectedTransformed = """
+            interface TestContent {
+              @Composable
+              abstract fun String.Content(%composer: Composer?, %changed: Int)
+            }
+            @Composable
+            @ComposableInferredTarget(scheme = "[0[0]]")
+            fun Test(content: TestContent, %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) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                with(content) {
+                  %this%with.Content(%composer, 0b0110)
+                }
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(content, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+            @Composable
+            fun CallTest(%composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(CallTest)<Test>:Test.kt")
+              if (%changed !== 0 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Test(class <no name provided> : TestContent {
+                  @Composable
+                  override fun Content(%this%Test: String, %composer: Composer?, %changed: Int) {
+                    %composer = %composer.startRestartGroup(<>)
+                    sourceInformation(%composer, "C(Content):Test.kt")
+                    val %dirty = %changed
+                    if (%changed and 0b1110 === 0) {
+                      %dirty = %dirty or if (%composer.changed(%this%Test)) 0b0100 else 0b0010
+                    }
+                    if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                      if (isTraceInProgress()) {
+                        traceEventStart(<>, %changed, -1, <>)
+                      }
+                      %this%Test.length
+                      if (isTraceInProgress()) {
+                        traceEventEnd()
+                      }
+                    } else {
+                      %composer.skipToGroupEnd()
+                    }
+                    val tmp0_rcvr = <this>
+                    %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                      tmp0_rcvr.Content(%this%Test, %composer, updateChangedFlags(%changed or 0b0001))
+                    }
+                  }
+                }
+                <no name provided>(), %composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                CallTest(%composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFunInterfaces() = verifyComposeIrTransform(
+        """
+            import androidx.compose.runtime.*
+
+            fun interface A {
+                fun compute(value: Int): Unit
+            }
+
+            @Composable
+            fun Example(a: A) {
+                Example { it -> a.compute(it) }
+            }
+        """,
+        """
+            interface A {
+              abstract fun compute(value: Int)
+            }
+            @Composable
+            fun Example(a: A, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<Exampl...>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Example(A { it: Int ->
+                  a.compute(it)
+                }, %composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Example(a, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testComposableFunInterfaces() = verifyComposeIrTransform(
+        """
+            import androidx.compose.runtime.*
+
+            fun interface A {
+                @Composable fun compute(value: Int): Unit
+            }
+            fun Example(a: A) {
+                Example { it -> a.compute(it) }
+            }
+        """,
+        """
+            interface A {
+              @Composable
+              abstract fun compute(value: Int, %composer: Composer?, %changed: Int)
+            }
+            fun Example(a: A) {
+              Example(class <no name provided> : A {
+                @Composable
+                override fun compute(it: Int, %composer: Composer?, %changed: Int) {
+                  %composer = %composer.startRestartGroup(<>)
+                  sourceInformation(%composer, "C(compute)<comput...>:Test.kt")
+                  val %dirty = %changed
+                  if (%changed and 0b1110 === 0) {
+                    %dirty = %dirty or if (%composer.changed(it)) 0b0100 else 0b0010
+                  }
+                  if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    if (isTraceInProgress()) {
+                      traceEventStart(<>, %dirty, -1, <>)
+                    }
+                    a.compute(it, %composer, 0b1110 and %dirty)
+                    if (isTraceInProgress()) {
+                      traceEventEnd()
+                    }
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  val tmp0_rcvr = <this>
+                  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                    tmp0_rcvr.compute(it, %composer, updateChangedFlags(%changed or 0b0001))
+                  }
+                }
+              }
+              <no name provided>())
+            }
+        """
+    )
+
+    @Test
+    fun testComposableFunInterfacesInVariance() = verifyComposeIrTransform(
+        """
+            import androidx.compose.runtime.*
+
+            fun interface Consumer<T> {
+                @Composable fun consume(t: T)
+            }
+
+            class Repro<T : Any> {
+                fun test(consumer: Consumer<in T>) {}
+            }
+
+            fun test() {
+                Repro<String>().test { string ->
+                    println(string)
+                }
+            }
+        """,
+        """
+            interface Consumer<T>  {
+              @Composable
+              abstract fun consume(t: T, %composer: Composer?, %changed: Int)
+            }
+            @StabilityInferred(parameters = 0)
+            class Repro<T: Any>  {
+              fun test(consumer: Consumer<in T>) { }
+              static val %stable: Int = 0
+            }
+            fun test() {
+              Repro().test(class <no name provided> : Consumer<Any?> {
+                @Composable
+                override fun consume(string: String, %composer: Composer?, %changed: Int) {
+                  %composer = %composer.startRestartGroup(<>)
+                  sourceInformation(%composer, "C(consume):Test.kt")
+                  val %dirty = %changed
+                  if (%changed and 0b1110 === 0) {
+                    %dirty = %dirty or if (%composer.changed(string)) 0b0100 else 0b0010
+                  }
+                  if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    if (isTraceInProgress()) {
+                      traceEventStart(<>, %changed, -1, <>)
+                    }
+                    println(string)
+                    if (isTraceInProgress()) {
+                      traceEventEnd()
+                    }
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  val tmp0_rcvr = <this>
+                  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                    tmp0_rcvr.consume(string, %composer, updateChangedFlags(%changed or 0b0001))
+                  }
+                }
+              }
+              <no name provided>())
+            }
+        """
+    )
+
+    @Test
+    fun testCaptureStableFunInterface() = verifyComposeIrTransform(
+        """
+            import androidx.compose.runtime.*
+
+            fun interface Consumer {
+                fun consume(t: Int)
+            }
+
+            @Composable fun Test(int: Int) {
+                Example {
+                    println(int)
+                }
+            }
+
+            @Composable inline fun Example(consumer: Consumer) {
+            }
+        """,
+        """
+            interface Consumer {
+              abstract fun consume(t: Int)
+            }
+            @Composable
+            fun Test(int: Int, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<{>,<Exampl...>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(int)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %dirty, -1, <>)
+                }
+                Example(remember(int, {
+                  Consumer { it: Int ->
+                    println(int)
+                  }
+                }, %composer, 0b1110 and %dirty), %composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(int, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+            @Composable
+            fun Example(consumer: Consumer, %composer: Composer?, %changed: Int) {
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "CC(Example):Test.kt")
+              %composer.endReplaceableGroup()
+            }
+        """
+    )
+
+    @Test
+    fun testNoCaptureFunInterface() = verifyComposeIrTransform(
+        """
+            import androidx.compose.runtime.*
+
+            fun interface Consumer {
+                fun consume(t: Int)
+            }
+
+            @Composable fun Test(int: Int) {
+                Example {
+                    println(it)
+                }
+            }
+
+            @Composable inline fun Example(consumer: Consumer) {
+            }
+        """,
+        """
+            interface Consumer {
+              abstract fun consume(t: Int)
+            }
+            @Composable
+            fun Test(int: Int, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Exampl...>:Test.kt")
+              if (%changed and 0b0001 !== 0 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Example(Consumer { it: Int ->
+                  println(it)
+                }, %composer, 0b0110)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(int, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+            @Composable
+            fun Example(consumer: Consumer, %composer: Composer?, %changed: Int) {
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "CC(Example):Test.kt")
+              %composer.endReplaceableGroup()
+            }
+        """
+    )
+}
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 d3f96e3..3817942 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
@@ -890,16 +890,13 @@
           }
           static val %stable: Int = 0
         }
-        val localBoxMeasurePolicy: MeasurePolicy = class <no name provided> : MeasurePolicy {
-          override fun measure(%this%MeasurePolicy: MeasureScope, <unused var>: List<Measurable>, constraints: Constraints): MeasureResult {
-            return %this%MeasurePolicy.layout(
-              width = constraints.minWidth,
-              height = constraints.minHeight
-            ) {
-            }
+        val localBoxMeasurePolicy: MeasurePolicy = MeasurePolicy { <unused var>: List<Measurable>, constraints: Constraints ->
+          %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) {
@@ -983,12 +980,9 @@
               sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
               Unit
               sourceInformationMarkerEnd(%composer)
-            }, null, class <no name provided> : MeasurePolicy {
-              override fun measure(%this%Layout: MeasureScope, <unused var>: List<Measurable>, <unused var>: Constraints): MeasureResult {
-                ${if (!useFir) "return " else ""}error("")
-              }
-            }
-            <no name provided>(), %composer, 0, 0b0010)
+            }, null, MeasurePolicy { <unused var>: List<Measurable>, <unused var>: Constraints ->
+              error("")
+            }, %composer, 0b000110000000, 0b0010)
             if (isTraceInProgress()) {
               traceEventEnd()
             }
@@ -1012,12 +1006,9 @@
             if (isTraceInProgress()) {
               traceEventStart(<>, %dirty, -1, <>)
             }
-            Layout(content, null, class <no name provided> : MeasurePolicy {
-              override fun measure(%this%Layout: MeasureScope, <unused var>: List<Measurable>, <unused var>: Constraints): MeasureResult {
-                ${if (!useFir) "return " else ""}error("")
-              }
-            }
-            <no name provided>(), %composer, 0b1110 and %dirty, 0b0010)
+            Layout(content, null, MeasurePolicy { <unused var>: List<Measurable>, <unused var>: Constraints ->
+              error("")
+            }, %composer, 0b000110000000 or 0b1110 and %dirty, 0b0010)
             if (isTraceInProgress()) {
               traceEventEnd()
             }
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 e1cd194b..066e0d3 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
@@ -70,6 +70,7 @@
 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.IrTypeOperatorCall
 import org.jetbrains.kotlin.ir.expressions.IrVararg
 import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrBranchImpl
@@ -886,7 +887,8 @@
                     else -> false
                 }
             }
-            is IrFunctionExpression ->
+            is IrFunctionExpression,
+            is IrTypeOperatorCall ->
                 context.irTrace[ComposeWritableSlices.IS_STATIC_FUNCTION_EXPRESSION, this] ?: false
             is IrGetField ->
                 // K2 sometimes produces `IrGetField` for reads from constant properties
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunInterfaceLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunInterfaceLowering.kt
index cb8f689..9bacca8 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunInterfaceLowering.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunInterfaceLowering.kt
@@ -16,15 +16,20 @@
 
 package androidx.compose.compiler.plugins.kotlin.lower
 
+import androidx.compose.compiler.plugins.kotlin.hasComposableAnnotation
 import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
+import org.jetbrains.kotlin.descriptors.Modality
 import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
 import org.jetbrains.kotlin.ir.expressions.IrExpression
 import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
-import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperator.IMPLICIT_CAST
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperator.SAM_CONVERSION
 import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
+import org.jetbrains.kotlin.ir.types.IrType
 import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
 import org.jetbrains.kotlin.ir.types.classOrNull
+import org.jetbrains.kotlin.ir.util.functions
 import org.jetbrains.kotlin.ir.util.isLambda
 import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
 import org.jetbrains.kotlin.platform.jvm.isJvm
@@ -40,32 +45,10 @@
         }
     }
 
-    private fun isFunInterfaceConversion(expression: IrTypeOperatorCall): Boolean {
-        val argument = expression.argument
-        val operator = expression.operator
-        val type = expression.typeOperand
-        val functionClass = type.classOrNull
-        return operator == IrTypeOperator.SAM_CONVERSION &&
-            argument is IrFunctionExpression &&
-            argument.origin.isLambda &&
-            functionClass != null &&
-            functionClass.owner.isFun
-        // IMPORTANT(b/178663739):
-        // We are transforming not just SAM conversions for composable fun interfaces, but ALL
-        // fun interfaces temporarily until KT-44622 gets fixed in the version of kotlin we
-        // are using, which should be in 1.4.30.
-        // Once it does, we should either add the below additional condition to this predicate,
-        // or, if possible, remove this lowering all together if kotlin's lowering works for
-        // composable fun interfaces as well.
-        //
-        // functionClass.functions.single {
-        //    it.owner.modality == Modality.ABSTRACT
-        // }.owner.annotations.hasAnnotation(ComposeFqNames.Composable)
-    }
-
     override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
-        if (isFunInterfaceConversion(expression)) {
-            val argument = expression.argument.transform(this, null) as IrFunctionExpression
+        val functionExpr = expression.findSamFunctionExpr()
+        if (functionExpr != null && expression.typeOperand.isComposableFunInterface()) {
+            val argument = functionExpr.transform(this, null) as IrFunctionExpression
             val superType = expression.typeOperand
             val superClass = superType.classOrNull ?: error("Expected non-null class")
             return FunctionReferenceBuilder(
@@ -81,3 +64,38 @@
         return super.visitTypeOperator(expression)
     }
 }
+
+private fun IrType.isComposableFunInterface(): Boolean =
+    classOrNull
+        ?.functions
+        ?.single { it.owner.modality == Modality.ABSTRACT }
+        ?.owner
+        ?.hasComposableAnnotation() == true
+
+internal fun IrTypeOperatorCall.findSamFunctionExpr(): IrFunctionExpression? {
+    val argument = argument
+    val operator = operator
+    val type = typeOperand
+    val functionClass = type.classOrNull
+
+    val isFunInterfaceConversion = operator == SAM_CONVERSION &&
+        functionClass != null &&
+        functionClass.owner.isFun
+
+    return if (isFunInterfaceConversion) {
+        // if you modify this logic, make sure to update wrapping of type operators
+        // in ComposerLambdaMemoization.kt
+        when {
+            argument is IrFunctionExpression && argument.origin.isLambda -> argument
+            // some expressions are wrapped with additional implicit cast operator
+            // unwrapping that allows to avoid SAM conversion that capture FunctionN and box params.
+            argument is IrTypeOperatorCall && argument.operator == IMPLICIT_CAST -> {
+                val functionExpr = argument.argument
+                functionExpr as? IrFunctionExpression
+            }
+            else -> null
+        }
+    } else {
+        null
+    }
+}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index 3a8ba5a..166307c 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -69,11 +69,14 @@
 import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
 import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
 import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
 import org.jetbrains.kotlin.ir.expressions.IrValueAccessExpression
 import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
+import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
 import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
 import org.jetbrains.kotlin.ir.symbols.IrSymbol
@@ -509,8 +512,70 @@
         return result
     }
 
+    override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
+        // SAM conversions are handled by Kotlin compiler
+        // We only need to make sure that remember is handled correctly around type operator
+        if (
+            expression.operator != IrTypeOperator.SAM_CONVERSION ||
+                currentFunctionContext?.canRemember != true
+        ) {
+            return super.visitTypeOperator(expression)
+        }
+
+        // Unwrap function from type operator
+        val originalFunctionExpression =
+            expression.findSamFunctionExpr() ?: return super.visitTypeOperator(expression)
+
+        // Record capture variables for this scope
+        val collector = CaptureCollector()
+        startCollector(collector)
+        // Handle inside of the function expression
+        val result = super.visitFunctionExpression(originalFunctionExpression)
+        stopCollector(collector)
+
+        // If the ancestor converted this then return
+        val newFunctionExpression = result as? IrFunctionExpression ?: return result
+
+        // Construct new type operator call to wrap remember around.
+        val newArgument = when (val argument = expression.argument) {
+            is IrFunctionExpression -> newFunctionExpression
+            is IrTypeOperatorCall -> {
+                require(
+                    argument.operator == IrTypeOperator.IMPLICIT_CAST &&
+                        argument.argument == originalFunctionExpression
+                ) {
+                    "Only implicit cast is supported inside SAM conversion"
+                }
+                IrTypeOperatorCallImpl(
+                    argument.startOffset,
+                    argument.endOffset,
+                    argument.type,
+                    argument.operator,
+                    argument.typeOperand,
+                    newFunctionExpression
+                )
+            }
+            else -> error("Unknown ")
+        }
+
+        val expressionToRemember =
+            IrTypeOperatorCallImpl(
+                expression.startOffset,
+                expression.endOffset,
+                expression.type,
+                IrTypeOperator.SAM_CONVERSION,
+                expression.typeOperand,
+                newArgument
+            )
+        return rememberExpression(
+            currentFunctionContext!!,
+            expressionToRemember,
+            collector.captures.toList()
+        )
+    }
+
     private fun visitNonComposableFunctionExpression(
-        expression: IrFunctionExpression
+        expression: IrFunctionExpression,
     ): IrExpression {
         val functionContext = currentFunctionContext
             ?: return super.visitFunctionExpression(expression)
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 6103318..04c7f2c 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
@@ -616,10 +616,12 @@
             IrTypeOperator.NOT_INSTANCEOF -> {
                 expression.argument.print()
             }
-            IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.SAFE_CAST -> {
+            IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST, IrTypeOperator.IMPLICIT_CAST -> {
                 expression.argument.print()
             }
             IrTypeOperator.SAM_CONVERSION -> {
+                print(expression.type.renderSrc())
+                print(" ")
                 expression.argument.print()
             }
             IrTypeOperator.IMPLICIT_NOTNULL -> {
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
index 62a25cc..8f8615b 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -3895,6 +3895,35 @@
         assertEquals(1, consumer.invokeCount)
     }
 
+    fun interface TestFunInterface {
+        fun compute(value: Int)
+    }
+
+    @Composable fun TestMemoizedFun(compute: TestFunInterface) {
+        val oldCompute = remember { compute }
+        assertEquals(oldCompute, compute)
+    }
+
+    @Test
+    fun funInterface_isMemoized() = compositionTest {
+        var recomposeTrigger by mutableStateOf(0)
+        val capture = 0
+        compose {
+            use(recomposeTrigger)
+            TestMemoizedFun {
+                // no captures
+                use(it)
+            }
+            TestMemoizedFun {
+                // stable captures
+                use(capture)
+            }
+        }
+
+        recomposeTrigger++
+        advance()
+    }
+
     private inline fun CoroutineScope.withGlobalSnapshotManager(block: CoroutineScope.() -> Unit) {
         val channel = Channel<Unit>(Channel.CONFLATED)
         val job = launch {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index 3a416f3..93604d6 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -2952,6 +2952,7 @@
     @Test
     fun instancesKeepDelegates() {
         var color by mutableStateOf(Color.Red)
+        var size by mutableStateOf(30)
         var m: Measurable? = null
         val layoutCaptureModifier = object : LayoutModifier {
             override fun MeasureScope.measure(
@@ -2960,15 +2961,22 @@
             ): MeasureResult {
                 m = measurable
                 val p = measurable.measure(constraints)
-                drawLatch.countDown()
                 return layout(p.width, p.height) {
                     p.place(0, 0)
                 }
             }
         }
+        val drawCaptureModifier = object : DrawModifier {
+            override fun ContentDrawScope.draw() {
+                drawLatch.countDown()
+            }
+        }
         activityTestRule.runOnUiThread {
             activity.setContent {
-                FixedSize(30, layoutCaptureModifier.background(color)) {}
+                FixedSize(
+                    size = size,
+                    modifier = layoutCaptureModifier.background(color).then(drawCaptureModifier)
+                ) {}
             }
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
@@ -2977,6 +2985,7 @@
 
         activityTestRule.runOnUiThread {
             m = null
+            size = 40
             color = Color.Blue
         }