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 {