diff --git a/plugins/compose/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt b/plugins/compose/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt index 9f56179cb8723..0b5630407a072 100644 --- a/plugins/compose/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt +++ b/plugins/compose/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt @@ -17,6 +17,7 @@ package androidx.compose.compiler.plugins.kotlin import kotlin.test.assertFalse +import kotlin.test.assertTrue import org.junit.Assume.assumeFalse import org.junit.Test @@ -796,11 +797,18 @@ class ComposeBytecodeCodegenTest(useFir: Boolean) : AbstractCodegenTest(useFir) """, validate = { // select Example function body - val match = Regex("public final static Example[\\s\\S]*?LOCALVARIABLE").find(it)!! + val func = Regex("public final static Example[\\s\\S]*?LOCALVARIABLE") + .findAll(it) + .single() assertFalse(message = "Function body should not contain a not-null check.") { - match.value.contains("Intrinsics.checkNotNullParameter") + func.value.contains("Intrinsics.checkNotNullParameter") + } + val stub = Regex("public final static synthetic Example[\\s\\S]*?LOCALVARIABLE") + .findAll(it) + .single() + assertTrue(message = "Function stub should contain a not-null check.") { + stub.value.contains("Intrinsics.checkNotNullParameter") } }, - dumpClasses = true ) } diff --git a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt index 105a58e1d7efe..32d82d10ce760 100644 --- a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt +++ b/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt @@ -583,12 +583,27 @@ class ComposerParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(use @JvmInline value class Data(val string: String) @JvmInline + value class NullableData(val string: String?) + @JvmInline value class IntData(val value: Int) """, source = """ import androidx.compose.runtime.* @Composable fun Example(data: Data = Data(""), intData: IntData = IntData(0)) {} + @Composable fun ExampleNullable(data: Data? = Data(""), intData: IntData = IntData(0)) {} + @Composable fun ExampleNullableData(data: NullableData = NullableData(null), intData: IntData = IntData(0)) {} + @Composable private fun PrivateExample(data: Data = Data(""), intData: IntData = IntData(0)) {} + @Composable internal fun InternalExample(data: Data = Data(""), intData: IntData = IntData(0)) {} + @Composable @PublishedApi internal fun PublishedExample(data: Data = Data(""), intData: IntData = IntData(0)) {} + + abstract class Test { + @Composable private fun PrivateExample(data: Data = Data("")) {} + @Composable fun PublicExample(data: Data = Data("")) {} + @Composable internal fun InternalExample(data: Data = Data("")) {} + @Composable @PublishedApi internal fun PublishedExample(data: Data = Data("")) {} + @Composable protected fun ProtectedExample(data: Data = Data("")) {} + } """ ) } diff --git a/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter[useFir = false].txt b/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter[useFir = false].txt index f43c062a9577c..222f44b86063f 100644 --- a/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter[useFir = false].txt +++ b/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter[useFir = false].txt @@ -5,6 +5,19 @@ import androidx.compose.runtime.* @Composable fun Example(data: Data = Data(""), intData: IntData = IntData(0)) {} +@Composable fun ExampleNullable(data: Data? = Data(""), intData: IntData = IntData(0)) {} +@Composable fun ExampleNullableData(data: NullableData = NullableData(null), intData: IntData = IntData(0)) {} +@Composable private fun PrivateExample(data: Data = Data(""), intData: IntData = IntData(0)) {} +@Composable internal fun InternalExample(data: Data = Data(""), intData: IntData = IntData(0)) {} +@Composable @PublishedApi internal fun PublishedExample(data: Data = Data(""), intData: IntData = IntData(0)) {} + +abstract class Test { + @Composable private fun PrivateExample(data: Data = Data("")) {} + @Composable fun PublicExample(data: Data = Data("")) {} + @Composable internal fun InternalExample(data: Data = Data("")) {} + @Composable @PublishedApi internal fun PublishedExample(data: Data = Data("")) {} + @Composable protected fun ProtectedExample(data: Data = Data("")) {} +} // // Transformed IR @@ -34,3 +47,266 @@ fun Example(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, Example(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) } } +@Composable +fun ExampleNullable(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(ExampleNullable)P(0:Data,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + ExampleNullable(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@Composable +fun ExampleNullableData(data: NullableData?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(ExampleNullableData)P(0:NullableData,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = NullableData(null) + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + ExampleNullableData(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@Composable +private fun PrivateExample(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PrivateExample)P(0:Data,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + PrivateExample(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@Composable +internal fun InternalExample(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(InternalExample)P(0:Data,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + InternalExample(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@Composable +@PublishedApi +internal fun PublishedExample(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PublishedExample)P(0:Data,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + PublishedExample(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@StabilityInferred(parameters = 1) +abstract class Test { + @Composable + private fun PrivateExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PrivateExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.PrivateExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + fun PublicExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PublicExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.PublicExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + internal fun InternalExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(InternalExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.InternalExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + @PublishedApi + internal fun PublishedExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PublishedExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.PublishedExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + protected fun ProtectedExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(ProtectedExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.ProtectedExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + static val %stable: Int = 0 + @Composable + @JvmSynthetic + fun PublicExample(data: Data, %composer: Composer?, %changed: Int, %default: Int) { + return PublicExample(data, %composer, %changed, %default) + } + @Composable + @PublishedApi + @JvmSynthetic + internal fun PublishedExample(data: Data, %composer: Composer?, %changed: Int, %default: Int) { + return PublishedExample(data, %composer, %changed, %default) + } + @Composable + @JvmSynthetic + protected fun ProtectedExample(data: Data, %composer: Composer?, %changed: Int, %default: Int) { + return ProtectedExample(data, %composer, %changed, %default) + } +} +@Composable +@JvmSynthetic +fun Example(data: Data, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + return Example(data, intData, %composer, %changed, %default) +} +@Composable +@PublishedApi +@JvmSynthetic +internal fun PublishedExample(data: Data, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + return PublishedExample(data, intData, %composer, %changed, %default) +} diff --git a/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter[useFir = true].txt b/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter[useFir = true].txt index f43c062a9577c..222f44b86063f 100644 --- a/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter[useFir = true].txt +++ b/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter[useFir = true].txt @@ -5,6 +5,19 @@ import androidx.compose.runtime.* @Composable fun Example(data: Data = Data(""), intData: IntData = IntData(0)) {} +@Composable fun ExampleNullable(data: Data? = Data(""), intData: IntData = IntData(0)) {} +@Composable fun ExampleNullableData(data: NullableData = NullableData(null), intData: IntData = IntData(0)) {} +@Composable private fun PrivateExample(data: Data = Data(""), intData: IntData = IntData(0)) {} +@Composable internal fun InternalExample(data: Data = Data(""), intData: IntData = IntData(0)) {} +@Composable @PublishedApi internal fun PublishedExample(data: Data = Data(""), intData: IntData = IntData(0)) {} + +abstract class Test { + @Composable private fun PrivateExample(data: Data = Data("")) {} + @Composable fun PublicExample(data: Data = Data("")) {} + @Composable internal fun InternalExample(data: Data = Data("")) {} + @Composable @PublishedApi internal fun PublishedExample(data: Data = Data("")) {} + @Composable protected fun ProtectedExample(data: Data = Data("")) {} +} // // Transformed IR @@ -34,3 +47,266 @@ fun Example(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, Example(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) } } +@Composable +fun ExampleNullable(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(ExampleNullable)P(0:Data,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + ExampleNullable(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@Composable +fun ExampleNullableData(data: NullableData?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(ExampleNullableData)P(0:NullableData,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = NullableData(null) + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + ExampleNullableData(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@Composable +private fun PrivateExample(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PrivateExample)P(0:Data,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + PrivateExample(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@Composable +internal fun InternalExample(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(InternalExample)P(0:Data,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + InternalExample(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@Composable +@PublishedApi +internal fun PublishedExample(data: Data?, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PublishedExample)P(0:Data,1:IntData):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (%default and 0b0010 != 0) { + intData = IntData(0) + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + PublishedExample(data, intData, %composer, updateChangedFlags(%changed or 0b0001), %default) + } +} +@StabilityInferred(parameters = 1) +abstract class Test { + @Composable + private fun PrivateExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PrivateExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.PrivateExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + fun PublicExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PublicExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.PublicExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + internal fun InternalExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(InternalExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.InternalExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + @PublishedApi + internal fun PublishedExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(PublishedExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.PublishedExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + @Composable + protected fun ProtectedExample(data: Data?, %composer: Composer?, %changed: Int, %default: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(ProtectedExample)P(0:Data):Test.kt") + if (%changed and 0b0001 != 0 || !%composer.skipping) { + if (%default and 0b0001 != 0) { + data = Data("") + } + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + val tmp0_rcvr = + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + tmp0_rcvr.ProtectedExample(data, %composer, updateChangedFlags(%changed or 0b0001), %default) + } + } + static val %stable: Int = 0 + @Composable + @JvmSynthetic + fun PublicExample(data: Data, %composer: Composer?, %changed: Int, %default: Int) { + return PublicExample(data, %composer, %changed, %default) + } + @Composable + @PublishedApi + @JvmSynthetic + internal fun PublishedExample(data: Data, %composer: Composer?, %changed: Int, %default: Int) { + return PublishedExample(data, %composer, %changed, %default) + } + @Composable + @JvmSynthetic + protected fun ProtectedExample(data: Data, %composer: Composer?, %changed: Int, %default: Int) { + return ProtectedExample(data, %composer, %changed, %default) + } +} +@Composable +@JvmSynthetic +fun Example(data: Data, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + return Example(data, intData, %composer, %changed, %default) +} +@Composable +@PublishedApi +@JvmSynthetic +internal fun PublishedExample(data: Data, intData: IntData, %composer: Composer?, %changed: Int, %default: Int) { + return PublishedExample(data, intData, %composer, %changed, %default) +} diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt index 835d4cfb77787..309f300d61a74 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt @@ -149,6 +149,7 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames +import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.util.OperatorNameConventions @@ -179,6 +180,12 @@ abstract class AbstractComposeLowering( protected val composableIrClass: IrClass get() = symbolRemapper.getReferencedClass(_composableIrClass.symbol).owner + protected val jvmSyntheticIrClass by guardedLazy { + context.referenceClass( + ClassId(StandardClassIds.BASE_JVM_PACKAGE, Name.identifier("JvmSynthetic")) + )!!.owner + } + fun referenceFunction(symbol: IrFunctionSymbol): IrFunctionSymbol { return symbolRemapper.getReferencedFunction(symbol) } diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt index ac1be9f43eebb..5c9d54478d517 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt @@ -30,6 +30,7 @@ import androidx.compose.compiler.plugins.kotlin.analysis.isUncertain import androidx.compose.compiler.plugins.kotlin.analysis.knownStable import androidx.compose.compiler.plugins.kotlin.analysis.knownUnstable import androidx.compose.compiler.plugins.kotlin.irTrace +import androidx.compose.compiler.plugins.kotlin.lower.ComposerParamTransformer.ComposeDefaultValueStubOrigin import androidx.compose.compiler.plugins.kotlin.lower.decoys.DecoyFqNames import kotlin.math.abs import kotlin.math.absoluteValue @@ -702,6 +703,12 @@ class ComposableFunctionBodyTransformer( val scope = currentFunctionScope // if the function isn't composable, there's nothing to do if (!scope.isComposable) return super.visitFunction(declaration) + if (declaration.origin == ComposeDefaultValueStubOrigin) { + // this is a synthetic function stub, don't touch the body, only remove the stub origin + declaration.origin = IrDeclarationOrigin.DEFINED + return declaration + } + val restartable = declaration.shouldBeRestartable() val isLambda = declaration.isLambda() diff --git a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt index f113fc49b79d0..db470b3673ab8 100644 --- a/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt +++ b/plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt @@ -33,13 +33,17 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOriginImpl +import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrLocalDelegatedProperty import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.declarations.copyAttributes +import org.jetbrains.kotlin.ir.declarations.createBlockBody import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrConstructorCall import org.jetbrains.kotlin.ir.expressions.IrExpression @@ -61,14 +65,18 @@ import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.classOrNull import org.jetbrains.kotlin.ir.types.createType +import org.jetbrains.kotlin.ir.types.defaultType import org.jetbrains.kotlin.ir.types.isMarkedNullable +import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.types.isPrimitiveType import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET +import org.jetbrains.kotlin.ir.util.addChild import org.jetbrains.kotlin.ir.util.constructors import org.jetbrains.kotlin.ir.util.copyTo import org.jetbrains.kotlin.ir.util.copyTypeParametersFrom +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.explicitParameters import org.jetbrains.kotlin.ir.util.functions @@ -84,6 +92,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.name.JvmStandardClassIds +import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.platform.isJs import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.resolve.DescriptorUtils @@ -424,15 +433,9 @@ class ComposerParamTransformer( // anonymous parameters has an issue where it is not safe to dex, so we sanitize // the names here to ensure that dex is always safe. val newName = dexSafeName(param.name) - - val newType = defaultParameterType( - param, - this.hasDefaultExpressionDefinedForValueParameter(param.index) - ).remapTypeParameters() param.copyTo( fn, name = newName, - type = newType, isAssignable = param.defaultValue != null, defaultValue = param.defaultValue?.copyWithNewTypeParams( source = this, target = fn @@ -566,6 +569,25 @@ class ComposerParamTransformer( } } + fn.makeStubForDefaultValuesIfNeeded()?.also { + when (val parent = fn.parent) { + is IrClass -> parent.addChild(it) + is IrFile -> parent.addChild(it) + else -> { + // ignore + } + } + } + + // update parameter types so they are ready to accept the default values + fn.valueParameters.fastForEach { param -> + val newType = defaultParameterType( + param, + fn.hasDefaultExpressionDefinedForValueParameter(param.index) + ) + param.type = newType + } + inlineLambdaInfo.scan(fn) fn.transformChildrenVoid(object : IrElementTransformerVoid() { @@ -653,7 +675,7 @@ class ComposerParamTransformer( /** * With klibs, composable functions are always deserialized from IR instead of being restored - * into stubs. + * * In this case, we need to avoid transforming those functions twice (because synthetic * parameters are being added). We know however, that all the other modules were compiled * before, so if the function comes from other [IrModuleFragment], we must skip it. @@ -666,4 +688,92 @@ class ComposerParamTransformer( decoysEnabled && valueParameters.firstOrNull { it.name == KtxNameConventions.COMPOSER_PARAMETER } != null + + /** + * Creates stubs for @Composable function with value class parameters that have a default and + * are wrapping a non-primitive instance. + * Before Compose compiler 1.5.12, not specifying such parameter resulted in a nullptr exception + * (b/330655412) at runtime, caused by Kotlin compiler inserting checkParameterNotNull. + * + * Converting such parameters to be nullable caused a binary compatibility issue because + * nullability changed the value class mangle on a function signature. This stub creates a + * binary compatible function to support old compilers while redirecting to a new function. + */ + private fun IrSimpleFunction.makeStubForDefaultValuesIfNeeded(): IrSimpleFunction? { + if (!visibility.isPublicAPI && !hasAnnotation(PublishedApiFqName)) { + return null + } + + if (!hasComposableAnnotation()) { + return null + } + + var makeStub = false + for (i in valueParameters.indices) { + val param = valueParameters[i] + if ( + hasDefaultExpressionDefinedForValueParameter(i) && + param.type.isInlineClassType() && + !param.type.isNullable() && + param.type.unboxInlineClass().let { + !it.isPrimitiveType() && !it.isNullable() + } + ) { + makeStub = true + break + } + } + + if (!makeStub) { + return null + } + + val source = this + val copy = source.deepCopyWithSymbols(parent) + copy.valueParameters.fastForEach { + it.defaultValue = null + } + copy.origin = ComposeDefaultValueStubOrigin + copy.annotations += IrConstructorCallImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + jvmSyntheticIrClass.defaultType, + jvmSyntheticIrClass.primaryConstructor!!.symbol, + 0, + 0, + 0, + ) + copy.body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) { + statements.add( + IrReturnImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + copy.returnType, + copy.symbol, + irCall(source).apply { + dispatchReceiver = copy.dispatchReceiverParameter?.let { irGet(it) } + extensionReceiver = copy.extensionReceiverParameter?.let { irGet(it) } + copy.typeParameters.fastForEachIndexed { index, param -> + putTypeArgument(index, param.defaultType) + } + copy.valueParameters.fastForEachIndexed { index, param -> + putValueArgument(index, irGet(param)) + } + } + ) + ) + } + + transformedFunctions[copy] = copy + transformedFunctionSet += copy + + return copy + } + + private val PublishedApiFqName = StandardClassIds.Annotations.PublishedApi.asSingleFqName() + + object ComposeDefaultValueStubOrigin : IrDeclarationOrigin { + override val name: String = "ComposeDefaultValueStubOrigin" + override val isSynthetic: Boolean = true + } }