From ca9fb23cffab4519e5b26a2bfd6b3b1598b8a55f Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Wed, 7 Aug 2024 16:17:46 +0100 Subject: [PATCH] Disallow open @Composable functions with default params Old version of Compose compiler allowed open functions with default params without a proper wrapper around it. We need to add some feature detection for precompiled artifacts to make sure we handle those functions correctly before enabling it. Fixes: 357878245 Relnote: Disallow open @Composable functions with default params to fix binary compatibility issues. --- .../kotlin/ComposeBytecodeCodegenTest.kt | 34 ++- .../plugins/kotlin/ComposeCrossModuleTests.kt | 4 +- .../kotlin/DefaultParamTransformTests.kt | 94 ++++++-- .../ComposableDeclarationCheckerTests.kt | 4 +- ...tParamComposableLambda[useFir = false].txt | 149 ++++++++++++ ...ltParamComposableLambda[useFir = true].txt | 149 ++++++++++++ ...efaultParamOnInterface[useFir = false].txt | 219 ++++++++++++++++++ ...DefaultParamOnInterface[useFir = true].txt | 219 ++++++++++++++++++ ...rrideExtensionReceiver[useFir = false].txt | 131 +++++++++++ ...errideExtensionReceiver[useFir = true].txt | 131 +++++++++++ .../test/DefaultParamCompositionTests.kt | 18 +- .../kotlin/k1/ComposableDeclarationChecker.kt | 35 ++- .../plugins/kotlin/k1/ComposeErrorMessages.kt | 4 + .../plugins/kotlin/k1/ComposeErrors.kt | 6 + .../kotlin/k2/ComposableFunctionChecker.kt | 14 ++ .../plugins/kotlin/k2/ComposeErrorMessages.kt | 5 + .../plugins/kotlin/k2/ComposeErrors.kt | 2 + .../lower/ComposableDefaultParamLowering.kt | 8 +- 18 files changed, 1179 insertions(+), 47 deletions(-) create mode 100644 plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamComposableLambda[useFir = false].txt create mode 100644 plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamComposableLambda[useFir = true].txt create mode 100644 plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOnInterface[useFir = false].txt create mode 100644 plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOnInterface[useFir = true].txt create mode 100644 plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOverrideExtensionReceiver[useFir = false].txt create mode 100644 plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOverrideExtensionReceiver[useFir = true].txt diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt index 958deecf27834..f2ae00f2ee0a8 100644 --- a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt @@ -18,6 +18,7 @@ package androidx.compose.compiler.plugins.kotlin import org.junit.Assume.assumeFalse import org.junit.Test +import kotlin.test.Ignore import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -623,25 +624,50 @@ class ComposeBytecodeCodegenTest(useFir: Boolean) : AbstractCodegenTest(useFir) ) @Test - fun testDefaultParametersInVirtualFunctions() = validateBytecode( + fun testDefaultParametersInAbstractFunctions() = validateBytecode( """ import androidx.compose.runtime.* interface Test { @Composable fun foo(param: Int = remember { 0 }) - @Composable fun bar(param: Int = remember { 0 }): Int = param } class TestImpl : Test { @Composable override fun foo(param: Int) {} + } + + @Composable fun CallWithDefaults(test: Test) { + test.foo() + test.foo(0) + } + """, + validate = { + assertTrue( + it.contains( + "INVOKESTATIC test/Test%ComposeDefaultImpls.foo%default (ILtest/Test;Landroidx/compose/runtime/Composer;II)V" + ), + "default static functions should be generated in ComposeDefaultsImpl class" + ) + } + ) + + @Ignore("b/357878245") + @Test + fun testDefaultParametersInOpenFunctions() = validateBytecode( + """ + import androidx.compose.runtime.* + + interface Test { + @Composable fun bar(param: Int = remember { 0 }): Int = param + } + + class TestImpl : Test { @Composable override fun bar(param: Int): Int { return super.bar(param) } } @Composable fun CallWithDefaults(test: Test) { - test.foo() - test.foo(0) test.bar() test.bar(0) } diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeCrossModuleTests.kt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeCrossModuleTests.kt index f11b05c48aadd..722feb9dc4e20 100644 --- a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeCrossModuleTests.kt +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeCrossModuleTests.kt @@ -25,6 +25,7 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import org.junit.runners.Parameterized +import kotlin.test.Ignore @RunWith(Parameterized::class) class ComposeCrossModuleTests(useFir: Boolean) : AbstractCodegenTest(useFir) { @@ -1224,8 +1225,9 @@ class ComposeCrossModuleTests(useFir: Boolean) : AbstractCodegenTest(useFir) { ) } + @Ignore("b/357878245") @Test - fun defaultParametersInFakeOverrideVirtualComposableFunctions() { + fun defaultParametersInFakeOverrideOpenComposableFunctions() { compile( mapOf( "Base" to mapOf( diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt index 4ce52b2e45e6a..05c77e0caae0f 100644 --- a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt @@ -17,6 +17,7 @@ package androidx.compose.compiler.plugins.kotlin import org.intellij.lang.annotations.Language +import org.junit.Ignore import org.junit.Test class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useFir) { @@ -405,16 +406,49 @@ class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useF ) @Test - fun testDefaultParamOnInterface() = defaultParams( + fun testAbstractDefaultParamOnInterface() = defaultParams( unchecked = """""", checked = """ interface Test { @Composable fun foo(param: Int = remember { 0 }) - @Composable fun bar(param: Int = remember { 0 }): Int = param } interface TestBetween : Test { @Composable fun betweenFoo(param: Int = remember { 0 }) + } + + class TestImpl : TestBetween { + @Composable override fun foo(param: Int) {} + @Composable override fun betweenFoo(param: Int) {} + } + + @Composable fun CallWithDefaults(test: Test, testBetween: TestBetween, testImpl: TestImpl) { + test.foo() + test.foo(0) + + testBetween.foo() + testBetween.foo(0) + testBetween.betweenFoo() + testBetween.betweenFoo(0) + + testImpl.foo() + testImpl.foo(0) + testImpl.betweenFoo() + testImpl.betweenFoo(0) + } + """ + ) + + @Ignore("b/357878245") + @Test + fun testOpenDefaultParamOnInterface() = defaultParams( + unchecked = """""", + checked = """ + interface Test { + @Composable fun bar(param: Int = remember { 0 }): Int = param + } + + interface TestBetween : Test { @Composable fun betweenFooDefault(param: Int = remember { 0 }) {} @Composable fun betweenBar(param: Int = remember { 0 }): Int = param } @@ -428,28 +462,18 @@ class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useF } @Composable fun CallWithDefaults(test: Test, testBetween: TestBetween, testImpl: TestImpl) { - test.foo() - test.foo(0) test.bar() test.bar(0) - testBetween.foo() - testBetween.foo(0) testBetween.bar() testBetween.bar(0) - testBetween.betweenFoo() - testBetween.betweenFoo(0) testBetween.betweenFooDefault() testBetween.betweenFooDefault(0) testBetween.betweenBar() testBetween.betweenBar(0) - testImpl.foo() - testImpl.foo(0) testImpl.bar() testImpl.bar(0) - testImpl.betweenFoo() - testImpl.betweenFoo(0) testImpl.betweenFooDefault() testImpl.betweenFooDefault(0) testImpl.betweenBar() @@ -458,6 +482,7 @@ class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useF """ ) + @Ignore("b/357878245") @Test fun testDefaultParamOverrideOpenFunction() = defaultParams( unchecked = """""", @@ -484,23 +509,41 @@ class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useF ) @Test - fun testDefaultParamOverrideExtensionReceiver() = defaultParams( + fun testAbstractDefaultParamOverrideExtensionReceiver() = defaultParams( unchecked = "", checked = """ interface Test { @Composable fun Int.foo(param: Int = remember { 0 }) - @Composable fun Int.bar(param: Int = remember { 0 }): Int = param } class TestImpl : Test { @Composable override fun Int.foo(param: Int) {} - @Composable override fun Int.bar(param: Int): Int = 0 } @Composable fun CallWithDefaults(test: Test) { with(test) { 42.foo() 42.foo(0) + } + } + """ + ) + + @Ignore("b/357878245") + @Test + fun testOpenDefaultParamOverrideExtensionReceiver() = defaultParams( + unchecked = "", + checked = """ + interface Test { + @Composable fun Int.bar(param: Int = remember { 0 }): Int = param + } + + class TestImpl : Test { + @Composable override fun Int.bar(param: Int): Int = 0 + } + + @Composable fun CallWithDefaults(test: Test) { + with(test) { 42.bar() 42.bar(0) } @@ -508,6 +551,7 @@ class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useF """ ) + @Ignore("b/357878245") @Test fun testDefaultParamFakeOverride() = defaultParams( unchecked = "", @@ -531,12 +575,30 @@ class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useF ) @Test - fun testDefaultParamComposableLambda() = defaultParams( + fun testAbstractDefaultParamComposableLambda() = defaultParams( unchecked = """ @Composable fun Text(value: String) {} """, checked = """ private interface DefaultParamInterface { + @Composable fun Content( + content: @Composable () -> Unit = @Composable { ComposedContent { Text("default") } } + ) + @Composable fun ComposedContent( + content: @Composable () -> Unit = @Composable { Text("default") } + ) + } + """, + ) + + @Ignore("b/357878245") + @Test + fun testOpenDefaultParamComposableLambda() = defaultParams( + unchecked = """ + @Composable fun Text(value: String) {} + """, + checked = """ + private interface DefaultParamInterface { @Composable fun Content( content: @Composable () -> Unit = @Composable { ComposedContent { Text("default") } } ) diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt index c29cee88d27e8..2ff8bccbb3332 100644 --- a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt @@ -290,7 +290,7 @@ class ComposableDeclarationCheckerTests(useFir: Boolean) : AbstractComposeDiagno """ import androidx.compose.runtime.Composable interface A { - @Composable fun foo(x: Int = 0) {} + @Composable fun foo(x: Int = 0) {} } """ ) @@ -314,7 +314,7 @@ class ComposableDeclarationCheckerTests(useFir: Boolean) : AbstractComposeDiagno """ import androidx.compose.runtime.Composable open class A { - @Composable open fun foo(x: Int = 0) {} + @Composable open fun foo(x: Int = 0) {} } """ ) diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamComposableLambda[useFir = false].txt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamComposableLambda[useFir = false].txt new file mode 100644 index 0000000000000..699868f5a7070 --- /dev/null +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamComposableLambda[useFir = false].txt @@ -0,0 +1,149 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + + +private interface DefaultParamInterface { + @Composable fun Content( + content: @Composable () -> Unit = @Composable { ComposedContent { Text("default") } } + ) + @Composable fun ComposedContent( + content: @Composable () -> Unit = @Composable { Text("default") } + ) +} + +// +// Transformed IR +// ------------------------------------------ + +private interface DefaultParamInterface { + @Composable + abstract fun Content(content: Function2, %composer: Composer?, %changed: Int) + @Composable + abstract fun ComposedContent(content: Function2, %composer: Composer?, %changed: Int) + class ComposeDefaultImpls { + @Composable + @ComposableInferredTarget(scheme = "[0[0]]") + fun ComposedContent%default(content: Function2?, %this%: DefaultParamInterface, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(ComposedContent%default):Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(content)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + content = ComposableSingletons%TestKt.lambda-1 + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.ComposedContent(content, %composer, 0b1110 and %dirty or 0b01110000 and %dirty) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + ComposedContent%default(content, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + @ComposableInferredTarget(scheme = "[0[0]]") + fun Content%default(content: Function2?, %this%: DefaultParamInterface, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(Content%default)<{>:Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(content)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + content = rememberComposableLambda(<>, true, { %composer: Composer?, %changed: Int -> + sourceInformation(%composer, "C:Test.kt") + if (%changed and 0b0011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + ComposedContent%default(ComposableSingletons%TestKt.lambda-2, %this%, %composer, 0b0110, 0) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + }, %composer, 0b00110110) + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.Content(content, %composer, 0b1110 and %dirty or 0b01110000 and %dirty) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + Content%default(content, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + } +} +internal object ComposableSingletons%TestKt { + val lambda-1: Function2 = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int -> + sourceInformation(%composer, "C:Test.kt") + if (%changed and 0b0011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + Text("default", %composer, 0b0110) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + } + val lambda-2: Function2 = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int -> + sourceInformation(%composer, "C:Test.kt") + if (%changed and 0b0011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + Text("default", %composer, 0b0110) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + } +} diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamComposableLambda[useFir = true].txt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamComposableLambda[useFir = true].txt new file mode 100644 index 0000000000000..699868f5a7070 --- /dev/null +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamComposableLambda[useFir = true].txt @@ -0,0 +1,149 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + + +private interface DefaultParamInterface { + @Composable fun Content( + content: @Composable () -> Unit = @Composable { ComposedContent { Text("default") } } + ) + @Composable fun ComposedContent( + content: @Composable () -> Unit = @Composable { Text("default") } + ) +} + +// +// Transformed IR +// ------------------------------------------ + +private interface DefaultParamInterface { + @Composable + abstract fun Content(content: Function2, %composer: Composer?, %changed: Int) + @Composable + abstract fun ComposedContent(content: Function2, %composer: Composer?, %changed: Int) + class ComposeDefaultImpls { + @Composable + @ComposableInferredTarget(scheme = "[0[0]]") + fun ComposedContent%default(content: Function2?, %this%: DefaultParamInterface, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(ComposedContent%default):Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(content)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + content = ComposableSingletons%TestKt.lambda-1 + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.ComposedContent(content, %composer, 0b1110 and %dirty or 0b01110000 and %dirty) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + ComposedContent%default(content, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + @ComposableInferredTarget(scheme = "[0[0]]") + fun Content%default(content: Function2?, %this%: DefaultParamInterface, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(Content%default)<{>:Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(content)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + content = rememberComposableLambda(<>, true, { %composer: Composer?, %changed: Int -> + sourceInformation(%composer, "C:Test.kt") + if (%changed and 0b0011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + ComposedContent%default(ComposableSingletons%TestKt.lambda-2, %this%, %composer, 0b0110, 0) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + }, %composer, 0b00110110) + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.Content(content, %composer, 0b1110 and %dirty or 0b01110000 and %dirty) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + Content%default(content, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + } +} +internal object ComposableSingletons%TestKt { + val lambda-1: Function2 = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int -> + sourceInformation(%composer, "C:Test.kt") + if (%changed and 0b0011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + Text("default", %composer, 0b0110) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + } + val lambda-2: Function2 = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int -> + sourceInformation(%composer, "C:Test.kt") + if (%changed and 0b0011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + Text("default", %composer, 0b0110) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + } +} diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOnInterface[useFir = false].txt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOnInterface[useFir = false].txt new file mode 100644 index 0000000000000..a498bea76ba9f --- /dev/null +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOnInterface[useFir = false].txt @@ -0,0 +1,219 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + + +interface Test { + @Composable fun foo(param: Int = remember { 0 }) +} + +interface TestBetween : Test { + @Composable fun betweenFoo(param: Int = remember { 0 }) +} + +class TestImpl : TestBetween { + @Composable override fun foo(param: Int) {} + @Composable override fun betweenFoo(param: Int) {} +} + +@Composable fun CallWithDefaults(test: Test, testBetween: TestBetween, testImpl: TestImpl) { + test.foo() + test.foo(0) + + testBetween.foo() + testBetween.foo(0) + testBetween.betweenFoo() + testBetween.betweenFoo(0) + + testImpl.foo() + testImpl.foo(0) + testImpl.betweenFoo() + testImpl.betweenFoo(0) +} + +// +// Transformed IR +// ------------------------------------------ + +interface Test { + @Composable + abstract fun foo(param: Int, %composer: Composer?, %changed: Int) + class ComposeDefaultImpls { + @Composable + @ComposableInferredTarget(scheme = "[0[0]]") + fun foo%default(param: Int, %this%: Test, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(foo%default):Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + param = { + sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(false) { + 0 + } + sourceInformationMarkerEnd(%composer) + tmp0_group + } + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.foo(param, %composer, 0b1110 and %dirty or 0b01110000 and %dirty) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + foo%default(param, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + } +} +interface TestBetween : Test { + @Composable + abstract fun betweenFoo(param: Int, %composer: Composer?, %changed: Int) + class ComposeDefaultImpls { + @Composable + fun betweenFoo%default(param: Int, %this%: TestBetween, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(betweenFoo%default):Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + param = { + sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(false) { + 0 + } + sourceInformationMarkerEnd(%composer) + tmp0_group + } + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.betweenFoo(param, %composer, 0b1110 and %dirty or 0b01110000 and %dirty) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + betweenFoo%default(param, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + } +} +@StabilityInferred(parameters = 1) +class TestImpl : TestBetween { + @Composable + override fun foo(param: Int, %composer: Composer?, %changed: Int) { + %composer.startReplaceGroup(<>) + sourceInformation(%composer, "C(foo):Test.kt") + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + } + @Composable + override fun betweenFoo(param: Int, %composer: Composer?, %changed: Int) { + %composer.startReplaceGroup(<>) + sourceInformation(%composer, "C(betweenFoo):Test.kt") + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + } + static val %stable: Int = 0 +} +@Composable +@ComposableInferredTarget(scheme = "[0[0]]") +fun CallWithDefaults(test: Test, testBetween: TestBetween, testImpl: TestImpl, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(CallWithDefaults),,,,,,,,,:Test.kt") + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (if (%changed and 0b1000 == 0) { + %composer.changed(test) + } else { + %composer.changedInstance(test) + } + ) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(testBetween) + } else { + %composer.changedInstance(testBetween) + } + ) 0b00100000 else 0b00010000 + } + if (%changed and 0b000110000000 == 0) { + %dirty = %dirty or if (%composer.changed(testImpl)) 0b000100000000 else 0b10000000 + } + if (%dirty and 0b10010011 != 0b10010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + foo%default(0, test, %composer, 0b01110000 and %dirty shl 0b0011, 0b0001) + foo%default(0, test, %composer, 0b0110 or 0b01110000 and %dirty shl 0b0011, 0) + foo%default(0, testBetween, %composer, 0b01110000 and %dirty, 0b0001) + foo%default(0, testBetween, %composer, 0b0110 or 0b01110000 and %dirty, 0) + betweenFoo%default(0, testBetween, %composer, 0b01110000 and %dirty, 0b0001) + betweenFoo%default(0, testBetween, %composer, 0b0110 or 0b01110000 and %dirty, 0) + foo%default(0, testImpl, %composer, 0b01110000 and %dirty shr 0b0011, 0b0001) + foo%default(0, testImpl, %composer, 0b0110 or 0b01110000 and %dirty shr 0b0011, 0) + betweenFoo%default(0, testImpl, %composer, 0b01110000 and %dirty shr 0b0011, 0b0001) + betweenFoo%default(0, testImpl, %composer, 0b0110 or 0b01110000 and %dirty shr 0b0011, 0) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + CallWithDefaults(test, testBetween, testImpl, %composer, updateChangedFlags(%changed or 0b0001)) + } +} diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOnInterface[useFir = true].txt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOnInterface[useFir = true].txt new file mode 100644 index 0000000000000..a498bea76ba9f --- /dev/null +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOnInterface[useFir = true].txt @@ -0,0 +1,219 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + + +interface Test { + @Composable fun foo(param: Int = remember { 0 }) +} + +interface TestBetween : Test { + @Composable fun betweenFoo(param: Int = remember { 0 }) +} + +class TestImpl : TestBetween { + @Composable override fun foo(param: Int) {} + @Composable override fun betweenFoo(param: Int) {} +} + +@Composable fun CallWithDefaults(test: Test, testBetween: TestBetween, testImpl: TestImpl) { + test.foo() + test.foo(0) + + testBetween.foo() + testBetween.foo(0) + testBetween.betweenFoo() + testBetween.betweenFoo(0) + + testImpl.foo() + testImpl.foo(0) + testImpl.betweenFoo() + testImpl.betweenFoo(0) +} + +// +// Transformed IR +// ------------------------------------------ + +interface Test { + @Composable + abstract fun foo(param: Int, %composer: Composer?, %changed: Int) + class ComposeDefaultImpls { + @Composable + @ComposableInferredTarget(scheme = "[0[0]]") + fun foo%default(param: Int, %this%: Test, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(foo%default):Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + param = { + sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(false) { + 0 + } + sourceInformationMarkerEnd(%composer) + tmp0_group + } + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.foo(param, %composer, 0b1110 and %dirty or 0b01110000 and %dirty) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + foo%default(param, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + } +} +interface TestBetween : Test { + @Composable + abstract fun betweenFoo(param: Int, %composer: Composer?, %changed: Int) + class ComposeDefaultImpls { + @Composable + fun betweenFoo%default(param: Int, %this%: TestBetween, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(betweenFoo%default):Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + param = { + sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(false) { + 0 + } + sourceInformationMarkerEnd(%composer) + tmp0_group + } + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.betweenFoo(param, %composer, 0b1110 and %dirty or 0b01110000 and %dirty) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + betweenFoo%default(param, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + } +} +@StabilityInferred(parameters = 1) +class TestImpl : TestBetween { + @Composable + override fun foo(param: Int, %composer: Composer?, %changed: Int) { + %composer.startReplaceGroup(<>) + sourceInformation(%composer, "C(foo):Test.kt") + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + } + @Composable + override fun betweenFoo(param: Int, %composer: Composer?, %changed: Int) { + %composer.startReplaceGroup(<>) + sourceInformation(%composer, "C(betweenFoo):Test.kt") + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + } + static val %stable: Int = 0 +} +@Composable +@ComposableInferredTarget(scheme = "[0[0]]") +fun CallWithDefaults(test: Test, testBetween: TestBetween, testImpl: TestImpl, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(CallWithDefaults),,,,,,,,,:Test.kt") + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (if (%changed and 0b1000 == 0) { + %composer.changed(test) + } else { + %composer.changedInstance(test) + } + ) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(testBetween) + } else { + %composer.changedInstance(testBetween) + } + ) 0b00100000 else 0b00010000 + } + if (%changed and 0b000110000000 == 0) { + %dirty = %dirty or if (%composer.changed(testImpl)) 0b000100000000 else 0b10000000 + } + if (%dirty and 0b10010011 != 0b10010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + foo%default(0, test, %composer, 0b01110000 and %dirty shl 0b0011, 0b0001) + foo%default(0, test, %composer, 0b0110 or 0b01110000 and %dirty shl 0b0011, 0) + foo%default(0, testBetween, %composer, 0b01110000 and %dirty, 0b0001) + foo%default(0, testBetween, %composer, 0b0110 or 0b01110000 and %dirty, 0) + betweenFoo%default(0, testBetween, %composer, 0b01110000 and %dirty, 0b0001) + betweenFoo%default(0, testBetween, %composer, 0b0110 or 0b01110000 and %dirty, 0) + foo%default(0, testImpl, %composer, 0b01110000 and %dirty shr 0b0011, 0b0001) + foo%default(0, testImpl, %composer, 0b0110 or 0b01110000 and %dirty shr 0b0011, 0) + betweenFoo%default(0, testImpl, %composer, 0b01110000 and %dirty shr 0b0011, 0b0001) + betweenFoo%default(0, testImpl, %composer, 0b0110 or 0b01110000 and %dirty shr 0b0011, 0) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + CallWithDefaults(test, testBetween, testImpl, %composer, updateChangedFlags(%changed or 0b0001)) + } +} diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOverrideExtensionReceiver[useFir = false].txt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOverrideExtensionReceiver[useFir = false].txt new file mode 100644 index 0000000000000..97b0a9ac4fb5a --- /dev/null +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOverrideExtensionReceiver[useFir = false].txt @@ -0,0 +1,131 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + + +interface Test { + @Composable fun Int.foo(param: Int = remember { 0 }) +} + +class TestImpl : Test { + @Composable override fun Int.foo(param: Int) {} +} + +@Composable fun CallWithDefaults(test: Test) { + with(test) { + 42.foo() + 42.foo(0) + } +} + +// +// Transformed IR +// ------------------------------------------ + +interface Test { + @Composable + abstract fun Int.foo(param: Int, %composer: Composer?, %changed: Int) + class ComposeDefaultImpls { + @Composable + @ComposableInferredTarget(scheme = "[0[0]]") + fun foo%default(param: Int, %this%: Test, %this%: Int, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(foo%default):Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%default and 0b0100 != 0) { + %dirty = %dirty or 0b000110000000 + } else if (%changed and 0b000110000000 == 0) { + %dirty = %dirty or if (%composer.changed(%this%)) 0b000100000000 else 0b10000000 + } + if (%dirty and 0b10010011 != 0b10010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + param = { + sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(false) { + 0 + } + sourceInformationMarkerEnd(%composer) + tmp0_group + } + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.foo(param, %composer, 0b1110 and %dirty shr 0b0110 or 0b01110000 and %dirty shl 0b0011 or 0b001110000000 and %dirty shl 0b0011) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + foo%default(param, %this%, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + } +} +@StabilityInferred(parameters = 1) +class TestImpl : Test { + @Composable + override fun Int.foo(param: Int, %composer: Composer?, %changed: Int) { + %composer.startReplaceGroup(<>) + sourceInformation(%composer, "C(foo):Test.kt") + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + } + static val %stable: Int = 0 +} +@Composable +@ComposableInferredTarget(scheme = "[0[0]]") +fun CallWithDefaults(test: Test, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(CallWithDefaults)*,:Test.kt") + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (if (%changed and 0b1000 == 0) { + %composer.changed(test) + } else { + %composer.changedInstance(test) + } + ) 0b0100 else 0b0010 + } + if (%dirty and 0b0011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + with(test) { + foo%default(0, %this%with, 42, %composer, 0b000110000000, 0b0001) + foo%default(0, %this%with, 42, %composer, 0b000110000110, 0) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + CallWithDefaults(test, %composer, updateChangedFlags(%changed or 0b0001)) + } +} diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOverrideExtensionReceiver[useFir = true].txt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOverrideExtensionReceiver[useFir = true].txt new file mode 100644 index 0000000000000..97b0a9ac4fb5a --- /dev/null +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/resources/golden/androidx.compose.compiler.plugins.kotlin.DefaultParamTransformTests/testAbstractDefaultParamOverrideExtensionReceiver[useFir = true].txt @@ -0,0 +1,131 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + + +interface Test { + @Composable fun Int.foo(param: Int = remember { 0 }) +} + +class TestImpl : Test { + @Composable override fun Int.foo(param: Int) {} +} + +@Composable fun CallWithDefaults(test: Test) { + with(test) { + 42.foo() + 42.foo(0) + } +} + +// +// Transformed IR +// ------------------------------------------ + +interface Test { + @Composable + abstract fun Int.foo(param: Int, %composer: Composer?, %changed: Int) + class ComposeDefaultImpls { + @Composable + @ComposableInferredTarget(scheme = "[0[0]]") + fun foo%default(param: Int, %this%: Test, %this%: Int, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(foo%default):Test.kt") + val %dirty = %changed + if (%default and 0b0001 != 0) { + %dirty = %dirty or 0b0110 + } else if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010 + } + if (%default and 0b0010 != 0) { + %dirty = %dirty or 0b00110000 + } else if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (if (%changed and 0b01000000 == 0) { + %composer.changed(%this%) + } else { + %composer.changedInstance(%this%) + } + ) 0b00100000 else 0b00010000 + } + if (%default and 0b0100 != 0) { + %dirty = %dirty or 0b000110000000 + } else if (%changed and 0b000110000000 == 0) { + %dirty = %dirty or if (%composer.changed(%this%)) 0b000100000000 else 0b10000000 + } + if (%dirty and 0b10010011 != 0b10010010 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + param = { + sourceInformationMarkerStart(%composer, <>, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(false) { + 0 + } + sourceInformationMarkerEnd(%composer) + tmp0_group + } + } + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %this%.foo(param, %composer, 0b1110 and %dirty shr 0b0110 or 0b01110000 and %dirty shl 0b0011 or 0b001110000000 and %dirty shl 0b0011) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + foo%default(param, %this%, %this%, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + } +} +@StabilityInferred(parameters = 1) +class TestImpl : Test { + @Composable + override fun Int.foo(param: Int, %composer: Composer?, %changed: Int) { + %composer.startReplaceGroup(<>) + sourceInformation(%composer, "C(foo):Test.kt") + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + } + static val %stable: Int = 0 +} +@Composable +@ComposableInferredTarget(scheme = "[0[0]]") +fun CallWithDefaults(test: Test, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(CallWithDefaults)*,:Test.kt") + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (if (%changed and 0b1000 == 0) { + %composer.changed(test) + } else { + %composer.changedInstance(test) + } + ) 0b0100 else 0b0010 + } + if (%dirty and 0b0011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + with(test) { + foo%default(0, %this%with, 42, %composer, 0b000110000000, 0b0001) + foo%default(0, %this%with, 42, %composer, 0b000110000110, 0) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + CallWithDefaults(test, %composer, updateChangedFlags(%changed or 0b0001)) + } +} diff --git a/plugins/compose/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/DefaultParamCompositionTests.kt b/plugins/compose/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/DefaultParamCompositionTests.kt index 76b557b30db47..225de2b57966e 100644 --- a/plugins/compose/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/DefaultParamCompositionTests.kt +++ b/plugins/compose/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/DefaultParamCompositionTests.kt @@ -64,19 +64,19 @@ private interface DefaultParamInterface { @Composable fun Content( content: @Composable () -> Unit = @Composable { ComposedContent() } ) + @Composable fun ComposedContent( content: @Composable () -> Unit = @Composable { Text("default") } - ) { - content() - } + ) } private class DefaultParamInterfaceImpl : DefaultParamInterface { @Composable override fun Content(content: @Composable () -> Unit) { content() } + @Composable override fun ComposedContent(content: @Composable () -> Unit) { - super.ComposedContent(content) + content() } } @@ -84,18 +84,18 @@ private abstract class DefaultParamAbstract { @Composable abstract fun Content( content: @Composable () -> Unit = @Composable { ComposedContent() } ) - @Composable open fun ComposedContent( + + @Composable abstract fun ComposedContent( content: @Composable () -> Unit = @Composable { Text("default") } - ) { - content() - } + ) } private class DefaultParamAbstractImpl : DefaultParamAbstract() { @Composable override fun Content(content: @Composable () -> Unit) { content() } + @Composable override fun ComposedContent(content: @Composable () -> Unit) { - super.ComposedContent(content) + content() } } diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposableDeclarationChecker.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposableDeclarationChecker.kt index 7145d1e44bebc..f1f2af2408047 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposableDeclarationChecker.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposableDeclarationChecker.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.container.useInstance import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor @@ -57,15 +58,15 @@ class ComposableDeclarationChecker : DeclarationChecker, StorageComponentContain ) { when { declaration is KtProperty && - descriptor is PropertyDescriptor -> checkProperty(declaration, descriptor, context) + descriptor is PropertyDescriptor -> checkProperty(declaration, descriptor, context) declaration is KtPropertyAccessor && - descriptor is PropertyAccessorDescriptor -> checkPropertyAccessor( + descriptor is PropertyAccessorDescriptor -> checkPropertyAccessor( declaration, descriptor, context ) declaration is KtFunction && - descriptor is FunctionDescriptor -> checkFunction(declaration, descriptor, context) + descriptor is FunctionDescriptor -> checkFunction(declaration, descriptor, context) } } @@ -84,8 +85,8 @@ class ComposableDeclarationChecker : DeclarationChecker, StorageComponentContain cls?.run { defaultType.supertypes().any { it.isFunctionType && - it.arguments.size == descriptor.arity + 1 && - it.hasComposableAnnotation() + it.arguments.size == descriptor.arity + 1 && + it.hasComposableAnnotation() } } ?: false } @@ -140,6 +141,18 @@ class ComposableDeclarationChecker : DeclarationChecker, StorageComponentContain } } + if ( + hasComposableAnnotation && descriptor.modality == Modality.OPEN + ) { + declaration.valueParameters.forEach { + val defaultValue = it.defaultValue + if (defaultValue != null) { + context.trace.report( + ComposeErrors.ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE.on(defaultValue) + ) + } + } + } val params = descriptor.valueParameters val ktparams = declaration.valueParameters if (params.size == ktparams.size) { @@ -155,9 +168,9 @@ class ComposableDeclarationChecker : DeclarationChecker, StorageComponentContain if (hasComposableAnnotation && descriptor.name.asString() == "main" && MainFunctionDetector( - context.trace.bindingContext, - context.languageVersionSettings - ).isMain(descriptor) + context.trace.bindingContext, + context.languageVersionSettings + ).isMain(descriptor) ) { context.trace.report( ComposeErrors.COMPOSABLE_FUN_MAIN.on( @@ -255,7 +268,7 @@ class ComposableDeclarationChecker : DeclarationChecker, StorageComponentContain } private val FunctionDescriptor.arity get(): Int = - if (extensionReceiverParameter != null) 1 else 0 + - contextReceiverParameters.size + - valueParameters.size + if (extensionReceiverParameter != null) 1 else 0 + + contextReceiverParameters.size + + valueParameters.size } diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeErrorMessages.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeErrorMessages.kt index a031d53d1ce9c..ce2b08cceb6ae 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeErrorMessages.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeErrorMessages.kt @@ -89,6 +89,10 @@ class ComposeErrorMessages : DefaultErrorMessages.Extension { ComposeErrors.COMPOSABLE_SUSPEND_FUN, "Composable function cannot be annotated as suspend" ) + MAP.put( + ComposeErrors.ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE, + "Open Composable functions with default values are not currently supported" + ) MAP.put( ComposeErrors.COMPOSABLE_FUN_MAIN, "Composable main functions are not currently supported" diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeErrors.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeErrors.kt index 127ec56bb888b..ec9da550536c8 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeErrors.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeErrors.kt @@ -71,6 +71,12 @@ object ComposeErrors { Severity.ERROR ) + @JvmField + val ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE = + DiagnosticFactory0.create( + Severity.ERROR + ) + @JvmField val COMPOSABLE_FUN_MAIN = DiagnosticFactory0.create( diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposableFunctionChecker.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposableFunctionChecker.kt index e2b821a6511b8..3c1970837fad0 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposableFunctionChecker.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposableFunctionChecker.kt @@ -24,6 +24,8 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFunctionChecker import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.FirFunction import org.jetbrains.kotlin.fir.declarations.getSingleMatchedExpectForActualOrNull +import org.jetbrains.kotlin.fir.declarations.utils.isAbstract +import org.jetbrains.kotlin.fir.declarations.utils.isOpen import org.jetbrains.kotlin.fir.declarations.utils.isOperator import org.jetbrains.kotlin.fir.declarations.utils.isSuspend import org.jetbrains.kotlin.fir.declarations.utils.nameOrSpecialName @@ -69,6 +71,18 @@ object ComposableFunctionChecker : FirFunctionChecker(MppCheckerKind.Common) { reporter.reportOn(declaration.source, ComposeErrors.COMPOSABLE_SUSPEND_FUN, context) } + // Check that there are no default arguments in abstract composable functions + if (declaration.isOpen) { + for (valueParameter in declaration.valueParameters) { + val defaultValue = valueParameter.defaultValue ?: continue + reporter.reportOn( + defaultValue.source, + ComposeErrors.ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE, + context + ) + } + } + // Composable main functions are not allowed. if (declaration.symbol.isMain(context.session)) { reporter.reportOn(declaration.source, ComposeErrors.COMPOSABLE_FUN_MAIN, context) diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposeErrorMessages.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposeErrorMessages.kt index 0a9b030668841..af02ccdc9a750 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposeErrorMessages.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposeErrorMessages.kt @@ -60,6 +60,11 @@ object ComposeErrorMessages : BaseDiagnosticRendererFactory() { FirDiagnosticRenderers.DECLARATION_NAME, ) + map.put( + ComposeErrors.ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE, + "Open Composable functions with default values are not currently supported" + ) + map.put( ComposeErrors.COMPOSABLE_SUSPEND_FUN, "Composable function cannot be annotated as suspend" diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposeErrors.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposeErrors.kt index 4da5a19232ca8..2c2ceaa1c7d7f 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposeErrors.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k2/ComposeErrors.kt @@ -65,6 +65,8 @@ object ComposeErrors { FirValueParameterSymbol, // marked FirCallableSymbol<*>>() + val ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE by error0() + val COMPOSABLE_SUSPEND_FUN by error0( SourceElementPositioningStrategies.DECLARATION_NAME ) diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableDefaultParamLowering.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableDefaultParamLowering.kt index beb76212643c8..67281c9ecc39c 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableDefaultParamLowering.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableDefaultParamLowering.kt @@ -192,10 +192,10 @@ class ComposableDefaultParamLowering( private fun IrSimpleFunction.isVirtualFunctionWithDefaultParam() = hasComposableAnnotation() && - !isExpect && - (modality == Modality.OPEN || modality == Modality.ABSTRACT) && // virtual function - overriddenSymbols.isEmpty() && // first in the chain of overrides - valueParameters.any { it.defaultValue != null } // has a default parameter + !isExpect && + modality == Modality.ABSTRACT && // virtual function + overriddenSymbols.isEmpty() && // first in the chain of overrides + valueParameters.any { it.defaultValue != null } // has a default parameter private fun makeDefaultParameterWrapper( source: IrSimpleFunction