Move invokeComposable to compose runtime
Bug: 228423246
Bug: 233035416
Relnote: moving utility functionality to runtime
Test: all tests should pass
Change-Id: I4f729ac5919a71218d89e8892ab9d96b8c76aa96
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index b405fc2..5654983 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -750,6 +750,18 @@
}
+package androidx.compose.runtime.reflect {
+
+ public final class ComposableMethodInvokerKt {
+ method @kotlin.jvm.Throws(exceptionClasses=NoSuchMethodException::class) public static java.lang.reflect.Method getDeclaredComposableMethod(Class<?>, String methodName, Class<?>... args) throws java.lang.NoSuchMethodException;
+ method public static java.lang.reflect.Parameter![] getRealParameters(java.lang.reflect.Method);
+ method public static int getRealParametersCount(java.lang.reflect.Method);
+ method public static Object? invokeComposable(java.lang.reflect.Method, androidx.compose.runtime.Composer composer, Object? instance, java.lang.Object?... args);
+ method public static boolean isComposable(java.lang.reflect.Method);
+ }
+
+}
+
package androidx.compose.runtime.snapshots {
public final class ListUtilsKt {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index e121f57..75793d0 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -815,6 +815,18 @@
}
+package androidx.compose.runtime.reflect {
+
+ public final class ComposableMethodInvokerKt {
+ method @kotlin.jvm.Throws(exceptionClasses=NoSuchMethodException::class) public static java.lang.reflect.Method getDeclaredComposableMethod(Class<?>, String methodName, Class<?>... args) throws java.lang.NoSuchMethodException;
+ method public static java.lang.reflect.Parameter![] getRealParameters(java.lang.reflect.Method);
+ method public static int getRealParametersCount(java.lang.reflect.Method);
+ method public static Object? invokeComposable(java.lang.reflect.Method, androidx.compose.runtime.Composer composer, Object? instance, java.lang.Object?... args);
+ method public static boolean isComposable(java.lang.reflect.Method);
+ }
+
+}
+
package androidx.compose.runtime.snapshots {
public final class ListUtilsKt {
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 786f2b7..2643f45 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -778,6 +778,18 @@
}
+package androidx.compose.runtime.reflect {
+
+ public final class ComposableMethodInvokerKt {
+ method @kotlin.jvm.Throws(exceptionClasses=NoSuchMethodException::class) public static java.lang.reflect.Method getDeclaredComposableMethod(Class<?>, String methodName, Class<?>... args) throws java.lang.NoSuchMethodException;
+ method public static java.lang.reflect.Parameter![] getRealParameters(java.lang.reflect.Method);
+ method public static int getRealParametersCount(java.lang.reflect.Method);
+ method public static Object? invokeComposable(java.lang.reflect.Method, androidx.compose.runtime.Composer composer, Object? instance, java.lang.Object?... args);
+ method public static boolean isComposable(java.lang.reflect.Method);
+ }
+
+}
+
package androidx.compose.runtime.snapshots {
public final class ListUtilsKt {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
index 368da08..3accee3 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
@@ -24,7 +24,7 @@
import androidx.compose.runtime.RecomposeScopeImpl
import androidx.compose.runtime.Stable
-private const val SLOTS_PER_INT = 10
+internal const val SLOTS_PER_INT = 10
private const val BITS_PER_SLOT = 3
internal fun bitsForSlot(bits: Int, slot: Int): Int {
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
index 3193f395..121e1e1 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
@@ -24,8 +24,6 @@
import androidx.compose.runtime.Stable
import kotlin.jvm.functions.FunctionN
-private const val SLOTS_PER_INT = 10
-
@Stable
internal class ComposableLambdaNImpl(
val key: Int,
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/reflect/ComposableMethodInvoker.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/reflect/ComposableMethodInvoker.kt
new file mode 100644
index 0000000..3b7777a
--- /dev/null
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/reflect/ComposableMethodInvoker.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.reflect
+
+import androidx.compose.runtime.Composer
+import androidx.compose.runtime.internal.SLOTS_PER_INT
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.lang.reflect.Parameter
+import kotlin.math.ceil
+
+private inline fun <reified T> T.dup(count: Int): Array<T> {
+ return (0 until count).map { this }.toTypedArray()
+}
+
+/**
+ * Find the given @Composable method by name.
+ */
+@Throws(NoSuchMethodException::class)
+fun Class<*>.getDeclaredComposableMethod(methodName: String, vararg args: Class<*>): Method {
+ val changedParams = changedParamCount(args.size, 0)
+ val method = try {
+ // without defaults
+ getDeclaredMethod(
+ methodName,
+ *args,
+ Composer::class.java, // composer param
+ *Int::class.java.dup(changedParams) // changed params
+ )
+ } catch (e: ReflectiveOperationException) {
+ val defaultParams = defaultParamCount(args.size)
+ try {
+ getDeclaredMethod(
+ methodName,
+ *args,
+ Composer::class.java, // composer param
+ *Int::class.java.dup(changedParams), // changed param
+ *Int::class.java.dup(defaultParams) // default param
+ )
+ } catch (e2: ReflectiveOperationException) {
+ null
+ }
+ } ?: throw NoSuchMethodException("$name.$methodName")
+
+ return method
+}
+
+/**
+ * Returns the default value for the [Class] type. This will be 0 for numeric types, false for
+ * boolean and null for object references.
+ */
+private fun Class<*>.getDefaultValue(): Any? = when (name) {
+ "int" -> 0.toInt()
+ "short" -> 0.toShort()
+ "byte" -> 0.toByte()
+ "long" -> 0.toLong()
+ "double" -> 0.toDouble()
+ "float" -> 0.toFloat()
+ "boolean" -> false
+ "char" -> 0.toChar()
+ else -> null
+}
+
+/**
+ * Structure intended to be used exclusively by [getComposableInfo].
+ */
+private data class ComposableInfo(
+ val isComposable: Boolean,
+ val realParamsCount: Int,
+ val changedParams: Int,
+ val defaultParams: Int
+)
+
+/**
+ * Checks whether the method is Composable function and returns result along with the real
+ * parameters count and changed parameter count (if composable) and packed in a structure.
+ */
+private fun Method.getComposableInfo(): ComposableInfo {
+ val realParamsCount = parameterTypes.indexOfLast { it == Composer::class.java }
+ if (realParamsCount == -1) {
+ return ComposableInfo(false, parameterTypes.size, 0, 0)
+ }
+ val thisParams = if (Modifier.isStatic(this.modifiers)) 0 else 1
+ val changedParams = changedParamCount(realParamsCount, thisParams)
+ val totalParamsWithoutDefaults = realParamsCount +
+ 1 + // composer
+ changedParams
+ val totalParams = parameterTypes.size
+ val isDefault = totalParams != totalParamsWithoutDefaults
+ val defaultParams = if (isDefault)
+ defaultParamCount(realParamsCount)
+ else
+ 0
+ return ComposableInfo(
+ totalParamsWithoutDefaults + defaultParams == totalParams,
+ realParamsCount,
+ changedParams,
+ defaultParams
+ )
+}
+
+/**
+ * Calls the Composable method on the given [instance]. If the method accepts default values, this
+ * function will call it with the correct options set.
+ */
+@Suppress("BanUncheckedReflection", "ListIterator")
+fun Method.invokeComposable(
+ composer: Composer,
+ instance: Any?,
+ vararg args: Any?
+): Any? {
+ val (isComposable, realParamsCount, changedParams, defaultParams) = getComposableInfo()
+
+ check(isComposable)
+
+ val totalParams = parameterTypes.size
+ val changedStartIndex = realParamsCount + 1
+ val defaultStartIndex = changedStartIndex + changedParams
+
+ val defaultsMasks = Array(defaultParams) { index ->
+ val start = index * BITS_PER_INT
+ val end = minOf(start + BITS_PER_INT, realParamsCount)
+ val useDefault =
+ (start until end).map { if (it >= args.size || args[it] == null) 1 else 0 }
+ val mask = useDefault.foldIndexed(0) { i, mask, default -> mask or (default shl i) }
+ mask
+ }
+
+ val arguments = Array(totalParams) { idx ->
+ when (idx) {
+ // pass in "empty" value for all real parameters since we will be using defaults.
+ in 0 until realParamsCount -> args.getOrElse(idx) {
+ parameterTypes[idx].getDefaultValue()
+ }
+ // the composer is the first synthetic parameter
+ realParamsCount -> composer
+ // since this is the root we don't need to be anything unique. 0 should suffice.
+ // changed parameters should be 0 to indicate "uncertain"
+ changedStartIndex -> 1
+ in changedStartIndex + 1 until defaultStartIndex -> 0
+ // Default values mask, all parameters set to use defaults
+ in defaultStartIndex until totalParams -> defaultsMasks[idx - defaultStartIndex]
+ else -> error("Unexpected index")
+ }
+ }
+ return invoke(instance, *arguments)
+}
+
+private const val BITS_PER_INT = 31
+
+private fun changedParamCount(realValueParams: Int, thisParams: Int): Int {
+ if (realValueParams == 0) return 1
+ val totalParams = realValueParams + thisParams
+ return ceil(
+ totalParams.toDouble() / SLOTS_PER_INT.toDouble()
+ ).toInt()
+}
+
+private fun defaultParamCount(realValueParams: Int): Int {
+ return ceil(
+ realValueParams.toDouble() / BITS_PER_INT.toDouble()
+ ).toInt()
+}
+
+/**
+ * Returns true if the method is a Composable function and false otherwise.
+ */
+val Method.isComposable: Boolean
+ get() = getComposableInfo().isComposable
+
+/**
+ * Returns real parameters count for the method, it returns the actual parameters count for the
+ * usual methods and for Composable functions it excludes the utility Compose-specific parameters
+ * from counting.
+ */
+val Method.realParametersCount: Int
+ get() {
+ val (isComposable, realParametersCount, _, _) = getComposableInfo()
+ if (isComposable) {
+ return realParametersCount
+ }
+ return parameterTypes.size
+ }
+
+/**
+ * Returns real parameters for the method, it returns the actual parameters for the usual methods
+ * and for Composable functions it excludes the utility Compose-specific parameters.
+ */
+val Method.realParameters: Array<out Parameter>
+ @Suppress("ClassVerificationFailure", "NewApi")
+ get() {
+ val (isComposable, realParametersCount, _, _) = getComposableInfo()
+ if (isComposable) {
+ return parameters.copyOfRange(0, realParametersCount)
+ }
+ return parameters
+ }
diff --git a/compose/runtime/runtime/src/jvmTest/kotlin/androidx/compose/runtime/reflect/ComposableMethodInvokerTest.kt b/compose/runtime/runtime/src/jvmTest/kotlin/androidx/compose/runtime/reflect/ComposableMethodInvokerTest.kt
new file mode 100644
index 0000000..a976fad
--- /dev/null
+++ b/compose/runtime/runtime/src/jvmTest/kotlin/androidx/compose/runtime/reflect/ComposableMethodInvokerTest.kt
@@ -0,0 +1,581 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.reflect
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Composer
+import androidx.compose.runtime.Composition
+import androidx.compose.runtime.MonotonicFrameClock
+import androidx.compose.runtime.Recomposer
+import androidx.compose.runtime.currentComposer
+import androidx.compose.runtime.mock.EmptyApplier
+import androidx.compose.runtime.withRunningRecomposer
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+import kotlinx.coroutines.runBlocking
+
+@Composable
+private fun composableFunction() {
+}
+
+private fun nonComposableFunction() {
+}
+
+@Suppress("UNUSED_PARAMETER")
+private fun nonComposableFunctionWithComposerParam(unused: Composer) {
+}
+
+@Composable
+private fun composableFunctionWithDefaults(
+ s1: String,
+ s2: String,
+ s3: String = "a",
+ s4: String = "a",
+ s5: String = "a"
+): String { return s1 + s2 + s3 + s4 + s5 }
+
+@Composable
+private fun overloadedComposable() {
+}
+
+@Suppress("UNUSED_PARAMETER")
+@Composable
+private fun overloadedComposable(s: String) {
+}
+
+@Suppress("UNUSED_PARAMETER")
+@Composable
+private fun overloadedComposable(
+ v1: String,
+ v2: String,
+ v3: String,
+ v4: String,
+ v5: String,
+ v6: String,
+ v7: String,
+ v8: String,
+ v9: String,
+ v10: String
+) { }
+
+@Suppress("UNUSED_PARAMETER")
+@Composable
+private fun overloadedComposable(
+ v1: String,
+ v2: String,
+ v3: String,
+ v4: String,
+ v5: String,
+ v6: String,
+ v7: String,
+ v8: String,
+ v9: String,
+ v10: String,
+ v11: String
+) { }
+
+@Suppress("UNUSED_PARAMETER")
+@Composable
+private fun overloadedComposable(
+ v1: String,
+ v2: String,
+ v3: String,
+ v4: String,
+ v5: String,
+ v6: String,
+ v7: String,
+ v8: String,
+ v9: String,
+ v10: String,
+ v11: String,
+ v12: String
+) { }
+
+@Suppress("UNUSED_PARAMETER")
+@Composable
+private fun differentParametersTypes(
+ v1: String,
+ v2: Any,
+ v3: Int,
+ v4: Float,
+ v5: Double,
+ v6: Long
+) { }
+
+private class ComposablesWrapper {
+ @Composable
+ fun composableMethod() {
+ }
+
+ fun nonComposableMethod() {
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun nonComposableMethodWithComposerParam(unused: Composer) {
+ }
+
+ @Composable
+ fun composableMethodWithDefaults(
+ s1: String,
+ s2: String,
+ s3: String = "a",
+ s4: String = "a",
+ s5: String = "a"
+ ): String { return s1 + s2 + s3 + s4 + s5 }
+
+ @Composable
+ fun overloadedComposableMethod() {
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ @Composable
+ fun overloadedComposableMethod(s: String) {
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ @Composable
+ fun overloadedComposableMethod(
+ v1: String,
+ v2: String,
+ v3: String,
+ v4: String,
+ v5: String,
+ v6: String,
+ v7: String,
+ v8: String,
+ v9: String,
+ v10: String
+ ) { }
+
+ @Suppress("UNUSED_PARAMETER")
+ @Composable
+ fun overloadedComposableMethod(
+ v1: String,
+ v2: String,
+ v3: String,
+ v4: String,
+ v5: String,
+ v6: String,
+ v7: String,
+ v8: String,
+ v9: String,
+ v10: String,
+ v11: String
+ ) { }
+
+ @Suppress("UNUSED_PARAMETER")
+ @Composable
+ fun overloadedComposableMethod(
+ v1: String,
+ v2: String,
+ v3: String,
+ v4: String,
+ v5: String,
+ v6: String,
+ v7: String,
+ v8: String,
+ v9: String,
+ v10: String,
+ v11: String,
+ v12: String
+ ) { }
+
+ @Suppress("UNUSED_PARAMETER")
+ @Composable
+ fun differentParametersTypesMethod(
+ v1: String,
+ v2: Any,
+ v3: Int,
+ v4: Float,
+ v5: Double,
+ v6: Long
+ ) { }
+}
+
+class ComposableMethodInvokerTest {
+ private val clazz =
+ Class.forName("androidx.compose.runtime.reflect.ComposableMethodInvokerTestKt")
+ private val wrapperClazz =
+ Class.forName("androidx.compose.runtime.reflect.ComposablesWrapper")
+
+ private val composable = clazz.declaredMethods.find { it.name == "composableFunction" }!!
+ private val nonComposable = clazz.declaredMethods.find { it.name == "nonComposableFunction" }!!
+ private val nonComposableWithComposer =
+ clazz.declaredMethods.find { it.name == "nonComposableFunctionWithComposerParam" }!!
+ private val composableMethod =
+ wrapperClazz.declaredMethods.find { it.name == "composableMethod" }!!
+ private val nonComposableMethod =
+ wrapperClazz.declaredMethods.find { it.name == "nonComposableMethod" }!!
+ private val nonComposableMethodWithComposer =
+ wrapperClazz.declaredMethods.find { it.name == "nonComposableMethodWithComposerParam" }!!
+
+ @Test
+ fun test_isComposable_correctly_checks_functions() {
+ assertTrue(composable.isComposable)
+ assertFalse(nonComposable.isComposable)
+ assertFalse(nonComposableWithComposer.isComposable)
+ assertTrue(composableMethod.isComposable)
+ assertFalse(nonComposableMethod.isComposable)
+ assertFalse(nonComposableMethodWithComposer.isComposable)
+ }
+
+ @Throws(NoSuchMethodException::class)
+ @Test
+ fun test_getDeclaredComposableMethod_differentiates_overloaded_functions() {
+ val method0 = clazz.getDeclaredComposableMethod("overloadedComposable")
+ val method1 = clazz.getDeclaredComposableMethod("overloadedComposable", String::class.java)
+ val method10 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(10) { String::class.java }
+ )
+ val method11 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(11) { String::class.java }
+ )
+ val method12 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(12) { String::class.java }
+ )
+
+ assertNotEquals(method0, method1)
+ assertNotEquals(method0, method10)
+ assertNotEquals(method0, method11)
+ assertNotEquals(method0, method12)
+ assertNotEquals(method1, method10)
+ assertNotEquals(method1, method11)
+ assertNotEquals(method1, method12)
+ assertNotEquals(method10, method11)
+ assertNotEquals(method10, method12)
+ assertNotEquals(method11, method12)
+ }
+
+ @Throws(NoSuchMethodException::class)
+ @Test
+ fun test_getDeclaredComposableMethod_differentiates_overloaded_methods() {
+ val method0 = wrapperClazz.getDeclaredComposableMethod("overloadedComposableMethod")
+ val method1 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ String::class.java
+ )
+ val method10 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(10) { String::class.java }
+ )
+ val method11 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(11) { String::class.java }
+ )
+ val method12 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(12) { String::class.java }
+ )
+
+ assertNotEquals(method0, method1)
+ assertNotEquals(method0, method10)
+ assertNotEquals(method0, method11)
+ assertNotEquals(method0, method12)
+ assertNotEquals(method1, method10)
+ assertNotEquals(method1, method11)
+ assertNotEquals(method1, method12)
+ assertNotEquals(method10, method11)
+ assertNotEquals(method10, method12)
+ assertNotEquals(method11, method12)
+ }
+
+ @Throws(NoSuchMethodException::class)
+ @Test
+ fun test_getDeclaredComposableMethod_works_with_default_params() {
+ clazz.getDeclaredComposableMethod(
+ "composableFunctionWithDefaults",
+ *Array(5) { String::class.java }
+ )
+
+ wrapperClazz.getDeclaredComposableMethod(
+ "composableMethodWithDefaults",
+ *Array(5) { String::class.java }
+ )
+ }
+
+ @Throws(NoSuchMethodException::class)
+ @Test
+ fun test_realParametersCount_returns_correct_number_of_parameters() {
+ val function0 = clazz.getDeclaredComposableMethod("overloadedComposable")
+ val function1 =
+ clazz.getDeclaredComposableMethod("overloadedComposable", String::class.java)
+ val function10 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(10) { String::class.java }
+ )
+ val function11 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(11) { String::class.java }
+ )
+ val function12 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(12) { String::class.java }
+ )
+
+ val method0 = wrapperClazz.getDeclaredComposableMethod("overloadedComposableMethod")
+ val method1 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod", String::class.java
+ )
+ val method10 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(10) { String::class.java }
+ )
+ val method11 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(11) { String::class.java }
+ )
+ val method12 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(12) { String::class.java }
+ )
+
+ assertEquals(0, function0.realParametersCount)
+ assertEquals(1, function1.realParametersCount)
+ assertEquals(10, function10.realParametersCount)
+ assertEquals(11, function11.realParametersCount)
+ assertEquals(12, function12.realParametersCount)
+
+ assertEquals(0, method0.realParametersCount)
+ assertEquals(1, method1.realParametersCount)
+ assertEquals(10, method10.realParametersCount)
+ assertEquals(11, method11.realParametersCount)
+ assertEquals(12, method12.realParametersCount)
+
+ assertEquals(0, nonComposable.realParametersCount)
+ assertEquals(1, nonComposableWithComposer.realParametersCount)
+ assertEquals(0, composableMethod.realParametersCount)
+ assertEquals(0, nonComposableMethod.realParametersCount)
+ assertEquals(1, nonComposableMethodWithComposer.realParametersCount)
+ }
+
+ @Suppress("ClassVerificationFailure", "NewApi")
+ @Throws(NoSuchMethodException::class)
+ @Test
+ fun test_realParameters_returns_correct_parameters() {
+ val function0 = clazz.getDeclaredComposableMethod("overloadedComposable")
+ val function1 =
+ clazz.getDeclaredComposableMethod("overloadedComposable", String::class.java)
+ val function10 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(10) { String::class.java }
+ )
+ val function11 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(11) { String::class.java }
+ )
+ val function12 =
+ clazz.getDeclaredComposableMethod(
+ "overloadedComposable",
+ *Array(12) { String::class.java }
+ )
+
+ val method0 = wrapperClazz.getDeclaredComposableMethod("overloadedComposableMethod")
+ val method1 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ String::class.java
+ )
+ val method10 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(10) { String::class.java }
+ )
+ val method11 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(11) { String::class.java }
+ )
+ val method12 =
+ wrapperClazz.getDeclaredComposableMethod(
+ "overloadedComposableMethod",
+ *Array(12) { String::class.java }
+ )
+
+ val diffParameters =
+ clazz.getDeclaredComposableMethod(
+ "differentParametersTypes",
+ String::class.java,
+ Any::class.java,
+ Int::class.java,
+ Float::class.java,
+ Double::class.java,
+ Long::class.java
+ )
+
+ val diffParametersMethod =
+ wrapperClazz.getDeclaredComposableMethod(
+ "differentParametersTypesMethod",
+ String::class.java,
+ Any::class.java,
+ Int::class.java,
+ Float::class.java,
+ Double::class.java,
+ Long::class.java
+ )
+
+ assertEquals(0, function0.realParameters.size)
+ assertEquals(1, function1.realParameters.size)
+ assertEquals(10, function10.realParameters.size)
+ assertEquals(11, function11.realParameters.size)
+ assertEquals(12, function12.realParameters.size)
+ assertEquals(12, function12.realParameters.size)
+
+ assertEquals(0, method0.realParameters.size)
+ assertEquals(1, method1.realParameters.size)
+ assertEquals(10, method10.realParameters.size)
+ assertEquals(11, method11.realParameters.size)
+ assertEquals(12, method12.realParameters.size)
+
+ assertEquals(0, nonComposable.realParameters.size)
+ assertEquals(1, nonComposableWithComposer.realParameters.size)
+ assertEquals(0, composableMethod.realParameters.size)
+ assertEquals(0, nonComposableMethod.realParameters.size)
+ assertEquals(1, nonComposableMethodWithComposer.realParameters.size)
+
+ assertEquals(6, diffParameters.realParameters.size)
+ assertEquals(
+ listOf(String::class.java, Any::class.java, Int::class.java, Float::class.java,
+ Double::class.java, Long::class.java),
+ diffParameters.realParameters.map { it.type })
+
+ assertEquals(6, diffParametersMethod.realParameters.size)
+ assertEquals(
+ listOf(String::class.java, Any::class.java, Int::class.java, Float::class.java,
+ Double::class.java, Long::class.java),
+ diffParametersMethod.realParameters.map { it.type })
+ }
+
+ private class TestFrameClock : MonotonicFrameClock {
+ override suspend fun <R> withFrameNanos(onFrame: (Long) -> R): R = onFrame(0L)
+ }
+
+ private fun <T> executeWithComposer(block: (composer: Composer) -> T): T =
+ runBlocking(TestFrameClock()) {
+ fun compose(
+ recomposer: Recomposer,
+ block: @Composable () -> Unit
+ ): Composition {
+ return Composition(
+ EmptyApplier(),
+ recomposer
+ ).apply {
+ setContent(block)
+ }
+ }
+
+ var res: T? = null
+ withRunningRecomposer { r ->
+ compose(r) {
+ res = block(currentComposer)
+ }
+ }
+ res!!
+ }
+
+ @Test
+ fun testInvokeComposableFunctions() {
+
+ val composableWithDefaults =
+ clazz.declaredMethods.find { it.name == "composableFunctionWithDefaults" }!!
+ composableWithDefaults.isAccessible = true
+
+ val resABAAA = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, null, "a", "b") as String
+ }
+
+ val resABCAA = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, null, "a", "b", "c") as String
+ }
+
+ val resABCDA = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, null, "a", "b", "c", "d") as String
+ }
+
+ val resABCDE = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, null, "a", "b", "c", "d", "e") as String
+ }
+
+ val resABADA = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, null, "a", "b", null, "d") as String
+ }
+
+ assertEquals("abaaa", resABAAA)
+ assertEquals("abcaa", resABCAA)
+ assertEquals("abcda", resABCDA)
+ assertEquals("abcde", resABCDE)
+ assertEquals("abada", resABADA)
+ }
+
+ @Test
+ fun testInvokeComposableMethods() {
+
+ val composableWithDefaults =
+ wrapperClazz.declaredMethods.find { it.name == "composableMethodWithDefaults" }!!
+ composableWithDefaults.isAccessible = true
+
+ val instance = ComposablesWrapper()
+
+ val resABAAA = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, instance, "a", "b") as String
+ }
+
+ val resABCAA = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, instance, "a", "b", "c") as String
+ }
+
+ val resABCDA = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, instance, "a", "b", "c", "d") as String
+ }
+
+ val resABCDE = executeWithComposer {
+ composableWithDefaults
+ .invokeComposable(it, instance, "a", "b", "c", "d", "e") as String
+ }
+
+ val resABADA = executeWithComposer {
+ composableWithDefaults.invokeComposable(it, instance, "a", "b", null, "d") as String
+ }
+
+ assertEquals("abaaa", resABAAA)
+ assertEquals("abcaa", resABCAA)
+ assertEquals("abcda", resABCDA)
+ assertEquals("abcde", resABCDE)
+ assertEquals("abada", resABADA)
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
index cfbe91f..6428016 100644
--- a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
@@ -1,9 +1,9 @@
// Signature format: 4.0
package androidx.compose.ui.tooling {
- @androidx.compose.ui.ExperimentalComposeUiApi public final class ComposableInvoker {
- method @androidx.compose.ui.ExperimentalComposeUiApi public void invokeComposable(String className, String methodName, androidx.compose.runtime.Composer composer, java.lang.Object?... args);
- field public static final androidx.compose.ui.tooling.ComposableInvoker INSTANCE;
+ @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public final class ComposableInvoker {
+ method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public void invokeComposable(String className, String methodName, androidx.compose.runtime.Composer composer, java.lang.Object?... args);
+ field @Deprecated public static final androidx.compose.ui.tooling.ComposableInvoker INSTANCE;
}
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
index 7c484d32..5d07d1a 100644
--- a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
+++ b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
@@ -49,7 +49,6 @@
import androidx.compose.ui.platform.LocalFontLoader
import androidx.compose.ui.platform.ViewRootForTest
import androidx.compose.ui.text.font.createFontFamilyResolver
-import androidx.compose.ui.tooling.ComposableInvoker.invokeComposable
import androidx.compose.ui.tooling.animation.PreviewAnimationClock
import androidx.compose.ui.tooling.data.Group
import androidx.compose.ui.tooling.data.SourceLocation
@@ -553,6 +552,7 @@
* @param onCommit callback invoked after every commit of the preview composable.
* @param onDraw callback invoked after every draw of the adapter. Only for test use.
*/
+ @Suppress("DEPRECATION")
@OptIn(ExperimentalComposeUiApi::class)
@VisibleForTesting
internal fun init(
@@ -587,7 +587,7 @@
// class loads correctly.
val composable = {
try {
- invokeComposable(
+ ComposableInvoker.invokeComposable(
className,
methodName,
composer,
diff --git a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/PreviewActivity.kt b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/PreviewActivity.kt
index 6e5cad2..ec5b8d9 100644
--- a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/PreviewActivity.kt
+++ b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/PreviewActivity.kt
@@ -28,7 +28,6 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.tooling.ComposableInvoker.invokeComposable
/**
* Activity used to run `@Composable` previews from Android Studio.
@@ -59,6 +58,7 @@
intent?.getStringExtra("composable")?.let { setComposableContent(it) }
}
+ @Suppress("DEPRECATION")
@OptIn(ExperimentalComposeUiApi::class)
private fun setComposableContent(composableFqn: String) {
Log.d(TAG, "PreviewActivity has composable $composableFqn")
@@ -71,7 +71,7 @@
}
Log.d(TAG, "Previewing '$methodName' without a parameter provider.")
setContent {
- invokeComposable(
+ ComposableInvoker.invokeComposable(
className,
methodName,
currentComposer
@@ -86,6 +86,7 @@
* Otherwise, the content will display a FAB that changes the argument value on click, cycling
* through all the values in the provider's sequence.
*/
+ @Suppress("DEPRECATION")
@OptIn(ExperimentalComposeUiApi::class)
private fun setParameterizedContent(
className: String,
@@ -107,7 +108,7 @@
Scaffold(
content = {
- invokeComposable(
+ ComposableInvoker.invokeComposable(
className,
methodName,
currentComposer,
@@ -124,7 +125,7 @@
}
} else {
setContent {
- invokeComposable(
+ ComposableInvoker.invokeComposable(
className,
methodName,
currentComposer,
diff --git a/compose/ui/ui-tooling/src/desktopMain/kotlin/androidx/compose/desktop/ui/tooling/preview/runtime/NonInteractivePreviewFacade.kt b/compose/ui/ui-tooling/src/desktopMain/kotlin/androidx/compose/desktop/ui/tooling/preview/runtime/NonInteractivePreviewFacade.kt
index e569bf9..5cec2cc 100644
--- a/compose/ui/ui-tooling/src/desktopMain/kotlin/androidx/compose/desktop/ui/tooling/preview/runtime/NonInteractivePreviewFacade.kt
+++ b/compose/ui/ui-tooling/src/desktopMain/kotlin/androidx/compose/desktop/ui/tooling/preview/runtime/NonInteractivePreviewFacade.kt
@@ -20,7 +20,6 @@
import androidx.compose.runtime.currentComposer
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.TestComposeWindow
-import androidx.compose.ui.tooling.ComposableInvoker
import androidx.compose.ui.unit.Density
/**
@@ -43,7 +42,7 @@
* preview classpath (a requested preview function or a requested frame size may differ).
* 7. A rendered frame is sent back to the IDE plugin and is shown in the IDE as an image.
*/
-@Suppress("unused")
+@Suppress("DEPRECATION", "unused")
internal class NonInteractivePreviewFacade {
companion object {
@JvmStatic
@@ -57,7 +56,7 @@
// We need to delay the reflection instantiation of the class until we are in the
// composable to ensure all the right initialization has happened and the Composable
// class loads correctly.
- ComposableInvoker.invokeComposable(
+ androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(
className,
methodName,
currentComposer
diff --git a/compose/ui/ui-tooling/src/desktopMain/kotlin/androidx/compose/desktop/ui/tooling/preview/runtime/PreviewRunner.kt b/compose/ui/ui-tooling/src/desktopMain/kotlin/androidx/compose/desktop/ui/tooling/preview/runtime/PreviewRunner.kt
index 89a9b0c..a64124c 100644
--- a/compose/ui/ui-tooling/src/desktopMain/kotlin/androidx/compose/desktop/ui/tooling/preview/runtime/PreviewRunner.kt
+++ b/compose/ui/ui-tooling/src/desktopMain/kotlin/androidx/compose/desktop/ui/tooling/preview/runtime/PreviewRunner.kt
@@ -19,7 +19,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.currentComposer
import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.tooling.ComposableInvoker.invokeComposable
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.launchApplication
import kotlinx.coroutines.DelicateCoroutinesApi
@@ -29,6 +28,7 @@
companion object {
private var previewComposition: @Composable () -> Unit = {}
+ @Suppress("DEPRECATION")
@JvmStatic
@OptIn(ExperimentalComposeUiApi::class)
fun main(args: Array<String>) {
@@ -40,7 +40,7 @@
// We need to delay the reflection instantiation of the class until we are in the
// composable to ensure all the right initialization has happened and the Composable
// class loads correctly.
- invokeComposable(
+ androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(
className,
methodName,
currentComposer
diff --git a/compose/ui/ui-tooling/src/jvmMain/kotlin/androidx/compose/ui/tooling/ComposableInvoker.kt b/compose/ui/ui-tooling/src/jvmMain/kotlin/androidx/compose/ui/tooling/ComposableInvoker.kt
index 20309f7..28d41ec 100644
--- a/compose/ui/ui-tooling/src/jvmMain/kotlin/androidx/compose/ui/tooling/ComposableInvoker.kt
+++ b/compose/ui/ui-tooling/src/jvmMain/kotlin/androidx/compose/ui/tooling/ComposableInvoker.kt
@@ -25,6 +25,7 @@
/**
* A utility object to invoke composable function by its name and containing class.
*/
+@Deprecated("Use androidx.compose.runtime.reflect.ComposableMethodInvoker instead")
@ExperimentalComposeUiApi
object ComposableInvoker {
@@ -56,7 +57,7 @@
}
private inline fun <reified T> T.dup(count: Int): Array<T> {
- return (0..count).map { this }.toTypedArray()
+ return (0 until count).map { this }.toTypedArray()
}
/**
@@ -71,7 +72,6 @@
methodName,
*args.mapNotNull { it?.javaClass }.toTypedArray(),
Composer::class.java, // composer param
- kotlin.Int::class.java, // key param
*kotlin.Int::class.java.dup(changedParams) // changed params
)
} catch (e: ReflectiveOperationException) {
@@ -97,7 +97,7 @@
"double" -> 0.toDouble()
"float" -> 0.toFloat()
"boolean" -> false
- "char" -> '0'
+ "char" -> 0.toChar()
else -> null
}
@@ -155,7 +155,7 @@
return invoke(instance, *arguments)
}
- private const val SLOTS_PER_INT = 15
+ private const val SLOTS_PER_INT = 10
private const val BITS_PER_INT = 31
private fun changedParamCount(realValueParams: Int, thisParams: Int): Int {