Generate binary compat stubs for nullable value classes

aosp/3017515 changed default parameter value class types to be nullable if inline class wraps a non-primitive value. Kotlin encodes nullability into the method mangle, however, and this breaks binary compatibility.

This change generates a wrapper function that simply redirects the call. This function will only be called if the module doesn't expect new mangle for the function and will be ignored normally.

Fixes: 335384193 ( https://issuetracker.google.com/issues/335384193 )
Test: compiler tests + verified that repro project successfully runs with recompiled artifact.
Change-Id: I894651168d0521cdc639d4f4c415b28c4e5d348f ( https://android-review.googlesource.com/q/I894651168d0521cdc639d4f4c415b28c4e5d348f )

Moved from: https://github.com/androidx/androidx/commit/60cef20b9fc9a6f0b48a7b2992fee5dcb5d0a676
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 9f56179..0b56304 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 @@
             """,
             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 105a58e..32d82d1 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 @@
                 @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\133useFir = false\135.txt" "b/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter\133useFir = false\135.txt"
index f43c062..222f44b 100644
--- "a/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter\133useFir = false\135.txt"
+++ "b/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter\133useFir = false\135.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 @@
     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 = <this>
+    %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 = <this>
+    %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 = <this>
+    %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 = <this>
+    %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 = <this>
+    %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\133useFir = true\135.txt" "b/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter\133useFir = true\135.txt"
index f43c062..222f44b 100644
--- "a/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter\133useFir = true\135.txt"
+++ "b/plugins/compose/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ComposerParamTransformTests/composeValueClassDefaultParameter\133useFir = true\135.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 @@
     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 = <this>
+    %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 = <this>
+    %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 = <this>
+    %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 = <this>
+    %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 = <this>
+    %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 835d4cf..309f300 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.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 @@
     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 ac1be9f..5c9d544 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.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 @@
         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 f113fc4..db470b3 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.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.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.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 @@
                 // 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 @@
                 }
             }
 
+            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 @@
 
     /**
      * 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 @@
         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
+    }
 }