Skip to content

Commit

Permalink
Support default parameters in virtual Composable functions
Browse files Browse the repository at this point in the history
Adds support for default parameters by generating a static wrapper that contains the function group + default preamble. The virtual function does not handle default values by itself (contrary to static functions) and its group is elided when possible. Super calls to such functions are also not restartable for the same reason. This behavior is very similar to DefaultImpls implementation in Kotlin compiler.

Relnote: "Add support for default parameters in abstract and open @composable functions"
Fixes: 165812010 ( https://issuetracker.google.com/issues/165812010 )
Change-Id: I69d28e59b4bd19b04f1266de4cd28a45f42e0131 ( https://android-review.googlesource.com/q/I69d28e59b4bd19b04f1266de4cd28a45f42e0131 )

Moved from: androidx/androidx@3f2cce4
  • Loading branch information
ShikaSD authored and Space Cloud committed Jun 3, 2024
1 parent 3c67cda commit 842a9e8
Show file tree
Hide file tree
Showing 56 changed files with 3,010 additions and 254 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import kotlin.test.assertTrue
import org.junit.Assume.assumeFalse
import org.junit.Test

/* ktlint-disable max-line-length */
class ComposeBytecodeCodegenTest(useFir: Boolean) : AbstractCodegenTest(useFir) {

@Test
Expand Down Expand Up @@ -620,4 +621,38 @@ class ComposeBytecodeCodegenTest(useFir: Boolean) : AbstractCodegenTest(useFir)
fun ReceiveValue(value: Int) { }
"""
)

@Test
fun testDefaultParametersInVirtualFunctions() = 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 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)
}
""",
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"
)
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,60 @@ class ComposeCrossModuleTests(useFir: Boolean) : AbstractCodegenTest(useFir) {
)
}

@Test
fun defaultParametersInFakeOverrideVirtualComposableFunctions() {
compile(
mapOf(
"Base" to mapOf(
"base/Base.kt" to """
package base
import androidx.compose.runtime.Composable
open class Test {
@Composable open fun Test(content: @Composable () -> Unit = {}) = content()
open fun runTest(content: @Composable () -> Unit = {}) {}
}
"""
),
"Intermediate" to mapOf(
"intermediate/Intermediate.kt" to """
package intermediate
import androidx.compose.runtime.Composable
import base.Test
open class DeviceTest : Test() {
@Composable open fun DeviceTest(content: @Composable () -> Unit = {}) = content()
}
"""
),
"Main" to mapOf(
"Main.kt" to """
package main
import base.Test
import intermediate.DeviceTest
import androidx.compose.runtime.Composable
class MainTest : DeviceTest()
@Composable fun CallWithDefaults(test: Test, deviceTest: DeviceTest, mainTest: MainTest) {
test.runTest {
test.Test()
test.Test { }
deviceTest.Test()
deviceTest.DeviceTest()
mainTest.Test()
mainTest.DeviceTest()
}
}
"""
)
)
)
}

private fun compile(
modules: Map<String, Map<String, String>>,
dumpClasses: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ class ComposerParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(use
object : IrElementVisitorVoid {
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
val composer = declaration.valueParameters.firstOrNull {
it.name == KtxNameConventions.COMPOSER_PARAMETER
it.name == ComposeNames.COMPOSER_PARAMETER
}
val oldComposer = currentComposer
if (composer != null) currentComposer = composer
Expand All @@ -268,7 +268,7 @@ class ComposerParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(use
val value = expression.symbol.owner
if (
value is IrValueParameter && value.name ==
KtxNameConventions.COMPOSER_PARAMETER
ComposeNames.COMPOSER_PARAMETER
) {
assertEquals(
"Composer unexpectedly captured",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,12 @@ class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useF
dumpTree: Boolean = false
) = verifyGoldenComposeIrTransform(
"""
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*
$checked
""".trimIndent(),
"""
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*
$unchecked
Expand Down Expand Up @@ -405,4 +403,149 @@ class DefaultParamTransformTests(useFir: Boolean) : AbstractIrTransformTest(useF
}
"""
)

@Test
fun testDefaultParamOnInterface() = 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 })
@Composable fun betweenFooDefault(param: Int = remember { 0 }) {}
@Composable fun betweenBar(param: Int = remember { 0 }): Int = param
}
class TestImpl : TestBetween {
@Composable override fun foo(param: Int) {}
@Composable override fun bar(param: Int): Int {
return super.bar(param)
}
@Composable override fun betweenFoo(param: Int) {}
}
@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()
testImpl.betweenBar(0)
}
"""
)

@Test
fun testDefaultParamOverrideOpenFunction() = defaultParams(
unchecked = """""",
checked = """
@Composable fun CallWithDefaults(test: Test) {
test.foo()
test.foo(0)
test.bar()
test.bar(0)
}
open class Test {
@Composable open fun foo(param: Int = remember { 0 }) {}
@Composable open fun bar(param: Int = remember { 0 }): Int = param
}
class TestImpl : Test() {
@Composable override fun foo(param: Int) {}
@Composable override fun bar(param: Int): Int {
return super.bar(param)
}
}
"""
)

@Test
fun testDefaultParamOverrideExtensionReceiver() = 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)
42.bar()
42.bar(0)
}
}
"""
)

@Test
fun testDefaultParamFakeOverride() = defaultParams(
unchecked = "",
checked = """
open class Test {
@Composable open fun foo(param: Int = remember { 0 }) {}
@Composable open 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)
test.bar()
test.bar(0)
}
"""
)

@Test
fun testDefaultParamComposableLambda() = 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") }
) {
content()
}
}
""",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ class ComposableDeclarationCheckerTests(useFir: Boolean) : AbstractComposeDiagno
"""
import androidx.compose.runtime.Composable
interface A {
@Composable fun foo(x: Int = <!ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE!>0<!>)
@Composable fun foo(x: Int = 0)
}
"""
)
Expand All @@ -290,7 +290,7 @@ class ComposableDeclarationCheckerTests(useFir: Boolean) : AbstractComposeDiagno
"""
import androidx.compose.runtime.Composable
interface A {
@Composable fun foo(x: Int = <!ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE!>0<!>) {}
@Composable fun foo(x: Int = 0) {}
}
"""
)
Expand All @@ -302,7 +302,7 @@ class ComposableDeclarationCheckerTests(useFir: Boolean) : AbstractComposeDiagno
"""
import androidx.compose.runtime.Composable
abstract class A {
@Composable abstract fun foo(x: Int = <!ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE!>0<!>)
@Composable abstract fun foo(x: Int = 0)
}
"""
)
Expand All @@ -314,7 +314,7 @@ class ComposableDeclarationCheckerTests(useFir: Boolean) : AbstractComposeDiagno
"""
import androidx.compose.runtime.Composable
open class A {
@Composable open fun foo(x: Int = <!ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE!>0<!>) {}
@Composable open fun foo(x: Int = 0) {}
}
"""
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Source
// ------------------------------------------

import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*


@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Source
// ------------------------------------------

import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*


@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Source
// ------------------------------------------

import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*


@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Source
// ------------------------------------------

import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*


@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Source
// ------------------------------------------

import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*


@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Source
// ------------------------------------------

import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*


@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Source
// ------------------------------------------

import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.*


@Composable
Expand Down
Loading

0 comments on commit 842a9e8

Please sign in to comment.