Introduce call and emit codegen with new resolver, remove KTX completely
Change-Id: I7c7629593bc890e38d19b65f0e2607018adbf420
diff --git a/compose/compose-compiler-hosted/integration-tests/build.gradle b/compose/compose-compiler-hosted/integration-tests/build.gradle
index 034f8e8..d4afe75 100644
--- a/compose/compose-compiler-hosted/integration-tests/build.gradle
+++ b/compose/compose-compiler-hosted/integration-tests/build.gradle
@@ -37,6 +37,7 @@
testImplementation(KOTLIN_COMPOSE_STDLIB)
testImplementation(project(":compose:compose-runtime"))
+ testImplementation(project(":ui:ui-framework"))
testImplementation(project(":ui:ui-android-view-non-ir"))
testImplementation(project(":compose:compose-compiler-hosted"))
}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/AbstractCompilerTest.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/AbstractCompilerTest.kt
index 0942b79..aaa7dcd 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/AbstractCompilerTest.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/AbstractCompilerTest.kt
@@ -99,7 +99,7 @@
}
protected open fun setupEnvironment(environment: KotlinCoreEnvironment) {
- ComposeComponentRegistrar().registerProjectComponents(
+ ComposeComponentRegistrar.registerProjectExtensions(
environment.project as MockProject,
environment.configuration
)
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/AbstractResolvedKtxCallsTest.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/AbstractResolvedKtxCallsTest.kt
deleted file mode 100644
index 8ff7ca4..0000000
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/AbstractResolvedKtxCallsTest.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import com.intellij.psi.PsiElement
-import com.intellij.psi.util.PsiTreeUtil
-import junit.framework.TestCase
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtPsiFactory
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import org.jetbrains.kotlin.resolve.BindingContext
-import kotlin.reflect.KClass
-
-abstract class AbstractResolvedKtxCallsTest : AbstractCodegenTest() {
-
- fun doTest(srcText: String, expected: String) {
- val (text, carets) = extractCarets(srcText)
-
- val environment = myEnvironment ?: error("Environment not initialized")
-
- val ktFile = KtPsiFactory(environment.project).createFile(text)
- val bindingContext = JvmResolveUtil.analyze(
- ktFile,
- environment
- ).bindingContext
-
- val resolvedCalls = carets.mapNotNull { caret ->
- val (element, ktxElementCall) = buildCachedCallAtIndex(bindingContext, ktFile, caret)
- val elementText = element?.text ?: error("KtxElement expected, but none found")
- val call = ktxElementCall ?: error("ResolvedKtxElementCall expected, but none found")
- elementText to call
- }
-
- val output = renderOutput(resolvedCalls)
-
- TestCase.assertEquals(expected.trimIndent(), output.trimIndent())
- }
-
- protected open fun renderOutput(
- resolvedCallsAt: List<Pair<String, ResolvedKtxElementCall>>
- ): String =
- resolvedCallsAt.joinToString("\n\n\n") { (_, resolvedCall) ->
- resolvedCall.print()
- }
-
- protected fun extractCarets(text: String): Pair<String, List<Int>> {
- val parts = text.split("<caret>")
- if (parts.size < 2) return text to emptyList()
- // possible to rewrite using 'scan' function to get partial sums of parts lengths
- val indices = mutableListOf<Int>()
- val resultText = buildString {
- parts.dropLast(1).forEach { part ->
- append(part)
- indices.add(this.length)
- }
- append(parts.last())
- }
- return resultText to indices
- }
-
- protected open fun buildCachedCallAtIndex(
- bindingContext: BindingContext,
- jetFile: KtFile,
- index: Int
- ): Pair<PsiElement?, ResolvedKtxElementCall?> {
- val element = jetFile.findElementAt(index)!!
- val expression = element.parentOfType<KtxElement>()
-
- val cachedCall = bindingContext[ComposeWritableSlices.RESOLVED_KTX_CALL, expression]
- return Pair(element, cachedCall)
- }
-}
-private inline fun <reified T : PsiElement> PsiElement.parentOfType(): T? = parentOfType(T::class)
-
-private fun <T : PsiElement> PsiElement.parentOfType(vararg classes: KClass<out T>): T? {
- return PsiTreeUtil.getParentOfType(this, *classes.map { it.java }.toTypedArray())
-}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallLoweringTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallLoweringTests.kt
new file mode 100644
index 0000000..0b6b72d
--- /dev/null
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallLoweringTests.kt
@@ -0,0 +1,1317 @@
+/*
+ * Copyright 2019 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.plugins.kotlin
+
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtPsiFactory
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import java.net.URLClassLoader
+import kotlin.reflect.KClass
+
+@RunWith(ComposeRobolectricTestRunner::class)
+@Config(
+ manifest = Config.NONE,
+ minSdk = 23,
+ maxSdk = 23
+)
+class ComposeCallLoweringTests : AbstractCodegenTest() {
+
+ @Test
+ fun testInlineNoinline(): Unit = ensureSetup {
+ codegen(
+ """
+ @Composable
+ inline fun PointerInputWrapper(
+ crossinline children: @Composable() () -> Unit
+ ) {
+ LinearLayout {
+ children()
+ }
+ }
+
+ @Composable
+ fun PressReleasedGestureDetector(children: @Composable() () -> Unit) {
+ PointerInputWrapper {
+ children()
+ }
+ }
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testInlinedComposable(): Unit = ensureSetup {
+ codegen(
+ """
+ @Composable
+ inline fun Foo(crossinline children: @Composable() () -> Unit) {
+ children()
+ }
+
+ @Composable fun test(children: @Composable() () -> Unit) {
+ Foo {
+ println("hello world")
+ children()
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun testGenericEmittables(): Unit = ensureSetup {
+ codegen(
+ """
+ class FooKey<T>(val name: String)
+ class Foo<T>(val key: FooKey<T>, var value: T): Emittable {
+ override fun emitInsertAt(index: Int, instance: Emittable) {
+
+ }
+
+ override fun emitRemoveAt(index: Int, count: Int) {
+
+ }
+
+ override fun emitMove(from: Int, to: Int, count: Int) {
+
+ }
+ }
+
+ val AnyKey = FooKey<Any>("any")
+
+ @Composable fun test(value: Any, children: @Composable() () -> Unit) {
+ Foo(key=AnyKey, value=value) {
+ children()
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun testSetViewContentIssue(): Unit = ensureSetup {
+ codegen(
+ """
+ import android.app.Activity
+ import android.os.Bundle
+ import android.view.Gravity
+ import android.view.ViewGroup
+ import android.widget.*
+ import androidx.compose.*
+ import androidx.ui.androidview.adapters.*
+
+ class RippleActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setViewContent {
+ val layoutParams = LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 0,
+ 1f
+ )
+ val gravity = Gravity.CENTER_HORIZONTAL
+ LinearLayout(orientation = LinearLayout.VERTICAL) {
+ TextView(gravity = gravity, text = "Compose card with ripple:")
+ LinearLayout(layoutParams = layoutParams) {
+ // RippleDemo()
+ }
+ TextView(gravity = gravity, text = "Platform button with ripple:")
+ LinearLayout(layoutParams = layoutParams, padding = 50.dp) {
+ // Button(background = getDrawable(R.drawable.ripple))
+ }
+ }
+ }
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun testGenericParameterOrderIssue(): Unit = ensureSetup {
+ codegen(
+ """
+@Composable
+fun A() {
+ val x = ""
+ val y = ""
+
+ B(bar = x, foo = y)
+}
+
+@Composable
+fun <T> B(foo: T, bar: String) { }
+ """
+ )
+ }
+
+ @Test
+ fun testArgumentOrderIssue(): Unit = ensureSetup {
+ codegen(
+ """
+ class A
+
+ @Composable
+ fun B() {
+ C(bar = 1, foo = 1f)
+ }
+
+ @Composable
+ fun C(
+ foo: Float,
+ bar: Int
+ ) {
+
+ }
+ """
+ )
+ }
+
+ @Test
+ fun testObjectName(): Unit = ensureSetup {
+ codegen(
+ """
+
+ @Composable fun SomeThing(children: @Composable() () -> Unit) {}
+
+ @Composable
+ fun Example() {
+ SomeThing {
+ val id = object {}
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun testWebViewBug(): Unit = ensureSetup {
+ codegen(
+ """
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.compose.Composable
+import androidx.compose.composer
+
+class WebContext {
+ var webView: WebView? = null
+}
+
+private fun WebView.setRef(ref: (WebView) -> Unit) {
+ ref(this)
+}
+
+@Composable
+fun WebComponent(
+ url: String,
+ webViewClient: WebViewClient = WebViewClient(),
+ webContext: WebContext
+) {
+
+ WebView(
+ ref = { webContext.webView = it }
+ )
+}
+ """
+ )
+ }
+
+ @Test
+ fun testStuffThatIWantTo(): Unit = ensureSetup {
+ codegen(
+ """
+
+ fun startCompose(block: @Composable() () -> Unit) {}
+
+ fun nonComposable() {
+ startCompose {
+ LinearLayout {
+
+ }
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun testSimpleFunctionResolution(): Unit = ensureSetup {
+ compose(
+ """
+ import androidx.compose.*
+
+ @Composable
+ fun noise(text: String) {}
+
+ @Composable
+ fun bar() {
+ noise(text="Hello World")
+ }
+ """,
+ """
+ """
+ )
+ }
+
+ @Test
+ fun testSimpleClassResolution(): Unit = ensureSetup {
+ compose(
+ """
+ import android.widget.TextView
+ import androidx.compose.*
+
+ @Composable
+ fun bar() {
+ TextView(text="Hello World")
+ }
+ """,
+ """
+ """
+ )
+ }
+
+ @Test
+ fun testSetContent(): Unit = ensureSetup {
+ codegen(
+ """
+ fun fakeCompose(block: @Composable() ()->Unit) { }
+
+ class Test {
+ fun test() {
+ fakeCompose {
+ LinearLayout(orientation = LinearLayout.VERTICAL) {}
+ }
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ @Ignore("b/142488002")
+ fun testReceiverScopeComposer(): Unit = ensureSetup {
+ codegen(
+ """
+ import androidx.compose.Applier
+ import androidx.compose.ApplyAdapter
+ import androidx.compose.Component
+ import androidx.compose.Composable
+ import androidx.compose.Composer
+ import androidx.compose.ComposerUpdater
+ import androidx.compose.CompositionContext
+ import androidx.compose.CompositionReference
+ import androidx.compose.Effect
+ import androidx.compose.Recomposer
+ import androidx.compose.SlotTable
+ import androidx.compose.ViewValidator
+ import androidx.compose.cache
+
+ class TextSpan(
+ val style: String? = null,
+ val text: String? = null,
+ val children: MutableList<TextSpan> = mutableListOf()
+ )
+
+ /**
+ * This adapter is used by [TextSpanComposer] to build the [TextSpan] tree.
+ * @see ApplyAdapter
+ */
+ internal class TextSpanApplyAdapter : ApplyAdapter<TextSpan> {
+ override fun TextSpan.start(instance: TextSpan) {}
+
+ override fun TextSpan.end(instance: TextSpan, parent: TextSpan) {}
+
+ override fun TextSpan.insertAt(index: Int, instance: TextSpan) {
+ children.add(index, instance)
+ }
+
+ override fun TextSpan.removeAt(index: Int, count: Int) {
+ repeat(count) {
+ children.removeAt(index)
+ }
+ }
+
+ override fun TextSpan.move(from: Int, to: Int, count: Int) {
+ if (from == to) return
+
+ if (from > to) {
+ val moved = mutableListOf<TextSpan>()
+ repeat(count) {
+ moved.add(children.removeAt(from))
+ }
+ children.addAll(to, moved)
+ } else {
+ // Number of elements between to and from is smaller than count, can't move.
+ if (count > to - from) return
+ repeat(count) {
+ val node = children.removeAt(from)
+ children.add(to - 1, node)
+ }
+ }
+ }
+ }
+
+ typealias TextSpanUpdater<T> = ComposerUpdater<TextSpan, T>
+
+ /**
+ * The composer of [TextSpan].
+ */
+ class TextSpanComposer internal constructor(
+ root: TextSpan,
+ recomposer: Recomposer
+ ) : Composer<TextSpan>(SlotTable(), Applier(root, TextSpanApplyAdapter()), recomposer)
+
+ @PublishedApi
+ internal val invocation = Object()
+
+ class TextSpanComposition(val composer: TextSpanComposer) {
+ @Suppress("NOTHING_TO_INLINE")
+ inline operator fun <V> Effect<V>.unaryPlus(): V = resolve(this@TextSpanComposition.composer)
+
+ inline fun emit(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> TextSpan,
+ update: TextSpanUpdater<TextSpan>.() -> Unit
+ ) = with(composer) {
+ startNode(key)
+ @Suppress("UNCHECKED_CAST") val node = if (inserting) ctor().also { emitNode(it) }
+ else useNode()
+ TextSpanUpdater(this, node).update()
+ endNode()
+ }
+
+ inline fun emit(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> TextSpan,
+ update: TextSpanUpdater<TextSpan>.() -> Unit,
+ children: () -> Unit
+ ) = with(composer) {
+ startNode(key)
+ @Suppress("UNCHECKED_CAST")val node = if (inserting) ctor().also { emitNode(it) }
+ else useNode()
+ TextSpanUpdater(this, node).update()
+ children()
+ endNode()
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun joinKey(left: Any, right: Any?): Any = composer.joinKey(left, right)
+
+ inline fun call(
+ key: Any,
+ /*crossinline*/
+ invalid: ViewValidator.() -> Boolean,
+ block: () -> Unit
+ ) = with(composer) {
+ startGroup(key)
+ if (ViewValidator(composer).invalid() || inserting) {
+ startGroup(invocation)
+ block()
+ endGroup()
+ } else {
+ skipCurrentGroup()
+ }
+ endGroup()
+ }
+
+ inline fun <T> call(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> T,
+ /*crossinline*/
+ invalid: ViewValidator.(f: T) -> Boolean,
+ block: (f: T) -> Unit
+ ) = with(composer) {
+ startGroup(key)
+ val f = cache(true, ctor)
+ if (ViewValidator(this).invalid(f) || inserting) {
+ startGroup(invocation)
+ block(f)
+ endGroup()
+ } else {
+ skipCurrentGroup()
+ }
+ endGroup()
+ }
+ }
+
+ /**
+ * As the name indicates, [Root] object is associated with a [TextSpan] tree root. It contains
+ * necessary information used to compose and recompose [TextSpan] tree. It's created and stored
+ * when the [TextSpan] container is composed for the first time.
+ */
+ private class Root : Component() {
+ fun update() = composer.compose()
+ lateinit var scope: TextSpanScope
+ lateinit var composer: CompositionContext
+ lateinit var composable: @Composable() TextSpanScope.() -> Unit
+ @Suppress("PLUGIN_ERROR")
+ override fun compose() {
+ with(scope.composer.composer) {
+ startGroup(0)
+ scope.composable()
+ endGroup()
+ }
+ }
+ }
+
+ /**
+ * The map used store the [Root] object for [TextSpan] trees.
+ */
+ private val TEXTSPAN_ROOT_COMPONENTS = HashMap<TextSpan, Root>()
+
+ /**
+ * Get the [Root] object of the given [TextSpan] root node.
+ */
+ private fun getRootComponent(node: TextSpan): Root? {
+ return TEXTSPAN_ROOT_COMPONENTS[node]
+ }
+
+ /**
+ * Store the [Root] object of [node].
+ */
+ private fun setRoot(node: TextSpan, component: Root) {
+ TEXTSPAN_ROOT_COMPONENTS[node] = component
+ }
+
+ /**
+ * Compose a [TextSpan] tree.
+ * @param container The root of [TextSpan] tree where the children TextSpans will be attached to.
+ * @param parent The parent composition reference, if applicable. Default is null.
+ * @param composable The composable function to compose the children of [container].
+ * @see CompositionReference
+ */
+ @Suppress("PLUGIN_ERROR")
+ fun compose(
+ container: TextSpan,
+ parent: CompositionReference? = null,
+ composable: @Composable() TextSpanScope.() -> Unit
+ ) {
+ var root = getRootComponent(container)
+ if (root == null) {
+ lateinit var composer: TextSpanComposer
+ root = Root()
+ setRoot(container, root)
+ root.composer = CompositionContext.prepare(root, parent) {
+ TextSpanComposer(container, this).also { composer = it }
+ }
+ root.scope = TextSpanScope(TextSpanComposition(composer))
+ root.composable = composable
+
+ root.update()
+ } else {
+ root.composable = composable
+
+ root.update()
+ }
+ }
+
+ /**
+ * Cleanup when the [TextSpan] is no longer used.
+ *
+ * @param container The root of the [TextSpan] to be disposed.
+ * @param parent The [CompositionReference] used together with [container] when [composer] is
+ * called.
+ */
+ fun disposeComposition(
+ container: TextSpan,
+ parent: CompositionReference? = null
+ ) {
+ // temporary easy way to call correct lifecycles on everything
+ compose(container, parent) {}
+ TEXTSPAN_ROOT_COMPONENTS.remove(container)
+ }
+
+ /**
+ * The receiver class of the children of Text and TextSpan. Such that [Span] can only be used
+ * within [Text] and [TextSpan].
+ */
+ class TextSpanScope internal constructor(val composer: TextSpanComposition)
+
+ @Composable
+ fun TextSpanScope.Span(
+ text: String? = null,
+ style: String? = null,
+ child: @Composable TextSpanScope.() -> Unit
+ ) {
+ TextSpan(text = text, style = style) {
+ child()
+ }
+ }
+
+ @Composable
+ fun TextSpanScope.Span(
+ text: String? = null,
+ style: String? = null
+ ) {
+ TextSpan(text = text, style = style)
+ }
+ """
+ )
+ }
+
+ @Test
+ fun testComposeWithResult(): Unit = ensureSetup {
+ compose(
+ """
+ fun <T> identity(block: @Composable() ()->T): T = block()
+
+ @Composable
+ fun TestCall() {
+ val value: Any = identity { 12 }
+ TextView(text = value.toString(), id = 100)
+ }
+ """,
+ "TestCall()"
+ ).then { activity ->
+ val textView = activity.findViewById<TextView>(100)
+ assertEquals("12", textView.text)
+ }
+ }
+
+ @Test
+ fun testObservable(): Unit = ensureSetup {
+ compose(
+ """
+ import android.widget.Button
+ import androidx.compose.*
+ import androidx.ui.androidview.adapters.setOnClick
+
+ @Model
+ class FancyButtonData() {
+ var x = 0
+ }
+
+ @Composable
+ fun SimpleComposable() {
+ FancyButton(state=FancyButtonData())
+ }
+
+ @Composable
+ fun FancyButton(state: FancyButtonData) {
+ Button(text=("Clicked "+state.x+" times"), id=42)
+ }
+ """,
+ "SimpleComposable()"
+ ).then { activity ->
+ val button = activity.findViewById(42) as Button
+ button.performClick()
+ button.performClick()
+ button.performClick()
+ }.then { activity ->
+ val button = activity.findViewById(42) as Button
+ assertEquals("Clicked 3 times", button.text)
+ }
+ }
+
+ @Test // java.lang.ClassNotFoundException: Z
+ fun testObservableLambda(): Unit = ensureSetup {
+ compose(
+ """
+ @Model
+ class FancyButtonCount() {
+ var count = 0
+ }
+
+ @Composable
+ fun SimpleComposable(state: FancyButtonCount) {
+ FancyBox2 {
+ Button(
+ text=("Button clicked "+state.count+" times"),
+ >
+ id=42
+ )
+ }
+ }
+
+ @Composable
+ fun FancyBox2(children: @Composable() ()->Unit) {
+ children()
+ }
+ """,
+ "SimpleComposable(state=+memo { FancyButtonCount() })"
+ ).then { activity ->
+ val button = activity.findViewById(42) as Button
+ button.performClick()
+ button.performClick()
+ button.performClick()
+ }.then { activity ->
+ val button = activity.findViewById(42) as Button
+ assertEquals("Button clicked 3 times", button.text)
+ }
+ }
+
+ @Test
+ fun testObservableGenericFunction(): Unit = ensureSetup {
+ compose("""
+ @Model
+ class Counter() {
+ var count = 0
+ }
+
+ @Composable
+ fun <T> SimpleComposable(state: Counter, value: T) {
+ Button(
+ text=("Button clicked "+state.count+" times: " + value),
+ >
+ id=42
+ )
+ }
+ """,
+ "SimpleComposable(state=+memo { Counter() }, value=\"Value\")"
+ ).then { activity ->
+ val button = activity.findViewById(42) as Button
+ button.performClick()
+ button.performClick()
+ button.performClick()
+ }.then { activity ->
+ val button = activity.findViewById(42) as Button
+ assertEquals("Button clicked 3 times: Value", button.text)
+ }
+ }
+
+ @Test
+ fun testObservableExtension(): Unit = ensureSetup {
+ compose("""
+ @Model
+ class Counter() {
+ var count = 0
+ }
+
+ @Composable
+ fun Counter.Composable() {
+ Button(
+ text="Button clicked "+count+" times",
+ >
+ id=42
+ )
+ }
+
+ val myCounter = Counter()
+ """,
+ "myCounter.Composable()"
+ ).then { activity ->
+ val button = activity.findViewById<Button>(42)
+ button.performClick()
+ button.performClick()
+ button.performClick()
+ }.then { activity ->
+ val button = activity.findViewById<Button>(42)
+ assertEquals("Button clicked 3 times", button.text)
+ }
+ }
+
+ @Test
+ fun testObserverableExpressionBody(): Unit = ensureSetup {
+ compose("""
+ @Model
+ class Counter() {
+ var count = 0
+ }
+
+ @Composable
+ fun SimpleComposable(counter: Counter) =
+ Button(
+ text="Button clicked "+counter.count+" times",
+ >
+ id=42
+ )
+
+ @Composable
+ fun SimpleWrapper(counter: Counter) = SimpleComposable(counter = counter)
+
+ val myCounter = Counter()
+ """,
+ "SimpleWrapper(counter = myCounter)"
+ ).then { activity ->
+ val button = activity.findViewById<Button>(42)
+ button.performClick()
+ button.performClick()
+ button.performClick()
+ }.then { activity ->
+ val button = activity.findViewById<Button>(42)
+ assertEquals("Button clicked 3 times", button.text)
+ }
+ }
+
+ @Test
+ fun testObservableInlineWrapper(): Unit = ensureSetup {
+ compose("""
+ @Model
+ class Counter() {
+ var count = 0
+ }
+
+ var inWrapper = false
+ val counter = Counter()
+
+ inline fun wrapper(block: () -> Unit) {
+ inWrapper = true
+ try {
+ block()
+ } finally {
+ inWrapper = false
+ }
+ }
+
+ @Composable
+ fun SimpleComposable(state: Counter) {
+ wrapper {
+ Button(
+ text=("Button clicked "+state.count+" times"),
+ >
+ id=42
+ )
+ }
+ }
+ """,
+ "SimpleComposable(state=counter)"
+ ).then { activity ->
+ val button = activity.findViewById(42) as Button
+ button.performClick()
+ button.performClick()
+ button.performClick()
+ }.then { activity ->
+ val button = activity.findViewById(42) as Button
+ assertEquals("Button clicked 3 times", button.text)
+ }
+ }
+
+ @Test
+ fun testObservableDefaultParameter(): Unit = ensureSetup {
+ compose("""
+ @Model
+ class Counter() {
+ var count = 0
+ }
+
+ val counter = Counter()
+
+ @Composable
+ fun SimpleComposable(state: Counter, a: Int = 1, b: Int = 2) {
+ Button(
+ text=("State: ${'$'}{state.count} a = ${'$'}a b = ${'$'}b"),
+ >
+ id=42
+ )
+ }
+ """,
+ "SimpleComposable(state=counter, b = 4)"
+ ).then { activity ->
+ val button = activity.findViewById(42) as Button
+ button.performClick()
+ button.performClick()
+ button.performClick()
+ }.then { activity ->
+ val button = activity.findViewById(42) as Button
+ assertEquals("State: 3 a = 1 b = 4", button.text)
+ }
+ }
+
+ @Test
+ fun testObservableEarlyReturn(): Unit = ensureSetup {
+ compose("""
+ @Model
+ class Counter() {
+ var count = 0
+ }
+
+ val counter = Counter()
+
+ @Composable
+ fun SimpleComposable(state: Counter) {
+ Button(
+ text=("State: ${'$'}{state.count}"),
+ >
+ id=42
+ )
+
+ if (state.count > 2) return
+
+ TextView(
+ text="Included text",
+ id=43
+ )
+ }
+ """,
+ "SimpleComposable(state=counter)"
+ ).then { activity ->
+ // Check that the text view is in the view
+ assertNotNull(activity.findViewById(43))
+ val button = activity.findViewById(42) as Button
+ button.performClick()
+ button.performClick()
+ button.performClick()
+ }.then { activity ->
+ val button = activity.findViewById<Button>(42)
+ assertEquals("State: 3", button.text)
+
+ // Assert that the text view is no longer in the view
+ assertNull(activity.findViewById<Button>(43))
+ }
+ }
+
+ @Test
+ fun testCGSimpleTextView(): Unit = ensureSetup {
+ compose(
+ """
+
+ """,
+ """
+ TextView(text="Hello, world!", id=42)
+ """
+ ).then { activity ->
+ val textView = activity.findViewById(42) as TextView
+ assertEquals("Hello, world!", textView.text)
+ }
+ }
+
+ @Test
+ fun testCGLocallyScopedFunction(): Unit = ensureSetup {
+ compose(
+ """
+ @Composable
+ fun Foo() {
+ @Composable fun Bar() {
+ TextView(text="Hello, world!", id=42)
+ }
+ Bar()
+ }
+ """,
+ """
+ Foo()
+ """
+ ).then { activity ->
+ val textView = activity.findViewById(42) as TextView
+ assertEquals("Hello, world!", textView.text)
+ }
+ }
+
+ @Test
+ fun testCGLocallyScopedExtensionFunction(): Unit = ensureSetup {
+ compose(
+ """
+ @Composable
+ fun Foo(x: String) {
+ @Composable fun String.Bar() {
+ TextView(text=this, id=42)
+ }
+ x.Bar()
+ }
+ """,
+ """
+ Foo(x="Hello, world!")
+ """
+ ).then { activity ->
+ val textView = activity.findViewById(42) as TextView
+ assertEquals("Hello, world!", textView.text)
+ }
+ }
+
+ @Test
+ fun testImplicitReceiverScopeCall(): Unit = ensureSetup {
+ compose(
+ """
+ import androidx.compose.*
+
+ class Bar(val text: String)
+
+ @Composable fun Bar.Foo() {
+ TextView(text=text,id=42)
+ }
+
+ @Composable
+ fun Bam(bar: Bar) {
+ with(bar) {
+ Foo()
+ }
+ }
+ """,
+ """
+ Bam(bar=Bar("Hello, world!"))
+ """
+ ).then { activity ->
+ val textView = activity.findViewById(42) as TextView
+ assertEquals("Hello, world!", textView.text)
+ }
+ }
+
+ @Test
+ fun testCGLocallyScopedInvokeOperator(): Unit = ensureSetup {
+ compose(
+ """
+ @Composable
+ fun Foo(x: String) {
+ @Composable
+ operator fun String.invoke() {
+ TextView(text=this, id=42)
+ }
+ x()
+ }
+ """,
+ """
+ Foo(x="Hello, world!")
+ """
+ ).then { activity ->
+ val textView = activity.findViewById(42) as TextView
+ assertEquals("Hello, world!", textView.text)
+ }
+ }
+
+ @Test
+ fun testTrivialExtensionFunction(): Unit = ensureSetup {
+ compose(
+ """ """,
+ """
+ val x = "Hello"
+ @Composable fun String.foo() {}
+ x.foo()
+ """
+ )
+ }
+
+ @Test
+ fun testTrivialInvokeExtensionFunction(): Unit = ensureSetup {
+ compose(
+ """ """,
+ """
+ val x = "Hello"
+ @Composable operator fun String.invoke() {}
+ x()
+ """
+ )
+ }
+
+ @Test
+ fun testCGNSimpleTextView(): Unit = ensureSetup {
+ compose(
+ """
+
+ """,
+ """
+ TextView(text="Hello, world!", id=42)
+ """
+ ).then { activity ->
+ val textView = activity.findViewById(42) as TextView
+ assertEquals("Hello, world!", textView.text)
+ }
+ }
+
+ @Test // java.lang.ClassNotFoundException: Z
+ fun testInliningTemp(): Unit = ensureSetup {
+ compose(
+ """
+ @Composable
+ fun Foo(x: Double, children: @Composable Double.() -> Unit) {
+ x.children()
+ }
+ """,
+ """
+ Foo(x=1.0) {
+ TextView(text=this.toString(), id=123)
+ }
+ """,
+ { mapOf("foo" to "bar") }
+ ).then { activity ->
+ val textView = activity.findViewById(123) as TextView
+ assertEquals("1.0", textView.text)
+ }
+ }
+
+ @Test
+ fun testInliningTemp2(): Unit = ensureSetup {
+ compose(
+ """
+ @Composable
+ fun Foo(onClick: Double.() -> Unit) {
+
+ }
+ """,
+ """
+ Foo(>
+ """,
+ { mapOf("foo" to "bar") }
+ ).then { }
+ }
+
+ @Test
+ fun testInliningTemp3(): Unit = ensureSetup {
+ compose(
+ """
+ @Composable
+ fun Foo(onClick: (Double) -> Unit) {
+
+ }
+ """,
+ """
+ Foo(>
+ """,
+ { mapOf("foo" to "bar") }
+ ).then { }
+ }
+
+ @Test
+ fun testInliningTemp4(): Unit = ensureSetup {
+ compose(
+ """
+ @Composable
+ fun Foo(onClick: (Double) -> Unit) {
+
+ }
+ """,
+ """
+ Foo(>
+ """,
+ { mapOf("foo" to "bar") }
+ ).then {}
+ }
+
+ @Test
+ fun testInline_NonComposable_Identity(): Unit = ensureSetup {
+ compose("""
+ inline fun InlineWrapper(base: Int, children: @Composable() ()->Unit) {
+ children()
+ }
+ """,
+ """
+ InlineWrapper(200) {
+ TextView(text = "Test", id=101)
+ }
+ """).then { activity ->
+ assertEquals("Test", activity.findViewById<TextView>(101).text)
+ }
+ }
+
+ @Test
+ fun testInline_Composable_Identity(): Unit = ensureSetup {
+ compose("""
+ """,
+ """
+ TextView(text="Test", id=101)
+ """).then { activity ->
+ assertEquals("Test", activity.findViewById<TextView>(101).text)
+ }
+ }
+
+ @Test
+ fun testInline_Composable_EmitChildren(): Unit = ensureSetup {
+ compose("""
+ @Composable
+ inline fun InlineWrapper(base: Int, crossinline children: @Composable() ()->Unit) {
+ LinearLayout(id = base + 0) {
+ children()
+ }
+ }
+ """,
+ """
+ InlineWrapper(200) {
+ TextView(text = "Test", id=101)
+ }
+ """).then { activity ->
+ val tv = activity.findViewById<TextView>(101)
+ // Assert the TextView was created with the correct text
+ assertEquals("Test", tv.text)
+ // and it is the first child of the linear layout
+ assertEquals(tv, activity.findViewById<LinearLayout>(200).getChildAt(0))
+ }
+ }
+
+ @Test // java.lang.ClassNotFoundException: Z
+ fun testCGNInlining(): Unit = ensureSetup {
+ compose(
+ """
+
+ """,
+ """
+ LinearLayout(orientation=LinearLayout.VERTICAL) {
+ TextView(text="Hello, world!", id=42)
+ }
+ """
+ ).then { activity ->
+ val textView = activity.findViewById(42) as TextView
+ assertEquals("Hello, world!", textView.text)
+ }
+ }
+
+ private var isSetup = false
+ override fun setUp() {
+ isSetup = true
+ super.setUp()
+ }
+
+ private fun <T> ensureSetup(block: () -> T): T {
+ if (!isSetup) setUp()
+ return block()
+ }
+
+ fun codegen(text: String, dumpClasses: Boolean = false): Unit = ensureSetup {
+ val className = "Test_${uniqueNumber++}"
+ val fileName = "$className.kt"
+
+ classLoader(
+ """
+ import android.content.Context
+ import android.widget.*
+ import androidx.compose.*
+
+ $text
+
+ """, fileName, dumpClasses
+ )
+ }
+
+ fun assertInterceptions(srcText: String) = ensureSetup {
+ val (text, carets) = extractCarets(srcText)
+
+ val environment = myEnvironment ?: error("Environment not initialized")
+
+ val ktFile = KtPsiFactory(environment.project).createFile(text)
+ val bindingContext = JvmResolveUtil.analyze(
+ ktFile,
+ environment
+ ).bindingContext
+
+ carets.forEachIndexed { index, (offset, calltype) ->
+ val resolvedCall = resolvedCallAtOffset(bindingContext, ktFile, offset)
+ ?: error("No resolved call found at index: $index, offset: $offset. Expected " +
+ "$calltype.")
+
+ when (calltype) {
+ "<normal>" -> assert(!resolvedCall.isCall() && !resolvedCall.isEmit())
+ "<emit>" -> assert(resolvedCall.isEmit())
+ "<call>" -> assert(resolvedCall.isCall())
+ else -> error("Call type of $calltype not recognized.")
+ }
+ }
+ }
+
+ fun compose(
+ supportingCode: String,
+ composeCode: String,
+ valuesFactory: () -> Map<String, Any> = { emptyMap() },
+ dumpClasses: Boolean = false
+ ): CompositionTest {
+ val className = "TestFCS_${uniqueNumber++}"
+ val fileName = "$className.kt"
+
+ val candidateValues = valuesFactory()
+
+ @Suppress("NO_REFLECTION_IN_CLASS_PATH")
+ val parameterList = candidateValues.map {
+ if (it.key.contains(':')) {
+ it.key
+ } else "${it.key}: ${it.value::class.qualifiedName}"
+ }.joinToString()
+ val parameterTypes = candidateValues.map {
+ it.value::class.javaPrimitiveType ?: it.value::class.javaObjectType
+ }.toTypedArray()
+
+ val compiledClasses = classLoader(
+ """
+ import android.content.Context
+ import android.widget.*
+ import androidx.compose.*
+ import androidx.ui.androidview.adapters.*
+
+ $supportingCode
+
+ class $className {
+
+ @Composable
+ fun test($parameterList) {
+ $composeCode
+ }
+ }
+ """, fileName, dumpClasses
+ )
+
+ val allClassFiles = compiledClasses.allGeneratedFiles.filter {
+ it.relativePath.endsWith(".class")
+ }
+
+ val loader = URLClassLoader(emptyArray(), this.javaClass.classLoader)
+
+ val instanceClass = run {
+ var instanceClass: Class<*>? = null
+ var loadedOne = false
+ for (outFile in allClassFiles) {
+ val bytes = outFile.asByteArray()
+ val loadedClass = loadClass(loader, null, bytes)
+ if (loadedClass.name == className) instanceClass = loadedClass
+ loadedOne = true
+ }
+ if (!loadedOne) error("No classes loaded")
+ instanceClass ?: error("Could not find class $className in loaded classes")
+ }
+
+ val instanceOfClass = instanceClass.newInstance()
+ val testMethod = instanceClass.getMethod("test", *parameterTypes)
+
+ return compose {
+ val values = valuesFactory()
+ val arguments = values.map { it.value }.toTypedArray()
+ testMethod.invoke(instanceOfClass, *arguments)
+ }
+ }
+
+ private fun ResolvedCall<*>.isEmit(): Boolean = candidateDescriptor is ComposableEmitDescriptor
+ private fun ResolvedCall<*>.isCall(): Boolean =
+ candidateDescriptor is ComposableFunctionDescriptor
+
+ private val callPattern = Regex("(<normal>)|(<emit>)|(<call>)")
+ private fun extractCarets(text: String): Pair<String, List<Pair<Int, String>>> {
+ val indices = mutableListOf<Pair<Int, String>>()
+ var offset = 0
+ val src = callPattern.replace(text) {
+ indices.add(it.range.first - offset to it.value)
+ offset += it.range.last - it.range.first + 1
+ ""
+ }
+ return src to indices
+ }
+
+ private fun resolvedCallAtOffset(
+ bindingContext: BindingContext,
+ jetFile: KtFile,
+ index: Int
+ ): ResolvedCall<*>? {
+ val element = jetFile.findElementAt(index)!!
+ val callExpression = element.parentOfType<KtCallExpression>()
+ return callExpression?.getResolvedCall(bindingContext)
+ }
+}
+
+private inline fun <reified T : PsiElement> PsiElement.parentOfType(): T? = parentOfType(T::class)
+
+private fun <T : PsiElement> PsiElement.parentOfType(vararg classes: KClass<out T>): T? {
+ return PsiTreeUtil.getParentOfType(this, *classes.map { it.java }.toTypedArray())
+}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolutionDiagnosticsTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolutionDiagnosticsTests.kt
new file mode 100644
index 0000000..3a03419
--- /dev/null
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolutionDiagnosticsTests.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2019 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.plugins.kotlin
+
+class ComposeCallResolutionDiagnosticsTests : AbstractComposeDiagnosticsTest() {
+
+ private var isSetup = false
+ override fun setUp() {
+ isSetup = true
+ super.setUp()
+ }
+
+ private fun <T> ensureSetup(block: () -> T): T {
+ if (!isSetup) setUp()
+ return block()
+ }
+
+ private fun setupAndDoTest(text: String) = ensureSetup { doTest(text) }
+
+ fun testImplicitlyPassedReceiverScope1() = setupAndDoTest(
+ """
+ import androidx.compose.*
+ import android.widget.*
+ import android.os.Bundle
+ import android.app.Activity
+ import android.widget.FrameLayout
+
+ val x: Any? = null
+
+ fun Activity.setViewContent(composable: @Composable() () -> Unit): CompositionContext? {
+ assert(composable != x)
+ return null
+ }
+
+ open class WebComponentActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setViewContent {
+ FrameLayout {
+ }
+ }
+ }
+ }
+ """
+ )
+
+ fun testSimpleReceiverScope() = setupAndDoTest(
+ """
+ import android.widget.FrameLayout
+ import androidx.compose.Composable
+
+ class SomeScope {
+ val composer get() = androidx.compose.composer
+ }
+
+ @Composable fun SomeScope.foo() {
+ FrameLayout { }
+ }
+
+ """
+ )
+
+ fun testReceiverScopeComposer() = setupAndDoTest(
+ """
+ import androidx.compose.Applier
+ import androidx.compose.ApplyAdapter
+ import androidx.compose.Component
+ import androidx.compose.Composable
+ import androidx.compose.Composer
+ import androidx.compose.ComposerUpdater
+ import androidx.compose.CompositionContext
+ import androidx.compose.CompositionReference
+ import androidx.compose.Effect
+ import androidx.compose.Recomposer
+ import androidx.compose.SlotTable
+ import androidx.compose.ViewValidator
+ import androidx.compose.cache
+
+ class TextSpan(
+ val style: String? = null,
+ val text: String? = null,
+ val children: MutableList<TextSpan> = mutableListOf()
+ )
+
+ /**
+ * This adapter is used by [TextSpanComposer] to build the [TextSpan] tree.
+ * @see ApplyAdapter
+ */
+ internal class TextSpanApplyAdapter : ApplyAdapter<TextSpan> {
+ override fun TextSpan.start(instance: TextSpan) {}
+
+ override fun TextSpan.end(instance: TextSpan, parent: TextSpan) {}
+
+ override fun TextSpan.insertAt(index: Int, instance: TextSpan) {
+ children.add(index, instance)
+ }
+
+ override fun TextSpan.removeAt(index: Int, count: Int) {
+ repeat(count) {
+ children.removeAt(index)
+ }
+ }
+
+ override fun TextSpan.move(from: Int, to: Int, count: Int) {
+ if (from == to) return
+
+ if (from > to) {
+ val moved = mutableListOf<TextSpan>()
+ repeat(count) {
+ moved.add(children.removeAt(from))
+ }
+ children.addAll(to, moved)
+ } else {
+ // Number of elements between to and from is smaller than count, can't move.
+ if (count > to - from) return
+ repeat(count) {
+ val node = children.removeAt(from)
+ children.add(to - 1, node)
+ }
+ }
+ }
+ }
+
+ typealias TextSpanUpdater<T> = ComposerUpdater<TextSpan, T>
+
+ /**
+ * The composer of [TextSpan].
+ */
+ class TextSpanComposer internal constructor(
+ root: TextSpan,
+ recomposer: Recomposer
+ ) : Composer<TextSpan>(SlotTable(), Applier(root, TextSpanApplyAdapter()), recomposer)
+
+ @PublishedApi
+ internal val invocation = Object()
+
+ class TextSpanComposition(val composer: TextSpanComposer) {
+ @Suppress("NOTHING_TO_INLINE")
+ inline operator fun <V> Effect<V>.unaryPlus(): V = resolve(this@TextSpanComposition.composer)
+
+ inline fun emit(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> TextSpan,
+ update: TextSpanUpdater<TextSpan>.() -> Unit
+ ) = with(composer) {
+ startNode(key)
+ @Suppress("UNCHECKED_CAST") val node = if (inserting) ctor().also { emitNode(it) }
+ else useNode()
+ TextSpanUpdater(this, node).update()
+ endNode()
+ }
+
+ inline fun emit(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> TextSpan,
+ update: TextSpanUpdater<TextSpan>.() -> Unit,
+ children: () -> Unit
+ ) = with(composer) {
+ startNode(key)
+ @Suppress("UNCHECKED_CAST")val node = if (inserting) ctor().also { emitNode(it) }
+ else useNode()
+ TextSpanUpdater(this, node).update()
+ children()
+ endNode()
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun joinKey(left: Any, right: Any?): Any = composer.joinKey(left, right)
+
+ inline fun call(
+ key: Any,
+ /*crossinline*/
+ invalid: ViewValidator.() -> Boolean,
+ block: () -> Unit
+ ) = with(composer) {
+ startGroup(key)
+ if (ViewValidator(composer).invalid() || inserting) {
+ startGroup(invocation)
+ block()
+ endGroup()
+ } else {
+ skipCurrentGroup()
+ }
+ endGroup()
+ }
+
+ inline fun <T> call(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> T,
+ /*crossinline*/
+ invalid: ViewValidator.(f: T) -> Boolean,
+ block: (f: T) -> Unit
+ ) = with(composer) {
+ startGroup(key)
+ val f = cache(true, ctor)
+ if (ViewValidator(this).invalid(f) || inserting) {
+ startGroup(invocation)
+ block(f)
+ endGroup()
+ } else {
+ skipCurrentGroup()
+ }
+ endGroup()
+ }
+ }
+
+ /**
+ * As the name indicates, [Root] object is associated with a [TextSpan] tree root. It contains
+ * necessary information used to compose and recompose [TextSpan] tree. It's created and stored
+ * when the [TextSpan] container is composed for the first time.
+ */
+ private class Root : Component() {
+ fun update() = composer.compose()
+ lateinit var scope: TextSpanScope
+ lateinit var composer: CompositionContext
+ lateinit var composable: @Composable() TextSpanScope.() -> Unit
+ @Suppress("PLUGIN_ERROR")
+ override fun compose() {
+ with(scope.composer.composer) {
+ startGroup(0)
+ scope.composable()
+ endGroup()
+ }
+ }
+ }
+
+ /**
+ * The map used store the [Root] object for [TextSpan] trees.
+ */
+ private val TEXTSPAN_ROOT_COMPONENTS = HashMap<TextSpan, Root>()
+
+ /**
+ * Get the [Root] object of the given [TextSpan] root node.
+ */
+ private fun getRootComponent(node: TextSpan): Root? {
+ return TEXTSPAN_ROOT_COMPONENTS[node]
+ }
+
+ /**
+ * Store the [Root] object of [node].
+ */
+ private fun setRoot(node: TextSpan, component: Root) {
+ TEXTSPAN_ROOT_COMPONENTS[node] = component
+ }
+
+ /**
+ * Compose a [TextSpan] tree.
+ * @param container The root of [TextSpan] tree where the children TextSpans will be attached to.
+ * @param parent The parent composition reference, if applicable. Default is null.
+ * @param composable The composable function to compose the children of [container].
+ * @see CompositionReference
+ */
+ @Suppress("PLUGIN_ERROR")
+ fun compose(
+ container: TextSpan,
+ parent: CompositionReference? = null,
+ composable: @Composable() TextSpanScope.() -> Unit
+ ) {
+ var root = getRootComponent(container)
+ if (root == null) {
+ lateinit var composer: TextSpanComposer
+ root = Root()
+ setRoot(container, root)
+ root.composer = CompositionContext.prepare(root, parent) {
+ TextSpanComposer(container, this).also { composer = it }
+ }
+ root.scope = TextSpanScope(TextSpanComposition(composer))
+ root.composable = composable
+
+ root.update()
+ } else {
+ root.composable = composable
+
+ root.update()
+ }
+ }
+
+ /**
+ * Cleanup when the [TextSpan] is no longer used.
+ *
+ * @param container The root of the [TextSpan] to be disposed.
+ * @param parent The [CompositionReference] used together with [container] when [composer] is
+ * called.
+ */
+ fun disposeComposition(
+ container: TextSpan,
+ parent: CompositionReference? = null
+ ) {
+ // temporary easy way to call correct lifecycles on everything
+ compose(container, parent) {}
+ TEXTSPAN_ROOT_COMPONENTS.remove(container)
+ }
+
+ /**
+ * The receiver class of the children of Text and TextSpan. Such that [Span] can only be used
+ * within [Text] and [TextSpan].
+ */
+ class TextSpanScope internal constructor(val composer: TextSpanComposition)
+
+ @Composable
+ fun TextSpanScope.Span(
+ text: String? = null,
+ style: String? = null,
+ child: @Composable TextSpanScope.() -> Unit
+ ) {
+ TextSpan(text = text, style = style) {
+ child()
+ }
+ }
+
+ @Composable
+ fun TextSpanScope.Span(
+ text: String? = null,
+ style: String? = null
+ ) {
+ TextSpan(text = text, style = style)
+ }
+ """
+ )
+}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolverTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolverTests.kt
index e5f25cb..7b6f48a 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolverTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolverTests.kt
@@ -34,7 +34,7 @@
import android.widget.TextView
@Composable fun Foo() {}
-
+
fun Bar() {}
@Composable
@@ -79,13 +79,7 @@
)
private fun <T> setup(block: () -> T): T {
- val original = ComposeFlags.NEW_CALL_RESOLUTION_INTERCEPTION
- try {
- ComposeFlags.NEW_CALL_RESOLUTION_INTERCEPTION = true
- return block()
- } finally {
- ComposeFlags.NEW_CALL_RESOLUTION_INTERCEPTION = original
- }
+ return block()
}
fun assertInterceptions(srcText: String) = setup {
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCodegenTestUtils.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCodegenTestUtils.kt
new file mode 100644
index 0000000..382af0c
--- /dev/null
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCodegenTestUtils.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019 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.
+ */
+
+@file:Suppress("MemberVisibilityCanBePrivate")
+
+package androidx.compose.plugins.kotlin
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.compose.Component
+import androidx.compose.CompositionContext
+import androidx.compose.Compose
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.Robolectric
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
+import java.net.URLClassLoader
+
+var uniqueNumber = 0
+
+fun loadClass(loader: ClassLoader, name: String?, bytes: ByteArray): Class<*> {
+ val defineClassMethod = ClassLoader::class.javaObjectType.getDeclaredMethod(
+ "defineClass",
+ String::class.javaObjectType,
+ ByteArray::class.javaObjectType,
+ Int::class.javaPrimitiveType,
+ Int::class.javaPrimitiveType
+ )
+ defineClassMethod.isAccessible = true
+ return defineClassMethod.invoke(loader, name, bytes, 0, bytes.size) as Class<*>
+}
+
+const val ROOT_ID = 18284847
+
+private class TestActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(LinearLayout(this).apply { id = ROOT_ID })
+ }
+}
+
+private val Activity.root get() = findViewById(ROOT_ID) as ViewGroup
+
+private class Root(val composable: () -> Unit) : Component() {
+ override fun compose() = composable()
+}
+
+class CompositionTest(val composable: () -> Unit) {
+
+ inner class ActiveTest(val activity: Activity, val cc: CompositionContext) {
+ fun then(block: (activity: Activity) -> Unit): ActiveTest {
+ val scheduler = RuntimeEnvironment.getMasterScheduler()
+ scheduler.advanceToLastPostedRunnable()
+ cc.compose()
+ scheduler.advanceToLastPostedRunnable()
+ block(activity)
+ return this
+ }
+ }
+
+ fun then(block: (activity: Activity) -> Unit): ActiveTest {
+ val scheduler = RuntimeEnvironment.getMasterScheduler()
+ scheduler.pause()
+ val controller = Robolectric.buildActivity(TestActivity::class.java)
+ val activity = controller.create().get()
+ val root = activity.root
+ val component = Root(composable)
+ val cc = Compose.createCompositionContext(root.context, root, component, null)
+ return ActiveTest(activity, cc).then(block)
+ }
+}
+
+fun compose(composable: () -> Unit) = CompositionTest(composable)
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeRobolectricTestRunner.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeRobolectricTestRunner.kt
index 3314aa1..8c2b058 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeRobolectricTestRunner.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeRobolectricTestRunner.kt
@@ -24,6 +24,7 @@
override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
val builder = InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
builder.doNotInstrumentPackage("androidx.compose")
+ builder.doNotInstrumentPackage("androidx.ui")
return builder.build()
}
}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsCodegenTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsCodegenTests.kt
index eb763b8..64ec08e 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsCodegenTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsCodegenTests.kt
@@ -39,6 +39,25 @@
class FcsCodegenTests : AbstractCodegenTest() {
@Test
+ fun testForDevelopment(): Unit = ensureSetup {
+ codegen(
+ """
+ import androidx.compose.*
+
+ @Composable
+ fun bar() {
+
+ }
+
+ @Composable
+ fun foo() {
+ TextView(text="Hello World")
+ }
+ """
+ )
+ }
+
+ @Test
fun testSimpleFunctionResolution(): Unit = ensureSetup {
compose(
"""
@@ -180,7 +199,7 @@
}
@Composable
- fun FancyBox2(@Children children: @Composable() ()->Unit) {
+ fun FancyBox2(children: @Composable() ()->Unit) {
children()
}
""",
@@ -564,7 +583,7 @@
compose(
"""
@Composable
- fun Foo(x: Double, @Children children: Double.() -> Unit) {
+ fun Foo(x: Double, children: @Composable Double.() -> Unit) {
x.children()
}
""",
@@ -1304,40 +1323,6 @@
}
}
- // @Test
- fun testCGNSimpleCall3(): Unit = ensureSetup {
- val tvId = 258
- var text = "Hello, world!"
- var someInt = 456
-
- compose(
- """
- @Stateful
- class SomeClassoawid(var x: String) {
- @Composable
- operator fun invoke(y: Int) {
- TextView(text="${"$"}x ${"$"}y", id=$tvId)
- }
- }
- """,
- { mapOf("text" to text, "someInt" to someInt) },
- """
- SomeClassoawid(x=text, y=someInt)
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals("Hello, world! 456", textView.text)
-
- text = "Other value"
- someInt = 123
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals("Other value 123", textView.text)
- }
- }
-
@Test
fun testCGNCallWithChildren(): Unit = ensureSetup {
val tvId = 258
@@ -1346,7 +1331,7 @@
compose(
"""
@Composable
- fun Block(@Children children: () -> Unit) {
+ fun Block(children: @Composable() () -> Unit) {
children()
}
""",
@@ -1733,7 +1718,7 @@
"""
var called = 0
- class TestContainer(@Children var children: @Composable() ()->Unit): Component() {
+ class TestContainer(var children: @Composable() ()->Unit): Component() {
override fun compose() {
LinearLayout {
children()
@@ -1768,7 +1753,7 @@
compose(
"""
- class ClassComponent(@Children private val callback: () -> Unit) : Component() {
+ class ClassComponent(private val callback: @Composable() () -> Unit) : Component() {
override fun compose() {
callback()
}
@@ -1907,7 +1892,7 @@
compose(
"""
@Composable
- fun Foo(a: Int = 42, b: String, @Children c: () -> Unit) {
+ fun Foo(a: Int = 42, b: String, c: @Composable() () -> Unit) {
c()
TextView(text=b, id=a)
}
@@ -1960,14 +1945,12 @@
override fun toString(): String = ""
}
-
class Canvas2() {
fun drawPath(path: Path3) {
System.out.println(""+path)
}
}
-
class Path3(private val internalPath: android.graphics.Path = android.graphics.Path()) {
}
"""
@@ -2075,7 +2058,7 @@
}
@Composable
- fun Box(@Children children: ()->Unit) {
+ fun Box(children: @Composable() ()->Unit) {
LinearLayout(orientation=LinearLayout.VERTICAL) {
children()
}
@@ -2175,7 +2158,7 @@
@Composable
fun DefineAction(
onAction: Action = Action(param = 1) {},
- @Children children: ()->Unit
+ children: @Composable() ()->Unit
) { }
"""
)
@@ -2244,7 +2227,7 @@
}
@Composable
- fun Main(v: ValueHolder) {
+ fun Main(v: ValueHolder, n: NotStable) {
TestSkipping(a=1, b=1f, c=2.0, d=NotStable(), e=v)
}
""", {
@@ -2254,7 +2237,7 @@
}, """
output = outerOutput
val v = ValueHolder(0)
- Main(v)
+ Main(v, NotStable())
""").then {
// Expect that all the methods are called in order
assertEquals(
@@ -2296,7 +2279,7 @@
fun log(msg: String) { output.add(msg) }
@Composable
- fun Container(@Children children: () -> Unit) {
+ fun Container(children: @Composable() () -> Unit) {
log("Container")
children()
}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsModelCodeGenTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsModelCodeGenTests.kt
index de276aa..987fc6e 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsModelCodeGenTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsModelCodeGenTests.kt
@@ -116,7 +116,6 @@
}
}
-
""", { mapOf("name" to name, "age" to age) }, """
fcs_president.name = name
fcs_president.age = age
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsTypeResolutionTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsTypeResolutionTests.kt
new file mode 100644
index 0000000..e982d99
--- /dev/null
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsTypeResolutionTests.kt
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2019 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.plugins.kotlin
+
+class FcsTypeResolutionTests : AbstractComposeDiagnosticsTest() {
+
+ fun testImplicitlyPassedReceiverScope1() = doTest(
+ """
+ import androidx.compose.*
+
+ @Composable
+ fun Int.Foo(children: @Composable Int.() -> Unit) {
+ children()
+ }
+ """
+ )
+
+ fun testImplicitlyPassedReceiverScope2() = doTest(
+ """
+ import androidx.compose.*
+
+ @Composable
+ fun Int.Foo(children: @Composable Int.(foo: String) -> Unit) {
+ children(<!NO_VALUE_FOR_PARAMETER, NO_VALUE_FOR_PARAMETER!>)<!>
+ }
+
+ @Composable
+ fun Bar(children: @Composable Int.() -> Unit) {
+ children(<!NO_VALUE_FOR_PARAMETER!>)<!>
+ }
+ """
+ )
+
+ fun testSmartCastsAndPunning() = doTest(
+ """
+ import androidx.compose.*
+
+ @Composable
+ fun Foo(bar: String) { print(bar) }
+
+ @Composable
+ fun test(bar: String?) {
+ Foo(<!TYPE_MISMATCH!>bar<!>)
+ if (bar != null) {
+ Foo(bar)
+ Foo(bar=bar)
+ }
+ }
+ """
+ )
+
+ fun testExtensionInvoke() = doTest(
+ """
+ import androidx.compose.*
+
+ class Foo {}
+ @Composable operator fun Foo.invoke() {}
+
+ @Composable fun test() {
+ Foo()
+ }
+ """
+ )
+
+ fun testResolutionInsideWhenExpression() = doTest(
+ """
+ import androidx.compose.*
+ import android.widget.TextView
+
+ @Composable fun doSomething(foo: Boolean) {
+ when (foo) {
+ true -> TextView(text="Started...")
+ false -> TextView(text="Continue...")
+ }
+ }
+ """
+ )
+
+ fun testUsedParameters() = doTest(
+ """
+ import androidx.compose.*
+ import android.widget.LinearLayout
+
+ @Composable fun Foo(x: Int, composeItem: @Composable() () -> Unit = {}) {
+ println(x)
+ print(composeItem == {})
+ }
+
+ @Composable fun test(
+ children: @Composable() () -> Unit,
+ value: Int,
+ x: Int,
+ children2: @Composable() () -> Unit,
+ value2: Int
+ ) {
+ LinearLayout {
+ // named argument
+ Foo(x=value)
+
+ // indexed argument
+ Foo(x)
+
+ // tag
+ children()
+ }
+ Foo(x=123, composeItem={
+ val abc = 123
+
+ // attribute value
+ Foo(x=abc)
+
+ // attribute value
+ Foo(x=value2)
+
+ // tag
+ children2()
+ })
+ }
+ """
+ )
+
+ fun testDispatchInvoke() = doTest(
+ """
+ import androidx.compose.*
+
+ class Bam {
+ @Composable fun Foo() {}
+ }
+
+ @Composable fun test() {
+ with(Bam()) {
+ Foo()
+ }
+ }
+ """
+ )
+
+ fun testDispatchAndExtensionReceiver() = doTest(
+ """
+ import androidx.compose.*
+
+ class Bam {
+ inner class Foo {}
+ }
+
+ @Composable operator fun Bam.Foo.invoke() {}
+
+ @Composable fun test() {
+ with(Bam()) {
+ Foo()
+ }
+ }
+ """
+ )
+
+ fun testDispatchAndExtensionReceiverLocal() = doTest(
+ """
+ import androidx.compose.*
+
+ class Foo {}
+
+ class Bam {
+ @Composable operator fun Foo.invoke() {}
+ @Composable operator fun invoke() {
+ Foo()
+ }
+ }
+
+ """
+ )
+
+ fun testMissingAttributes() = doTest(
+ """
+ import androidx.compose.*
+
+ data class Foo(val value: Int)
+
+ @Composable fun A(x: Foo) { println(x) }
+
+ // NOTE: It's important that the diagnostic be only over the call target, and not the
+ // entire element so that a single error doesn't end up making a huge part of an
+ // otherwise correct file "red".
+ @Composable fun Test(F: @Composable() (x: Foo) -> Unit) {
+ // NOTE: constructor attributes and fn params get a "missing parameter" diagnostic
+ A(<!NO_VALUE_FOR_PARAMETER!>)<!>
+
+ // local
+ F(<!NO_VALUE_FOR_PARAMETER!>)<!>
+
+ val x = Foo(123)
+
+ A(x)
+ F(x)
+ A(x=x)
+ F(x=x)
+ }
+
+ """.trimIndent()
+ )
+
+ fun testDuplicateAttributes() = doTest(
+ """
+ import androidx.compose.*
+
+ data class Foo(val value: Int)
+
+ @Composable fun A(x: Foo) { println(x) }
+
+ @Composable fun Test() {
+ val x = Foo(123)
+
+ // NOTE: It's important that the diagnostic be only over the attribute key, so that
+ // we don't make a large part of the elements red when the type is otherwise correct
+ A(x=x, <!ARGUMENT_PASSED_TWICE!>x<!>=x)
+ }
+
+ """.trimIndent()
+ )
+
+ fun testChildrenNamedAndBodyDuplicate() = doTest(
+ """
+ import androidx.compose.*
+
+ @Composable fun A(children: @Composable() () -> Unit) { children() }
+
+ @Composable fun Test() {
+ A(children={}) <!TOO_MANY_ARGUMENTS!>{ }<!>
+ }
+
+ """.trimIndent()
+ )
+
+ fun testAbstractClassTags() = doTest(
+ """
+ import androidx.compose.*
+ import android.content.Context
+ import android.widget.LinearLayout
+
+ abstract class Foo {}
+
+ abstract class Bar(context: Context) : LinearLayout(context) {}
+
+ @Composable fun Test() {
+ <!CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS!>Foo()<!>
+ <!CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS!>Bar(<!NO_VALUE_FOR_PARAMETER!>)<!><!>
+ }
+
+ """.trimIndent()
+ )
+
+ fun testGenerics() = doTest(
+ """
+ import androidx.compose.*
+
+ class A { fun a() {} }
+ class B { fun b() {} }
+
+ @Composable fun <T> Bar(x: Int, value: T, f: (T) -> Unit) { println(value); println(f); println(x) }
+
+ @Composable fun Test() {
+
+ val fa: (A) -> Unit = { it.a() }
+ val fb: (B) -> Unit = { it.b() }
+
+ Bar(x=1, value=A(), f={ it.a() })
+ Bar(x=1, value=B(), f={ it.b() })
+ Bar(x=1, value=A(), f=fa)
+ Bar(x=1, value=B(), f=fb)
+ Bar(x=1, value=B(), f={ it.<!UNRESOLVED_REFERENCE!>a<!>() })
+ Bar(x=1, value=A(), f={ it.<!UNRESOLVED_REFERENCE!>b<!>() })
+ <!TYPE_INFERENCE_CONFLICTING_SUBSTITUTIONS!>Bar<!>(
+ x=1,
+ value=A(),
+ f=fb
+ )
+ <!TYPE_INFERENCE_CONFLICTING_SUBSTITUTIONS!>Bar<!>(
+ x=1,
+ value=B(),
+ f=fa
+ )
+ }
+
+ """.trimIndent()
+ )
+
+ fun testUnresolvedAttributeValueResolvedTarget() = doTest(
+ """
+ import androidx.compose.*
+
+ @Composable fun Fam(bar: Int, x: Int) {
+ print(bar)
+ print(x)
+ }
+
+ @Composable fun Test() {
+ Fam(
+ bar=<!UNRESOLVED_REFERENCE!>undefined<!>,
+ x=1
+ )
+ Fam(
+ bar=1,
+ x=<!UNRESOLVED_REFERENCE!>undefined<!>
+ )
+ Fam(
+ <!UNRESOLVED_REFERENCE!>bar<!>,
+ <!UNRESOLVED_REFERENCE!>x<!>
+ )
+
+ Fam(
+ bar=<!TYPE_MISMATCH!>""<!>,
+ x=<!TYPE_MISMATCH!>""<!>
+ )
+ }
+
+ """.trimIndent()
+ )
+
+ // TODO(lmr): this triggers an exception!
+ fun testEmptyAttributeValue() = doTest(
+ """
+ import androidx.compose.*
+
+ @Composable fun Foo(abc: Int, xyz: Int) {
+ print(abc)
+ print(xyz)
+ }
+
+ @Composable fun Test() {
+ Foo(abc=<!NO_VALUE_FOR_PARAMETER!>)<!>
+
+ // NOTE(lmr): even though there is NO diagnostic here, there *is* a parse
+ // error. This is intentional and done to mimic how kotlin handles function
+ // calls with no value expression in a call parameter list (ie, `Foo(123,)`)
+ Foo(abc=123, xyz=)
+ }
+
+ """.trimIndent()
+ )
+
+ fun testMismatchedAttributes() = doTest(
+ """
+ import androidx.compose.*
+
+ open class A {}
+ class B : A() {}
+
+ @Composable fun Foo(x: A = A(), y: A = B(), z: B = B()) {
+ print(x)
+ print(y)
+ print(z)
+ }
+
+ @Composable fun Test() {
+ Foo(
+ x=A(),
+ y=A(),
+ z=<!TYPE_MISMATCH!>A()<!>
+ )
+ Foo(
+ x=B(),
+ y=B(),
+ z=B()
+ )
+ Foo(
+ x=<!CONSTANT_EXPECTED_TYPE_MISMATCH!>1<!>,
+ y=<!CONSTANT_EXPECTED_TYPE_MISMATCH!>1<!>,
+ z=<!CONSTANT_EXPECTED_TYPE_MISMATCH!>1<!>
+ )
+ }
+
+ """.trimIndent()
+ )
+
+ fun testErrorAttributeValue() = doTest(
+ """
+ import androidx.compose.*
+
+ @Composable fun Foo(x: Int = 1) { print(x) }
+
+ @Composable fun Test() {
+ Foo(
+ x=<!UNRESOLVED_REFERENCE!>someUnresolvedValue<!>,
+ <!NAMED_PARAMETER_NOT_FOUND!>y<!>=<!UNRESOLVED_REFERENCE!>someUnresolvedValue<!>
+ )
+ }
+
+ """.trimIndent()
+ )
+
+ fun testUnresolvedQualifiedTag() = doTest(
+ """
+ import androidx.compose.*
+
+ object MyNamespace {
+ @Composable fun Bar(children: @Composable() () -> Unit = {}) {
+ children()
+ }
+
+ var Baz = @Composable { }
+
+ var someString = ""
+ class NonComponent {}
+ }
+
+ class Boo {
+ @Composable fun Wat() { }
+ }
+
+ @Composable fun Test() {
+
+ MyNamespace.Bar()
+ MyNamespace.Baz()
+ MyNamespace.<!UNRESOLVED_REFERENCE!>Qoo<!>()
+ MyNamespace.<!FUNCTION_EXPECTED!>someString<!>()
+ MyNamespace.NonComponent()
+ MyNamespace.Bar {}
+ MyNamespace.Baz <!TOO_MANY_ARGUMENTS!>{}<!>
+
+ val obj = Boo()
+ Boo.<!UNRESOLVED_REFERENCE!>Wat<!>()
+ obj.Wat()
+
+ MyNamespace.<!UNRESOLVED_REFERENCE!>Bam<!>()
+ <!UNRESOLVED_REFERENCE!>SomethingThatDoesntExist<!>.Foo()
+
+ obj.Wat <!TOO_MANY_ARGUMENTS!>{
+ }<!>
+
+ MyNamespace.<!UNRESOLVED_REFERENCE!>Qoo<!> {
+ }
+
+ MyNamespace.<!FUNCTION_EXPECTED!>someString<!> {
+ }
+
+ <!UNRESOLVED_REFERENCE!>SomethingThatDoesntExist<!>.Foo {
+ }
+
+ MyNamespace.NonComponent <!TOO_MANY_ARGUMENTS!>{}<!>
+
+ MyNamespace.<!UNRESOLVED_REFERENCE!>Bam<!> {}
+
+ }
+
+ """.trimIndent()
+ )
+
+ // TODO(lmr): overloads creates resolution exception
+ fun testChildren() = doTest(
+ """
+ import androidx.compose.*
+ import android.widget.Button
+ import android.widget.LinearLayout
+
+ @Composable fun ChildrenRequired2(children: @Composable() () -> Unit) { children() }
+
+ @Composable fun ChildrenOptional3(children: @Composable() () -> Unit = {}){ children() }
+
+ @Composable fun NoChildren2() {}
+
+ @Composable
+ fun MultiChildren(c: @Composable() (x: Int) -> Unit = {}) { c(1) }
+
+ @Composable
+ fun MultiChildren(c: @Composable() (x: Int, y: Int) -> Unit = { x, y ->println(x + y) }) { c(1,1) }
+
+ @Composable fun Test() {
+ ChildrenRequired2 {}
+ ChildrenRequired2(<!NO_VALUE_FOR_PARAMETER!>)<!>
+
+ ChildrenOptional3 {}
+ ChildrenOptional3()
+
+ NoChildren2 <!TOO_MANY_ARGUMENTS!>{}<!>
+ NoChildren2()
+
+ <!OVERLOAD_RESOLUTION_AMBIGUITY!>MultiChildren<!> {}
+ MultiChildren { x ->
+ println(x)
+ }
+ MultiChildren { x, y ->
+ println(x + y)
+ }
+ <!NONE_APPLICABLE!>MultiChildren<!> { <!CANNOT_INFER_PARAMETER_TYPE!>x<!>,
+ <!CANNOT_INFER_PARAMETER_TYPE!>y<!>, <!CANNOT_INFER_PARAMETER_TYPE!>z<!> ->
+ println(x + y + z)
+ }
+
+ Button()
+ LinearLayout()
+
+ LinearLayout {}
+
+ Button <!TOO_MANY_ARGUMENTS!>{}<!>
+ }
+
+ """.trimIndent()
+ )
+}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/GenerateWrapperViewTest.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/GenerateWrapperViewTest.kt
deleted file mode 100644
index bead928..0000000
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/GenerateWrapperViewTest.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import junit.framework.TestCase
-
-class GenerateWrapperViewTest : AbstractCodegenTest() {
-
- fun testPlaceholder() {
- // do nothing, in order to prevent warning
- }
-
- fun xtestWrapperViewGeneration() {
-
- val klass = loadClass("MainComponent", """
- import android.app.Activity
- import android.os.Bundle
- import androidx.compose.Component
- import androidx.compose.CompositionContext
-
- class MainActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val inst = MainComponent.createInstance(this)
- inst.setFoo("string")
- setContentView(inst)
- }
- }
-
- class MainComponent : Component() {
- lateinit var foo: String
- override fun compose() {}
- }
- """)
-
- val wrapperClass = klass.declaredClasses.find {
- it.name == "MainComponent\$MainComponentWrapperView"
- }
- TestCase.assertNotNull("wrapper view gets generated", wrapperClass)
- if (wrapperClass == null) return
- TestCase.assertEquals(
- "Wrapper view subclasses LinearLayout", "android.widget.LinearLayout",
- wrapperClass.superclass?.name
- )
- val setFoo = wrapperClass.declaredMethods.find { it.name == "setFoo" }
- TestCase.assertNotNull("has a setter method for properties", setFoo)
-
- val companionClass =
- klass.declaredClasses.find { it.name == "MainComponent\$R4HStaticRenderCompanion" }
- TestCase.assertNotNull("companion class gets generated", companionClass)
- if (companionClass == null) return
- val createInstanceFn = companionClass.declaredMethods.find { it.name == "createInstance" }
- TestCase.assertNotNull("createInstance function gets generated", createInstanceFn)
- }
-}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCallResolutionTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCallResolutionTests.kt
deleted file mode 100644
index 422f7b6..0000000
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCallResolutionTests.kt
+++ /dev/null
@@ -1,1191 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-class KtxCallResolutionTests : AbstractResolvedKtxCallsTest() {
-
- fun testReceiverScopeCall() = doTest(
- """
- import androidx.compose.*
-
- @Composble fun Foo(onClick: Double.() -> Unit) {}
-
- @Composable
- fun test() {
- <caret><Foo />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Double.() -> Unit): Boolean
- assignment = <null>
- attribute = onClick
- call = NonMemoizedCallNode:
- resolvedCall = fun Foo(Double.() -> Unit)
- params = onClick
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = onClick
- unusedAttributes = <empty>
- """
- )
-
- fun testImplicitReceiverScopeCall() = doTest(
- """
- import androidx.compose.*
-
- class Bar {}
-
- @Composable fun Bar.Foo() {}
-
- @Composable
- fun test(bar: Bar) {
- with(bar) {
- <caret><Foo />
- }
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations = <empty>
- call = NonMemoizedCallNode:
- resolvedCall = fun Bar.Foo()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = <empty>
- unusedAttributes = <empty>
- """
- )
-
- fun testSomethingQualifiedTag() = doTest(
- """
- import androidx.compose.*
-
- object Foo {
- class Bar {
- @Composable
- operator fun invoke() {}
- }
- }
-
- @Composable
- fun test() {
- <caret><Foo.Bar />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations = <empty>
- call = NonMemoizedCallNode:
- resolvedCall = Bar()
- params = <empty>
- postAssignments = <empty>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = <empty>
- unusedAttributes = <empty>
- """
- )
-
- fun testReceiverScopeTag() = doTest(
- """
- import androidx.compose.*
-
- class Foo {}
-
- @Composable
- fun test(children: Foo.() -> Unit) {
- val foo = Foo()
- <caret><foo.children />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations = <empty>
- call = NonMemoizedCallNode:
- resolvedCall = fun Foo.invoke()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = <empty>
- unusedAttributes = <empty>
- """
- )
-
- fun testSomething() = doTest(
- """
- import androidx.compose.*
-
- class Bar(var y: Int = 0) {
- @Composable
- operator fun invoke(z: Int) {
-
- }
- }
-
- @Composable
- fun test() {
- <caret><Bar y=2 z=3>
- </Bar>
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = var y: Int
- attribute = y
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = z
- call = NonMemoizedCallNode:
- resolvedCall = Bar(Int)
- params = y
- postAssignments = <empty>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = z
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = y, z
- unusedAttributes = <children>
- """
- )
-
- fun testNestedCalls() = doTest(
- """
- import androidx.compose.*
-
- @Stateful
- class Bar(var y: Int = 0) {
- @Composable
- operator fun invoke(z: Int) {
-
- }
- }
-
- @Stateful
- @Composable
- fun Foo(a: Int): @Composable() (y: Int) -> Bar = { y: Int -> Bar(y) }
-
- @Composable
- fun test() {
- <caret><Foo a=1 y=2 z=3 />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> (Int) -> Bar, ViewValidator.((Int) -> Bar) -> Boolean, ((Int) -> Bar) -> Unit)
- pivotals = a
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = fun Foo(Int): (Int) -> Bar
- ctorParams = a
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = y
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = z
- call = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> Bar, ViewValidator.(Bar) -> Boolean, (Bar) -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = fun invoke(Int): Bar
- ctorParams = y
- validations =
- - ValidatedAssignment(UPDATE):
- validationCall = fun update(Int, (Int) -> Unit): Boolean
- assignment = var y: Int
- attribute = y
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = z
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = z
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = z, y, a
- unusedAttributes = <empty>
- """
- )
-
- fun testTopLevelFunction() = doTest(
- """
- import androidx.compose.*
-
- @Composable
- fun Foo(a: Int, z: Int) {
-
- }
-
- @Composable
- fun test() {
- <caret><Foo a=1 z=3 />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = a
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = z
- call = NonMemoizedCallNode:
- resolvedCall = fun Foo(Int, Int)
- params = a, z
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = a, z
- unusedAttributes = <empty>
- """
- )
-
- fun testParameterNamesForInstantiatedClassObjects() = doTest(
- """
- import androidx.compose.*
-
- class Bar {
- @Composable
- operator fun invoke(z: Int) {
-
- }
- }
-
- @Composable
- fun test() {
- val x = Bar()
- <caret><x z=3 />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Bar): Boolean
- assignment = <null>
- attribute = <tag>
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = z
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = z
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = z, <tag>
- unusedAttributes = <empty>
- """
- )
-
- fun testParameterNamesForLambdas() = doTest(
- """
- import androidx.compose.*
-
- @Composable
- fun test() {
- val x: (z: Int) -> Unit = { z: Int -> }
- <caret><x z=3 />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed((Int) -> Unit): Boolean
- assignment = <null>
- attribute = <tag>
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = z
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = z
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = z, <tag>
- unusedAttributes = <empty>
- """
- )
-
- fun testSomethingWithChildren() = doTest(
- """
- import androidx.compose.*
-
- class Bar(var y: Int = 0) {
- @Children var children: @Composable() () -> Unit = {}
- @Composable
- operator fun invoke(z: Int) {
-
- }
- }
-
- @Composable
- fun test() {
- <caret><Bar y=2 z=3>
- </Bar>
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = var y: Int
- attribute = y
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(() -> Unit): Boolean
- assignment = <null>
- attribute = <children>
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = z
- call = NonMemoizedCallNode:
- resolvedCall = Bar(Int)
- params = y
- postAssignments =
- - ValidatedAssignment(SET):
- validationCall = fun set(() -> Unit, (() -> Unit) -> Unit): Boolean
- assignment = var children: () -> Unit
- attribute = <children>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = z
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = y, <children>, z
- unusedAttributes = <empty>
- """
- )
-
- fun testViewResolution() = doTest(
- """
- import androidx.compose.*
- import android.widget.Button
-
- @Composable
- fun test() {
- <caret><Button text="some text" enabled=false />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = EmitCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun emit(Any, (Context) -> Button, ViewUpdater<Button>.() -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = constructor Button(Context!)
- ctorParams = (implicit)context
- validations =
- - ValidatedAssignment(SET):
- validationCall = fun set(CharSequence!, Button.(CharSequence!) -> Unit)
- assignment = fun setText(CharSequence!)
- attribute = text
- - ValidatedAssignment(SET):
- validationCall = fun set(Boolean, Button.(Boolean) -> Unit)
- assignment = fun setEnabled(Boolean)
- attribute = enabled
- usedAttributes = text, enabled
- unusedAttributes = <empty>
- """
- )
-
- fun testNonMemoizableClassComponent() = doTest(
- """
- import androidx.compose.*
-
- class Bar(var a: Int) {
- @Composable
- operator fun invoke(b: Int) {}
- }
-
- @Composable
- fun test() {
- <caret><Bar a=1 b=2 />
- }
- /*
- call(k, { changed(a) + changed(b) }) {
- Bar(a)(b)
- }
- */
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = var a: Int
- attribute = a
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = b
- call = NonMemoizedCallNode:
- resolvedCall = Bar(Int)
- params = a
- postAssignments = <empty>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = b
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = a, b
- unusedAttributes = <empty>
- """
- )
-
- fun testChildrenLambdaSetterWithMultipleParams() = doTest(
- """
- import androidx.compose.*
-
- class Example {
- @Children
- fun setChildren(fn: (x: Int, y: String) -> Unit) {}
- @Composable
- operator fun invoke() {}
- }
-
- @Composable
- fun helper(x: Int = 4, y: Int) {
- }
-
- fun run(text: String) {
-
-// <helper y=123 />
-
-// val lambda = { x, y -> ... }
-// composer.call(
-// 123,
-// { changed(lambda) },
-// { Example().apply { setChildren(lambda) }() }
-// )
-
- <caret><Example> x, y ->
- println("hello ${"$"}x ${"$"}y")
- </Example>
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed((Int, String) -> Unit): Boolean
- assignment = <null>
- attribute = <children>
- call = NonMemoizedCallNode:
- resolvedCall = Example()
- params = <empty>
- postAssignments =
- - ValidatedAssignment(SET):
- validationCall = fun set((Int, String) -> Unit, ((Int, String) -> Unit) -> Unit): Boolean
- assignment = fun setChildren((Int, String) -> Unit)
- attribute = <children>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = <children>
- unusedAttributes = <empty>
- """
- )
-
- fun testMemoizableClassComponent() = doTest(
- """
- import androidx.compose.*
-
- @Stateful
- class Bar(var a: Int) {
- @Composable
- operator fun invoke(b: Int) {}
- }
-
- @Composable
- fun test() {
- <caret><Bar a=1 b=2 />
- }
- /*
- call(k, { Bar(a) }, { update(a) { a = it } + changed(b) }) { bar ->
- bar(b)
- }
- */
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> Bar, ViewValidator.(Bar) -> Boolean, (Bar) -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = Bar(Int)
- ctorParams = a
- validations =
- - ValidatedAssignment(UPDATE):
- validationCall = fun update(Int, (Int) -> Unit): Boolean
- assignment = var a: Int
- attribute = a
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = b
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = b
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = b, a
- unusedAttributes = <empty>
- """
- )
-
- fun testMemoizableClassComponentComponent() = doTest(
- """
- import androidx.compose.*
-
- class Bar(var a: Int): Component() {
- override fun compose() {}
- }
-
- @Composable
- fun test() {
- <caret><Bar a=1 />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> Bar, ViewValidator.(Bar) -> Boolean, (Bar) -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = Bar(Int)
- ctorParams = a
- validations =
- - ValidatedAssignment(UPDATE):
- validationCall = fun update(Int, (Int) -> Unit): Boolean
- assignment = var a: Int
- attribute = a
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = a
- unusedAttributes = <empty>
- """
- )
-
- fun testMemoizableClassComponentComponent2() = doTest(
- """
- import androidx.compose.*
-
- class TestContainer(@Children var children: @Composable() (x: Double)->Unit): Component() {
- override fun compose() {}
- }
-
- @Composable
- fun test() {
- <caret><TestContainer> x->
-
- </TestContainer>
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> TestContainer, ViewValidator.(TestContainer) -> Boolean, (TestContainer) -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = TestContainer((Double) -> Unit)
- ctorParams = <children>
- validations =
- - ValidatedAssignment(UPDATE):
- validationCall = fun update((Double) -> Unit, ((Double) -> Unit) -> Unit): Boolean
- assignment = var children: (Double) -> Unit
- attribute = <children>
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = <children>
- unusedAttributes = <empty>
- """
- )
-
- fun testDynamicTagsMemoizableClassComponent() = doTest(
- """
- import androidx.compose.*
-
- @Stateful
- class Bar(var a: Int) {
- @Composable
- operator fun invoke(b: Int) {}
- }
-
- @Composable
- fun test() {
- val bar = Bar()
- <caret><bar b=2 />
- }
- /*
- call(k, { changed(bar) + changed(b) }) {
- bar(b)
- }
- */
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Bar): Boolean
- assignment = <null>
- attribute = <tag>
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = b
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = b
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = b, <tag>
- unusedAttributes = <empty>
- """
- )
-
- fun testDynamicTagsInnerClassMemoizableClassComponent() = doTest(
- """
- import androidx.compose.*
-
-
- class Bar(var a: Int) {
- @Stateful
- inner class Foo {
- @Composable
- operator fun invoke(b: Int) {}
- }
- }
-
- @Composable
- fun test() {
- val bar = Bar()
- <caret><bar.Foo b=2 />
- }
- /*
- call(k, { changed(bar) + changed(b) }) {
- bar.Foo(b)
- }
- */
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> Bar.Foo, ViewValidator.(Bar.Foo) -> Boolean, (Bar.Foo) -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = Foo()
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Bar): Boolean
- assignment = <null>
- attribute = <tag>
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = b
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = b
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = b, <tag>
- unusedAttributes = <empty>
- """
- )
-
- fun testDeepNestedCalls() = doTest(
- """
- import androidx.compose.*
-
-
- class A { operator fun invoke(a: Int): B { return B() } }
- class B { operator fun invoke(b: Int): C { return C() } }
- class C { operator fun invoke(c: Int): D { return D() } }
- class D { operator fun invoke(d: Int): E { return E() } }
- class E { operator fun invoke(e: Int) {} }
-
- @Composable
- fun test() {
- <caret><A a=1 b=2 c=3 d=4 e=5 />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = a
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = b
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = c
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = d
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = e
- call = NonMemoizedCallNode:
- resolvedCall = A()
- params = <empty>
- postAssignments = <empty>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int): B
- params = a
- postAssignments = <empty>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int): C
- params = b
- postAssignments = <empty>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int): D
- params = c
- postAssignments = <empty>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int): E
- params = d
- postAssignments = <empty>
- nextCall = NonMemoizedCallNode:
- resolvedCall = fun invoke(Int)
- params = e
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = a, b, c, d, e
- unusedAttributes = <empty>
- """
- )
-
- fun testRecursionLimitNoParams() = doTest(
- """
- import androidx.compose.*
-
-
- class A { operator fun invoke(): B { return B() } }
- class B { operator fun invoke(): C { return C() } }
- class C { operator fun invoke(): D { return D() } }
- class D { operator fun invoke(): E { return E() } }
- class E { operator fun invoke() {} }
-
- @Composable
- fun test() {
- <caret><A />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations = <empty>
- call = NonMemoizedCallNode:
- resolvedCall = A()
- params = <empty>
- postAssignments = <empty>
- nextCall = <ERROR:RecursionLimitError>
- usedAttributes = <empty>
- unusedAttributes = <empty>
- """
- )
-
- fun testPivotalsAreNotValidated() = doTest(
- """
- import androidx.compose.*
-
- @Composable
- fun A(@Pivotal x: Int, y: Int) {
- println(x)
- println(y)
- }
-
- @Composable
- fun test(x: Int, y: Int) {
- <caret><A x y />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = x
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = x
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(Int): Boolean
- assignment = <null>
- attribute = y
- call = NonMemoizedCallNode:
- resolvedCall = fun A(Int, Int)
- params = x, y
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = x, y
- unusedAttributes = <empty>
- """
- )
-
- fun testConstructorParamsArePivotal() = doTest(
- """
- import androidx.compose.*
-
- class Foo(a: Int, private val b: Int, var c: Int, d: Int) : Component() {
- var d: Int = d
- override fun compose() {}
- }
-
- @Composable
- fun test(a: Int, b: Int, c: Int, d: Int) {
- <caret><Foo a b c d />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> Foo, ViewValidator.(Foo) -> Boolean, (Foo) -> Unit)
- pivotals = a, b
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = Foo(Int, Int, Int, Int)
- ctorParams = a, b, c, d
- validations =
- - ValidatedAssignment(UPDATE):
- validationCall = fun update(Int, (Int) -> Unit): Boolean
- assignment = var c: Int
- attribute = c
- - ValidatedAssignment(UPDATE):
- validationCall = fun update(Int, (Int) -> Unit): Boolean
- assignment = var d: Int
- attribute = d
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = a, b, c, d
- unusedAttributes = <empty>
- """
- )
-
- fun testValChildren() = doTest(
- """
- import androidx.compose.*
-
- class A(@Children val children: () -> Unit) : Component() {
- override fun compose() {}
- }
-
- @Composable
- fun test() {
- <caret><A></A>
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> A, ViewValidator.(A) -> Boolean, (A) -> Unit)
- pivotals = <children>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = A(() -> Unit)
- ctorParams = <children>
- validations = <empty>
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = <children>
- unusedAttributes = <empty>
- """
- )
-
- fun testPrivateValChildren() = doTest(
- """
- import androidx.compose.*
-
- class A(@Children private val children: () -> Unit) : Component() {
- override fun compose() {}
- }
-
- @Composable
- fun test() {
- <caret><A></A>
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, () -> A, ViewValidator.(A) -> Boolean, (A) -> Unit)
- pivotals = <children>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = A(() -> Unit)
- ctorParams = <children>
- validations = <empty>
- call = NonMemoizedCallNode:
- resolvedCall = fun invoke()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = <children>
- unusedAttributes = <empty>
- """
- )
-
- fun testIsStaticNature() = doTest(
- """
- import androidx.compose.*
-
-// class Bar {
-// @Composable
-// operator fun invoke() {}
-// }
-
- val Bar get() = @Composable { }
-
- // All of these should be "static"
- // ===============================
-
- // class A { operator fun invoke() }
- // <A />
-
- // class A { companion object { class B { operator fun invoke() {} } } }
- // <A.B />
-
- // class A { companion object { fun B() {} } }
- // <A.B />
-
- // class A { class B { operator fun invoke() {} } } }
- // <A.B />
-
- // object A { fun B() {} }
- // <A.B />
-
- // object A { object B { fun C() {} } }
- // <A.B.C />
-
- // object A { class B { operator fun invoke() {} } }
- // <A.B />
-
- // fun A() {}
- // <A />
-
- // val A = @Composable {}
- // <A />
-
- // class A { inner class B { operator fun invoke() {} } }
- // val a = A()
- // <a.B />
-
-
- // All of these should be "dynamic"
- // ===============================
-
- // var A = @Composable {}
- // <A />
-
- // val A get() = @Composable {}
- // <A />
-
- // object A { inner class B { operator fun invoke() {} } }
- // <A.B />
-
- // class A { inner class B { operator fun invoke() {} } }
- // A().let { <it.B /> }
-
-
- class UI {
- @Composable
- fun Foo() {}
- }
- val ui = UI()
-
- @Composable
- fun test() {
- <caret><ui.Foo />
- }
- """,
- """
- ResolvedKtxElementCall:
- emitOrCall = MemoizedCallNode:
- memoize = ComposerCallInfo:
- composerCall = fun call(Any, ViewValidator.() -> Boolean, () -> Unit)
- pivotals = <empty>
- joinKeyCall = fun joinKey(Any, Any?): Any
- ctorCall = <null>
- ctorParams = <empty>
- validations =
- - ValidatedAssignment(CHANGED):
- validationCall = fun changed(UI): Boolean
- assignment = <null>
- attribute = <tag>
- call = NonMemoizedCallNode:
- resolvedCall = fun Foo()
- params = <empty>
- postAssignments = <empty>
- nextCall = <null>
- usedAttributes = <tag>
- unusedAttributes = <empty>
- """
- )
-}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCodegenTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCodegenTests.kt
deleted file mode 100644
index 5418a2d..0000000
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCodegenTests.kt
+++ /dev/null
@@ -1,1885 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-@file:Suppress("MemberVisibilityCanBePrivate")
-
-package androidx.compose.plugins.kotlin
-
-import android.app.Activity
-import android.os.Bundle
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.compose.Component
-import androidx.compose.CompositionContext
-import androidx.compose.Compose
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.Robolectric
-import org.robolectric.RuntimeEnvironment
-import org.robolectric.annotation.Config
-import java.net.URLClassLoader
-
-@RunWith(ComposeRobolectricTestRunner::class)
-@Config(
- manifest = Config.NONE,
- minSdk = 23,
- maxSdk = 23
-)
-class KtxCodegenTests : AbstractCodegenTest() {
-
- @Test
- fun testModelOne(): Unit = ensureSetup {
- codegen(
- """
-@Model
-class ModelClass() {
- var x = 0
-}
- """
- )
- }
-
- @Test
- fun testObservable(): Unit = ensureSetup {
- compose(
- """
- import android.widget.Button
- import androidx.compose.*
- import androidx.ui.androidview.adapters.setOnClick
-
- @Model
- class FancyButtonData() {
- var x = 0
- }
-
- @Composable
- fun SimpleComposable() {
- <FancyButton state=FancyButtonData() />
- }
-
- @Composable
- fun FancyButton(state: FancyButtonData) {
- <Button text=("Clicked "+state.x+" times") id=42 />
- }
- """,
- { mapOf<String, String>() },
- "<SimpleComposable />"
- ).then { activity ->
- val button = activity.findViewById(42) as Button
- button.performClick()
- button.performClick()
- button.performClick()
- }.then { activity ->
- val button = activity.findViewById(42) as Button
- assertEquals("Clicked 3 times", button.text)
- }
- }
-
- @Test
- fun testObservableLambda(): Unit = ensureSetup {
- compose(
- """
- import android.widget.*
- import androidx.compose.*
- import androidx.ui.androidview.adapters.setOnClick
-
- @Model
- class FancyButtonCount() {
- var count = 0
- }
-
- @Composable
- fun SimpleComposable(state: FancyButtonCount) {
- <FancyBox2>
- <Button
- text=("Button clicked "+state.count+" times")
- id=42 />
- </FancyBox2>
- }
-
- @Composable
- fun FancyBox2(@Children children: ()->Unit) {
- <children />
- }
- """,
- { mapOf<String, String>() },
- "<SimpleComposable state=+memo { FancyButtonCount() } />"
- ).then { activity ->
- val button = activity.findViewById(42) as Button
- button.performClick()
- button.performClick()
- button.performClick()
- }.then { activity ->
- val button = activity.findViewById(42) as Button
- assertEquals("Button clicked 3 times", button.text)
- }
- }
-
- @Test
- fun testCGSimpleTextView(): Unit = ensureSetup {
- compose(
- """
- <TextView text="Hello, world!" id=42 />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testCGLocallyScopedFunction(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo() {
- @Composable fun Bar() {
- <TextView text="Hello, world!" id=42 />
- }
- <Bar />
- }
- """,
- { mapOf<String, String>() },
- """
- <Foo />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testCGLocallyScopedExtensionFunction(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo(x: String) {
- @Composable fun String.Bar() {
- <TextView text=this id=42 />
- }
- <x.Bar />
- }
- """,
- { mapOf<String, String>() },
- """
- <Foo x="Hello, world!" />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testImplicitReceiverScopeCall(): Unit = ensureSetup {
- compose(
- """
- import androidx.compose.*
-
- class Bar(val text: String)
-
- @Composable fun Bar.Foo() {
- <TextView text=text id=42 />
- }
-
- @Composable
- fun Bam(bar: Bar) {
- with(bar) {
- <Foo />
- }
- }
- """,
- { mapOf<String, String>() },
- """
- <Bam bar=Bar("Hello, world!") />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testCGLocallyScopedInvokeOperator(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo(x: String) {
- @Composable operator fun String.invoke() {
- <TextView text=this id=42 />
- }
- <x />
- }
- """,
- { mapOf<String, String>() },
- """
- <Foo x="Hello, world!" />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testCGNSimpleTextView(): Unit = ensureSetup {
- compose(
- """
- <TextView text="Hello, world!" id=42 />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testInliningTemp(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo(x: Double, @Children children: Double.() -> Unit) {
- <x.children />
- }
- """,
- { mapOf("foo" to "bar") },
- """
- <Foo x=1.0>
- <TextView text=this.toString() id=123 />
- </Foo>
- """
- ).then { activity ->
- val textView = activity.findViewById(123) as TextView
- assertEquals("1.0", textView.text)
- }
- }
-
- @Test
- fun testInliningTemp2(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo(onClick: Double.() -> Unit) {
-
- }
- """,
- { mapOf("foo" to "bar") },
- """
- <Foo />
- """
- ).then {
- }
- }
-
- @Test
- fun testInliningTemp3(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo(onClick: (Double) -> Unit) {
-
- }
- """,
- { mapOf("foo" to "bar") },
- """
- <Foo />
- """
- ).then {
- }
- }
-
- @Test
- fun testInliningTemp4(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo(onClick: (Double) -> Unit) {
-
- }
- """,
- { mapOf("foo" to "bar") },
- """
- <Foo />
- """
- ).then {
- }
- }
-
- @Test
- fun testCGNInlining(): Unit = ensureSetup {
- compose(
- """
- <LinearLayout orientation=LinearLayout.VERTICAL>
- <TextView text="Hello, world!" id=42 />
- </LinearLayout>
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testCGUpdatedComposition(): Unit = ensureSetup {
- var value = "Hello, world!"
-
- compose(
- { mapOf("value" to value) }, """
- <TextView text=value id=42 />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
-
- value = "Other value"
- }.then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Other value", textView.text)
- }
- }
-
- @Test
- fun testCGNUpdatedComposition(): Unit = ensureSetup {
- var value = "Hello, world!"
-
- compose(
- { mapOf("value" to value) }, """
- <TextView text=value id=42 />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
-
- value = "Other value"
- }.then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Other value", textView.text)
- }
- }
-
- @Test
- fun testCGViewGroup(): Unit = ensureSetup {
- val tvId = 258
- val llId = 260
- var text = "Hello, world!"
- var orientation = LinearLayout.HORIZONTAL
-
- compose(
- { mapOf("text" to text, "orientation" to orientation) }, """
- <LinearLayout orientation id=$llId>
- <TextView text id=$tvId />
- </LinearLayout>
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val linearLayout = activity.findViewById(llId) as LinearLayout
-
- assertEquals(text, textView.text)
- assertEquals(orientation, linearLayout.orientation)
-
- text = "Other value"
- orientation = LinearLayout.VERTICAL
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val linearLayout = activity.findViewById(llId) as LinearLayout
-
- assertEquals(text, textView.text)
- assertEquals(orientation, linearLayout.orientation)
- }
- }
-
- @Test
- fun testCGNAmbient(): Unit = ensureSetup {
- val tvId = 258
- var text = "Hello, world!"
-
- compose(
- """
-
- val StringAmbient = Ambient.of<String> { "default" }
-
- @Composable fun Foo() {
- val value = +ambient(StringAmbient)
- <TextView id=$tvId text=value />
- }
-
- """,
- { mapOf("text" to text) },
- """
- <StringAmbient.Provider value=text>
- <Foo />
- </StringAmbient.Provider>
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
- text = "wat"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
- }
- }
-
- @Test
- fun testAmbientNesting(): Unit = ensureSetup {
- val tvId1 = 345
- val tvId2 = 456
-
- compose(
- """
- val A1 = Ambient.of("one") { 0 }
- val A2 = Ambient.of("two") { 0 }
- var changing = 0
-
- @Composable
- fun Foo() {
- <A1.Provider value=2>
- <ConsumeBoth id=$tvId1 />
- </A1.Provider>
- }
-
- @Composable
- fun Bar() {
- <ConsumeBoth id=$tvId2 />
- }
-
- @Composable
- fun ConsumeBoth(id: Int) {
- val notUsed = +ambient(A2)
- val value = +ambient(A1)
- <TextView id=id text=("" + value) />
- }
-
- """,
- { mapOf("text" to "") },
- """
- <A1.Provider value=1>
- <A2.Provider value=changing++>
- <Foo />
- <Bar />
- </A2.Provider>
- </A1.Provider>
- """
- ).then { activity ->
- val tv1 = activity.findViewById(tvId1) as TextView
- val tv2 = activity.findViewById(tvId2) as TextView
-
- assertEquals("2", tv1.text)
- assertEquals("1", tv2.text)
- }.then { activity ->
- val tv1 = activity.findViewById(tvId1) as TextView
- val tv2 = activity.findViewById(tvId2) as TextView
-
- assertEquals("2", tv1.text)
- assertEquals("1", tv2.text)
- }
- }
-
- @Test
- fun testAmbientPortal1(): Unit = ensureSetup {
- val llId = 123
- val tvId = 345
- var text = "Hello, world!"
-
- compose(
- """
- val StringAmbient = Ambient.of<String> { "default" }
-
- @Composable fun App(value: String) {
- <StringAmbient.Provider value>
- <Parent />
- </StringAmbient.Provider>
- }
-
- @Composable fun Parent() {
- val compositionRef = +compositionReference()
- val viewRef = +memo { Ref<LinearLayout>() }
-
- <LinearLayout id=$llId ref=viewRef />
-
- +onPreCommit {
- Compose.composeInto(
- container = viewRef.value ?: error("No View Ref!"),
- parent = compositionRef
- ) {
- <Child />
- }
- }
- }
-
- @Composable fun Child() {
- val value = +ambient(StringAmbient)
- <TextView id=$tvId text=value />
- }
-
- """,
- { mapOf("text" to text) },
- """
- <App value=text />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val layout = activity.findViewById(llId) as LinearLayout
-
- assertEquals(1, layout.childCount)
- assertEquals(text, textView.text)
- text = "wat"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val layout = activity.findViewById(llId) as LinearLayout
-
- assertEquals(1, layout.childCount)
- assertEquals(text, textView.text)
- }
- }
-
- @Test
- fun testAmbientPortal2(): Unit = ensureSetup {
- val llId = 123
- val tvId = 345
- var text = "Hello, world!"
-
- compose(
- """
- val StringAmbient = Ambient.of<String> { "default" }
-
- @Composable fun App(value: String) {
- <StringAmbient.Provider value>
- <Parent />
- </StringAmbient.Provider>
- }
-
- @Composable fun Parent() {
- val compositionRef = +compositionReference()
- val viewRef = +memo { Ref<LinearLayout>() }
-
- <LinearLayout id=$llId ref=viewRef />
-
- +onPreCommit {
- Compose.composeInto(
- container = viewRef.value ?: error("No View Ref!"),
- parent = compositionRef
- ) {
- <Child />
- }
- }
- }
-
- @Composable fun Child() {
- val value = +ambient(StringAmbient)
- <TextView id=$tvId text=value />
- }
-
- """,
- { mapOf("text" to text) },
- """
- <App value=text />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val layout = activity.findViewById(llId) as LinearLayout
-
- assertEquals(1, layout.childCount)
- assertEquals(text, textView.text)
- text = "wat"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val layout = activity.findViewById(llId) as LinearLayout
-
- assertEquals(1, layout.childCount)
- assertEquals(text, textView.text)
- }
- }
-
- @Test
- fun testCGNClassComponent(): Unit = ensureSetup {
- var text = "Hello, world!"
- val tvId = 123
-
- compose(
- """
- class Foo {
- var text = ""
- @Composable
- operator fun invoke(bar: Int) {
- <TextView id=$tvId text=text />
- }
- }
-
- """,
- { mapOf("text" to text) },
- """
- <Foo text bar=123 />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
- text = "wat"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
- }
- }
-
- @Test
- fun testCGNFunctionComponent(): Unit = ensureSetup {
- var text = "Hello, world!"
- val tvId = 123
-
- compose(
- """
- @Composable
- fun Foo(text: String) {
- <TextView id=$tvId text=text />
- }
-
- """,
- { mapOf("text" to text) },
- """
- <Foo text />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
- text = "wat"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
- }
- }
-
- @Test
- fun testAmbientReference(): Unit = ensureSetup {
- val outerId = 123
- val innerId = 345
- val buttonId = 456
-
- compose(
- """
- fun <T> refFor() = memo { Ref<T>() }
-
- val textAmbient = Ambient.of { "default" }
-
- @Composable fun DisplayTest(id: Int) {
- val text = +ambient(textAmbient)
- <TextView id text />
- }
-
- @Composable fun PortalTest() {
- val portal = +compositionReference()
- val ref = +refFor<LinearLayout>()
- <DisplayTest id=$outerId />
-
- <LinearLayout ref />
-
- val root = ref.value ?: error("Expected a linear")
-
- Compose.composeInto(root, portal) {
- <DisplayTest id=$innerId />
- }
- }
-
- @Composable
- fun TestApp() {
- val inc = +state { 1 }
-
- <Button id=$buttonId text="Click Me" inc.value += 1 } />
-
- <textAmbient.Provider value="value: ${"$"}{inc.value}">
- <PortalTest />
- </textAmbient.Provider>
- }
- """,
- { mapOf("text" to "") },
- """
- <TestApp />
- """
- ).then { activity ->
- val inner = activity.findViewById(innerId) as TextView
- val outer = activity.findViewById(outerId) as TextView
- val button = activity.findViewById(buttonId) as Button
-
- assertEquals("inner", "value: 1", inner.text)
- assertEquals("outer", "value: 1", outer.text)
-
- button.performClick()
- }.then { activity ->
- val inner = activity.findViewById(innerId) as TextView
- val outer = activity.findViewById(outerId) as TextView
- val button = activity.findViewById(buttonId) as Button
-
- assertEquals("inner", "value: 2", inner.text)
- assertEquals("outer", "value: 2", outer.text)
-
- button.performClick()
- }.then { activity ->
- val inner = activity.findViewById(innerId) as TextView
- val outer = activity.findViewById(outerId) as TextView
-
- assertEquals("inner", "value: 3", inner.text)
- assertEquals("outer", "value: 3", outer.text)
- }
- }
-
- @Test
- fun testCGNViewGroup(): Unit = ensureSetup {
- val tvId = 258
- val llId = 260
- var text = "Hello, world!"
- var orientation = LinearLayout.HORIZONTAL
-
- compose(
- { mapOf("text" to text, "orientation" to orientation) }, """
- <LinearLayout orientation id=$llId>
- <TextView text id=$tvId />
- </LinearLayout>
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val linearLayout = activity.findViewById(llId) as LinearLayout
-
- assertEquals(text, textView.text)
- assertEquals(orientation, linearLayout.orientation)
-
- text = "Other value"
- orientation = LinearLayout.VERTICAL
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val linearLayout = activity.findViewById(llId) as LinearLayout
-
- assertEquals(text, textView.text)
- assertEquals(orientation, linearLayout.orientation)
- }
- }
-
- @Test
- fun testMemoization(): Unit = ensureSetup {
- val tvId = 258
- val tagId = (3 shl 24) or "composed_set".hashCode()
-
- @Suppress("UNCHECKED_CAST")
- fun View.getComposedSet(): Set<String>? = getTag(tagId) as? Set<String>
-
- compose(
- """
- import android.view.View
-
- var composedSet = mutableSetOf<String>()
- var inc = 1
-
- fun View.setComposed(composed: Set<String>) = setTag($tagId, composed)
-
- @Composable fun ComposePrimitive(value: Int) {
- composedSet.add("ComposePrimitive(" + value + ")")
- }
-
- class MutableThing(var value: String)
-
- val constantMutableThing = MutableThing("const")
-
- @Composable fun ComposeMutable(value: MutableThing) {
- composedSet.add("ComposeMutable(" + value.value + ")")
- }
- """,
- { mapOf("text" to "") },
- """
- composedSet.clear()
-
- <ComposePrimitive value=123 />
- <ComposePrimitive value=inc />
- <ComposeMutable value=constantMutableThing />
- <ComposeMutable value=MutableThing("new") />
-
- <TextView id=$tvId composed=composedSet />
-
- inc++
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val composedSet = textView.getComposedSet() ?: error("expected a compose set to exist")
-
- fun assertContains(contains: Boolean, key: String) {
- assertEquals("composedSet contains key '$key'", contains, composedSet.contains(key))
- }
-
- assertContains(true, "ComposePrimitive(123)")
- assertContains(true, "ComposePrimitive(1)")
- assertContains(true, "ComposeMutable(const)")
- assertContains(true, "ComposeMutable(new)")
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- val composedSet = textView.getComposedSet() ?: error("expected a compose set to exist")
-
- fun assertContains(contains: Boolean, key: String) {
- assertEquals("composedSet contains key '$key'", contains, composedSet.contains(key))
- }
-
- // the primitive component skips based on equality
- assertContains(false, "ComposePrimitive(123)")
-
- // since the primitive changed, this one recomposes again
- assertContains(true, "ComposePrimitive(2)")
-
- // since this is a potentially mutable object, we don't skip based on it
- assertContains(true, "ComposeMutable(const)")
-
- // since its a new one every time, we definitely don't skip
- assertContains(true, "ComposeMutable(new)")
- }
- }
-
- @Test
- fun testCGNSimpleCall(): Unit = ensureSetup {
- val tvId = 258
- var text = "Hello, world!"
-
- compose(
- """
- @Composable fun SomeFun(x: String) {
- <TextView text=x id=$tvId />
- }
- """,
- { mapOf("text" to text) },
- """
- <SomeFun x=text />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
-
- text = "Other value"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
- }
- }
-
- @Test
- fun testCGNSimpleCall2(): Unit = ensureSetup {
- val tvId = 258
- var text = "Hello, world!"
- var someInt = 456
-
- compose(
- """
- class SomeClass(var x: String) {
- @Composable
- operator fun invoke(y: Int) {
- <TextView text="${"$"}x ${"$"}y" id=$tvId />
- }
- }
- """,
- { mapOf("text" to text, "someInt" to someInt) },
- """
- <SomeClass x=text y=someInt />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals("Hello, world! 456", textView.text)
-
- text = "Other value"
- someInt = 123
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals("Other value 123", textView.text)
- }
- }
-
- @Test
- fun testCGNSimpleCall3(): Unit = ensureSetup {
- val tvId = 258
- var text = "Hello, world!"
- var someInt = 456
-
- compose(
- """
- @Stateful
- class SomeClassoawid(var x: String) {
- @Composable
- operator fun invoke(y: Int) {
- <TextView text="${"$"}x ${"$"}y" id=$tvId />
- }
- }
- """,
- { mapOf("text" to text, "someInt" to someInt) },
- """
- <SomeClassoawid x=text y=someInt />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals("Hello, world! 456", textView.text)
-
- text = "Other value"
- someInt = 123
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals("Other value 123", textView.text)
- }
- }
-
- @Test
- fun testCGNCallWithChildren(): Unit = ensureSetup {
- val tvId = 258
- var text = "Hello, world!"
-
- compose(
- """
- @Composable
- fun Block(@Children children: () -> Unit) {
- <children />
- }
- """,
- { mapOf("text" to text) },
- """
- <Block>
- <Block>
- <TextView text id=$tvId />
- </Block>
- </Block>
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
-
- text = "Other value"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
-
- assertEquals(text, textView.text)
- }
- }
-
- @Test
- fun testCGNStuff(): Unit = ensureSetup {
- val tvId = 258
- var num = 123
-
- compose(
- """
- class OneArg {
- var foo = 0
- @Composable
- operator fun invoke() {
- <TextView text="${"$"}foo" id=$tvId />
- }
- }
- fun OneArg.setBar(bar: Int) { foo = bar }
- """,
- { mapOf("num" to num) },
- """
- <OneArg bar=num />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- assertEquals("$num", textView.text)
-
- num = 456
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- assertEquals("$num", textView.text)
- }
- }
-
- @Test
- fun testTagBasedMemoization(): Unit = ensureSetup {
- val tvId = 258
- var text = "Hello World"
-
- compose(
- """
- class A {
- var foo = ""
- inner class B {
- @Composable
- operator fun invoke() {
- <TextView text=foo id=$tvId />
- }
- }
- }
- """,
- { mapOf("text" to text) },
- """
- val a = A()
- a.foo = text
- <a.B />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- assertEquals(text, textView.text)
-
- text = "new value"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- assertEquals(text, textView.text)
- }
- }
-
- @Test
- fun testCGComposableFunctionInvocationOneParameter(): Unit = ensureSetup {
- val tvId = 91
- var phone = "(123) 456-7890"
- compose(
- """
- fun Phone(value: String) {
- <TextView text=value id=$tvId />
- }
- """, { mapOf("phone" to phone) }, """
- <Phone value=phone />
- """
- ).then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- assertEquals(phone, textView.text)
-
- phone = "(123) 456-7899"
- }.then { activity ->
- val textView = activity.findViewById(tvId) as TextView
- assertEquals(phone, textView.text)
- }
- }
-
- @Test
- fun testCGComposableFunctionInvocationTwoParameters(): Unit = ensureSetup {
- val tvId = 111
- val rsId = 112
- var left = 0
- var right = 0
- compose(
- """
- var addCalled = 0
-
- fun AddView(left: Int, right: Int) {
- addCalled++
- <TextView text="${'$'}left + ${'$'}right = ${'$'}{left + right}" id=$tvId />
- <TextView text="${'$'}addCalled" id=$rsId />
- }
- """, { mapOf("left" to left, "right" to right) }, """
- <AddView left right />
- """
- ).then { activity ->
- // Should be called on the first compose
- assertEquals("1", (activity.findViewById(rsId) as TextView).text)
- assertEquals(
- "$left + $right = ${left + right}",
- (activity.findViewById(tvId) as TextView).text
- )
- }.then { activity ->
- // Should be skipped on the second compose
- assertEquals("1", (activity.findViewById(rsId) as TextView).text)
- assertEquals(
- "$left + $right = ${left + right}",
- (activity.findViewById(tvId) as TextView).text
- )
-
- left = 1
- }.then { activity ->
- // Should be called again because left changed.
- assertEquals("2", (activity.findViewById(rsId) as TextView).text)
- assertEquals(
- "$left + $right = ${left + right}",
- (activity.findViewById(tvId) as TextView).text
- )
-
- right = 41
- }.then { activity ->
- // Should be called again because right changed
- assertEquals("3", (activity.findViewById(rsId) as TextView).text)
- assertEquals(
- "$left + $right = ${left + right}",
- (activity.findViewById(tvId) as TextView).text
- )
- }.then { activity ->
- // Should be skipped because nothing changed
- assertEquals("3", (activity.findViewById(rsId) as TextView).text)
- }
- }
-
- @Test
- fun testImplicitReceiverPassing1(): Unit = ensureSetup {
- compose(
- """
- fun Int.Foo(x: @Composable() Int.() -> Unit) {
- <x />
- }
- """,
- { mapOf<String, String>() },
- """
- val id = 42
-
- <id.Foo x={
- <TextView text="Hello, world!" id=this />
- } />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testImplicitReceiverPassing2(): Unit = ensureSetup {
- compose(
- """
- fun Int.Foo(x: @Composable() Int.(text: String) -> Unit, text: String) {
- <x text />
- }
- """,
- { mapOf<String, String>() },
- """
- val id = 42
-
- <id.Foo text="Hello, world!" x={ text ->
- <TextView text id=this />
- } />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testEffects1(): Unit = ensureSetup {
- compose(
- """
- import androidx.ui.androidview.adapters.*
-
- @Composable
- fun Counter() {
- <Observe>
- var count = +state { 0 }
- <TextView
- text=("Count: " + count.value)
- >
- count.value += 1
- }
- id=42
- />
- </Observe>
- }
- """,
- { mapOf<String, String>() },
- """
- <Counter />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Count: 0", textView.text)
- textView.performClick()
- }.then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Count: 1", textView.text)
- }
- }
-
- @Test
- fun testEffects2(): Unit = ensureSetup {
- compose(
- """
- import androidx.ui.androidview.adapters.*
-
- @Model class MyState<T>(var value: T)
-
- @Composable
- fun Counter() {
- <Observe>
- var count = +memo { MyState(0) }
- <TextView
- text=("Count: " + count.value)
- >
- count.value += 1
- }
- id=42
- />
- </Observe>
- }
- """,
- { mapOf<String, String>() },
- """
- <Counter />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Count: 0", textView.text)
- textView.performClick()
- }.then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Count: 1", textView.text)
- }
- }
-
- @Test
- fun testEffects3(): Unit = ensureSetup {
- val log = StringBuilder()
- compose(
- """
- import androidx.ui.androidview.adapters.*
-
- @Composable
- fun Counter(log: StringBuilder) {
- <Observe>
- var count = +state { 0 }
- +onPreCommit {
- log.append("a")
- }
- +onActive {
- log.append("b")
- }
- <TextView
- text=("Count: " + count.value)
- >
- count.value += 1
- }
- id=42
- />
- </Observe>
- }
- """,
- { mapOf("log" to log) },
- """
- <Counter log />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Count: 0", textView.text)
- assertEquals("ab", log.toString())
-
- execute {
- textView.performClick()
- }
-
- assertEquals("Count: 1", textView.text)
- assertEquals("aba", log.toString())
- }
- }
-
- @Test
- fun testEffects4(): Unit = ensureSetup {
- val log = StringBuilder()
- compose(
- """
- import androidx.ui.androidview.adapters.*
-
- fun printer(log: StringBuilder, str: String) = effectOf<Unit> {
- +onPreCommit {
- log.append(str)
- }
- }
-
- @Composable
- fun Counter(log: StringBuilder) {
- <Observe>
- var count = +state { 0 }
- +printer(log, "" + count.value)
- <TextView
- text=("Count: " + count.value)
- >
- count.value += 1
- }
- id=42
- />
- </Observe>
- }
- """,
- { mapOf("log" to log) },
- """
- <Counter log />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Count: 0", textView.text)
- assertEquals("0", log.toString())
-
- execute {
- textView.performClick()
- }
-
- assertEquals("Count: 1", textView.text)
- assertEquals("01", log.toString())
- }
- }
-
- // b/118610495
- @Test
- fun testCGChildCompose(): Unit = ensureSetup {
- val tvId = 153
-
- var text = "Test 1"
-
- compose(
- """
- var called = 0
-
- class TestContainer(@Children var children: @Composable() ()->Unit): Component() {
- override fun compose() {
- <LinearLayout>
- <children />
- </LinearLayout>
- }
- }
-
- class TestClass(var text: String): Component() {
- override fun compose() {
- <TestContainer>
- <TextView text id=$tvId />
- </TestContainer>
- }
- }
- """, { mapOf("text" to text) }, """
- <TestClass text />
- """
- ).then { activity ->
- val tv = activity.findViewById(tvId) as TextView
- assertEquals(text, tv.text)
-
- text = "Test 2"
- }.then { activity ->
- val tv = activity.findViewById(tvId) as TextView
- assertEquals(text, tv.text)
- }
- }
-
- @Test
- fun testPrivatePivotalProperties(): Unit = ensureSetup {
- val tvId = 153
-
- compose(
- """
- class ClassComponent(@Children private val callback: () -> Unit) : Component() {
- override fun compose() {
- <callback />
- }
- }
- """, { mapOf("text" to "") }, """
- <ClassComponent>
- <TextView id=$tvId text="Hello world!" />
- </ClassComponent>
- """
- ).then { activity ->
- val tv = activity.findViewById(tvId) as TextView
- assertEquals("Hello world!", tv.text)
- }.then { activity ->
- val tv = activity.findViewById(tvId) as TextView
- assertEquals("Hello world!", tv.text)
- }
- }
-
- @Test
- fun testVariableCalls1(): Unit = ensureSetup {
- compose(
- """
- val component = @Composable {
- <TextView text="Hello, world!" id=42 />
- }
- """,
- { mapOf<String, String>() },
- """
- <component />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testVariableCalls2(): Unit = ensureSetup {
- compose(
- """
- val component = @Composable {
- <TextView text="Hello, world!" id=42 />
- }
- class HolderA(val composable: @Composable() () -> Unit)
-
- val holder = HolderA(component)
-
- """,
- { mapOf<String, String>() },
- """
- <holder.composable />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testVariableCalls3(): Unit = ensureSetup {
- compose(
- """
- val component = @Composable {
- <TextView text="Hello, world!" id=42 />
- }
- class HolderB(val composable: @Composable() () -> Unit) {
- @Composable
- fun Foo() {
- <composable />
- }
- }
-
- val holder = HolderB(component)
-
- """,
- { mapOf<String, String>() },
- """
- <holder.Foo />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testVariableCalls4(): Unit = ensureSetup {
- compose(
- """
- val component = @Composable {
- <TextView text="Hello, world!" id=42 />
- }
- class HolderC(val composable: @Composable() () -> Unit) {
- inner class Foo(): Component() {
- override fun compose() {
- <composable />
- }
- }
- }
-
- val holder = HolderC(component)
-
- """,
- { mapOf<String, String>() },
- """
- <holder.Foo />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- // b/123721921
- @Test
- fun testDefaultParameters1(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo(a: Int = 42, b: String) {
- <TextView text=b id=a />
- }
- """,
- { mapOf<String, String>() },
- """
- <Foo b="Hello, world!" />
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testDefaultParameters2(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun Foo(a: Int = 42, b: String, @Children c: () -> Unit) {
- <c />
- <TextView text=b id=a />
- }
- """,
- { mapOf<String, String>() },
- """
- <Foo b="Hello, world!">
- </Foo>
- """
- ).then { activity ->
- val textView = activity.findViewById(42) as TextView
- assertEquals("Hello, world!", textView.text)
- }
- }
-
- @Test
- fun testPropertiesAndCtorParamsOnEmittables(): Unit = codegen(
- """
- class SimpleEmittable(label: String? = null) : Emittable {
- var label: String? = null
- override fun emitInsertAt(index: Int, instance: Emittable) {}
- override fun emitMove(from: Int, to: Int, count: Int) {}
- override fun emitRemoveAt(index: Int, count: Int) {}
- }
-
- @Composable
- fun foo() {
- <SimpleEmittable label="Foo" />
- }
- """
- )
-
- @Test
- fun testMovement(): Unit = ensureSetup {
- val tvId = 50
- val btnIdAdd = 100
- val btnIdUp = 200
- val btnIdDown = 300
-
- // Duplicate the steps to reproduce an issue discovered in the Reorder example
- compose(
- """
- fun <T> List<T>.move(from: Int, to: Int): List<T> {
- if (to < from) return move(to, from)
- val item = get(from)
- val currentItem = get(to)
- val left = if (from > 0) subList(0, from) else emptyList()
- val right = if (to < size) subList(to + 1, size) else emptyList()
- val middle = if (to - from > 1) subList(from + 1, to) else emptyList()
- return left + listOf(currentItem) + middle + listOf(item) + right
- }
-
- @Composable
- fun Reordering() {
- <Observe>
- val items = +state { listOf(1, 2, 3, 4, 5) }
-
- <LinearLayout orientation=LinearLayout.VERTICAL>
- items.value.forEachIndexed { index, id ->
- <Item
- id
- amount ->
- val next = index + amount
- if (next >= 0 && next < items.value.size) {
- items.value = items.value.move(index, index + amount)
- }
- }
- />
- }
- </LinearLayout>
- </Observe>
- }
-
- @Composable
- private fun Item(@Pivotal id: Int, onMove: (Int) -> Unit) {
- <Observe>
- val count = +state { 0 }
- <LinearLayout orientation=LinearLayout.HORIZONTAL>
- <TextView id=(id+$tvId) text="id: ${'$'}id amt: ${'$'}{count.value}" />
- <Button id=(id+$btnIdAdd) text="+" count.value++ } />
- <Button id=(id+$btnIdUp) text="Up" onMove(1) } />
- <Button id=(id+$btnIdDown) text="Down" onMove(-1) } />
- </LinearLayout>
- </Observe>
- }
- """, { emptyMap<String, String>() },
- """
- <Reordering />
- """
- ).then { activity ->
- // Click 5 add
- val button = activity.findViewById(btnIdAdd + 5) as Button
- button.performClick()
- }.then { activity ->
- // Click 5 down
- val button = activity.findViewById(btnIdDown + 5) as Button
- button.performClick()
- }.then { activity ->
- // Click 5 down
- val button = activity.findViewById(btnIdDown + 5) as Button
- button.performClick()
- }.then { activity ->
- // Click 5 up
- val button = activity.findViewById(btnIdUp + 5) as Button
- button.performClick()
- }.then { activity ->
- // Click 5 up
- val button = activity.findViewById(btnIdUp + 5) as Button
- button.performClick()
- }.then { activity ->
- // Click 5 add
- val button = activity.findViewById(btnIdAdd + 5) as Button
- button.performClick()
- }.then { activity ->
- val textView = activity.findViewById(tvId + 5) as TextView
- assertEquals("id: 5 amt: 2", textView.text)
- }
- }
-
- @Test
- fun testObserveKtxWithInline(): Unit = ensureSetup {
- compose(
- """
- @Composable
- fun SimpleComposable() {
- val count = +state { 1 }
- <Box>
- repeat(count.value) {
- <Button text="Increment" count.value += 1 } id=(41+it) />
- }
- </Box>
- }
-
- @Composable
- fun Box(@Children children: ()->Unit) {
- <LinearLayout orientation=LinearLayout.VERTICAL>
- <children />
- </LinearLayout>
- }
- """, { emptyMap<String, String>() },
- """
- <SimpleComposable />
- """
- ).then { activity ->
- val button = activity.findViewById(41) as Button
- button.performClick()
- button.performClick()
- button.performClick()
- button.performClick()
- button.performClick()
- }.then { activity ->
- assertNotNull(activity.findViewById(46))
- }
- }
-
- @Test
- fun testKeyTag(): Unit = ensureSetup {
- compose(
- """
- val list = mutableListOf(0,1,2,3)
-
- @Composable
- fun Reordering() {
- <LinearLayout>
- <Recompose> recompose ->
- <Button id=50 text="Recompose!" >
- list.add(list.removeAt(0)); recompose();
- } />
- <LinearLayout id=100>
- for(id in list) {
- <Key key=id>
- <StatefulButton />
- </Key>
- }
- </LinearLayout>
- </Recompose>
- </LinearLayout>
- }
-
- @Composable
- private fun StatefulButton() {
- val count = +state { 0 }
- <Button text="Clicked ${'$'}{count.value} times!" count.value++ } />
- }
- """, { emptyMap<String, String>() },
- """
- <Reordering />
- """
- ).then { activity ->
- val layout = activity.findViewById(100) as LinearLayout
- layout.getChildAt(0).performClick()
- }.then { activity ->
- val recomposeButton = activity.findViewById(50) as Button
- recomposeButton.performClick()
- }.then { activity ->
- val layout = activity.findViewById(100) as LinearLayout
- assertEquals("Clicked 0 times!", (layout.getChildAt(0) as Button).text)
- assertEquals("Clicked 0 times!", (layout.getChildAt(1) as Button).text)
- assertEquals("Clicked 0 times!", (layout.getChildAt(2) as Button).text)
- assertEquals("Clicked 1 times!", (layout.getChildAt(3) as Button).text)
- }
- }
-
- override fun setUp() {
- isSetup = true
- super.setUp()
- }
-
- private var isSetup = false
- private inline fun <T> ensureSetup(block: () -> T): T {
- if (!isSetup) setUp()
- return block()
- }
-
- fun codegen(text: String, dumpClasses: Boolean = false): Unit = ensureSetup {
- val className = "Test_${uniqueNumber++}"
- val fileName = "$className.kt"
-
- classLoader(
- """
- import android.content.Context
- import android.widget.*
- import androidx.compose.*
-
- $text
-
- """, fileName, dumpClasses
- )
- }
-
- fun compose(text: String, dumpClasses: Boolean = false): CompositionTest =
- compose({ mapOf<String, Any>() }, text, dumpClasses)
-
- fun <T : Any> compose(
- valuesFactory: () -> Map<String, T>,
- text: String,
- dumpClasses: Boolean = false
- ) = compose("", valuesFactory, text, dumpClasses)
-
- private fun execute(block: () -> Unit) {
- val scheduler = RuntimeEnvironment.getMasterScheduler()
- scheduler.pause()
- block()
- scheduler.advanceToLastPostedRunnable()
- }
-
- fun <T : Any> compose(
- prefix: String,
- valuesFactory: () -> Map<String, T>,
- text: String,
- dumpClasses: Boolean = false
- ): CompositionTest {
- val className = "Test_${uniqueNumber++}"
- val fileName = "$className.kt"
-
- val candidateValues = valuesFactory()
-
- @Suppress("NO_REFLECTION_IN_CLASS_PATH")
- val parameterList = candidateValues.map {
- "${it.key}: ${it.value::class.qualifiedName}"
- }.joinToString()
- val parameterTypes = candidateValues.map {
- it.value::class.javaPrimitiveType
- ?: it.value::class.javaObjectType
- }.toTypedArray()
-
- val compiledClasses = classLoader(
- """
- import android.content.Context
- import android.widget.*
- import androidx.compose.*
- import androidx.compose.adapters.*
- import androidx.ui.androidview.adapters.*
-
- $prefix
-
- class $className {
-
- fun test($parameterList) {
- $text
- }
- }
- """, fileName, dumpClasses
- )
-
- val allClassFiles =
- compiledClasses.allGeneratedFiles.filter { it.relativePath.endsWith(".class") }
-
- val loader = URLClassLoader(emptyArray(), this.javaClass.classLoader)
-
- val instanceClass = run {
- var instanceClass: Class<*>? = null
- var loadedOne = false
- for (outFile in allClassFiles) {
- val bytes = outFile.asByteArray()
- val loadedClass = loadClass(loader, null, bytes)
- if (loadedClass.name == className) instanceClass = loadedClass
- loadedOne = true
- }
- if (!loadedOne) error("No classes loaded")
- instanceClass ?: error("Could not find class $className in loaded classes")
- }
-
- val instanceOfClass = instanceClass.newInstance()
- val testMethod = instanceClass.getMethod("test", *parameterTypes)
-
- return compose {
- val values = valuesFactory()
- val arguments = values.map { it.value as Any }.toTypedArray()
- testMethod.invoke(instanceOfClass, *arguments)
- }
- }
-}
-
-var uniqueNumber = 0
-
-fun loadClass(loader: ClassLoader, name: String?, bytes: ByteArray): Class<*> {
- val defineClassMethod = ClassLoader::class.javaObjectType.getDeclaredMethod(
- "defineClass",
- String::class.javaObjectType,
- ByteArray::class.javaObjectType,
- Int::class.javaPrimitiveType,
- Int::class.javaPrimitiveType
- )
- defineClassMethod.isAccessible = true
- return defineClassMethod.invoke(loader, name, bytes, 0, bytes.size) as Class<*>
-}
-
-const val ROOT_ID = 18284847
-
-private class TestActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(LinearLayout(this).apply { id = ROOT_ID })
- }
-}
-
-private val Activity.root get() = findViewById(ROOT_ID) as ViewGroup
-
-private class Root(val composable: () -> Unit) : Component() {
- override fun compose() = composable()
-}
-
-class CompositionTest(val composable: () -> Unit) {
-
- inner class ActiveTest(val activity: Activity, val cc: CompositionContext) {
- fun then(block: (activity: Activity) -> Unit): ActiveTest {
- val scheduler = RuntimeEnvironment.getMasterScheduler()
- scheduler.advanceToLastPostedRunnable()
- cc.compose()
- scheduler.advanceToLastPostedRunnable()
- block(activity)
- return this
- }
- }
-
- fun then(block: (activity: Activity) -> Unit): ActiveTest {
- val scheduler = RuntimeEnvironment.getMasterScheduler()
- scheduler.pause()
- val controller = Robolectric.buildActivity(TestActivity::class.java)
- val activity = controller.create().get()
- val root = activity.root
- val component = Root(composable)
- val cc = Compose.createCompositionContext(root.context, root, component, null)
- return ActiveTest(activity, cc).then(block)
- }
-}
-
-fun compose(composable: () -> Unit) = CompositionTest(composable)
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCrossModuleTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCrossModuleTests.kt
index c35a9ad..2aec6de 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCrossModuleTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxCrossModuleTests.kt
@@ -47,6 +47,120 @@
class KtxCrossModuleTests : AbstractCodegenTest() {
@Test
+ fun testCrossModule_SimpleCompositionxxx(): Unit = ensureSetup {
+ val tvId = 29
+
+ compose(
+ "TestF", mapOf(
+ "library module" to mapOf(
+ "my/test/lib/InternalComp.kt" to """
+ package my.test.lib
+
+ import androidx.compose.Composable
+ import androidx.compose.composer
+ import androidx.compose.Emittable
+ import androidx.ui.core.DataNode
+ import androidx.ui.core.ParentData
+ import androidx.ui.core.ParentDataKey
+
+ class Bar
+
+ class XDataNodeKey<T>(val name: String)
+
+ class XDataNode<T>(var key: XDataNodeKey<T>, var value: T) : Emittable {
+ override fun emitInsertAt(index: Int, instance: Emittable) {
+
+ }
+
+ override fun emitRemoveAt(index: Int, count: Int) {
+
+ }
+
+ override fun emitMove(from: Int, to: Int, count: Int) {
+
+ }
+ }
+
+ val Key1 = XDataNodeKey<String>("foo")
+ val Key2 = XDataNodeKey<Bar>("bar")
+
+ class ViewEmitWrapper(context: android.content.Context) : android.view.View(context) {
+ var emittable: Emittable? = null
+ }
+
+ class EmitViewWrapper : Emittable {
+ var view: android.view.View? = null
+ override fun emitInsertAt(index: Int, instance: Emittable) {
+
+ }
+
+ override fun emitRemoveAt(index: Int, count: Int) {
+
+ }
+
+ override fun emitMove(from: Int, to: Int, count: Int) {
+
+ }
+ }
+
+ """
+ ),
+ "Main" to mapOf(
+ "my/test/app/Main.kt" to """
+ package my.test.app
+
+ import android.widget.*
+ import androidx.compose.*
+ import my.test.lib.*
+
+ var doRecompose: () -> Unit = {}
+
+ class TestF {
+ @Composable
+ fun compose() {
+
+ composer.registerAdapter { parent, child ->
+ when (parent) {
+ is android.view.ViewGroup -> when (child) {
+ is android.view.View -> child
+ is Emittable -> ViewEmitWrapper(composer.composer.context).apply {
+ emittable = child }
+ else -> null
+ }
+ is Emittable -> when (child) {
+ is android.view.View -> EmitViewWrapper().apply { view = child }
+ is Emittable -> child
+ else -> null
+ }
+ else -> null
+ }
+ }
+
+ Recompose { recompose ->
+ Foo()
+ }
+ }
+
+ fun advance() {
+ doRecompose()
+ }
+ }
+
+ @Composable
+ fun Foo() {
+ val s = ""
+ val b = Bar()
+ XDataNode(key = Key2, value = b)
+ }
+ """
+ )
+ )
+ ).then { activity ->
+ assert(true)
+ }
+ }
+
+ @Test
fun testCrossModule_SimpleComposition(): Unit = ensureSetup {
val tvId = 29
@@ -58,11 +172,8 @@
import androidx.compose.*
- class InternalComp(@Children var block: () -> Unit) : Component() {
-
- override fun compose() {
- <block />
- }
+ @Composable fun InternalComp(block: @Composable() () -> Unit) {
+ block()
}
"""
),
@@ -80,10 +191,10 @@
class TestF {
@Composable
fun compose() {
- <Recompose> recompose ->
+ Recompose { recompose ->
doRecompose = recompose
- <Foo bar />
- </Recompose>
+ Foo(bar)
+ }
}
fun advance() {
@@ -94,9 +205,9 @@
@Composable
fun Foo(bar: Int) {
- <InternalComp>
- <TextView text="${'$'}bar" id=$tvId />
- </InternalComp>
+ InternalComp {
+ TextView(text="${'$'}bar", id=$tvId)
+ }
}
"""
)
@@ -126,10 +237,10 @@
@Composable
fun ComponentFunction(name: String, age: Int) {
- <LinearLayout>
- <TextView text=name id=$tvName />
- <TextView text="${'$'}age" id=$tvAge />
- </LinearLayout>
+ LinearLayout {
+ TextView(text=name, id=$tvName)
+ TextView(text="${'$'}age", id=$tvAge)
+ }
}
"""
),
@@ -148,10 +259,10 @@
class TestF {
@Composable
fun compose() {
- <Recompose> recompose ->
+ Recompose { recompose ->
doRecompose = recompose
- <Foo name age />
- </Recompose>
+ Foo(name=name, age=age)
+ }
}
fun advance() {
@@ -163,7 +274,7 @@
@Composable
fun Foo(name: String, age: Int) {
- <ComponentFunction name age />
+ ComponentFunction(name, age)
}
"""
)
@@ -198,10 +309,10 @@
object Container {
@Composable
fun ComponentFunction(name: String, age: Int) {
- <LinearLayout>
- <TextView text=name id=$tvName />
- <TextView text="${'$'}age" id=$tvAge />
- </LinearLayout>
+ LinearLayout {
+ TextView(text=name, id=$tvName)
+ TextView(text="${'$'}age", id=$tvAge)
+ }
}
}
"""
@@ -221,10 +332,10 @@
class TestF {
@Composable
fun compose() {
- <Recompose> recompose ->
+ Recompose { recompose ->
doRecompose = recompose
- <Foo name age />
- </Recompose>
+ Foo(name, age)
+ }
}
fun advance() {
@@ -236,7 +347,7 @@
@Composable
fun Foo(name: String, age: Int) {
- <Container.ComponentFunction name age />
+ Container.ComponentFunction(name, age)
}
"""
)
@@ -254,157 +365,6 @@
}
}
- @Test
- fun testCrossModule_ConstructorProperties(): Unit = ensureSetup {
- val tvId = 29
-
- compose(
- "TestF", mapOf(
- "library module" to mapOf(
- "my/test/lib/MyComponent.kt" to """
- package my.test.lib
-
- import androidx.compose.*
-
- class MyComponent(
- var a: Int,
- var b: String,
- @Children var children: (a: Int, b: String)->Unit
- ) : Component() {
-
- override fun compose() {
- <children a b />
- }
- }
- """
- ),
- "Main" to mapOf(
- "my/test/app/Main.kt" to """
- package my.test.app
-
- import android.widget.*
- import androidx.compose.*
- import my.test.lib.*
-
- var bar = 0
- var doRecompose: () -> Unit = {}
-
- class TestF {
- @Composable
- fun compose() {
- <Recompose> recompose ->
- doRecompose = recompose
- <Foo bar />
- </Recompose>
- }
-
- fun advance() {
- bar++
- doRecompose()
- }
- }
-
- @Composable
- fun Foo(bar: Int) {
- <MyComponent b="SomeValue" a=bar> c, d ->
- <TextView text="${'$'}d: ${'$'}c" id=$tvId />
- </MyComponent>
- }
- """
- )
- )
- ).then { activity ->
- val tv = activity.findViewById(tvId) as TextView
- assertEquals("SomeValue: 0", tv.text)
- }.then { activity ->
- val tv = activity.findViewById(tvId) as TextView
- assertEquals("SomeValue: 1", tv.text)
- }
- }
-
- @Test
- fun testCrossModule_ConstructorParameters(): Unit = ensureSetup {
- val tvId = 29
-
- compose(
- "TestF", mapOf(
- "library module" to mapOf(
- "my/test/lib/MyComponent.kt" to """
- package my.test.lib
-
- import androidx.compose.*
-
- class MyComponent(
- a: Int,
- b: String,
- @Children var children: (a: Int, b: String)->Unit
- ) : Component() {
- val aValue = a
- val bValue = b
-
- override fun compose() {
- <children a=aValue b=bValue />
- }
- }
- """
- ),
- "Main" to mapOf(
- "my/test/app/Main.kt" to """
- package my.test.app
-
- import android.widget.*
- import androidx.compose.*
- import my.test.lib.*
-
- var bar = 0
- var doRecompose: () -> Unit = {}
-
- class MyComponent(
- a: Int,
- b: String,
- @Children var children: (a: Int, b: String)->Unit
- ) : Component() {
- val aValue = a
- val bValue = b
-
- override fun compose() {
- <children a=aValue b=bValue />
- }
- }
-
- class TestF {
- @Composable
- fun compose() {
- <Recompose> recompose ->
- doRecompose = recompose
- <Foo bar />
- </Recompose>
- }
-
- fun advance() {
- bar++
- doRecompose()
- }
- }
-
- @Composable
- fun Foo(bar: Int) {
- <MyComponent b="SomeValue" a=bar> c, d ->
- <TextView text="${'$'}d: ${'$'}c" id=$tvId />
- </MyComponent>
- }
- """
- )
- )
- ).then { activity ->
- val tv = activity.findViewById(tvId) as TextView
- assertEquals("SomeValue: 0", tv.text)
- }.then { activity ->
- val tv = activity.findViewById(tvId) as TextView
- assertEquals("SomeValue: 1", tv.text)
- }
- }
-
fun compose(
mainClassName: String,
modules: Map<String, Map<String, String>>,
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxModelCodeGenTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxModelCodeGenTests.kt
index f5aca24..b031da3 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxModelCodeGenTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxModelCodeGenTests.kt
@@ -79,10 +79,10 @@
@Composable
fun PersonView4(person: Person4) {
- <Observe>
- <TextView text=person.name id=$tvNameId />
- <TextView text=person.age.toString() id=$tvAgeId />
- </Observe>
+ Observe {
+ TextView(text=person.name, id=$tvNameId)
+ TextView(text=person.age.toString(), id=$tvAgeId)
+ }
}
val president = Person4("$PRESIDENT_NAME_1", $PRESIDENT_AGE_1)
@@ -90,7 +90,7 @@
president.name = name
president.age = age
""", """
- <PersonView4 person=president />
+ PersonView4(person=president)
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
@@ -124,18 +124,17 @@
@Composable
fun PresidentView() {
- <Observe>
- <TextView text=president.name id=$tvNameId />
- <TextView text=president.age.toString() id=$tvAgeId />
- </Observe>
+ Observe {
+ TextView(text=president.name, id=$tvNameId)
+ TextView(text=president.age.toString(), id=$tvAgeId)
+ }
}
-
""", { mapOf("name" to name, "age" to age) }, """
president.name = name
president.age = age
""", """
- <PresidentView />
+ PresidentView()
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
@@ -166,10 +165,10 @@
@Composable
fun PersonViewB(person: PersonB) {
- <Observe>
- <TextView text=person.name id=$tvNameId />
- <TextView text=person.age.toString() id=$tvAgeId />
- </Observe>
+ Observe {
+ TextView(text=person.name, id=$tvNameId)
+ TextView(text=person.age.toString(), id=$tvAgeId)
+ }
}
val president = PersonB("$PRESIDENT_NAME_1", $PRESIDENT_AGE_1)
@@ -177,7 +176,7 @@
president.name = name
president.age = age
""", """
- <PersonViewB person=president />
+ PersonViewB(person=president)
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
@@ -208,10 +207,10 @@
@Composable
fun PersonViewC(person: PersonC) {
- <Observe>
- <TextView text=person.name id=$tvNameId />
- <TextView text=person.age.toString() id=$tvAgeId />
- </Observe>
+ Observe {
+ TextView(text=person.name, id=$tvNameId)
+ TextView(text=person.age.toString(), id=$tvAgeId)
+ }
}
val president = FrameManager.unframed { PersonC("$PRESIDENT_NAME_1", $PRESIDENT_AGE_1) }
@@ -219,7 +218,7 @@
president.name = name
president.age = age
""", """
- <PersonViewC person=president />
+ PersonViewC(person=president)
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
@@ -250,10 +249,10 @@
@Composable
fun PersonViewD(person: PersonD) {
- <Observe>
- <TextView text=person.name id=$tvNameId />
- <TextView text=person.age.toString() id=$tvAgeId />
- </Observe>
+ Observe {
+ TextView(text=person.name, id=$tvNameId)
+ TextView(text=person.age.toString(), id=$tvAgeId)
+ }
}
val president = FrameManager.framed { PersonD("$PRESIDENT_NAME_1", $PRESIDENT_AGE_1).apply { age = $PRESIDENT_AGE_1 } }
@@ -261,7 +260,7 @@
president.name = name
president.age = age
""", """
- <PersonViewD person=president />
+ PersonViewD(person=president)
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTransformationTest.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTransformationTest.kt
index e918f5e..f4a2054 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTransformationTest.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTransformationTest.kt
@@ -31,12 +31,12 @@
@Composable
fun SimpleComposable() {
- <FancyButton state=FancyButtonData() />
+ FancyButton(state=FancyButtonData())
}
@Composable
fun FancyButton(state: FancyButtonData) {
- <Button text=("Clicked "+state.x+" times") id=42 />
+ Button(text=("Clicked "+state.x+" times"), id=42)
}
"""
)
@@ -60,7 +60,7 @@
class Foo {
@Composable
operator fun invoke() {
- <TextView />
+ TextView()
}
}
"""
@@ -74,9 +74,9 @@
class Foo {
@Composable
operator fun invoke() {
- <TextView />
- <TextView />
- <TextView />
+ TextView()
+ TextView()
+ TextView()
}
}
"""
@@ -90,13 +90,13 @@
class Foo {
@Composable
operator fun invoke() {
- <LinearLayout>
- <TextView />
- <LinearLayout>
- <TextView />
- <TextView />
- </LinearLayout>
- </LinearLayout>
+ LinearLayout {
+ TextView()
+ LinearLayout {
+ TextView()
+ TextView()
+ }
+ }
}
}
"""
@@ -106,15 +106,13 @@
"""
import androidx.compose.*
- class Bar {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bar() {}
class Foo {
@Composable
operator fun invoke() {
- <Bar />
+ Bar()
}
}
"""
@@ -124,17 +122,15 @@
"""
import androidx.compose.*
- class Bar {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bar() {}
class Foo {
@Composable
operator fun invoke() {
- <Bar />
- <Bar />
- <Bar />
+ Bar()
+ Bar()
+ Bar()
}
}
"""
@@ -145,58 +141,15 @@
import androidx.compose.*
import android.widget.*
- class Bar {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bar() {}
class Foo {
@Composable
operator fun invoke() {
- <LinearLayout>
- <Bar />
- </LinearLayout>
- }
- }
- """
- )
-
- fun testAttributes() = testCompile(
- """
- import androidx.compose.*
- import android.widget.*
-
- class Bar {
- var num: Int = 0
- var a: String = ""
- var b: String = ""
- @Composable
- operator fun invoke() {}
- }
-
- class Foo {
- @Composable
- operator fun invoke() {
- val s = "foo" + "bar"
- <LinearLayout orientation=LinearLayout.VERTICAL>
- <Bar num=123 a=s b="const" />
- </LinearLayout>
- }
- }
- """
- )
-
- // NOTE: test the key attribute separately as it receives different handling.
- // TODO(lmr): add test in compose-runtime around behavior of this attribute
- fun testKeyAttributes() = testCompile(
- """
- import androidx.compose.*
-
- class Foo {
- var key: Int = 0
- @Composable
- operator fun invoke() {
- <Foo key=123 />
+ LinearLayout {
+ Bar()
+ }
}
}
"""
@@ -206,16 +159,14 @@
"""
import androidx.compose.*
- class Bar {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bar() {}
class Foo {
@Composable
operator fun invoke() {
listOf(1, 2, 3).forEach {
- <Bar />
+ Bar()
}
}
}
@@ -226,16 +177,14 @@
"""
import androidx.compose.*
- class Bar {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bar() {}
class Foo {
@Composable
operator fun invoke() {
for (i in listOf(1, 2, 3)) {
- <Bar />
+ Bar()
}
}
}
@@ -246,10 +195,8 @@
"""
import androidx.compose.*
- class Bar {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bar() {}
class Foo {
var visible: Boolean = false
@@ -257,7 +204,7 @@
operator fun invoke() {
if (!visible) return
else "" // TODO: Remove this line when fixed upstream
- <Bar />
+ Bar()
}
}
"""
@@ -266,26 +213,21 @@
fun testConditionalRendering() = testCompile(
"""
import androidx.compose.*
- import java.util.Random
- class Bar {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bar() {}
- class Bam {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bam() {}
class Foo {
var visible: Boolean = false
@Composable
operator fun invoke() {
if (!visible) {
- <Bar />
+ Bar()
} else {
- <Bam />
+ Bam()
}
}
}
@@ -296,94 +238,18 @@
"""
import androidx.compose.*
- class Bar {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Bar() {}
+
class Foo {
@Composable
operator fun invoke() {
val foo = object: Function0<Unit> {
override fun invoke() {
- <Bar />
+ Bar()
}
}
- <foo />
- }
- }
- """
- )
-
- fun testFunctionInstanceMultipleArgs() = testCompile(
- """
- import androidx.compose.*
-
- class Bar {
- @Composable
- operator fun invoke() {}
- }
- class Foo {
- @Composable
- operator fun invoke() {
- val foo = object: Function2<@kotlin.ParameterName("x") String,
- @kotlin.ParameterName("y")Int, Unit> {
- override fun invoke(x: String, y: Int) {
- <Bar />
- }
- }
- <foo x="foo" y=123 />
- }
- }
- """
- )
-
- fun testComposeAttribute() = testCompile(
- """
- import androidx.compose.*
-
- class Bar {
- @Composable
- operator fun invoke() {}
- }
- class Foo {
- lateinit var children: () -> Unit
- @Composable
- operator fun invoke() {
- val children = children
- <children />
- }
- }
- """
- )
-
- fun testComposeWithParamsAttribute() = testCompile(
- """
- import androidx.compose.*
-
- class Bar {
- @Composable
- operator fun invoke() {}
- }
- class Foo {
- lateinit var children: (x: Int) -> Unit
- @Composable
- operator fun invoke() {
- val children = children
- <children x=123 />
- }
- }
- """
- )
-
- fun testComposeAttributeFunctionType() = testCompile(
- """
- import androidx.compose.*
-
- class X {
- lateinit var composeItem: Function1<@kotlin.ParameterName("arg0") Int, Unit>
- fun fn() {
- val composeItem = composeItem
- <composeItem arg0=123 />
+ foo()
}
}
"""
@@ -400,36 +266,7 @@
class X {
@Composable
operator fun invoke() {
- <LinearLayout someExtension=123 />
- }
- }
- """
- )
-
- fun testChildrenOfComponent() = testCompile(
- """
- import android.widget.*
- import androidx.compose.*
-
- class HelperComponent {
- private lateinit var children: () -> Unit
-
- @Children
- fun setChildren2(x: () -> Unit) { children = x }
- @Composable
- operator fun invoke() {
- <children />
- }
- }
-
- class MainComponent {
- var name = "World"
- @Composable
- operator fun invoke() {
- <HelperComponent>
- <TextView text="some child content2!" />
- <TextView text="some child content!3" />
- </HelperComponent>
+ LinearLayout(someExtension=123)
}
}
"""
@@ -440,56 +277,21 @@
import android.widget.*
import androidx.compose.*
- class HelperComponent {
- private lateinit var children: (title: String, rating: Int) -> Unit
- @Children fun setChildren2(x: (title: String, rating: Int) -> Unit) { children = x }
-
- @Composable
- operator fun invoke() {
- val children = this.children
- <children title="Hello World!" rating=5 />
- <children title="Kompose is awesome!" rating=5 />
- <children title="Bitcoin!" rating=4 />
- }
+ @Composable fun HelperComponent(
+ children: @Composable() (title: String, rating: Int) -> Unit
+ ) {
+ children("Hello World!", 5)
+ children("Kompose is awesome!", 5)
+ children("Bitcoin!", 4)
}
class MainComponent {
var name = "World"
@Composable
operator fun invoke() {
- <HelperComponent> title: String, rating: Int ->
- <TextView text=(title+" ("+rating+" stars)") />
- </HelperComponent>
- }
- }
- """
- )
-
- fun testChildrenWithUntypedParameters() = testCompile(
- """
- import android.widget.*
- import androidx.compose.*
-
- class HelperComponent {
- private lateinit var children: (title: String, rating: Int) -> Unit
-
- @Children
- fun setChildren2(x: (title: String, rating: Int) -> Unit) { children = x }
- @Composable
- operator fun invoke() {
- <children title="Hello World!" rating=5 />
- <children title="Kompose is awesome!" rating=5 />
- <children title="Bitcoin!" rating=4 />
- }
- }
-
- class MainComponent {
- var name = "World"
- @Composable
- operator fun invoke() {
- <HelperComponent> title, rating ->
- <TextView text=(title+" ("+rating+" stars)") />
- </HelperComponent>
+ HelperComponent { title: String, rating: Int ->
+ TextView(text=(title+" ("+rating+" stars)"))
+ }
}
}
"""
@@ -500,14 +302,7 @@
import android.widget.*
import androidx.compose.*
- class HelperComponent {
- lateinit private var children: () -> Unit
- @Children
- fun setChildren2(x: () -> Unit) { children = x }
- @Composable
- operator fun invoke() {
- val children = this.children
- }
+ @Composable fun HelperComponent(children: @Composable() () -> Unit) {
}
class MainComponent {
@@ -515,9 +310,9 @@
@Composable
operator fun invoke() {
val childText = "Hello World!"
- <HelperComponent>
- <TextView text=childText />
- </HelperComponent>
+ HelperComponent {
+ TextView(text=childText + name)
+ }
}
}
"""
@@ -528,24 +323,12 @@
import android.widget.*
import androidx.compose.*
- class A {
- lateinit private var children: () -> Unit
- @Children
- fun setChildren2(x: () -> Unit) { children = x }
- @Composable
- operator fun invoke() {
- val children = this.children
- }
+ @Composable fun A(children: @Composable() () -> Unit) {
+ children()
}
- class B {
- lateinit private var children: () -> Unit
- @Children
- fun setChildren2(x: () -> Unit) { children = x }
- @Composable
- operator fun invoke() {
- val children = this.children
- }
+ @Composable fun B(children: @Composable() () -> Unit) {
+ children()
}
class MainComponent {
@@ -553,11 +336,11 @@
@Composable
operator fun invoke() {
val childText = "Hello World!"
- <A>
- <B>
- println(childText)
- </B>
- </A>
+ A {
+ B {
+ println(childText + name)
+ }
+ }
}
}
"""
@@ -568,24 +351,12 @@
import android.widget.*
import androidx.compose.*
- class A {
- lateinit private var children: (String) -> Unit
- @Children
- fun setChildren2(x: (String) -> Unit) { children = x }
- @Composable
- operator fun invoke() {
- val children = this.children
- }
+ @Composable fun A(children: @Composable() (x: String) -> Unit) {
+ children("")
}
- class B {
- lateinit private var children: (String) -> Unit
- @Children
- fun setChildren2(x: (String) -> Unit) { children = x }
- @Composable
- operator fun invoke() {
- val children = this.children
- }
+ @Composable fun B(children: @Composable() (y: String) -> Unit) {
+ children("")
}
class MainComponent {
@@ -593,11 +364,11 @@
@Composable
operator fun invoke() {
val childText = "Hello World!"
- <A> x ->
- <B> y ->
- println(childText + x + y)
- </B>
- </A>
+ A { x ->
+ B { y ->
+ println(childText + name + x + y)
+ }
+ }
}
}
"""
@@ -611,10 +382,10 @@
class MainComponent {
@Composable
operator fun invoke() {
- <LinearLayout>
- <TextView text="some child content2!" />
- <TextView text="some child content!3" />
- </LinearLayout>
+ LinearLayout {
+ TextView(text="some child content2!")
+ TextView(text="some child content!3")
+ }
}
}
"""
@@ -625,54 +396,29 @@
import android.widget.*
import androidx.compose.*
- class HelperComponent {
- private lateinit var children: () -> Unit
- @Children
- fun setChildren2(x: () -> Unit) { children = x }
- @Composable
- operator fun invoke() {
- val children = this.children
- }
- }
+ @Composable fun HelperComponent(children: @Composable() () -> Unit) {}
class MainComponent {
@Composable
operator fun invoke() {
val x = "Hello"
val y = "World"
- <HelperComponent>
+ HelperComponent {
for(i in 1..100) {
- <TextView text=(x+y+i) />
+ TextView(text=x+y+i)
}
- Unit // NOTE(lmr): this Unit is needed here but it's a general compiler bug,
- // not our bug. Remove when fixed.
- </HelperComponent>
+ }
}
}
"""
)
- fun testForLoopIrBug() = testCompile(
- """
- var z = {
- for (i in 1..100) {
- print("wat")
- }
- Unit // NOTE(lmr): this Unit is needed here but it's a general compiler bug,
- // not our bug. Remove when fixed.
- }
- """
- )
-
fun testGenericsInnerClass() = testCompile(
"""
import androidx.compose.*
class A<T>(val value: T) {
- inner class Getter {
- var x: T? = null
- @Composable
- operator fun invoke() {}
+ @Composable fun Getter(x: T? = null) {
}
}
@@ -680,26 +426,7 @@
val a = A(123)
// a.Getter() here has a bound type argument through A
- <a.Getter x=456 />
- }
- """
- )
-
- fun testXGenericInnerClassConstructor() = testCompile(
- """
- import androidx.compose.*
-
- class A<T>(val value: T) {
- inner class C {
- @Composable
- operator fun invoke() {}
- }
- }
-
- fun doStuff() {
- val B = A(123)
-
- <B.C />
+ a.Getter(x=456)
}
"""
)
@@ -708,27 +435,25 @@
"""
import androidx.compose.*
- class A<T>(
- val value: T
+ @Composable fun <T> A(
+ value: T,
+ list: List<T>? = null
) {
- var list2: List<T>? = null
- fun setList(list: List<T>) {}
- @Composable
- operator fun invoke() {}
+
}
+ @Composable
fun doStuff() {
val x = 123
// we can create element with just value, no list
- <A value=x />
+ A(value=x)
// if we add a list, it can infer the type
- <A
- value=x
+ A(
+ value=x,
list=listOf(234, x)
- list2=listOf(234, x)
- />
+ )
}
"""
)
@@ -737,172 +462,12 @@
"""
import androidx.compose.*
- class Simple {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Simple() {}
+ @Composable
fun run() {
- <Simple />
- }
- """
- )
-
- fun testSimpleVarInConstructor() = testCompile(
- """
- import androidx.compose.*
-
- class SimpleConstructorArg(var foo: String) {
- @Composable
- operator fun invoke() {}
- }
-
- fun run() {
- <SimpleConstructorArg foo="string" />
- }
- """
- )
-
- fun testLateInitProp() = testCompile(
- """
- import androidx.compose.*
-
- class SimpleLateInitArg {
- lateinit var foo: String
- @Composable
- operator fun invoke() {}
- }
-
- fun run() {
- <SimpleLateInitArg foo="string" />
- }
- """
- )
-
- fun testGenericCtorArg() = testCompile(
- """
- import androidx.compose.*
-
- class GenericCtorArg<T>(var foo: T) {
- @Composable
- operator fun invoke() {}
- }
-
- fun run() {
- <GenericCtorArg foo="string" />
- <GenericCtorArg foo=123 />
- }
- """
- )
-
- fun testPropsAndSettersAndExtensionSetters() = testCompile(
- """
- import androidx.compose.*
-
- class OneArg {
- var bar: String? = null
- var baz: String? = null
- fun setBam(bam: String) {
- bar = bam
- }
- @Composable
- operator fun invoke() {}
- }
-
- fun OneArg.setJazz(x: String) {}
-
- fun OneArg.setJazz(y: Int) {}
-
- fun run() {
- <OneArg bar="string" />
- val bar = "string"
- val num = 123
- <OneArg
- bar
- baz=bar
- bam=bar
- jazz=num
- />
- }
- """
- )
-
- fun testGenericAttribute() = testCompile(
- """
- import androidx.compose.*
-
- class Simple {
- @Composable
- operator fun invoke() {}
- }
-
- class Ref<T> {
- var value: T? = null
- }
-
- fun <T: Any> T.setRef(ref: Ref<T>) {
-
- }
-
- fun run() {
- val ref = Ref<Simple>()
- <Simple ref=ref />
- }
- """
- )
-
- fun testSimpleFunctionComponent() = testCompile(
- """
- import androidx.compose.*
-
- fun OneArg(foo: String) {}
-
- fun run() {
- <OneArg foo="string" />
- }
- """
- )
-
- fun testOverloadedFunctions() = testCompile(
- """
- import androidx.compose.*
-
- fun OneArg(foo: String) {}
- fun OneArg(foo: Int) {}
-
- fun run() {
- <OneArg foo=("string") />
- <OneArg foo=123 />
- }
- """
- )
-
- fun testConstructorVal() = testCompile(
- """
- import androidx.compose.*
-
- class Foo(val x: Int) {
- @Composable
- operator fun invoke() {}
- }
-
- fun run() {
- <Foo x=123 />
- }
- """
- )
-
- fun testConstructorNonPropertyParam() = testCompile(
- """
- import androidx.compose.*
-
- class Foo(x: Int) {
- @Composable
- operator fun invoke() {}
- }
-
- fun run() {
- <Foo x=123 />
+ Simple()
}
"""
)
@@ -912,14 +477,13 @@
import androidx.compose.*
object Obj {
- class B {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun B() {}
}
+ @Composable
fun run() {
- <Obj.B />
+ Obj.B()
}
"""
)
@@ -928,65 +492,9 @@
"""
import androidx.compose.*
+ @Composable
fun run() {
- <android.widget.TextView text="bar" />
- }
- """
- )
-
- fun testDotQualifiedClassToClass() = testCompile(
- """
- import androidx.compose.*
-
- class Y {
- class Z {
- class F {
- @Composable
- operator fun invoke() {}
- }
- }
- }
-
- fun run() {
- <Y.Z.F />
- }
- """
- )
-
- fun testInnerClass() = testCompile(
- """
- import androidx.compose.*
-
- class A(var foo: String) {
- inner class B(var bar: String) {
- @Composable
- operator fun invoke() {}
- }
- }
-
- fun run() {
- val X = A("string")
- <X.B bar="string" />
- }
- """
- )
-
- fun testGenericInnerClass() = testCompile(
- """
- import androidx.compose.*
-
- class A<T>(var foo: T) {
- inner class B(var bar: T) {
- @Composable
- operator fun invoke() {}
- }
- }
-
- fun run() {
- val X = A("string")
- val Y = A(123)
- <X.B bar="string" />
- <Y.B bar=123 />
+ android.widget.TextView(text="bar")
}
"""
)
@@ -995,14 +503,12 @@
"""
import androidx.compose.*
- class Simple {
- @Composable
- operator fun invoke() {}
- }
+ @Composable
+ fun Simple() {}
fun run() {
- val foo = { <Simple /> }
- <foo />
+ val foo = @Composable { Simple() }
+ foo()
}
"""
)
@@ -1011,10 +517,10 @@
"""
import androidx.compose.*
- class Test(var children: () -> Unit) {
+ class Test(var children: @Composable () () -> Unit) {
@Composable
operator fun invoke() {
- <children />
+ children()
}
}
"""
@@ -1024,10 +530,10 @@
"""
import androidx.compose.*
- class Test(var children: (x: Int) -> Unit) {
+ class Test(var children: @Composable() (x: Int) -> Unit) {
@Composable
operator fun invoke() {
- <children x=123 />
+ children(x=123)
}
}
"""
@@ -1038,275 +544,46 @@
import androidx.compose.*
class Test {
+ @Composable
fun doStuff() {}
@Composable
operator fun invoke() {
- <doStuff />
+ doStuff()
}
}
"""
)
- fun testPunningProperty() = testCompile(
+ fun testSimpleLambdaChildren() = testCompile(
"""
import androidx.compose.*
+ import android.widget.*
+ import android.content.*
- class Simple(var foo: String) {
- fun setBar(bar: String) {}
- @Composable
- operator fun invoke() {}
+ @Composable fun Example(children: @Composable() () -> Unit) {
+
}
- class Test(var foo: String, var bar: String) {
- @Composable
- operator fun invoke() {
- <Simple foo bar />
+ fun run(text: String) {
+ Example {
+ println("hello ${"$"}text")
}
}
"""
)
- fun testPunningLocalVar() = testCompile(
- """
- import androidx.compose.*
-
- class Simple() {
- var bar: String? = null
- fun setFoo(foo: String) {}
- @Composable
- operator fun invoke() {}
- }
-
- class Test {
- @Composable
- operator fun invoke() {
- val foo = "string"
- val bar = "other"
- <Simple foo bar />
- }
- }
- """
- )
-
- fun testSimpleLambdaChildrenSetter() = testCompile(
- """
- import androidx.compose.*
- import android.widget.*
- import android.content.*
-
- class Example {
- @Children
- fun setChildren(fn: () -> Unit) {}
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
- <Example>
- println("hello ${"$"}text")
- </Example>
- }
- """
- )
-
- // disabled: b/133441299
- fun xtestSimpleLambdaChildrenProperty() = testCompile(
- """
- import androidx.compose.*
- import android.widget.*
- import android.content.*
-
- class Example {
- @Children
- var children: (() -> Unit)? = null
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
- <Example>
- println("hello ${"$"}text")
- </Example>
- }
- """
- )
-
- fun testSimpleLambdaChildrenPropertyInCtor() = testCompile(
- """
- import androidx.compose.*
- import android.widget.*
- import android.content.*
-
- class Example(
- @Children var children: () -> Unit
- ) {
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
- <Example>
- println("hello ${"$"}text")
- </Example>
- }
- """
- )
-
- fun testBlockChildrenForViews() = testCompile(
- """
- import androidx.compose.*
- import android.widget.*
-
- fun run(text: String) {
- <LinearLayout>
- println("hello ${"$"}text")
- </LinearLayout>
- }
- """
- )
-
- fun testChildrenLambdaSetterWithSingleParam() = testCompile(
- """
- import androidx.compose.*
-
- class Example {
- @Children
- fun setChildren(fn: (x: Int) -> Unit) {}
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
- <Example> x ->
- println("hello ${"$"}x")
- </Example>
- }
- """
- )
-
- // disabled: b/133441299
- fun xtestChildrenLambdaPropertyWithSingleParam() = testCompile(
- """
- import androidx.compose.*
-
- class Example {
- @Children
- var children: ((x: Int) -> Unit)? = null
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
- <Example> x ->
- println("hello ${"$"}x")
- </Example>
- }
- """
- )
-
- fun testChildrenLambdaSetterWithMultipleParams() = testCompile(
- """
- import androidx.compose.*
-
- class Example {
- @Children
- fun setChildren(fn: (x: Int, y: String) -> Unit) {}
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
-
-// val lambda = { x, y -> ... }
-// composer.call(
-// 123,
-// { changed(lambda) },
-// { Example().also { setChildren(lambda) }() }
-// )
-
-
- <Example> x, y ->
- println("hello ${"$"}x ${"$"}y")
- </Example>
- }
- """
- )
-
- // disabled: b/133441299
- fun xtestChildrenLambdaPropertyWithMultipleParams() = testCompile(
- """
- import androidx.compose.*
-
- class Example {
- @Children
- var children: ((x: Int, y: String) -> Unit)? = null
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
- <Example> x, y ->
- println("hello ${"$"}x ${"$"}y")
- </Example>
- }
- """
- )
-
- fun testGenericChildrenArgSetter() = testCompile(
- """
- import androidx.compose.*
-
- class Example<T>(var value: T) {
- @Children
- fun setChildren(fn: (x: T) -> Unit) {}
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
- <Example value="string"> x ->
- println("hello ${"$"}x")
- </Example>
- <Example value=123> x ->
- println("hello ${"$"}{x + 1}")
- </Example>
- }
- """
- )
-
- // disabled b/133441299
- fun xtestGenericChildrenArgProperty() = testCompile(
- """
- import androidx.compose.*
-
- class Example<T>(var value: T) {
- @Children
- var children: ((x: T) -> Unit)? = null
- @Composable
- operator fun invoke() {}
- }
-
- fun run(text: String) {
- <Example value="string"> x ->
- println("hello ${"$"}x")
- </Example>
- <Example value=123> x ->
- println("hello ${"$"}{x + 1}")
- </Example>
- }
- """
- )
-
fun testFunctionComponentsWithChildrenSimple() = testCompile(
"""
import androidx.compose.*
- fun Example(@Children children: () -> Unit) {}
+ @Composable
+ fun Example(children: @Composable() () -> Unit) {}
+ @Composable
fun run(text: String) {
- <Example>
+ Example {
println("hello ${"$"}text")
- </Example>
+ }
}
"""
)
@@ -1315,30 +592,14 @@
"""
import androidx.compose.*
- fun Example(@Children children: (String) -> Unit) {}
+ @Composable
+ fun Example(children: @Composable() (String) -> Unit) {}
+ @Composable
fun run(text: String) {
- <Example> x ->
+ Example { x ->
println("hello ${"$"}x")
- </Example>
- }
- """
- )
-
- // disabled: b/133441299
- fun xtestFunctionComponentWithGenericChildren() = testCompile(
- """
- import androidx.compose.*
-
- fun <T> Example(foo: T, @Children children: (T) -> Unit) {}
-
- fun run(text: String) {
- <Example foo="string"> x ->
- println("hello ${"$"}x")
- </Example>
- <Example foo=123> x ->
- println("hello ${"$"}{x + 1}")
- </Example>
+ }
}
"""
)
@@ -1351,8 +612,8 @@
fun foo() {
val lambda = @Composable { }
for(x in 1..5) {
- <lambda />
- <lambda />
+ lambda()
+ lambda()
}
}
"""
@@ -1363,91 +624,44 @@
import androidx.compose.*
import android.widget.TextView
+ @Composable
fun foo(x: Boolean) {
- val lambda = @Composable { <TextView text="Hello World" /> }
+ val lambda = @Composable { TextView(text="Hello World") }
if(true) {
- <lambda />
- <lambda />
- <lambda />
+ lambda()
+ lambda()
+ lambda()
} else {
- <lambda />
+ lambda()
}
}
"""
)
- fun testLateUsingObjectLiteral() = testCompile(
- """
- import androidx.compose.*
-
- class Example {
- lateinit var callback: (Int) -> Unit
- var index = 0
- @Composable
- operator fun invoke() {
- <Example callback=(object : Function1<Int, Unit> {
- override fun invoke(p1: Int) {
- index = p1
- }
- }) />
- }
- }
- """
- )
-
fun testMultiplePivotalAttributesOdd() = testCompile(
"""
import androidx.compose.*
- class Foo(
- val a: Int,
- val b: Int,
- val c: Int,
- val d: Int,
- val e: Int
+ @Composable fun Foo(
+ @Pivotal a: Int,
+ @Pivotal b: Int,
+ @Pivotal c: Int,
+ @Pivotal d: Int,
+ @Pivotal e: Int
) {
- @Composable
- operator fun invoke() {}
+
}
class Bar {
@Composable
operator fun invoke() {
- <Foo
- a=1
- b=2
- c=3
- d=4
+ Foo(
+ a=1,
+ b=2,
+ c=3,
+ d=4,
e=5
- />
- }
- }
- """
- )
-
- fun testMultiplePivotalAttributesEven() = testCompile(
- """
- import androidx.compose.*
-
- class Foo(
- val a: Int,
- val b: Int,
- val c: Int,
- val d: Int
- ) {
- @Composable
- operator fun invoke() {}
- }
-
- class Bar {
- @Composable
- operator fun invoke() {
- <Foo
- a=1
- b=2
- c=3
- d=4
- />
+ )
}
}
"""
@@ -1457,154 +671,18 @@
"""
import androidx.compose.*
- class Foo(
- val a: Int
+ @Composable fun Foo(
+ @Pivotal a: Int
) {
- @Composable
- operator fun invoke() {}
+
}
class Bar {
@Composable
operator fun invoke() {
- <Foo
+ Foo(
a=1
- />
- }
- }
- """
- )
-
- fun testKeyAttributeWithPivotal() = testCompile(
- """
- import androidx.compose.*
- import androidx.ui.androidview.adapters.*
-
- class Foo(
- val a: Int,
- var b: Int
- ) {
- @Composable
- operator fun invoke() {}
- }
-
- fun Foo.setKey(@Pivotal key: Any?) {}
-
- class Bar {
- @Composable
- operator fun invoke() {
- <Foo
- a=1
- b=2
- key=3
- />
- }
- }
- """
- )
-
- fun testKeyAttributeWithoutPivotal() = testCompile(
- """
- import androidx.compose.*
- import androidx.ui.androidview.adapters.*
-
- @Pivotal
- fun Foo.setKey(x: Any?) {}
-
- class Foo(
- var a: Int,
- var b: Int
- ) {
- @Composable
- operator fun invoke() {}
- }
-
- class Bar {
- @Composable
- operator fun invoke() {
- <Foo
- a=1
- b=2
- key=3
- />
- }
- }
- """
- )
-
- fun testNamedChildrenAttributeProperty() = testCompile(
- """
- import androidx.compose.*
- import androidx.ui.androidview.adapters.*
-
- class Foo {
- @Children var children: (() -> Unit)? = null
- @Composable
- operator fun invoke() {}
- }
-
- class Bar {
- @Composable
- operator fun invoke() {
- <Foo
- children={ }
- />
- }
- }
- """
- )
-
- fun testNamedChildrenAttributeSetter() = testCompile(
- """
- import androidx.compose.*
- import androidx.ui.androidview.adapters.*
-
- class Foo {
- @Children fun setChildren(children: () -> Unit) {}
- @Composable
- operator fun invoke() {}
- }
-
- class Bar {
- @Composable
- operator fun invoke() {
- <Foo
- children={ }
- />
- }
- }
- """
- )
-
- // disabled: b/133441299
- fun xtestOverloadedChildren() = testCompile(
- """
- import androidx.compose.*
- import androidx.ui.androidview.adapters.*
-
- class Foo {
- @Children var children: ((x: Int, y: Int) -> Unit)? = null
- @Children fun setChildren(children: (x: Int) -> Unit) {}
- @Children fun setChildren(children: () -> Unit) {}
- @Composable
- operator fun invoke() {}
- }
-
- class Bar {
- @Composable
- operator fun invoke() {
- <Foo children={ -> } />
- <Foo children={ x -> println(x) } />
- <Foo children={ x, y -> println(x + y) } />
- <Foo>
- println("")
- </Foo>
- <Foo> x ->
- println(x)
- </Foo>
- <Foo> x, y ->
- println(x + y)
- </Foo>
+ )
}
}
"""
@@ -1615,134 +693,27 @@
import androidx.compose.*
import androidx.ui.androidview.adapters.*
- class Foo(@Children var children: (sub: () -> Unit) -> Unit) {
- @Composable
- operator fun invoke() {}
+ @Composable fun Foo(children: @Composable() (sub: @Composable() () -> Unit) -> Unit) {
+
}
- class Boo(@Children var children: () -> Unit) {
- @Composable
- operator fun invoke() {}
+ @Composable fun Boo(children: @Composable() () -> Unit) {
+
}
class Bar {
@Composable
operator fun invoke() {
- <Foo> sub ->
- <Boo>
- <sub />
- </Boo>
- </Foo>
+ Foo { sub ->
+ Boo {
+ sub()
+ }
+ }
}
}
"""
)
- // disabled: b/133441299
- fun xtestPassChildrenLambdaVarWithCorrectType() = testCompile(
- """
- import androidx.compose.*
- import androidx.ui.androidview.adapters.*
-
- class Boo(@Children var children: () -> Unit) {
- @Composable
- operator fun invoke() {}
- }
-
- class Bar(var data: List<Int>) {
- @Composable
- operator fun invoke() {
- val children = { Unit; }
- <Boo children />
- }
- }
- """
- )
-
- fun testPassChildrenLambdaLiteralWithCorrectType() = testCompile(
- """
- import androidx.compose.*
- import androidx.ui.androidview.adapters.*
-
- class Boo {
- @Children lateinit var children: () -> Unit
- @Composable
- operator fun invoke() {}
- }
-
- class Bar(var data: List<Int>) {
- @Composable
- operator fun invoke() {
- <Boo
- children={ Unit; }
- />
- }
- }
- """
- )
-
- // disabled: b/133441299
- fun xtestChildrenLiteral() = testCompile(
- """
- import androidx.compose.*
- import androidx.ui.androidview.adapters.*
-
- class Boo(@Children var children: () -> Unit) {
- @Composable
- operator fun invoke() {}
- }
-
- class Bar(var data: List<Int>) {
- @Composable
- operator fun invoke() {
- <Boo
- children={ Unit; }
- />
- }
- }
- """
- )
-
- fun testKtxLambdaCapturedVariableInAssignment() = testCompile(
- """
- import androidx.compose.*
-
- class Tabs(@Children var children: () -> Unit) {
- @Composable
- operator fun invoke() {}
- }
-
- class Bar {
- @Composable
- operator fun invoke() {
- val bam = "x"
- <Tabs>
- val qoo = bam
- </Tabs>
- }
- }
- """
- )
-
- fun testKtxParameterlessFunction() = testCompile(
- """
- import androidx.compose.*
- import android.widget.*
-
- @Composable
- fun Paramless() {
- <TextView text="Hello!" />
- }
-
- class Bar {
- @Composable
- operator fun invoke() {
- <Paramless />
- }
- }
- """
- )
-
fun testKtxEmittable() = testCompile(
"""
import androidx.compose.*
@@ -1760,7 +731,7 @@
class Comp {
@Composable
operator fun invoke() {
- <MyEmittable a=2 />
+ MyEmittable(a=2)
}
}
"""
@@ -1783,12 +754,12 @@
class Comp {
@Composable
operator fun invoke() {
- <MyEmittable a=1>
- <MyEmittable a=2 />
- <MyEmittable a=3 />
- <MyEmittable a=4 />
- <MyEmittable a=5 />
- </MyEmittable>
+ MyEmittable(a=1) {
+ MyEmittable(a=2)
+ MyEmittable(a=3)
+ MyEmittable(a=4)
+ MyEmittable(a=5)
+ }
}
}
"""
@@ -1805,7 +776,7 @@
@Composable
fun test() {
val foo = Foo()
- <foo />
+ foo()
}
"""
)
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt
deleted file mode 100644
index e4bb666..0000000
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt
+++ /dev/null
@@ -1,848 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-class KtxTypeResolutionTests : AbstractComposeDiagnosticsTest() {
-
- fun testImplicitlyPassedReceiverScope1() = doTest(
- """
- import androidx.compose.*
-
- @Composable
- fun Int.Foo(@Children children: Int.() -> Unit) {
- <children />
- }
- """
- )
-
- fun testImplicitlyPassedReceiverScope2() = doTest(
- """
- import androidx.compose.*
-
- @Composable
- fun Int.Foo(@Children children: Int.(foo: String) -> Unit) {
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_ATTRIBUTES!>children<!> />
- }
-
- @Composable
- fun Bar(@Children children: Int.() -> Unit) {
- <<!NO_VALUE_FOR_PARAMETER!>children<!> />
- }
- """
- )
-
- fun testThatUnresolvedTagDiagnosticIsOnlyOnTagName() = doTest(
- """
- import androidx.compose.*
-
- class Foo {
- @Composable operator fun invoke() {}
- }
-
- @Composable fun test() {
- <<!UNRESOLVED_TAG!>SomeNameThatWillNotResolve<!> <!UNRESOLVED_ATTRIBUTE_KEY!>foo<!>=123>
- <Foo />
- </<!INVALID_TAG_DESCRIPTOR!>SomeNameThatWillNotResolve<!>>
- }
- """
- )
-
- fun testAmbiguousKtxTags() = doTest(
- """
- import androidx.compose.*
-
- class Foo {
- var foo: Int = 0
- @Composable operator fun invoke() {}
- }
-
- @Composable
- fun Foo(foo: Int) { print(foo) }
-
- @Composable fun test() {
- <<!AMBIGUOUS_KTX_CALL!>Foo<!> foo=0 />
- }
- """
- )
-
- fun testSmartCastsAndPunning() = doTest(
- """
- import androidx.compose.*
-
- @Composable
- fun Foo(bar: String) { print(bar) }
-
- @Composable
- fun test(bar: String?) {
- <Foo <!TYPE_MISMATCH!>bar<!> />
- if (bar != null) {
- <Foo bar />
- <Foo bar=bar />
- }
- }
- """
- )
-
- fun testExtensionInvoke() = doTest(
- """
- import androidx.compose.*
-
- class Foo {}
- @Composable operator fun Foo.invoke() {}
-
- @Composable fun test() {
- <Foo />
- }
- """
- )
-
- fun testResolutionInsideWhenExpression() = doTest(
- """
- import androidx.compose.*
- import android.widget.TextView
-
- @Composable fun doSomething(foo: Boolean) {
- when (foo) {
- true -> <TextView text="Started..." />
- false -> <TextView text="Continue..." />
- }
- }
- """
- )
-
- fun testComposerExtensions() = doTest(
- """
- import androidx.compose.*
-
- open class Foo {}
- class Bar : Foo() {}
-
- class Bam {}
-
- fun <T : Foo> ViewComposition.emit(key: Any, ctor: () -> T, update: ViewUpdater<T>.() -> Unit) {
- print(key)
- print(ctor)
- print(update)
- }
-
- @Composable fun test() {
- <Bar />
- <<!INVALID_TAG_TYPE!>Bam<!> />
- }
- """
- )
-
- fun testUsedParameters() = doTest(
- """
- import androidx.compose.*
- import android.widget.LinearLayout
-
- class Foo {
- var composeItem: @Composable() () -> Unit = {}
- @Composable operator fun invoke(x: Int) {
- println(x)
- }
- }
-
-
- @Composable fun test(
- children: @Composable() () -> Unit,
- value: Int,
- x: Int,
- children2: @Composable() () -> Unit,
- value2: Int
- ) {
- <LinearLayout>
- // attribute value
- <Foo x=value />
-
- // punned attribute
- <Foo x />
-
- // tag
- <children />
- </LinearLayout>
- <Foo x=123 composeItem={
- val abc = 123
-
- // attribute value
- <Foo x=abc />
-
- // attribute value
- <Foo x=value2 />
-
- // tag
- <children2 />
- } />
- }
- """
- )
-
- fun testDispatchInvoke() = doTest(
- """
- import androidx.compose.*
-
- class Bam {
- @Composable fun Foo() {}
- }
-
- @Composable fun test() {
- with(Bam()) {
- <Foo />
- }
- }
- """
- )
-
- fun testDispatchAndExtensionReceiver() = doTest(
- """
- import androidx.compose.*
-
- class Bam {
- inner class Foo {}
- }
-
- @Composable operator fun Bam.Foo.invoke() {}
-
- @Composable fun test() {
- with(Bam()) {
- <Foo />
- }
- }
- """
- )
-
- fun testDispatchAndExtensionReceiverLocal() = doTest(
- """
- import androidx.compose.*
-
-
- class Foo {}
-
- class Bam {
- @Composable operator fun Foo.invoke() {}
- @Composable operator fun invoke() {
- <Foo />
- }
- }
-
- """
- )
-
- fun testMissingAttributes() = doTest(
- """
- import androidx.compose.*
-
- data class Foo(val value: Int)
-
- @Composable fun A(x: Foo) { println(x) }
- class B(var x: Foo) { @Composable operator fun invoke() { println(x) } }
- class C(x: Foo) { init { println(x) } @Composable operator fun invoke() { } }
- class D(val x: Foo) { @Composable operator fun invoke() { println(x) } }
- class E {
- lateinit var x: Foo
- @Composable operator fun invoke() { println(x) }
- }
-
- // NOTE: It's important that the diagnostic be only over the tag target, and not the entire element
- // so that a single error doesn't end up making a huge part of an otherwise correct file "red".
- @Composable fun Test(F: @Composable() (x: Foo) -> Unit) {
- // NOTE: constructor attributes and fn params get a "missing parameter" diagnostic
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_ATTRIBUTES!>A<!> />
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_ATTRIBUTES!>B<!> />
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_ATTRIBUTES!>C<!> />
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_ATTRIBUTES!>D<!> />
-
- // NOTE: lateinit attributes get only the "missing required attribute" diagnostic
- <<!MISSING_REQUIRED_ATTRIBUTES!>E<!> />
-
- // local
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_ATTRIBUTES!>F<!> />
-
- val x = Foo(123)
-
- <A x />
- <B x />
- <C x />
- <D x />
- <E x />
- <F x />
- }
-
- """.trimIndent()
- )
-
- fun testDuplicateAttributes() = doTest(
- """
- import androidx.compose.*
-
- data class Foo(val value: Int)
-
- @Composable fun A(x: Foo) { println(x) }
- class B(var x: Foo) { @Composable operator fun invoke() { println(x) } }
- class C(x: Foo) { init { println(x) } @Composable operator fun invoke() { } }
- class D(val x: Foo) { @Composable operator fun invoke() { println(x) } }
- class E {
- lateinit var x: Foo
- @Composable operator fun invoke() { println(x) }
- }
-
- @Composable fun Test() {
- val x = Foo(123)
-
- // NOTE: It's important that the diagnostic be only over the attribute key, so that
- // we don't make a large part of the elements red when the type is otherwise correct
- <A x=x <!DUPLICATE_ATTRIBUTE!>x<!>=x />
- <B x=x <!DUPLICATE_ATTRIBUTE!>x<!>=x />
- <C x=x <!DUPLICATE_ATTRIBUTE!>x<!>=x />
- <D x=x <!DUPLICATE_ATTRIBUTE!>x<!>=x />
- <E x=x <!DUPLICATE_ATTRIBUTE!>x<!>=x />
- }
-
- """.trimIndent()
- )
-
- fun testChildrenNamedAndBodyDuplicate() = doTest(
- """
- import androidx.compose.*
-
- @Composable fun A(@Children children: @Composable() () -> Unit) { <children /> }
- class B(@Children var children: @Composable() () -> Unit) { @Composable operator fun invoke() { <children /> } }
- class C {
- @Children var children: @Composable() () -> Unit = {}
- @Composable operator fun invoke() { <children /> }
- }
- class D {
- @Children lateinit var children: @Composable() () -> Unit
- @Composable operator fun invoke() { <children /> }
- }
-
- @Composable fun Test() {
- <A <!CHILDREN_ATTR_USED_AS_BODY_AND_KEYED_ATTRIBUTE!>children<!>={}></A>
- <B <!CHILDREN_ATTR_USED_AS_BODY_AND_KEYED_ATTRIBUTE!>children<!>={}></B>
- <C <!CHILDREN_ATTR_USED_AS_BODY_AND_KEYED_ATTRIBUTE!>children<!>={}></C>
- <D <!CHILDREN_ATTR_USED_AS_BODY_AND_KEYED_ATTRIBUTE!>children<!>={}></D>
- }
-
- """.trimIndent()
- )
-
- fun testAbstractClassTags() = doTest(
- """
- import androidx.compose.*
- import android.content.Context
- import android.widget.LinearLayout
-
- abstract class Foo {}
-
- abstract class Bar(context: Context) : LinearLayout(context) {}
-
- @Composable fun Test() {
- <<!CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS, INVALID_TAG_TYPE!>Foo<!> />
- <<!CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS!>Bar<!> />
- }
-
- """.trimIndent()
- )
-
- fun testAmbiguousTags() = doTest(
- """
- import androidx.compose.*
-
- @Composable fun Foo() {}
- @Composable fun Foo(x: Int) { println(x) }
-
-
- class Wat {
- operator fun invoke(): Xam {
- return Xam()
- }
- }
-
- class Xam {
- @Composable
- operator fun invoke(x: Int) { println(x) }
- }
-
- @Composable fun Test() {
-// <Foo x=123 />
-// <Foo />
- <Wat x=123 />
- }
-
- """.trimIndent()
- )
-
- fun testOverloadedTargets() = doTest(
- """
- import androidx.compose.*
-
- data class FooModel(val x: Int, val y: Double)
-
- class Foo(model: FooModel) {
- init { println(model) }
- constructor(x: Int, y: Double) : this(FooModel(x, y))
-
- @Composable operator fun invoke() {}
- }
-
-
- @Composable fun Bar(x: Int, y: Double) { <Bar model=FooModel(x, y) /> }
- @Composable fun Bar(model: FooModel) { println(model) }
-
- @Composable fun Test() {
- val x = 1
- val y = 1.0
- val model = FooModel(x, y)
-
- <Foo x y />
- <Foo model />
- <Foo x y <!UNRESOLVED_ATTRIBUTE_KEY!>model<!> />
-
- <Bar x y />
- <Bar model />
- <Bar x y <!UNRESOLVED_ATTRIBUTE_KEY!>model<!> />
- }
-
- """.trimIndent()
- )
-
- fun testGenerics() = doTest(
- """
- import androidx.compose.*
-
- class A { fun a() {} }
- class B { fun b() {} }
-
- class Foo<T>(var value: T, var f: (T) -> Unit) {
- @Composable operator fun invoke() {}
- }
-
- @Composable fun <T> Bar(x: Int, value: T, f: (T) -> Unit) { println(value); println(f); println(x) }
-
- @Composable fun Test() {
-
- val fa: (A) -> Unit = { it.a() }
- val fb: (B) -> Unit = { it.b() }
-
- <Foo value=A() f={ it.a() } />
- <Foo value=B() f={ it.b() } />
- <Foo value=A() f=fa />
- <Foo value=B() f=fb />
- <Foo value=B() f={ it.<!UNRESOLVED_REFERENCE!>a<!>() } />
- <Foo value=A() f={ it.<!UNRESOLVED_REFERENCE!>b<!>() } />
- <<!TYPE_INFERENCE_CONFLICTING_SUBSTITUTIONS!>Foo<!> <!MISMATCHED_INFERRED_ATTRIBUTE_TYPE!>value<!>=A() <!MISMATCHED_INFERRED_ATTRIBUTE_TYPE!>f<!>=fb />
- <<!TYPE_INFERENCE_CONFLICTING_SUBSTITUTIONS!>Foo<!> <!MISMATCHED_INFERRED_ATTRIBUTE_TYPE!>value<!>=B() <!MISMATCHED_INFERRED_ATTRIBUTE_TYPE!>f<!>=fa />
-
-
- <Bar x=1 value=A() f={ it.a() } />
- <Bar x=1 value=B() f={ it.b() } />
- <Bar x=1 value=A() f=fa />
- <Bar x=1 value=B() f=fb />
- <Bar x=1 value=B() f={ it.<!UNRESOLVED_REFERENCE!>a<!>() } />
- <Bar x=1 value=A() f={ it.<!UNRESOLVED_REFERENCE!>b<!>() } />
- <<!TYPE_INFERENCE_CONFLICTING_SUBSTITUTIONS!>Bar<!> x=1 <!MISMATCHED_INFERRED_ATTRIBUTE_TYPE!>value<!>=A() <!MISMATCHED_INFERRED_ATTRIBUTE_TYPE!>f<!>=fb />
- <<!TYPE_INFERENCE_CONFLICTING_SUBSTITUTIONS!>Bar<!> x=1 <!MISMATCHED_INFERRED_ATTRIBUTE_TYPE!>value<!>=B() <!MISMATCHED_INFERRED_ATTRIBUTE_TYPE!>f<!>=fa />
- }
-
- """.trimIndent()
- )
-
- fun testUnresolvedAttributeValueResolvedTarget() = doTest(
- """
- import androidx.compose.*
-
- class Foo {
- var bar: Int = 0
- @Composable operator fun invoke(x: Int) {
- print(x)
- }
- }
-
- @Composable fun Fam(bar: Int, x: Int) {
- print(bar)
- print(x)
- }
-
- @Composable fun Test() {
- <Foo <!MISMATCHED_ATTRIBUTE_TYPE!>bar<!>=<!UNRESOLVED_REFERENCE!>undefined<!> x=1 />
- <Foo bar=1 x=<!UNRESOLVED_REFERENCE!>undefined<!> />
- <Foo <!UNRESOLVED_REFERENCE, MISMATCHED_ATTRIBUTE_TYPE!>bar<!> <!UNRESOLVED_REFERENCE!>x<!> />
- <Fam bar=<!UNRESOLVED_REFERENCE!>undefined<!> x=1 />
- <Fam bar=1 x=<!UNRESOLVED_REFERENCE!>undefined<!> />
- <Fam <!UNRESOLVED_REFERENCE!>bar<!> <!UNRESOLVED_REFERENCE!>x<!> />
-
- <Fam <!MISMATCHED_ATTRIBUTE_TYPE!>bar<!>=<!TYPE_MISMATCH!>""<!> <!MISMATCHED_ATTRIBUTE_TYPE!>x<!>=<!TYPE_MISMATCH!>""<!> />
- }
-
- """.trimIndent()
- )
-
- fun testEmptyAttributeValue() = doTest(
- """
- import androidx.compose.*
-
- @Composable fun Foo(abc: Int, xyz: Int) {
- print(abc)
- print(xyz)
- }
-
- @Composable fun Test() {
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_ATTRIBUTES!>Foo<!> abc= />
-
- // NOTE(lmr): even though there is NO diagnostic here, there *is* a parse
- // error. This is intentional and done to mimic how kotlin handles function
- // calls with no value expression in a call parameter list (ie, `Foo(123,)`)
- <Foo abc=123 xyz= />
- }
-
- """.trimIndent()
- )
-
- fun testMissingComposer() = doTest(
- """
- import androidx.compose.Composable
-
- @Composable fun A() {}
- @Composable fun B(a: Int, b: Int) { print(a + b) }
- fun C(a: Int): @Composable() (b: Int) -> Unit = { b: Int -> print(a + b) }
- class D(var a: Int) {
- var b: Int = 0
- @Composable operator fun invoke(c: Int) { print(c) }
- }
-
- class E(var a: Int) {
- var b: Int = 0
- }
-
- @Composable fun Test() {
- <<!NO_COMPOSER_FOUND!>A<!> />
-
- <<!NO_COMPOSER_FOUND, NO_VALUE_FOR_PARAMETER!>B<!> />
- <<!NO_COMPOSER_FOUND, NO_VALUE_FOR_PARAMETER!>B<!> a=1 />
- <<!NO_COMPOSER_FOUND!>B<!> a=1 b=1 />
- <<!NO_COMPOSER_FOUND!>B<!> a=1 <!MISMATCHED_ATTRIBUTE_TYPE!>b<!>=<!TYPE_MISMATCH!>""<!> />
-
- <<!NO_COMPOSER_FOUND, NO_VALUE_FOR_PARAMETER!>C<!> />
- <<!NO_COMPOSER_FOUND!>C<!> a=1 />
- <<!NO_COMPOSER_FOUND!>C<!> a=1 b=1 />
- <<!NO_COMPOSER_FOUND!>C<!> a=1 <!MISMATCHED_ATTRIBUTE_TYPE!>b<!>=<!TYPE_MISMATCH!>""<!> />
-
- <<!NO_COMPOSER_FOUND, NO_VALUE_FOR_PARAMETER!>D<!> />
- <<!NO_COMPOSER_FOUND!>D<!> a=1 />
- <<!NO_COMPOSER_FOUND!>D<!> a=1 b=1 />
- <<!NO_COMPOSER_FOUND!>D<!> a=1 b=1 c=1 />
- <<!NO_COMPOSER_FOUND!>D<!> a=1 b=1 <!MISMATCHED_ATTRIBUTE_TYPE!>c<!>=<!TYPE_MISMATCH!>""<!> />
-
- <<!NO_COMPOSER_FOUND, NO_VALUE_FOR_PARAMETER!>E<!> />
- <<!NO_COMPOSER_FOUND!>E<!> a=1 />
- <<!NO_COMPOSER_FOUND!>E<!> a=1 b=1 />
- <<!NO_COMPOSER_FOUND!>E<!> a=1 <!MISMATCHED_ATTRIBUTE_TYPE!>b<!>="" />
- }
-
- """.trimIndent()
- )
-
- fun testValidInvalidAttributes() = doTest(
- """
- import androidx.compose.*
-
- class Foo(val a: Int, var b: Int, c: Int, d: Int = 1) {
- init { println(c); println(d); }
- var e = 1
- var f: Int? = null
- var g: Int
- get() = 1
- set(_: Int) {}
- val h: Int get() = 1
- val i = 1
-
- fun setJ(j: Int) { println(j) }
-
- val k by lazy { 123 }
- private var l = 1
- private var m: Int
- get() = 1
- set(v: Int) { println(v) }
- private val n = 1
- @Composable operator fun invoke() {}
- }
-
- @Composable fun Test() {
- <Foo
- a=1
- b=1
- c=1
- d=1
- e=1
- f=null
- g=1
- <!MISMATCHED_ATTRIBUTE_TYPE!>h<!>=1
- <!MISMATCHED_ATTRIBUTE_TYPE!>i<!>=1
- j=1
- <!MISMATCHED_ATTRIBUTE_TYPE!>k<!>=1
- <!UNRESOLVED_ATTRIBUTE_KEY!>z<!>=1
- <!INVISIBLE_MEMBER!>l<!>=1
- <!INVISIBLE_MEMBER!>m<!>=1
- <!MISMATCHED_ATTRIBUTE_TYPE!>n<!>=1
- />
- }
-
- """.trimIndent()
- )
-
- fun testMismatchedAttributes() = doTest(
- """
- import androidx.compose.*
-
- open class A {}
- class B : A() {}
-
- class Foo() {
- var x: A = A()
- var y: A = B()
- var z: B = B()
- @Composable operator fun invoke() {}
- }
-
- @Composable fun Test() {
- <Foo
- x=A()
- y=A()
- <!MISMATCHED_ATTRIBUTE_TYPE!>z<!>=A()
- />
- <Foo
- x=B()
- y=B()
- z=B()
- />
- <Foo
- <!MISMATCHED_ATTRIBUTE_TYPE!>x<!>=1
- <!MISMATCHED_ATTRIBUTE_TYPE!>y<!>=1
- <!MISMATCHED_ATTRIBUTE_TYPE!>z<!>=1
- />
- }
-
- """.trimIndent()
- )
-
- fun testErrorAttributeValue() = doTest(
- """
- import androidx.compose.*
-
- class Foo() {
- var x: Int = 1
- @Composable operator fun invoke() {}
- }
-
- @Composable fun Test() {
- <Foo
- <!MISMATCHED_ATTRIBUTE_TYPE!>x<!>=<!UNRESOLVED_REFERENCE!>someUnresolvedValue<!>
- <!UNRESOLVED_ATTRIBUTE_KEY_UNKNOWN_TYPE!>y<!>=<!UNRESOLVED_REFERENCE!>someUnresolvedValue<!>
- />
- }
-
- """.trimIndent()
- )
-
- fun testUnresolvedQualifiedTag() = doTest(
- """
- import androidx.compose.*
-
- object MyNamespace {
- class Foo() {
- @Children var children: @Composable() () -> Unit = {}
- @Composable operator fun invoke() { <children /> }
- }
- @Composable fun Bar(@Children children: @Composable() () -> Unit = {}) { <children /> }
-
- var Baz = @Composable { }
-
- var someString = ""
- class NonComponent {}
- }
-
- class Boo {
- class Wat {
- @Children var children: @Composable() () -> Unit = {}
- @Composable operator fun invoke() { <children /> }
- }
- inner class Qoo {
- @Children var children: @Composable() () -> Unit = {}
- @Composable operator fun invoke() { <children /> }
- }
- }
-
- @Composable fun Test() {
-
- <MyNamespace.Foo />
- <MyNamespace.Bar />
- <MyNamespace.Baz />
- <<!INVALID_TAG_DESCRIPTOR!>MyNamespace.<!UNRESOLVED_REFERENCE!>Qoo<!><!> />
- <<!INVALID_TAG_TYPE!>MyNamespace.<!UNRESOLVED_REFERENCE!>someString<!><!> />
- <<!INVALID_TAG_TYPE!>MyNamespace.NonComponent<!> />
- <MyNamespace.Foo></MyNamespace.Foo>
- <MyNamespace.Bar></MyNamespace.Bar>
- <<!CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED!>MyNamespace.Baz<!>></MyNamespace.Baz>
-
-
- val obj = Boo()
- <obj.Qoo />
- <Boo.Wat />
- <obj.Qoo></obj.Qoo>
- <Boo.Wat></Boo.Wat>
- <<!INVALID_TAG_DESCRIPTOR!>obj.<!UNRESOLVED_REFERENCE!>Wat<!><!> />
-
- <<!INVALID_TAG_DESCRIPTOR!>MyNamespace.<!UNRESOLVED_REFERENCE!>Bam<!><!> />
- <<!UNRESOLVED_REFERENCE!>SomethingThatDoesntExist<!>.Foo />
-
- <<!INVALID_TAG_DESCRIPTOR!>obj.<!UNRESOLVED_REFERENCE!>Wat<!><!>>
- </<!INVALID_TAG_DESCRIPTOR!>obj.Wat<!>>
-
- <<!INVALID_TAG_DESCRIPTOR!>MyNamespace.<!UNRESOLVED_REFERENCE!>Qoo<!><!>>
- </<!INVALID_TAG_DESCRIPTOR!>MyNamespace.Qoo<!>>
-
- <<!INVALID_TAG_TYPE!>MyNamespace.<!UNRESOLVED_REFERENCE!>someString<!><!>>
- </<!INVALID_TAG_TYPE!>MyNamespace.someString<!>>
-
- <<!CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED!><!UNRESOLVED_REFERENCE!>SomethingThatDoesntExist<!>.Foo<!>>
- </SomethingThatDoesntExist.Foo>
-
- <<!INVALID_TAG_TYPE!>MyNamespace.NonComponent<!>>
- </<!INVALID_TAG_TYPE!>MyNamespace.NonComponent<!>>
-
- <<!INVALID_TAG_DESCRIPTOR!>MyNamespace.<!UNRESOLVED_REFERENCE!>Bam<!><!>>
- </<!INVALID_TAG_DESCRIPTOR!>MyNamespace.Bam<!>>
-
- }
-
- """.trimIndent()
- )
-
- fun testExtensionAttributes() = doTest(
- """
- import androidx.compose.*
-
- class Foo() {
- var x: Int = 1
- @Composable operator fun invoke() {}
- }
-
- fun Foo.setBar(x: Int) { println(x) }
-
- fun Foo.setX(s: String) { println(s) }
-
- @Composable fun Test() {
- <Foo
- x=1
- />
-
- <Foo
- x="a"
- />
-
- <Foo
- bar=123
- />
-
- <Foo
- <!MISMATCHED_ATTRIBUTE_TYPE!>bar<!>=123.0
- <!MISMATCHED_ATTRIBUTE_TYPE!>x<!>=123.0
- />
- }
-
- """.trimIndent()
- )
-
- fun testChildren() = doTest(
- """
- import androidx.compose.*
- import android.widget.Button
- import android.widget.LinearLayout
-
- class ChildrenRequired1(@Children var children: @Composable() () -> Unit) {
- @Composable operator fun invoke() {}
- }
-
- @Composable fun ChildrenRequired2(@Children children: @Composable() () -> Unit) { <children /> }
-
- class ChildrenOptional1(@Children var children: @Composable() () -> Unit = {}) {
- @Composable operator fun invoke() {}
- }
- class ChildrenOptional2() {
- @Children var children: @Composable() () -> Unit = {}
- @Composable operator fun invoke() {}
- }
-
- @Composable fun ChildrenOptional3(@Children children: @Composable() () -> Unit = {}) { <children /> }
-
- class NoChildren1() {
- @Composable operator fun invoke() {}
- }
- @Composable fun NoChildren2() {}
-
-
- class MultiChildren() {
- @Children var c1: @Composable() () -> Unit = {}
- @Children var c2: @Composable() (x: Int) -> Unit = {}
- @Children var c3: @Composable() (x: Int, y: Int) -> Unit = { x, y -> println(x + y) }
-
- @Composable operator fun invoke() {}
- }
-
- @Composable fun Test() {
- <ChildrenRequired1></ChildrenRequired1>
- <ChildrenRequired2></ChildrenRequired2>
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_CHILDREN!>ChildrenRequired1<!> />
- <<!NO_VALUE_FOR_PARAMETER, MISSING_REQUIRED_CHILDREN!>ChildrenRequired2<!> />
-
- <ChildrenOptional1></ChildrenOptional1>
- <ChildrenOptional2></ChildrenOptional2>
- <ChildrenOptional3></ChildrenOptional3>
- <ChildrenOptional1 />
- <ChildrenOptional2 />
- <ChildrenOptional3 />
-
-
- <<!CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED!>NoChildren1<!>></NoChildren1>
- <<!CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED!>NoChildren2<!>></NoChildren2>
- <NoChildren1 />
- <NoChildren2 />
-
- <MultiChildren></MultiChildren>
- <MultiChildren> x ->
- println(x)
- </MultiChildren>
- <MultiChildren> x, y ->
- println(x + y)
- </MultiChildren>
- <<!UNRESOLVED_CHILDREN!>MultiChildren<!>> <!TYPE_MISMATCH!><!EXPECTED_PARAMETERS_NUMBER_MISMATCH!><!CANNOT_INFER_PARAMETER_TYPE!>x<!>, <!CANNOT_INFER_PARAMETER_TYPE!>y<!>, <!CANNOT_INFER_PARAMETER_TYPE!>z<!><!> ->
- println(x + y + z)<!>
- </MultiChildren>
-
- <Button />
- <LinearLayout />
-
- <LinearLayout>
- </LinearLayout>
-
- <<!CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED!>Button<!>></Button>
- }
-
- """.trimIndent()
- )
-}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt
deleted file mode 100644
index dedcd26..0000000
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin.analysis
-
-import androidx.compose.plugins.kotlin.AbstractComposeDiagnosticsTest
-
-class ChildrenAnnotationTest : AbstractComposeDiagnosticsTest() {
-
- fun testReportChildrenOnWrongParameter() {
- doTest("""
- import androidx.compose.*;
-
- @Composable fun MyWidget(<!CHILDREN_MUST_BE_LAST!>@Children children: ()->Unit<!>, value: Int) {
- System.out.println(""+children+value)
- }
- """)
- }
-}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ComposableCheckerTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ComposableCheckerTests.kt
index 9bc8f04..2612abb 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ComposableCheckerTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ComposableCheckerTests.kt
@@ -9,13 +9,6 @@
class ComposableCheckerTests : AbstractComposeDiagnosticsTest() {
- companion object {
- val MODE_KTX_CHECKED = 1
- val MODE_KTX_STRICT = 2
- val MODE_KTX_PEDANTIC = 4
- val MODE_FCS = 8
- }
-
override fun setUp() {
// intentionally don't call super.setUp() here since we are recreating an environment
// every test
@@ -66,11 +59,11 @@
import android.widget.TextView;
fun myStatelessFunctionalComponent() {
- <TextView text="Hello World!" />
+ TextView(text="Hello World!")
}
fun foo() {
- <myStatelessFunctionalComponent />
+ myStatelessFunctionalComponent()
}
""")
check("""
@@ -79,12 +72,12 @@
@Composable
fun myStatelessFunctionalComponent() {
- <TextView text="Hello World!" />
+ TextView(text="Hello World!")
}
@Composable
fun foo() {
- <myStatelessFunctionalComponent />
+ myStatelessFunctionalComponent()
}
""")
}
@@ -94,15 +87,15 @@
import androidx.compose.*;
import android.widget.TextView;
- val myLambda1 = { <TextView text="Hello World!" /> }
- val myLambda2: ()->Unit = { <TextView text="Hello World!" /> }
+ val myLambda1 = { TextView(text="Hello World!") }
+ val myLambda2: () -> Unit = { TextView(text="Hello World!") }
""")
check("""
import androidx.compose.*;
import android.widget.TextView;
- val myLambda1 = @Composable() { <TextView text="Hello World!" /> }
- val myLambda2: @Composable() ()->Unit = { <TextView text="Hello World!" /> }
+ val myLambda1 = @Composable() { TextView(text="Hello World!") }
+ val myLambda2: @Composable() ()->Unit = { TextView(text="Hello World!") }
""")
}
@@ -111,8 +104,8 @@
import androidx.compose.*;
import android.widget.TextView;
- fun <!KTX_IN_NON_COMPOSABLE!>myRandomFunction<!>() {
- <TextView text="Hello World!" />
+ fun myRandomFunction() {
+ <!NONE_APPLICABLE!>TextView<!>(text="Hello World!")
}
""")
}
@@ -124,25 +117,12 @@
@Composable
fun foo() {
- val myRandomLambda = <!KTX_IN_NON_COMPOSABLE!>{ <TextView text="Hello World!" /> }<!>
+ val myRandomLambda = { <!NONE_APPLICABLE!>TextView<!>(text="Hello World!") }
System.out.println(myRandomLambda)
}
""")
}
- fun testComposableReporting005() {
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- class MyComponent : Component() {
- override fun compose() {
- <TextView text="Hello World!" />
- }
- }
- """)
- }
-
fun testComposableReporting006() {
checkFail("""
import androidx.compose.*;
@@ -150,9 +130,9 @@
fun foo() {
val bar = {
- <TextView />
+ TextView()
}
- <bar />
+ bar()
System.out.println(bar)
}
""")
@@ -163,9 +143,9 @@
@Composable
fun foo() {
val bar = @Composable {
- <TextView />
+ TextView()
}
- <bar />
+ bar()
System.out.println(bar)
}
""")
@@ -189,7 +169,7 @@
fun foo() {
val bar: @Composable() ()->Unit = @Composable {
- <TextView />
+ TextView()
}
<!SVC_INVOCATION!>bar<!>()
System.out.println(bar)
@@ -203,7 +183,7 @@
import android.widget.TextView;
fun myStatelessFunctionalComponent() {
- <TextView text="Hello World!" />
+ TextView(text="Hello World!")
}
fun noise() {
@@ -216,7 +196,7 @@
@Composable
fun myStatelessFunctionalComponent() {
- <TextView text="Hello World!" />
+ TextView(text="Hello World!")
}
fun noise() {
@@ -225,123 +205,6 @@
""")
}
- fun testComposableReporting010() {
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- class MyComponent : Component() {
- @Children lateinit var children: @Composable() ()->Unit
- override fun compose() {
- val children = this.children
- <children />
- System.out.println(children)
- }
- }
- """)
- }
-
- fun testComposableReporting011() {
- checkFail("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- class MyComponent : Component() {
- @Children lateinit var children: @Composable() ()->Unit
- override fun compose() {
- <!SVC_INVOCATION!>children<!>()
- }
- }
- """)
- }
-
- fun testComposableReporting012() {
- checkFail("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- class MyComponent : Component() {
- @Children lateinit var children: ()->Unit
- override fun compose() {
- <!SVC_INVOCATION!>children<!>()
- }
- }
- """)
- }
-
- fun testComposableReporting013() {
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- class MyComponent : Component() {
- @Children(composable=false) lateinit var children: (value: Int)->Unit
- override fun compose() {
- children(5)
- }
- }
- """)
- }
-
- fun testComposableReporting014() {
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- class MyReceiver {}
-
- class MyComponent : Component() {
- @Children(composable=false) lateinit var children: MyReceiver.()->Unit
- override fun compose() {
- MyReceiver().children()
- }
- }
- """)
- }
-
- fun testComposableReporting015() {
- checkFail("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- class MyComponent : Component() {
- override fun compose() {
- <helper1 />
- <helper2 />
- }
-
- fun helper1() {
- <TextView text="Hello Helper" />
- }
-
- @Composable
- fun helper2() {
- <TextView text="Hello Helper" />
- }
- }
- """)
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- class MyComponent : Component() {
- override fun compose() {
- <helper1 />
- <helper2 />
- }
-
- fun <!KTX_IN_NON_COMPOSABLE!>helper1<!>() {
- <TextView text="Hello Helper" />
- }
-
- @Composable
- fun helper2() {
- <TextView text="Hello Helper" />
- }
- }
- """)
- }
-
fun testComposableReporting016() {
check("""
import androidx.compose.*;
@@ -378,13 +241,13 @@
import android.widget.TextView;
@Composable
- fun Foo(@Children(composable=false) children: ()->Unit) {
+ fun Foo(children: ()->Unit) {
children()
}
@Composable
fun main() {
- <Foo><TextView text="Hello" /></Foo>
+ Foo { TextView(text="Hello") }
}
""")
check("""
@@ -393,13 +256,13 @@
import android.widget.TextView;
@Composable
- fun Foo(@Children(composable=false) children: ()->Unit) {
+ fun Foo(children: ()->Unit) {
children()
}
@Composable
fun main() {
- <Foo><!KTX_IN_NON_COMPOSABLE!><TextView text="Hello" /><!></Foo>
+ Foo { <!NONE_APPLICABLE!>TextView<!>(text="Hello") }
}
""")
}
@@ -410,7 +273,7 @@
import android.widget.TextView;
fun foo() {
- val myVariable: ()->Unit = @Composable { <TextView text="Hello World!" /> }
+ val myVariable: ()->Unit = @Composable { TextView(text="Hello World!") }
System.out.println(myVariable)
}
""")
@@ -419,51 +282,13 @@
import android.widget.TextView;
fun foo() {
- val myVariable: ()->Unit = <!TYPE_INFERENCE_EXPECTED_TYPE_MISMATCH!>@Composable { <TextView text="Hello World!" /> }<!>
+ val myVariable: ()->Unit = <!TYPE_INFERENCE_EXPECTED_TYPE_MISMATCH!>@Composable {
+ TextView(text="Hello World!") }<!>
System.out.println(myVariable)
}
""")
}
- fun testComposableReporting019() {
- checkFail("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- @Composable
- fun foo() {
- val myVariable: ()->Unit = { }
- <<!NON_COMPOSABLE_INVOCATION!>myVariable<!> />
- System.out.println(myVariable)
- }
- """)
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- @Composable
- fun foo() {
- val nonComposableLambda: ()->Unit = { }
- <<!NON_COMPOSABLE_INVOCATION!>nonComposableLambda<!> />
- System.out.println(nonComposableLambda)
- }
- """)
- }
-
- fun testComposableReporting020() {
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- fun nonComposableFunction() {}
-
- @Composable
- fun foo() {
- <<!NON_COMPOSABLE_INVOCATION!>nonComposableFunction<!> />
- }
- """)
- }
-
fun testComposableReporting021() {
check("""
import androidx.compose.*;
@@ -472,7 +297,10 @@
@Composable
fun foo() {
val myList = listOf(1,2,3,4,5)
- myList.forEach @Composable { value: Int -> <TextView text=value.toString() />; System.out.println(value); }
+ myList.forEach @Composable { value: Int ->
+ TextView(text=value.toString())
+ System.out.println(value)
+ }
}
""")
}
@@ -484,34 +312,26 @@
fun foo() {
val myList = listOf(1,2,3,4,5)
- myList.forEach { value: Int -> <TextView text=value.toString() />; System.out.println(value); }
+ myList.forEach { value: Int ->
+ TextView(text=value.toString())
+ System.out.println(value)
+ }
}
""")
check("""
import androidx.compose.*;
import android.widget.TextView;
- fun <!KTX_IN_NON_COMPOSABLE!>foo<!>() {
+ fun foo() {
val myList = listOf(1,2,3,4,5)
- myList.forEach @Composable { value: Int -> <TextView text=value.toString() />; System.out.println(value); }
+ myList.forEach @Composable { value: Int ->
+ <!NONE_APPLICABLE!>TextView<!>(text=value.toString())
+ System.out.println(value)
+ }
}
""")
}
- fun testComposableReporting023() {
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- fun foo() {}
-
- @Composable
- fun bar() {
- <<!NON_COMPOSABLE_INVOCATION!>foo<!> />
- }
- """)
- }
-
fun testComposableReporting024() {
check("""
import androidx.compose.*;
@@ -519,7 +339,7 @@
import android.widget.LinearLayout;
fun foo(ll: LinearLayout) {
- ll.setViewContent({ <TextView text="Hello World!" /> })
+ ll.setViewContent({ TextView(text="Hello World!") })
}
""")
}
@@ -531,7 +351,7 @@
@Composable
fun foo() {
- listOf(1,2,3,4,5).forEach { <TextView text="Hello World!" /> }
+ listOf(1,2,3,4,5).forEach { TextView(text="Hello World!") }
}
""")
}
@@ -544,9 +364,9 @@
@Composable
fun foo() {
- <LinearLayout>
- <TextView text="Hello Jim!" />
- </LinearLayout>
+ LinearLayout {
+ TextView(text="Hello Jim!")
+ }
}
""")
}
@@ -559,11 +379,11 @@
@Composable
fun foo() {
- <LinearLayout>
+ LinearLayout {
listOf(1,2,3).forEach {
- <TextView text="Hello Jim!" />
+ TextView(text="Hello Jim!")
}
- </LinearLayout>
+ }
}
""")
}
@@ -589,29 +409,6 @@
""")
}
- fun testComposableReporting029() {
- checkFail("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- @Composable
- fun foo(v: ()->Unit) {
- val myVariable: @Composable() ()->Unit = v;
- <myVariable />
- }
- """)
- check("""
- import androidx.compose.*;
- import android.widget.TextView;
-
- @Composable
- fun foo(v: ()->Unit) {
- val myVariable: @Composable() ()->Unit = <!TYPE_INFERENCE_EXPECTED_TYPE_MISMATCH!>v<!>;
- <myVariable />
- }
- """)
- }
-
fun testComposableReporting030() {
check("""
import androidx.compose.*;
@@ -620,7 +417,7 @@
@Composable
fun foo() {
val myVariable: @Composable() ()->Unit = {};
- <myVariable />
+ myVariable()
}
""")
}
@@ -631,7 +428,7 @@
import android.widget.TextView;
fun foo() {
- val myVariable: ()->Unit = <!KTX_IN_NON_COMPOSABLE!>{ <TextView text="Hello" /> }<!>;
+ val myVariable: ()->Unit = { <!NONE_APPLICABLE!>TextView<!>(text="Hello") };
myVariable();
}
""")
@@ -640,15 +437,14 @@
fun testComposableReporting032() {
check("""
import androidx.compose.*;
- import androidx.compose.Children;
import android.widget.TextView;
@Composable
- fun MyComposable(@Children children: ()->Unit) { <children /> }
+ fun MyComposable(children: @Composable() ()->Unit) { children() }
@Composable
fun foo() {
- <MyComposable><TextView text="Hello" /></MyComposable>
+ MyComposable { TextView(text="Hello") }
}
""")
}
@@ -656,15 +452,14 @@
fun testComposableReporting033() {
check("""
import androidx.compose.*;
- import androidx.compose.Children;
import android.widget.TextView;
@Composable
- fun MyComposable(@Children children: ()->Unit) { <children /> }
+ fun MyComposable(children: @Composable() ()->Unit) { children() }
@Composable
fun foo() {
- <MyComposable children={<TextView text="Hello" />} />
+ MyComposable(children={ TextView(text="Hello")})
}
""")
}
@@ -679,7 +474,7 @@
@Composable
fun test(f: @Composable() ()->Unit) {
val f2: @Composable() ()->Unit = identity(f);
- <f2 />
+ f2()
}
""")
check("""
@@ -691,7 +486,7 @@
@Composable
fun test(f: @Composable() ()->Unit) {
val f2: @Composable() ()->Unit = <!TYPE_INFERENCE_EXPECTED_TYPE_MISMATCH!>identity(<!TYPE_INFERENCE_EXPECTED_TYPE_MISMATCH!>f<!>)<!>;
- <f2 />
+ f2()
}
""")
}
@@ -703,7 +498,7 @@
@Composable
fun Foo(x: String) {
@Composable operator fun String.invoke() {}
- <x />
+ x()
}
""")
}
@@ -715,7 +510,7 @@
fun Foo() {
repeat(5) {
- <TextView text="Hello World" />
+ TextView(text="Hello World")
}
}
@@ -729,7 +524,7 @@
fun Foo() {
repeat(5) {
- <TextView text="Hello World" />
+ TextView(text="Hello World")
}
}
@@ -746,7 +541,7 @@
fun Foo() {
fun Noise() {
- <TextView text="Hello World" />
+ TextView(text="Hello World")
}
}
@@ -765,9 +560,9 @@
fun repeat(x: Int, l: ()->Unit) { for(i in 1..x) l() }
fun Foo() {
- repeat(5) <!KTX_IN_NON_COMPOSABLE!>{
- <TextView text="Hello World" />
- }<!>
+ repeat(5) {
+ <!NONE_APPLICABLE!>TextView<!>(text="Hello World")
+ }
}
""")
}
@@ -781,7 +576,7 @@
fun Foo() {
composeInto {
- <TextView text="Hello World" />
+ TextView(text="Hello World")
}
}
@@ -797,7 +592,7 @@
fun Foo() {
noise {
- <TextView text="Hello World" />
+ TextView(text="Hello World")
}
}
@@ -807,24 +602,6 @@
""")
}
- fun testComposableReporting040() {
- check("""
- import androidx.compose.*
- import android.widget.TextView;
-
- @Composable
- fun ComposeWrapperComposable(children: @Composable() () -> Unit) {
- <MyComposeWrapper>
- <children />
- </MyComposeWrapper>
- }
-
- class MyComposeWrapper(var children: @Composable() () -> Unit) : Component() {
- override fun compose() { }
- }
- """)
- }
-
fun testComposableReporting041() {
check("""
import androidx.compose.*
@@ -834,50 +611,13 @@
@Composable
fun ComposeWrapperComposable(children: COMPOSABLE_UNIT_LAMBDA) {
- <MyComposeWrapper>
- <children />
- </MyComposeWrapper>
- }
-
- class MyComposeWrapper(var children: COMPOSABLE_UNIT_LAMBDA) : Component() {
- override fun compose() { }
- }
- """)
- }
-
- fun testComposableReporting042() {
- check("""
- import androidx.compose.*
- import android.widget.TextView;
-
- fun composeInto(l: @Composable() ()->Unit) { System.out.println(l) }
-
- @Composable
- fun FancyButton() {}
-
- fun Foo() {
- composeInto {
- FancyButton()
+ MyComposeWrapper {
+ children()
}
}
- fun Bar() {
- Foo()
- }
- """)
- check("""
- import androidx.compose.*
- import android.widget.TextView;
-
- inline fun noise(l: ()->Unit) { l() }
-
- @Composable
- fun FancyButton() {}
-
- fun <!COMPOSABLE_INVOCATION_IN_NON_COMPOSABLE!>Foo<!>() {
- noise {
- <!COMPOSABLE_INVOCATION_IN_NON_COMPOSABLE!>FancyButton<!>()
- }
+ @Composable fun MyComposeWrapper(children: COMPOSABLE_UNIT_LAMBDA) {
+ print(children.hashCode())
}
""")
}
@@ -928,17 +668,18 @@
""")
}
- fun testComposableReporting046() {
+ fun testComposableReporting047() {
check("""
- import androidx.compose.*;
- import android.widget.TextView;
+ import androidx.compose.*
+ import android.widget.LinearLayout;
- class MyComponent : Component() {
- @Children lateinit var children: @Composable() ()->Unit
- override fun compose() {
- val children = this.children
- children()
- System.out.println(children)
+ @Composable
+ fun FancyButton() {}
+
+ @Composable
+ fun Foo() {
+ LinearLayout {
+ FancyButton()
}
}
""")
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/UnionCheckerTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/UnionCheckerTests.kt
index f16144c..4a03459 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/UnionCheckerTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/UnionCheckerTests.kt
@@ -31,9 +31,9 @@
@Composable
fun bar() {
- <foo value=1 />
- <foo value="1" />
- <foo value=<!ILLEGAL_ASSIGN_TO_UNIONTYPE!>1f<!> />
+ foo(value=1)
+ foo(value="1")
+ foo(value=<!ILLEGAL_ASSIGN_TO_UNIONTYPE!>1f<!>)
}
""")
}
@@ -49,7 +49,7 @@
@Composable
fun bar(value: @UnionType(Int::class, String::class) Any) {
- <foo value />
+ foo(value)
}
""")
}
@@ -65,7 +65,7 @@
@Composable
fun bar(value: @UnionType(Int::class, String::class) Any) {
- <foo value />
+ foo(value)
}
""")
}
@@ -81,7 +81,7 @@
@Composable
fun bar(value: @UnionType(Int::class, String::class, Float::class) Any) {
- <foo <!ILLEGAL_ASSIGN_TO_UNIONTYPE!>value<!> />
+ foo(<!ILLEGAL_ASSIGN_TO_UNIONTYPE!>value<!>)
}
""")
}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/frames/ComposeClassAnalysisHandlerExtensionTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/frames/ComposeClassAnalysisHandlerExtensionTests.kt
deleted file mode 100644
index e4637a5..0000000
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/frames/ComposeClassAnalysisHandlerExtensionTests.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package androidx.compose.plugins.kotlin.frames
-
-import androidx.compose.plugins.kotlin.AbstractComposeDiagnosticsTest
-
-class ComposeClassAnalysisHandlerExtensionTests : AbstractComposeDiagnosticsTest() {
-
- fun testReportOpen() {
- doTest("""
- import androidx.compose.Component;
-
- open class <!OPEN_COMPONENT!>MyComponent<!> : Component() {
- override fun compose() { }
- }
- """)
- }
-
- fun testAllowClosed() {
- doTest("""
- import androidx.compose.Component;
-
- class MyComponent: Component() {
- override fun compose() { }
- }
- """)
- }
-
- fun testReportAbstract() {
- doTest("""
- import androidx.compose.Component;
-
- abstract class <!OPEN_COMPONENT!>MyComponent<!>: Component() {
- override fun compose() { }
- }
- """)
- }
-}
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/frames/FrameTransformExtensionTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/frames/FrameTransformExtensionTests.kt
index f9aab45..1ef4269 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/frames/FrameTransformExtensionTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/frames/FrameTransformExtensionTests.kt
@@ -226,7 +226,6 @@
var strVal get() = myStrVal; set(value) { myStrVal = value }
}
-
class Test {
fun test() {
val instance = frame { MyModel() }
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt
deleted file mode 100644
index 2616c8f..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
-import org.jetbrains.kotlin.psi.KtDeclaration
-import org.jetbrains.kotlin.psi.KtElement
-import androidx.compose.plugins.kotlin.analysis.ComposeErrors
-import org.jetbrains.kotlin.container.StorageComponentContainer
-import org.jetbrains.kotlin.container.useInstance
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor
-import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
-import org.jetbrains.kotlin.platform.TargetPlatform
-import org.jetbrains.kotlin.platform.jvm.isJvm
-import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
-import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
-
-open class ChildAnnotationChecker() : DeclarationChecker, StorageComponentContainerContributor {
- override fun check(
- declaration: KtDeclaration,
- descriptor: DeclarationDescriptor,
- context: DeclarationCheckerContext
- ) {
- if (descriptor is FunctionDescriptor) {
- descriptor.valueParameters.forEachIndexed { index, param ->
- if (param.hasChildrenAnnotation() &&
- index != descriptor.valueParameters.lastIndex) {
- context.trace.report(
- ComposeErrors.CHILDREN_MUST_BE_LAST.on(param.findPsi() as KtElement)
- )
- }
- }
- }
- }
-
- override fun registerModuleComponents(
- container: StorageComponentContainer,
- platform: TargetPlatform,
- moduleDescriptor: ModuleDescriptor
- ) {
- if (!platform.isJvm()) return
- container.useInstance(this)
- }
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComponentsClosedDeclarationChecker.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComponentsClosedDeclarationChecker.kt
deleted file mode 100644
index 11a987f..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComponentsClosedDeclarationChecker.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import org.jetbrains.kotlin.container.StorageComponentContainer
-import org.jetbrains.kotlin.container.useInstance
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.ClassKind
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor
-import org.jetbrains.kotlin.diagnostics.reportFromPlugin
-import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.psi.KtClass
-import org.jetbrains.kotlin.psi.KtDeclaration
-import androidx.compose.plugins.kotlin.analysis.ComponentMetadata
-import androidx.compose.plugins.kotlin.analysis.ComposeDefaultErrorMessages
-import androidx.compose.plugins.kotlin.analysis.ComposeErrors
-import org.jetbrains.kotlin.platform.TargetPlatform
-import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
-import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
-import org.jetbrains.kotlin.platform.jvm.isJvm
-
-class ComponentsClosedDeclarationChecker :
- DeclarationChecker,
- StorageComponentContainerContributor {
-
- override fun registerModuleComponents(
- container: StorageComponentContainer,
- platform: TargetPlatform,
- moduleDescriptor: ModuleDescriptor
- ) {
- if (!platform.isJvm()) return
- container.useInstance(ComponentsClosedDeclarationChecker())
- }
-
- override fun check(
- declaration: KtDeclaration,
- descriptor: DeclarationDescriptor,
- context: DeclarationCheckerContext
- ) {
- if (descriptor !is ClassDescriptor || declaration !is KtClass) return
- if (descriptor.kind != ClassKind.CLASS) return
- if (!ComponentMetadata.isComposeComponent(descriptor)) return
-
- if (declaration.hasModifier(KtTokens.OPEN_KEYWORD) ||
- declaration.hasModifier(KtTokens.ABSTRACT_KEYWORD)) {
- val element = declaration.nameIdentifier ?: declaration
- context.trace.reportFromPlugin(
- ComposeErrors.OPEN_COMPONENT.on(element),
- ComposeDefaultErrorMessages
- )
- }
- }
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableAnnotationChecker.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableAnnotationChecker.kt
index aa1d342..3582970 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableAnnotationChecker.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableAnnotationChecker.kt
@@ -51,18 +51,20 @@
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.kotlin.analysis.ComponentMetadata
import androidx.compose.plugins.kotlin.analysis.ComposeDefaultErrorMessages
import androidx.compose.plugins.kotlin.analysis.ComposeErrors
import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices.COMPOSABLE_ANALYSIS
import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices.FCS_RESOLVEDCALL_COMPOSABLE
import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices.INFERRED_COMPOSABLE_DESCRIPTOR
+import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.jvm.isJvm
+import org.jetbrains.kotlin.psi.KtFunctionLiteral
+import org.jetbrains.kotlin.psi.KtLambdaArgument
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
@@ -80,21 +82,11 @@
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.upperIfFlexible
import org.jetbrains.kotlin.util.OperatorNameConventions
+import org.jetbrains.kotlin.utils.addToStdlib.cast
open class ComposableAnnotationChecker : CallChecker, DeclarationChecker,
AdditionalTypeChecker, AdditionalAnnotationChecker, StorageComponentContainerContributor {
- enum class Mode {
- /** @Composable annotations are enforced when inferred/specified **/
- KTX_CHECKED,
- /** @Composable annotations are explicitly required on all composables **/
- KTX_STRICT,
- /** @Composable annotations are explicitly required and are not subtypes of their unannotated types **/
- KTX_PEDANTIC,
- /** @Composable annotations are explicitly required and are not subtypes, but use FCS **/
- FCS
- }
-
companion object {
fun get(project: Project): ComposableAnnotationChecker {
return StorageComponentContainerContributor.getInstances(project).single {
@@ -109,8 +101,6 @@
if (resolvedCall is VariableAsFunctionResolvedCall) {
if (resolvedCall.variableCall.candidateDescriptor.type.hasComposableAnnotation())
return true
- if (resolvedCall.variableCall.candidateDescriptor.isComposableFromChildrenAnnotation())
- return true
if (resolvedCall.functionCall.resultingDescriptor.hasComposableAnnotation()) return true
return false
}
@@ -131,30 +121,27 @@
}
}
if (candidateDescriptor is ValueParameterDescriptor) {
- if (candidateDescriptor.isComposableFromChildrenAnnotation()) return true
return candidateDescriptor.type.hasComposableAnnotation()
}
if (candidateDescriptor is LocalVariableDescriptor) {
return candidateDescriptor.type.hasComposableAnnotation()
}
if (candidateDescriptor is PropertyDescriptor) {
- if (candidateDescriptor.isComposableFromChildrenAnnotation()) return true
return candidateDescriptor.type.hasComposableAnnotation()
}
return candidateDescriptor.hasComposableAnnotation()
}
fun analyze(trace: BindingTrace, descriptor: FunctionDescriptor): Composability {
- val unwrappedDescriptor =
- if (descriptor is
- ComposeCallResolutionInterceptorExtension.ComposableInvocationDescriptor)
- descriptor.original
- else descriptor
+ val unwrappedDescriptor = when (descriptor) {
+ is ComposableFunctionDescriptor -> descriptor.underlyingDescriptor
+ else -> descriptor
+ }
val psi = unwrappedDescriptor.findPsi() as? KtElement
psi?.let { trace.bindingContext.get(COMPOSABLE_ANALYSIS, it)?.let { return it } }
if (unwrappedDescriptor.name == Name.identifier("compose") &&
unwrappedDescriptor.containingDeclaration is ClassDescriptor &&
- ComponentMetadata.isComposeComponent(unwrappedDescriptor.containingDeclaration)
+ ComposeUtils.isComposeComponent(unwrappedDescriptor.containingDeclaration)
) return Composability.MARKED
var composability = Composability.NOT_COMPOSABLE
if (trace.bindingContext.get(
@@ -176,6 +163,13 @@
is JavaMethodDescriptor ->
if (unwrappedDescriptor.hasComposableAnnotation()) composability =
Composability.MARKED
+ is AnonymousFunctionDescriptor -> {
+ if (unwrappedDescriptor.hasComposableAnnotation()) composability =
+ Composability.MARKED
+ if (psi is KtFunctionLiteral && psi.isEmitInline(trace.bindingContext)) {
+ composability = Composability.MARKED
+ }
+ }
else -> if (unwrappedDescriptor.hasComposableAnnotation()) composability =
Composability.MARKED
}
@@ -193,14 +187,9 @@
signatureComposability: Composability
): Composability {
var composability = signatureComposability
- var localKtx = false
var localFcs = false
var isInlineLambda = false
element.accept(object : KtTreeVisitorVoid() {
- override fun visitKtxElement(element: KtxElement) {
- localKtx = true
- }
-
override fun visitDeclaration(dcl: KtDeclaration) {
if (dcl == element) {
super.visitDeclaration(dcl)
@@ -219,6 +208,22 @@
}
override fun visitCallExpression(expression: KtCallExpression) {
+ val resolvedCall = expression.getResolvedCall(trace.bindingContext)
+ when (resolvedCall?.candidateDescriptor) {
+ is ComposableEmitDescriptor,
+ is ComposableFunctionDescriptor -> {
+ localFcs = true
+ if (!isInlineLambda && composability != Composability.MARKED) {
+ // Report error on composable element to make it obvious which invocation is offensive
+ val reportElement = expression.calleeExpression ?: expression
+ trace.reportFromPlugin(
+ ComposeErrors.COMPOSABLE_INVOCATION_IN_NON_COMPOSABLE
+ .on(reportElement),
+ ComposeDefaultErrorMessages
+ )
+ }
+ }
+ }
// Can be null in cases where the call isn't resolvable (eg. due to a bug/typo in the user's code)
val isCallComposable = trace.get(FCS_RESOLVEDCALL_COMPOSABLE, expression)
if (isCallComposable == true) {
@@ -237,7 +242,7 @@
}
}, null)
if (
- (localKtx || localFcs) &&
+ localFcs &&
!isInlineLambda && composability != Composability.MARKED
) {
val reportElement = when (element) {
@@ -250,14 +255,8 @@
ComposeDefaultErrorMessages
)
}
- if (localKtx) {
- trace.reportFromPlugin(
- ComposeErrors.KTX_IN_NON_COMPOSABLE.on(reportElement),
- ComposeDefaultErrorMessages
- )
- }
}
- if (localKtx && composability == Composability.NOT_COMPOSABLE)
+ if (localFcs && composability == Composability.NOT_COMPOSABLE)
composability =
Composability.INFERRED
return composability
@@ -267,7 +266,7 @@
* Analyze a KtElement
* - Determine if it is @Compoasble (eg. the element or inferred type has an @Composable annotation)
* - Update the binding context to cache analysis results
- * - Report errors (eg. KTX tags occur in a non-composable, invocations of an @Composable, etc)
+ * - Report errors (eg. invocations of an @Composable, etc)
* - Return true if element is @Composable, else false
*/
fun analyze(trace: BindingTrace, element: KtElement, type: KotlinType?): Composability {
@@ -283,7 +282,7 @@
trace.bindingContext.get(BindingContext.ANNOTATION, it)?.isComposableAnnotation
?: false
}
- if (annotationEntry != null && !ComponentMetadata.isComposeComponent(descriptor)) {
+ if (annotationEntry != null && !ComposeUtils.isComposeComponent(descriptor)) {
trace.report(
Errors.WRONG_ANNOTATION_TARGET.on(
annotationEntry,
@@ -291,19 +290,11 @@
)
)
}
- if (ComponentMetadata.isComposeComponent(descriptor)) {
+ if (ComposeUtils.isComposeComponent(descriptor)) {
composability += Composability.MARKED
}
}
if (element is KtParameter) {
- val childrenAnnotation = element.annotationEntries
- .mapNotNull { trace.bindingContext.get(BindingContext.ANNOTATION, it) }
- .singleOrNull { it.isComposableChildrenAnnotation }
-
- if (childrenAnnotation != null) {
- composability += Composability.MARKED
- }
-
val composableAnnotation = element
.typeReference
?.annotationEntries
@@ -315,14 +306,6 @@
}
}
if (element is KtParameter) {
- val childrenAnnotation = element.annotationEntries
- .mapNotNull { trace.bindingContext.get(BindingContext.ANNOTATION, it) }
- .singleOrNull { it.isComposableChildrenAnnotation }
-
- if (childrenAnnotation != null) {
- composability += Composability.MARKED
- }
-
val composableAnnotation = element
.typeReference
?.annotationEntries
@@ -356,12 +339,18 @@
if (descriptor.isComposableAnnotation) {
composability += Composability.MARKED
}
- if (descriptor.isComposableChildrenAnnotation) {
- composability += Composability.MARKED
- }
}
if (element is KtLambdaExpression || element is KtFunction) {
+ val associatedCall = parent?.parent as? KtCallExpression
+
+ if (associatedCall != null && parent is KtLambdaArgument) {
+ val resolvedCall = associatedCall.getResolvedCall(trace.bindingContext)
+ if (resolvedCall?.candidateDescriptor is ComposableEmitDescriptor) {
+ composability += Composability.MARKED
+ }
+ }
+
composability = analyzeFunctionContents(trace, element, composability)
}
@@ -400,7 +389,7 @@
)?.isComposableAnnotation ?: false
}
if (composableAnnotationEntry != null &&
- !ComponentMetadata.isComposeComponent(classDescriptor)) {
+ !ComposeUtils.isComposeComponent(classDescriptor)) {
trace.report(
Errors.WRONG_ANNOTATION_TARGET.on(
composableAnnotationEntry,
@@ -453,6 +442,19 @@
)
if (isInlineable) return
+ if (expression.parent is KtLambdaArgument) {
+ val callDescriptor = expression
+ .parent
+ ?.parent
+ ?.cast<KtCallExpression>()
+ ?.getResolvedCall(c.trace.bindingContext)
+ ?.candidateDescriptor
+
+ if (callDescriptor is ComposableEmitDescriptor) {
+ return
+ }
+ }
+
val reportOn =
if (expression.parent is KtAnnotatedExpression)
expression.parent as KtExpression
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableEmitDescriptor.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableEmitDescriptor.kt
index 02ae09b..f9301e9 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableEmitDescriptor.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableEmitDescriptor.kt
@@ -17,6 +17,7 @@
package androidx.compose.plugins.kotlin
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.Modality
@@ -28,12 +29,20 @@
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.resolve.calls.tower.ImplicitScopeTower
+import org.jetbrains.kotlin.resolve.BindingTrace
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.replace
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
class ComposableEmitDescriptor(
- val ktxCall: ResolvedKtxElementCall,
+ val composer: ResolvedCall<*>,
+ val emitCall: ResolvedCall<*>,
+ val hasChildren: Boolean,
+ val pivotals: List<String>,
+ val ctorCall: ResolvedCall<*>,
+ val ctorParams: List<String>,
+ val validations: List<ValidatedAssignment>,
containingDeclaration: DeclarationDescriptor,
original: SimpleFunctionDescriptor?,
annotations: Annotations,
@@ -50,25 +59,32 @@
) {
companion object {
- fun fromKtxCall(
- ktxCall: ResolvedKtxElementCall,
- scopeTower: ImplicitScopeTower,
+ fun build(
+ hasChildren: Boolean,
+ emitCall: ResolvedCall<*>,
+ pivotals: List<String>,
+ ctorCall: ResolvedCall<*>,
+ ctorParams: List<String>,
+ validations: List<ValidatedAssignment>,
+ composerCall: ResolvedCall<*>,
name: Name
- ): ComposableEmitDescriptor? {
+ ): ComposableEmitDescriptor {
val builtIns = DefaultBuiltIns.Instance
- val emitOrCall = ktxCall.emitOrCall
- if (emitOrCall !is EmitCallNode) {
- return null
- }
- val resolvedCall = emitOrCall.primaryCall ?: return null
+ val resolvedCall = ctorCall
- val original = resolvedCall.candidateDescriptor
- as? SimpleFunctionDescriptor
+ val original = resolvedCall.resultingDescriptor as? SimpleFunctionDescriptor
+
val descriptor = ComposableEmitDescriptor(
- ktxCall,
- ktxCall.infixOrCall!!.candidateDescriptor.containingDeclaration,
+ composerCall,
+ emitCall,
+ hasChildren,
+ pivotals,
+ ctorCall,
+ ctorParams,
+ validations,
+ emitCall.candidateDescriptor.containingDeclaration,
original,
Annotations.EMPTY,
name,
@@ -77,18 +93,21 @@
)
val valueArgs = mutableListOf<ValueParameterDescriptor>()
+ val paramSet = mutableSetOf<String>()
- ktxCall.usedAttributes.forEachIndexed { index, attributeInfo ->
+ for (paramName in ctorParams) {
+ if (paramSet.contains(paramName)) continue
+ val param = resolvedCall.resultingDescriptor.valueParameters.find {
+ it.name.identifier == paramName
+ } ?: continue
+
+ paramSet.add(paramName)
valueArgs.add(
ValueParameterDescriptorImpl(
- descriptor, null, index,
+ descriptor, null, valueArgs.size,
Annotations.EMPTY,
- Name.identifier(
- if (attributeInfo.name == CHILDREN_KEY)
- attributeInfo.descriptor.name.identifier
- else attributeInfo.name
- ),
- attributeInfo.type, false,
+ param.name,
+ param.type, false,
false,
false, null,
SourceElement.NO_SOURCE
@@ -96,14 +115,40 @@
)
}
+ for (validation in validations) {
+ if (paramSet.contains(validation.name)) continue
+ paramSet.add(validation.name)
+ valueArgs.add(
+ ValueParameterDescriptorImpl(
+ descriptor,
+ null,
+ valueArgs.size,
+ Annotations.EMPTY,
+ Name.identifier(validation.name),
+ validation.type,
+ false,
+ false,
+ false,
+ null,
+ SourceElement.NO_SOURCE
+ )
+ )
+ }
+
val unitLambdaType = builtIns.getFunction(
0
).defaultType.replace(
listOf(builtIns.unitType.asTypeProjection())
- ).makeComposable(scopeTower.module)
- (emitOrCall as? EmitCallNode)?.inlineChildren?.let {
+ )
+ // NOTE(lmr): it's actually kind of important that this is *not* a composable lambda,
+ // so that the observe patcher doesn't insert an observe scope.
+ // In the future, we should reconsider how this is done since semantically a composable
+ // lambda is more correct here. I tried, but had trouble passing enough information to
+ // the observe patcher so it knew not to do this.
+ /*.makeComposable(scopeTower.module)*/
+ if (hasChildren) {
valueArgs.add(
- ValueParameterDescriptorImpl(
+ EmitChildrenValueParameterDescriptor(
descriptor, null, valueArgs.size,
Annotations.EMPTY,
Name.identifier("\$CHILDREN"),
@@ -128,4 +173,30 @@
return descriptor
}
}
-}
\ No newline at end of file
+}
+
+class EmitChildrenValueParameterDescriptor(
+ containingDeclaration: CallableDescriptor,
+ original: ValueParameterDescriptor?,
+ index: Int,
+ annotations: Annotations,
+ name: Name,
+ outType: KotlinType,
+ declaresDefaultValue: Boolean,
+ isCrossinline: Boolean,
+ isNoinline: Boolean,
+ varargElementType: KotlinType?,
+ source: SourceElement
+) : ValueParameterDescriptorImpl(
+ containingDeclaration,
+ original,
+ index,
+ annotations,
+ name,
+ outType,
+ declaresDefaultValue,
+ isCrossinline,
+ isNoinline,
+ varargElementType,
+ source
+)
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableFunctionDescriptor.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableFunctionDescriptor.kt
index c02b0dd..fd88da2 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableFunctionDescriptor.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableFunctionDescriptor.kt
@@ -17,6 +17,22 @@
package androidx.compose.plugins.kotlin
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.VariableDescriptor
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.types.TypeSubstitutor
-class ComposableFunctionDescriptor(val underlyingDescriptor: FunctionDescriptor) :
- FunctionDescriptor by underlyingDescriptor
\ No newline at end of file
+class ComposableFunctionDescriptor(
+ val underlyingDescriptor: FunctionDescriptor,
+ val composerCall: ResolvedCall<*>,
+ val composerMetadata: ComposerMetadata
+) : FunctionDescriptor by underlyingDescriptor {
+ override fun substitute(substitutor: TypeSubstitutor): FunctionDescriptor? {
+ return underlyingDescriptor.substitute(substitutor)?.let {
+ ComposableFunctionDescriptor(
+ underlyingDescriptor = it,
+ composerCall = composerCall,
+ composerMetadata = composerMetadata
+ )
+ }
+ }
+}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
index 8246059..ac4e1a9 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
@@ -17,39 +17,35 @@
package androidx.compose.plugins.kotlin
import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION
import com.intellij.psi.PsiElement
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality
-import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
-import org.jetbrains.kotlin.descriptors.SourceElement
-import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
-import org.jetbrains.kotlin.descriptors.Visibilities
-import org.jetbrains.kotlin.descriptors.annotations.Annotations
-import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
+import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.extensions.CallResolutionInterceptorExtension
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
-import org.jetbrains.kotlin.psi.KtLambdaArgument
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.CallResolver
import org.jetbrains.kotlin.resolve.calls.CandidateResolver
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
+import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache
+import org.jetbrains.kotlin.resolve.calls.model.DataFlowInfoForArgumentsImpl
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
import org.jetbrains.kotlin.resolve.calls.tower.ImplicitScopeTower
import org.jetbrains.kotlin.resolve.calls.tower.NewResolutionOldInference
-import org.jetbrains.kotlin.resolve.inline.InlineUtil.isInlinedArgument
+import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.scopes.ResolutionScope
+import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
-import org.jetbrains.kotlin.types.replace
-import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
-import org.jetbrains.kotlin.util.OperatorNameConventions
class ComposeCallResolutionInterceptorExtension : CallResolutionInterceptorExtension {
override fun interceptCandidates(
@@ -71,62 +67,59 @@
name: Name,
location: LookupLocation
): Collection<FunctionDescriptor> {
- if (ComposeFlags.NEW_CALL_RESOLUTION_INTERCEPTION) {
- val callResolver =
- (scopeTower as NewResolutionOldInference.ImplicitScopeTowerImpl).callResolver
- val element = resolutionContext.call.callElement as KtExpression
- val project = element.project
- val psiFactory = KtPsiFactory(project, markGenerated = false)
+ val callResolver =
+ (scopeTower as NewResolutionOldInference.ImplicitScopeTowerImpl).callResolver
+ val element = resolutionContext.call.callElement as KtExpression
+ val project = element.project
- return ComposeCallResolver(
- callResolver,
- project,
- psiFactory
- ).interceptCandidates(
- candidates,
- scopeTower,
- resolutionContext,
- resolutionScope,
- name,
- location
- )
- }
if (candidates.isEmpty()) return candidates
val bindingContext = resolutionContext.trace.bindingContext
val call = resolutionContext.call
- val shouldIgnore = bindingContext[IGNORE_COMPOSABLE_INTERCEPTION, call] ?: false
+ val shouldIgnore = bindingContext[
+ ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION,
+ call
+ ] ?: false
+
if (shouldIgnore) return candidates
- for (arg in resolutionContext.call.valueArguments) {
- if (arg is KtLambdaArgument) continue
- if (arg.getArgumentName() != null) continue
- return candidates
+ val composables = mutableListOf<FunctionDescriptor>()
+ val nonComposablesNonConstructors = mutableListOf<FunctionDescriptor>()
+ val constructors = mutableListOf<ConstructorDescriptor>()
+
+ var needToLookupComposer = false
+
+ for (candidate in candidates) {
+ when {
+ candidate.hasComposableAnnotation() -> {
+ needToLookupComposer = true
+ composables.add(candidate)
+ }
+ candidate is ConstructorDescriptor -> {
+ needToLookupComposer = true
+ constructors.add(candidate)
+ }
+ else -> nonComposablesNonConstructors.add(candidate)
+ }
}
- val element = resolutionContext.call.callElement as KtExpression
- val composableAnnotationChecker =
- ComposableAnnotationChecker.get(element.project)
-
- val context = ExpressionTypingContext.newContext(
- resolutionContext.trace,
- resolutionContext.scope,
- resolutionContext.dataFlowInfo,
- resolutionContext.expectedType,
- resolutionContext.languageVersionSettings,
- resolutionContext.dataFlowValueFactory
- )
+ // If none of the candidates are composable or constructors, then it's unnecessary for us
+ // to do any work at all, since it will never be anything we intercept
+ if (!needToLookupComposer) return candidates
val temporaryTraceForKtxCall =
- TemporaryTraceAndCache.create(
- context,
- "trace to resolve ktx call", element
- )
- val temporaryForKtxCall = context.replaceTraceAndCache(temporaryTraceForKtxCall)
+ TemporaryTraceAndCache.create(
+ resolutionContext,
+ "trace to resolve ktx call", element
+ )
+
+ val composableAnnotationChecker =
+ ComposableAnnotationChecker.get(call.callElement.project)
// Ensure we are in a composable context
- var walker: PsiElement? = element
+ // TODO(lmr): there ought to be a better way to do this
+ var walker: PsiElement? = call.callElement
var isComposableContext = false
- var composableDescriptor: SimpleFunctionDescriptor? = null
+// var composableDescriptor: SimpleFunctionDescriptor? = null
while (walker != null) {
val descriptor = try {
resolutionContext.trace[BindingContext.FUNCTION, walker]
@@ -139,189 +132,198 @@
descriptor
)
isComposableContext =
- composability != ComposableAnnotationChecker.Composability.NOT_COMPOSABLE
- if (isComposableContext) composableDescriptor = descriptor
+ composability != ComposableAnnotationChecker.Composability.NOT_COMPOSABLE
+// if (isComposableContext) composableDescriptor = descriptor
// If the descriptor is for an inlined lambda, infer composability from the
// outer scope
if (!(walker is KtFunction) ||
- !isInlinedArgument(
- walker,
- context.trace.bindingContext,
- true))
+ !InlineUtil.isInlinedArgument(
+ walker,
+ resolutionContext.trace.bindingContext,
+ true
+ )
+ )
break
}
walker = try { walker.parent } catch (e: Throwable) { null }
}
if (!isComposableContext) return candidates
- val callResolver =
- (scopeTower as NewResolutionOldInference.ImplicitScopeTowerImpl).callResolver
- val ktxCallResolver = KtxCallResolver(
+ // use the call resolver to find any variable that would resolve with "composer" in scope.
+ val composerCall = callResolver.resolveComposer(resolutionContext)
+ val composer = composerCall?.resultingDescriptor as? VariableDescriptor
+
+ // If there is no composer in scope, then we cannot intercept. This means that we need to
+ // remove any @Composable from the candidates
+ if (composer == null) {
+ return nonComposablesNonConstructors + constructors
+ }
+
+ val psiFactory = KtPsiFactory(project, markGenerated = false)
+
+ // If we made it this far, we need to check and see if the constructors qualify as emit
+ // calls instead of constructor calls. First, we need to look at the composer to see
+ // what kinds of "emittables" it accepts.
+ // We cache the metadata into a writeable slice based on the descriptor
+ val composerMetadata = ComposerMetadata.getOrBuild(
+ composer,
callResolver,
- null,
- element.project,
- composableAnnotationChecker
+ psiFactory,
+ resolutionContext
)
- val resolvedKtxElementCall = try {
+ // TODO(lmr):
+ // We might decide there are some composers that are not "valid", ie, I shouldn't be able
+ // to call a composable if I just have `val composer = Unit` in scope... Right now we are
+ // just seeing if there are call descriptors. In the future, more validation might be
+ // warranted
+ val isValidComposer = composerMetadata.callDescriptors.isNotEmpty()
- ktxCallResolver.initializeFromCall(call, temporaryForKtxCall)
-
- ktxCallResolver.resolveFromCall(
- call,
- temporaryForKtxCall
- )
- } catch (e: Throwable) {
- e.printStackTrace()
- throw e
+ if (!isValidComposer) {
+ return nonComposablesNonConstructors + constructors
}
- // Doesn't appear to be resolvable to a composable; return normal resolution candidates
- val resolvedCall = when (resolvedKtxElementCall.emitOrCall) {
- is MemoizedCallNode -> resolvedKtxElementCall.emitOrCall.call.resolvedCalls().first()
- is EmitCallNode -> resolvedKtxElementCall.emitOrCall.resolvedCalls().first()
- is ErrorNode -> return candidates
- else ->
- throw Error("Unexpectd type: " + resolvedKtxElementCall.emitOrCall.javaClass)
- }
-
- if (resolvedKtxElementCall.emitOrCall is MemoizedCallNode &&
- !ComposableAnnotationChecker.get(element.project).shouldInvokeAsTag(
- temporaryForKtxCall.trace,
- resolvedCall
- ))
- return candidates
- val candidateDescriptor = resolvedCall.candidateDescriptor
- if (candidateDescriptor is FunctionDescriptor && candidateDescriptor.isOperator &&
- candidateDescriptor.name == OperatorNameConventions.INVOKE)
- return candidates
-
- // If control flow gets here, we are intercepting this call.
-
- val original = when (resolvedKtxElementCall.emitOrCall) {
- is MemoizedCallNode -> {
- resolvedKtxElementCall.emitOrCall.call.resolvedCalls().first().candidateDescriptor
- as? SimpleFunctionDescriptor
+ // If there are no constructors, then all of the candidates are either composables or
+ // non-composable functions, and we follow normal resolution rules.
+ if (constructors.isEmpty()) {
+ // we wrap the composable descriptors into a ComposableFunctionDescriptor so we know
+ // to intercept it in the backend.
+ return nonComposablesNonConstructors + composables.map {
+ ComposableFunctionDescriptor(it, composerCall, composerMetadata)
}
- else -> null
}
- val descriptor =
- ComposableInvocationDescriptor(
- element,
- resolvedKtxElementCall,
- resolvedKtxElementCall.infixOrCall!!.candidateDescriptor.containingDeclaration,
- original,
- Annotations.EMPTY,
- name,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE
- )
+ val emittables = constructors.filter {
+ composerMetadata.isEmittable(it.returnType) && !it.returnType.isAbstract()
+ }
+ val hasEmittableCandidate = emittables.isNotEmpty()
- val valueArgs = mutableListOf<ValueParameterDescriptor>()
-
- resolvedKtxElementCall.usedAttributes.forEachIndexed { index, attributeInfo ->
- valueArgs.add(
- ValueParameterDescriptorImpl(
- descriptor, null, index,
- Annotations.EMPTY,
- Name.identifier(
- if (attributeInfo.name == CHILDREN_KEY)
- attributeInfo.descriptor.name.identifier
- else attributeInfo.name
- ),
- attributeInfo.type, false,
- false,
- false, null,
- SourceElement.NO_SOURCE
- )
- )
+ // if none of the constructors are emittables, then all of the candidates are valid
+ if (!hasEmittableCandidate) {
+ return nonComposablesNonConstructors + constructors + composables.map {
+ ComposableFunctionDescriptor(it, composerCall, composerMetadata)
+ }
}
- val unitLambdaType = scopeTower.module.builtIns.getFunction(
- 0
- ).defaultType.replace(
- listOf(scopeTower.module.builtIns.unitType.asTypeProjection())
- ).makeComposable(scopeTower.module)
- (resolvedKtxElementCall.emitOrCall as? EmitCallNode)?.inlineChildren?.let {
- valueArgs.add(
- ValueParameterDescriptorImpl(
- descriptor, null, valueArgs.size,
- Annotations.EMPTY,
- Name.identifier("\$CHILDREN"),
- unitLambdaType, false,
- false,
- false, null,
- SourceElement.NO_SOURCE
- )
- )
- }
+ // since some of the constructors are emittables, we fall back to resolving using the
+ // emit resolver.
+ val emitResolver = ComposeEmitResolver(
+ callResolver,
+ project,
+ composerMetadata
+ )
+ val context = ExpressionTypingContext.newContext(
+ resolutionContext.trace,
+ resolutionContext.scope,
+ resolutionContext.dataFlowInfo,
+ resolutionContext.expectedType,
+ resolutionContext.languageVersionSettings,
+ resolutionContext.dataFlowValueFactory
+ )
+
+ val temporaryForKtxCall = context.replaceTraceAndCache(temporaryTraceForKtxCall)
+
+ val emitCandidates = emitResolver.resolveCandidates(
+ call,
+ emittables,
+ composerCall,
+ name,
+ temporaryForKtxCall
+ )
+
+ // TODO(lmr): deal with the RESTART_CALLS_NEEDED stuff for restartGroups
// Once we know we have a valid binding to a composable function call see if the scope need
// the startRestartGroup and endRestartGroup information
- val trace = temporaryTraceForKtxCall.trace
- if (trace.get(
- ComposeWritableSlices.RESTART_CALLS_NEEDED,
- composableDescriptor!!
- ) != false) {
- val recordingContext = TemporaryTraceAndCache.create(
- context,
- "trace to resolve ktx call", element
- )
- val recordingTrace = recordingContext.trace
- recordingTrace.record(
- ComposeWritableSlices.RESTART_CALLS_NEEDED,
- composableDescriptor,
- false
- )
+// val trace = temporaryTraceForKtxCall.trace
+// if (trace.get(
+// ComposeWritableSlices.RESTART_CALLS_NEEDED,
+// composableDescriptor!!
+// ) != false) {
+// val recordingContext = TemporaryTraceAndCache.create(
+// context,
+// "trace to resolve ktx call", element
+// )
+// val recordingTrace = recordingContext.trace
+// recordingTrace.record(
+// ComposeWritableSlices.RESTART_CALLS_NEEDED,
+// composableDescriptor,
+// false
+// )
+//
+// val startRestartGroup = ktxCallResolver.resolveStartRestartGroup(
+// call,
+// temporaryForKtxCall
+// )
+// val endRestartGroup = ktxCallResolver.resolveEndRestartGroup(call, temporaryForKtxCall)
+// val composerCall = ktxCallResolver.resolveComposerCall()
+// if (startRestartGroup != null && endRestartGroup != null && composerCall != null) {
+// recordingTrace.record(
+// ComposeWritableSlices.RESTART_CALLS, composableDescriptor,
+// ResolvedRestartCalls(
+// startRestartGroup = startRestartGroup,
+// endRestartGroup = endRestartGroup,
+// composer = composerCall
+// )
+// )
+// }
+// recordingTrace.commit()
+// }
- val startRestartGroup = ktxCallResolver.resolveStartRestartGroup(
- call,
- temporaryForKtxCall
- )
- val endRestartGroup = ktxCallResolver.resolveEndRestartGroup(call, temporaryForKtxCall)
- val composerCall = ktxCallResolver.resolveComposerCall()
- if (startRestartGroup != null && endRestartGroup != null && composerCall != null) {
- recordingTrace.record(
- ComposeWritableSlices.RESTART_CALLS, composableDescriptor,
- ResolvedRestartCalls(
- startRestartGroup = startRestartGroup,
- endRestartGroup = endRestartGroup,
- composer = composerCall
- )
- )
- }
- recordingTrace.commit()
- }
-
- descriptor.initialize(
- null,
- null,
- mutableListOf(),
- valueArgs,
- scopeTower.module.builtIns.unitType,
- Modality.FINAL,
- Visibilities.DEFAULT_VISIBILITY
- )
-
- return listOf(descriptor)
+ return nonComposablesNonConstructors +
+ composables.map {
+ ComposableFunctionDescriptor(it, composerCall, composerMetadata)
+ } +
+ constructors.filter { !composerMetadata.isEmittable(it.returnType) } +
+ emitCandidates
}
- class ComposableInvocationDescriptor(
- val element: KtExpression,
- val ktxCall: ResolvedKtxElementCall,
- containingDeclaration: DeclarationDescriptor,
- original: SimpleFunctionDescriptor?,
- annotations: Annotations,
+ private fun CallResolver.resolveVar(
name: Name,
- kind: CallableMemberDescriptor.Kind,
- source: SourceElement
- ) : SimpleFunctionDescriptorImpl(
- containingDeclaration,
- original,
- annotations,
- name,
- kind,
- source)
+ context: BasicCallResolutionContext
+ ): OverloadResolutionResults<CallableDescriptor> {
+ val temporaryForVariable = TemporaryTraceAndCache.create(
+ context,
+ "trace to resolve variable",
+ context.call.callElement as KtExpression
+ )
+ val call = makeCall(context.call.callElement)
+ val contextForVariable = BasicCallResolutionContext.create(
+ context.replaceTraceAndCache(temporaryForVariable),
+ call,
+ CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
+ DataFlowInfoForArgumentsImpl(context.dataFlowInfo, call)
+
+ )
+ contextForVariable.trace.record(
+ ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION,
+ call,
+ true
+ )
+ return computeTasksAndResolveCall<CallableDescriptor>(
+ contextForVariable,
+ name,
+ TracingStrategy.EMPTY,
+ NewResolutionOldInference.ResolutionKind.Variable
+ )
+ }
+
+ private fun CallResolver.resolveComposer(context: BasicCallResolutionContext):
+ ResolvedCall<CallableDescriptor>? {
+
+ // The composer is currently resolved as whatever is currently in scope with the name "composer".
+ val resolvedComposer = resolveVar(KtxNameConventions.COMPOSER, context)
+
+ if (!resolvedComposer.isSuccess) {
+ return null
+ }
+
+ return resolvedComposer.resultingCall
+ }
+
+ private fun KotlinType.isAbstract(): Boolean {
+ val modality = (constructor.declarationDescriptor as? ClassDescriptor)?.modality
+ return modality == Modality.ABSTRACT || modality == Modality.SEALED
+ }
}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolver.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolver.kt
deleted file mode 100644
index d745bbb..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolver.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION
-import com.intellij.openapi.project.Project
-import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.VariableDescriptor
-import org.jetbrains.kotlin.extensions.CallResolutionInterceptorExtension
-import org.jetbrains.kotlin.incremental.components.LookupLocation
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtPsiFactory
-import org.jetbrains.kotlin.resolve.calls.CallResolver
-import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
-import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache
-import org.jetbrains.kotlin.resolve.calls.tower.ImplicitScopeTower
-import org.jetbrains.kotlin.resolve.scopes.LexicalScope
-import org.jetbrains.kotlin.resolve.scopes.ResolutionScope
-import org.jetbrains.kotlin.resolve.scopes.utils.collectVariables
-import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
-
-class ComposeCallResolver(
- private val callResolver: CallResolver,
- private val project: Project,
- private val psiFactory: KtPsiFactory
-) {
-
- @Suppress("UNUSED_PARAMETER")
- fun interceptCandidates(
- candidates: Collection<FunctionDescriptor>,
- scopeTower: ImplicitScopeTower,
- resolutionContext: BasicCallResolutionContext,
- resolutionScope: ResolutionScope,
- name: Name,
- location: LookupLocation
- ): Collection<FunctionDescriptor> {
- if (candidates.isEmpty()) return candidates
- val bindingContext = resolutionContext.trace.bindingContext
- val call = resolutionContext.call
- val shouldIgnore = bindingContext[IGNORE_COMPOSABLE_INTERCEPTION, call] ?: false
-
- if (shouldIgnore) return candidates
-
- val composables = mutableListOf<FunctionDescriptor>()
- val nonComposables = mutableListOf<FunctionDescriptor>()
- val constructors = mutableListOf<ConstructorDescriptor>()
-
- var needToLookupComposer = false
-
- for (candidate in candidates) {
- if (candidate.hasComposableAnnotation()) {
- needToLookupComposer = true
- composables.add(candidate)
- } else {
- nonComposables.add(candidate)
- }
- if (candidate is ConstructorDescriptor) {
- needToLookupComposer = true
- constructors.add(candidate)
- }
- }
-
- // If none of the candidates are composable or constructors, then it's unnecessary for us
- // to do any work at all, since it will never be anything we intercept
- if (!needToLookupComposer) return candidates
-
- // use the scope tower to find any variable that would resolve with "composer" in scope.
- val composer = scopeTower
- .lexicalScope
- .collectVariables(KtxNameConventions.COMPOSER, location)
- .firstOrNull()
-
- // If there is no composer in scope, then we cannot intercept. This means that we need to
- // remove any @Composable from the candidates
- if (composer == null) {
- return nonComposables
- }
-
- // TODO(lmr): figure out if we need to do something here
- // We might decide there are some composers that are not "valid", ie, I shouldn't be able
- // to call a composable if I just have `val composer = Unit` in scope... but with this
- // logic, you'd be able to. This will get refined as we pass a composer as a variable.
- val isValidComposer = true
-
- // If there are no constructors, then all of the candidates are either composables or
- // non-composable functions, and we follow normal resolution rules.
- if (isValidComposer && constructors.isEmpty()) {
- // we wrap the composable descriptors into a ComposableFunctionDescriptor so we know
- // to intercept it in the backend.
- return nonComposables + composables.map { ComposableFunctionDescriptor(it) }
- }
-
- // If we made it this far, we need to check and see if the constructors qualify as emit
- // calls instead of constructor calls. First, we need to look at the composer to see
- // what kinds of "emittables" it accepts.
- // We cache the metadata into a writeable slice based on the descriptor
- val emitMetadata = ComposerEmitMetadata.getOrBuild(
- composer,
- callResolver,
- psiFactory,
- resolutionContext
- )
-
- val hasEmittableCandidate = constructors.any { emitMetadata.isEmittable(it.returnType) }
-
- // if none of the constructors are emittables, then all of the candidates are valid
- if (!hasEmittableCandidate) {
- return nonComposables + composables.map { ComposableFunctionDescriptor(it) }
- }
-
- // since some of the constructors are emittables, we fall back to resolving using the
- // ktx call resolver. This needs to be refactored to be simpler, but this should work as
- // a starting point.
- //
- // TODO(lmr): refactor this to remove KtxCallResolver
- val ktxCallResolver = KtxCallResolver(
- callResolver,
- null,
- project,
- ComposableAnnotationChecker.get(project)
- )
-
- val context = ExpressionTypingContext.newContext(
- resolutionContext.trace,
- resolutionContext.scope,
- resolutionContext.dataFlowInfo,
- resolutionContext.expectedType,
- resolutionContext.languageVersionSettings,
- resolutionContext.dataFlowValueFactory
- )
-
- val element = call.callElement as KtExpression
-
- val temporaryTraceForKtxCall =
- TemporaryTraceAndCache.create(
- context,
- "trace to resolve ktx call", element
- )
-
- val temporaryForKtxCall = context.replaceTraceAndCache(temporaryTraceForKtxCall)
-
- ktxCallResolver.initializeFromCall(call, temporaryForKtxCall)
-
- val resolvedKtxElementCall = ktxCallResolver.resolveFromCall(
- call,
- temporaryForKtxCall
- )
-
- val result = ComposableEmitDescriptor.fromKtxCall(
- resolvedKtxElementCall,
- scopeTower,
- name
- )
-
- if (result == null) {
- return nonComposables +
- composables.map { ComposableFunctionDescriptor(it) } +
- constructors.filter { !emitMetadata.isEmittable(it.returnType) }
- }
-
- // TODO(lmr): deal with this RESTART_CALLS_NEEDED stuff
- // Once we know we have a valid binding to a composable function call see if the scope need
- // the startRestartGroup and endRestartGroup information
-
- return listOf(result)
- }
-}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeEmitResolver.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeEmitResolver.kt
new file mode 100644
index 0000000..8739253
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeEmitResolver.kt
@@ -0,0 +1,1584 @@
+/*
+ * Copyright 2019 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.plugins.kotlin
+
+import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
+import com.intellij.lang.ASTNode
+import com.intellij.openapi.project.Project
+import com.intellij.psi.impl.source.tree.LeafPsiElement
+import com.intellij.util.SmartList
+import org.jetbrains.kotlin.builtins.DefaultBuiltIns
+import org.jetbrains.kotlin.builtins.createFunctionType
+import org.jetbrains.kotlin.builtins.extractParameterNameFromFunctionTypeArgument
+import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
+import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
+import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
+import org.jetbrains.kotlin.builtins.isExtensionFunctionType
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.ClassKind
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
+import org.jetbrains.kotlin.descriptors.PropertyDescriptor
+import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
+import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
+import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
+import org.jetbrains.kotlin.descriptors.VariableDescriptor
+import org.jetbrains.kotlin.descriptors.Visibilities
+import org.jetbrains.kotlin.descriptors.annotations.Annotations
+import org.jetbrains.kotlin.diagnostics.Severity
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.Call
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtLambdaArgument
+import org.jetbrains.kotlin.psi.KtPsiFactory
+import org.jetbrains.kotlin.psi.KtQualifiedExpression
+import org.jetbrains.kotlin.psi.KtReferenceExpression
+import org.jetbrains.kotlin.psi.KtSimpleNameExpression
+import org.jetbrains.kotlin.psi.ValueArgument
+import org.jetbrains.kotlin.psi.ValueArgumentName
+import org.jetbrains.kotlin.builtins.isFunctionType
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.descriptors.Modality
+import org.jetbrains.kotlin.descriptors.SourceElement
+import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
+import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
+import org.jetbrains.kotlin.psi.KtTypeArgumentList
+import org.jetbrains.kotlin.psi.KtTypeProjection
+import org.jetbrains.kotlin.psi.KtValueArgumentList
+import org.jetbrains.kotlin.psi.LambdaArgument
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.BindingTrace
+import org.jetbrains.kotlin.resolve.DescriptorFactory
+import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver
+import org.jetbrains.kotlin.resolve.calls.CallResolver
+import org.jetbrains.kotlin.resolve.calls.checkers.UnderscoreUsageChecker
+import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
+import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
+import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
+import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
+import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache
+import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
+import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus
+import org.jetbrains.kotlin.resolve.calls.model.ArgumentUnmapped
+import org.jetbrains.kotlin.resolve.calls.model.DataFlowInfoForArgumentsImpl
+import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
+import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
+import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
+import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil
+import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
+import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate
+import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
+import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategyImpl
+import org.jetbrains.kotlin.resolve.calls.tower.NewResolutionOldInference
+import org.jetbrains.kotlin.resolve.calls.util.CallMaker
+import org.jetbrains.kotlin.resolve.descriptorUtil.module
+import org.jetbrains.kotlin.resolve.scopes.HierarchicalScope
+import org.jetbrains.kotlin.resolve.scopes.MemberScope
+import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier
+import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
+import org.jetbrains.kotlin.resolve.scopes.receivers.PackageQualifier
+import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
+import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
+import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
+import org.jetbrains.kotlin.resolve.scopes.receivers.TypeAliasQualifier
+import org.jetbrains.kotlin.resolve.scopes.receivers.TypeParameterQualifier
+import org.jetbrains.kotlin.resolve.scopes.receivers.expression
+import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
+import org.jetbrains.kotlin.resolve.scopes.utils.findFirstFromMeAndParent
+import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
+import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
+import org.jetbrains.kotlin.synthetic.isVisibleOutside
+import org.jetbrains.kotlin.types.ErrorUtils
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.TypeConstructor
+import org.jetbrains.kotlin.types.TypeSubstitutor
+import org.jetbrains.kotlin.types.TypeUtils
+import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
+import org.jetbrains.kotlin.types.checker.KotlinTypeCheckerImpl
+import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
+import org.jetbrains.kotlin.types.expressions.KotlinTypeInfo
+import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
+import org.jetbrains.kotlin.types.typeUtil.equalTypesOrNulls
+import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
+import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
+import org.jetbrains.kotlin.types.typeUtil.isUnit
+import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
+import java.util.Locale
+
+/**
+ * This class is used to resolve a KTX Element to the corresponding set of calls on the composer, and the individual calls for
+ * each attribute, etc.
+ */
+class ComposeEmitResolver(
+ callResolver: CallResolver,
+ project: Project,
+ private val composer: ComposerMetadata
+) {
+
+ private val callResolver = NoInterceptionCallResolver(callResolver)
+
+ private class TempParameterInfo(
+ val attribute: AttributeInfo,
+ val descriptor: DeclarationDescriptor
+ )
+
+ // NOTE(lmr): I am unsure of the performance consequences of using KtPsiFactory. it appears to create a file for every
+ // call, which seems like something we definitely do not want, but it's also used in resolving for(..) loops, so
+ // maybe it's not terrible.
+ private val psiFactory = KtPsiFactory(project, markGenerated = false)
+
+ private fun KotlinType.isEmittable() = composer.isEmittable(this)
+
+ private fun KotlinType.isCompoundEmittable() = composer.isCompoundEmittable(this)
+
+ private fun isImplicitConstructorParam(
+ param: ValueParameterDescriptor,
+ fn: CallableDescriptor
+ ) = composer.isImplicitConstructorParam(param, fn)
+
+ fun resolveCandidates(
+ call: Call,
+ candidates: Collection<FunctionDescriptor>,
+ getComposerCall: ResolvedCall<*>,
+ name: Name,
+ context: ExpressionTypingContext
+ ): List<ComposableEmitDescriptor> {
+ val openTagExpr = call.calleeExpression ?: error("Expected calleeExpression")
+
+ val tmpTraceAndCache = TemporaryTraceAndCache.create(
+ context,
+ "trace for ktx tag",
+ openTagExpr
+ )
+
+ val contextToUse = context.replaceTraceAndCache(tmpTraceAndCache)
+
+ val attrInfos = mutableMapOf<String, AttributeInfo>()
+
+ for (arg in call.valueArguments) {
+ if (arg is KtLambdaArgument) continue
+
+ // NOTE: emits *must* use all named parameters. If they don't, we will not resolve them.
+ val argName = arg.getArgumentName() ?: return emptyList()
+
+ val key = argName.referenceExpression
+ val value = arg.getArgumentExpression()
+ val identifier = argName.asName.asString()
+
+ // NOTE: We don't have to check for duplicate argument names, that will be done elsewhere.
+
+ // NOTE: We don't have to deal with punning. punning isn't supported in FCS.
+
+ attrInfos[identifier] = AttributeInfo(
+ value = value ?: error("expected a value expression"),
+ key = key,
+ name = identifier
+ )
+ }
+
+ for (arg in call.functionLiteralArguments) {
+ if (attrInfos.containsKey(CHILDREN_KEY))
+ error("Only one children argument supported at a time")
+ attrInfos[CHILDREN_KEY] =
+ AttributeInfo(
+ value = arg.getLambdaExpression() ?: error("expected a value expression"),
+ key = null,
+ name = CHILDREN_KEY
+ )
+ }
+
+ val receiver = resolveReceiver(openTagExpr, contextToUse)
+
+ attrInfos[TAG_KEY] =
+ AttributeInfo(
+ value = when (receiver) {
+ is ExpressionReceiver -> receiver.expression
+ else -> openTagExpr
+ },
+ key = null,
+ name = TAG_KEY
+ )
+
+ val result = resolveChild(
+ openTagExpr,
+ candidates,
+ getComposerCall,
+ name,
+ makeCall(
+ callElement = call.callElement,
+ calleeExpression = call.calleeExpression,
+ receiver = call.explicitReceiver,
+ dispatchReceiver = call.dispatchReceiver
+ ),
+ attrInfos,
+ context
+ )
+
+ tmpTraceAndCache.commit()
+ return result
+ }
+
+ private fun resolveChild(
+ expression: KtExpression,
+ candidates: Collection<FunctionDescriptor>,
+ getComposerCall: ResolvedCall<*>,
+ name: Name,
+ call: Call,
+ attributes: Map<String, AttributeInfo>,
+ context: ExpressionTypingContext
+ ): List<ComposableEmitDescriptor> {
+ return candidates
+ .mapNotNull { result ->
+ val tmpForCandidate = TemporaryTraceAndCache.create(
+ context, "trace to resolve ktx element", expression
+ )
+
+ val trace: BindingTrace = tmpForCandidate.trace
+
+ val candidateContext = context
+ .replaceTraceAndCache(tmpForCandidate)
+ .replaceBindingTrace(trace)
+
+ val attrsUsedInCall = mutableSetOf<String>()
+
+ val attrsUsedInSets = mutableSetOf<String>()
+
+ val subMissingRequiredAttributes = mutableListOf<DeclarationDescriptor>()
+
+ val usedAttributeInfos = mutableListOf<TempParameterInfo>()
+
+ val candidateResults = resolveCandidate(
+ result,
+ call,
+ attributes,
+ attrsUsedInCall,
+ usedAttributeInfos,
+ subMissingRequiredAttributes,
+ candidateContext
+ )
+
+ if (candidateResults.isNothing)
+ return@mapNotNull null
+
+ val resolvedCall = candidateResults.resultingCalls.first()
+
+ if (!candidateResults.isSuccess) {
+ when (candidateResults.resultCode) {
+ OverloadResolutionResults.Code.SINGLE_CANDIDATE_ARGUMENT_MISMATCH -> {
+ resolvedCall.call.valueArguments.map {
+ resolvedCall.getArgumentMapping(it)
+ }.forEach {
+ when (it) {
+ is ArgumentMatch -> {
+ when (it.status) {
+ ArgumentMatchStatus.TYPE_MISMATCH -> {
+ return@forEach
+ }
+ ArgumentMatchStatus.MATCH_MODULO_UNINFERRED_TYPES -> {
+ return@forEach
+ }
+ ArgumentMatchStatus.UNKNOWN -> {
+ // NOTE(lmr): This can happen with the implicit constructor params. ignore it
+ }
+ ArgumentMatchStatus.SUCCESS -> {
+ // do nothing
+ }
+ ArgumentMatchStatus.ARGUMENT_HAS_NO_TYPE -> {
+ // NOTE(lmr): This can happen when the attribute has no value expression, since
+ // we pass in a fake psi node for the value in that case.
+ }
+ }
+ }
+ is ArgumentUnmapped -> {
+ return@mapNotNull null
+ }
+ }
+ }
+ }
+ OverloadResolutionResults.Code.INCOMPLETE_TYPE_INFERENCE,
+ OverloadResolutionResults.Code.MANY_FAILED_CANDIDATES,
+ OverloadResolutionResults.Code.CANDIDATES_WITH_WRONG_RECEIVER -> {
+ return@mapNotNull null
+ }
+ else -> {
+ error("new kind of resolution problem. figure out why this happened...")
+ }
+ }
+ }
+
+ if (
+ !descriptorsEqualWithSubstitution(
+ resolvedCall.semanticCall.candidateDescriptor.original,
+ result.original
+ )
+ ) {
+ // if this happens, we've likely resolved the candidate to one of the other candidates, and we don't want that.
+ // we fail in this case, since it will succeed once we actually process the candidate that this resolved to.
+ return@mapNotNull null
+ }
+
+ val returnType = resolvedCall.resultingDescriptor.returnType ?: builtIns.unitType
+
+ if (!returnType.isEmittable()) return@mapNotNull null
+
+ var hasChildren = false
+
+ val emitCall = resolveComposerEmit(
+ constructedType = returnType,
+ hasBody = attributes.contains(CHILDREN_KEY),
+ implicitCtorTypes = resolvedCall.call.valueArguments.mapNotNull {
+ (it as? ImplicitCtorValueArgument)?.type
+ },
+ expressionToReportErrorsOn = expression,
+ context = candidateContext
+ ) ?: return@mapNotNull null
+
+ if (attributes.contains(CHILDREN_KEY) && returnType.isCompoundEmittable()) {
+ attrsUsedInSets.add(CHILDREN_KEY)
+ hasChildren = true
+ }
+
+ val updateReceiverScope = emitCall
+ .resultingDescriptor
+ .valueParameters
+ .first { it.name == KtxNameConventions.EMIT_UPDATER_PARAMETER }
+ .type
+ .getReceiverTypeFromFunctionType() ?: error("Expected receiver type")
+
+ val setterValidations = resolveAllSetAttributes(
+ expressionToReportErrorsOn = expression,
+ receiverScope = updateReceiverScope,
+ type = returnType,
+ attributes = attributes,
+ attributesUsedInCall = attrsUsedInCall,
+ consumedAttributes = attrsUsedInSets,
+ missingRequiredAttributes = subMissingRequiredAttributes,
+ context = candidateContext
+ )
+
+ val pivotals = resolvePivotalAttributes(
+ attributes,
+ attrsUsedInCall,
+ usedAttributeInfos,
+ setterValidations,
+ returnType
+ )
+
+ return@mapNotNull ComposableEmitDescriptor.build(
+ hasChildren = hasChildren,
+ ctorCall = resolvedCall,
+ validations = setterValidations,
+ pivotals = pivotals,
+ ctorParams = resolvedCall.buildParamsFromAttributes(attributes),
+ composerCall = getComposerCall,
+ emitCall = emitCall,
+ name = name
+ )
+ }
+ }
+
+ private fun resolvePivotalAttributes(
+ attributes: Map<String, AttributeInfo>,
+ attrsUsedInCall: Set<String>,
+ callParamInfos: List<TempParameterInfo>,
+ validations: List<ValidatedAssignment>,
+ returnType: KotlinType?
+ ): List<String> {
+ val result = mutableListOf<String>()
+
+ if (returnType == null || returnType.isUnit()) {
+ return callParamInfos
+ .filter { it.descriptor.hasPivotalAnnotation() }
+ .map {
+ it.attribute.name
+ }
+ }
+
+ val validationSet = validations.map { it.name }.toSet()
+
+ // if you were in the ctor call but not in the sets, you *have* to be pivotal
+ for (info in callParamInfos) {
+ if (validationSet.contains(info.attribute.name)) continue
+ attributes[info.attribute.name] ?: continue
+ result.add(info.attribute.name)
+ }
+
+ // There are additional cases where attributes can be pivotal:
+ // 1. It is annotated as @Pivotal
+ // 2. It is a `val` ctor parameter
+ for (assignment in validations) {
+ val name = assignment.name
+ val descriptor = assignment.descriptor
+
+ if (descriptor.hasPivotalAnnotation()) {
+ result.add(name)
+ continue
+ }
+ if (descriptor is PropertyDescriptor &&
+ attrsUsedInCall.contains(name) && !descriptor.isVar) {
+ result.add(name)
+ continue
+ }
+ }
+
+ return result
+ }
+
+ private fun ResolvedCall<*>.buildParamsFromAttributes(
+ attributes: Map<String, AttributeInfo>
+ ): List<String> {
+ val possbileChildrenParam = valueArguments.keys.possibleChildrenParameter
+ return valueArguments.mapNotNull { (param, value) ->
+ val name = param.name.asString()
+// var type = param.type
+ var attr = attributes[name]
+
+ if (param == possbileChildrenParam && attr == null) {
+ val childrenAttr = attributes[CHILDREN_KEY]
+ if (childrenAttr != null) {
+ attr = childrenAttr
+ }
+// if (param.isComposableFromChildrenAnnotation()) {
+// type = type.makeComposable(module)
+// }
+ }
+
+ if (value is DefaultValueArgument) {
+ return@mapNotNull null
+ }
+
+ if (attr == null && isImplicitConstructorParam(param, resultingDescriptor)) {
+ return@mapNotNull null
+// ImplicitCtorValueNode(
+// name = name,
+// descriptor = param,
+// type = type
+// )
+ }
+
+ if (attr == null) {
+ error("Couldn't find attribute but expected to. param=$param name=$name")
+ }
+
+ attr.name
+ }
+ }
+
+ private fun resolveAllSetAttributes(
+ expressionToReportErrorsOn: KtExpression,
+ receiverScope: KotlinType,
+ type: KotlinType?,
+ attributes: Map<String, AttributeInfo>,
+ attributesUsedInCall: Set<String>,
+ consumedAttributes: MutableSet<String>,
+ missingRequiredAttributes: MutableList<DeclarationDescriptor>,
+ context: ExpressionTypingContext
+ ): List<ValidatedAssignment> {
+ if (type == null) return emptyList()
+ val results = mutableListOf<ValidatedAssignment>()
+
+ val tempForAttributes = TemporaryTraceAndCache.create(
+ context, "temp for attributes", expressionToReportErrorsOn
+ )
+
+ val tempForValidations = TemporaryTraceAndCache.create(
+ context, "temp for validations", expressionToReportErrorsOn
+ )
+
+ for ((name, attribute) in attributes) {
+ if (name == TAG_KEY) continue
+ if (name == CHILDREN_KEY) {
+ continue
+ }
+ val keyExpr = attribute.key ?: error("key expected")
+
+ val expectedTypes = mutableListOf<KotlinType>()
+
+ var resolvedCall: ResolvedCall<*>? = null
+
+ // NOTE(lmr): A ktx element that has access (like it's a recursive call or a nested class) to the private property
+ // of the tag will be able to set it as an attribute... I'm not sure if that'a s good thing or not, but unless we
+ // do something extra, that is indeed possible. Perhaps it's something we should look into.
+
+ if (resolvedCall == null) {
+ resolvedCall = resolveAttributeAsSetter(
+ type,
+ attribute.name,
+ keyExpr,
+ attribute.value,
+ expectedTypes,
+ context.replaceTraceAndCache(tempForAttributes)
+ )
+ }
+
+ if (resolvedCall == null) {
+ resolvedCall = resolveAttributeAsProperty(
+ type,
+ keyExpr,
+ attribute.value,
+ expectedTypes,
+ context.replaceTraceAndCache(tempForAttributes)
+ )
+ }
+
+ if (resolvedCall != null) {
+
+ val validationType = when {
+ attributesUsedInCall.contains(name) -> ValidationType.UPDATE
+ else -> ValidationType.SET
+ }
+
+ val attrType = when (val descriptor = resolvedCall.resultingDescriptor) {
+ is FunctionDescriptor -> descriptor.valueParameters.firstOrNull()?.type
+ is PropertyDescriptor -> descriptor.type
+ else -> null
+ } ?: continue
+
+ val (validationCall, uncheckedValidationCall, lambdaDescriptor) =
+ resolveValidationCall(
+ expressionToReportErrorsOn = expressionToReportErrorsOn,
+ receiverScope = receiverScope,
+ assignmentReceiverScope = type,
+ validationType = validationType,
+ attrType = attrType,
+ context = context.replaceTraceAndCache(tempForValidations)
+ )
+
+ results.add(
+ ValidatedAssignment(
+ validationType = validationType,
+ assignment = resolvedCall,
+ assignmentLambda = lambdaDescriptor,
+ name = name,
+ validationCall = validationCall,
+ uncheckedValidationCall = uncheckedValidationCall,
+ descriptor = resolvedCall.resultingDescriptor,
+ type = attrType
+ )
+ )
+ consumedAttributes.add(name)
+ }
+ }
+
+ if (!type.isUnit()) {
+ val cls = type.constructor.declarationDescriptor as? ClassDescriptor
+ ?: error("unexpected classifier descriptor")
+ val requiredAttributes = cls.unsubstitutedMemberScope
+ .getContributedDescriptors()
+ .mapNotNull { it as? PropertyDescriptor }
+ // NOTE(lmr): I think we should consider not marking lateinit properties as required. It would maybe align
+ // ourselves more with the language semantic of `lateinit`
+ .filter {
+ it.isLateInit && it.visibility.isVisibleOutside() &&
+ !Visibilities.isPrivate(it.visibility)
+ }
+ .filter { !it.hasHiddenAttributeAnnotation() }
+
+ requiredAttributes
+ .filter { !consumedAttributes.contains(it.name.asString()) }
+ .ifNotEmpty { missingRequiredAttributes.addAll(this) }
+ }
+
+ tempForAttributes.commit()
+ tempForValidations.commit()
+ return results
+ }
+
+ private fun resolveAttributeAsSetter(
+ instanceType: KotlinType,
+ name: String,
+ keyExpr: KtReferenceExpression,
+ valueExpr: KtExpression,
+ expectedTypes: MutableCollection<KotlinType>,
+ context: ExpressionTypingContext
+ ): ResolvedCall<*>? {
+ val setterName = Name.identifier(
+ ComposeUtils.setterMethodFromPropertyName(
+ name
+ )
+ )
+ val receiver = TransientReceiver(instanceType)
+
+ val call = makeCall(
+ keyExpr,
+ calleeExpression = keyExpr,
+ valueArguments = listOf(CallMaker.makeValueArgument(valueExpr)),
+ receiver = receiver
+ )
+
+ val temporaryForFunction = TemporaryTraceAndCache.create(
+ context, "trace to resolve as function call", keyExpr
+ )
+
+ val results = callResolver.computeTasksAndResolveCall<FunctionDescriptor>(
+ BasicCallResolutionContext.create(
+ context.replaceTraceAndCache(temporaryForFunction),
+ call,
+ CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
+ DataFlowInfoForArgumentsImpl(context.dataFlowInfo, call)
+ ),
+ setterName,
+ keyExpr,
+ NewResolutionOldInference.ResolutionKind.Function
+ )
+
+ if (results.isNothing) {
+ return null
+ }
+
+ if (results.isAmbiguity || temporaryForFunction.trace.hasTypeMismatchErrorsOn(valueExpr)) {
+ expectedTypes.addAll(
+ results.resultingCalls.mapNotNull {
+ it.resultingDescriptor.valueParameters.firstOrNull()
+ }.map { it.type }
+ )
+ return null
+ }
+
+ val resolvedCall = OverloadResolutionResultsUtil.getResultingCall(results, context)
+ ?: return null
+
+ temporaryForFunction.commit()
+
+ return resolvedCall
+ }
+
+ private fun resolveAttributeAsProperty(
+ instanceType: KotlinType,
+ keyExpr: KtSimpleNameExpression,
+ valueExpr: KtExpression,
+ expectedTypes: MutableCollection<KotlinType>,
+ context: ExpressionTypingContext
+ ): ResolvedCall<*>? {
+
+ // NOTE(lmr): I'm not sure what the consequences are of using the tagExpr as the receiver...
+ val receiver = TransientReceiver(instanceType)
+
+ val temporaryForVariable = TemporaryTraceAndCache.create(
+ context, "trace to resolve as local variable or property", keyExpr
+ )
+
+ val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
+
+ val call = CallMaker.makePropertyCall(receiver, null, keyExpr)
+
+ val contextForVariable = BasicCallResolutionContext.create(
+ contextToUse,
+ call,
+ CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS
+ )
+
+ val results = callResolver.resolveSimpleProperty(contextForVariable)
+
+ if (results.isNothing) {
+ return null
+ }
+
+ val resolvedCall = OverloadResolutionResultsUtil.getResultingCall(results, contextToUse)
+ ?: return null
+
+ val descriptor = resolvedCall.resultingDescriptor as PropertyDescriptor
+ val expectedType = descriptor.type
+
+ if (temporaryForVariable.trace.hasTypeMismatchErrorsOn(valueExpr)) {
+ expectedTypes.add(expectedType)
+ return null
+ }
+
+ if (descriptor.setter == null) {
+ // NOTE(lmr): we probably shouldn't do this. if this ends up being a cross-module compile, then the
+ // setter will be gone. I'm not sure the right way to figure out whether or not a property is
+ // settable or not...
+ return null
+ }
+
+ temporaryForVariable.commit()
+
+ return resolvedCall
+ }
+
+ private fun resolveCandidate(
+ candidate: FunctionDescriptor,
+ original: Call,
+ attributes: Map<String, AttributeInfo>,
+ usedAttributes: MutableSet<String>,
+ usedAttributeInfos: MutableList<TempParameterInfo>,
+ missingRequiredAttributes: MutableList<DeclarationDescriptor>,
+ context: ExpressionTypingContext
+ ): OverloadResolutionResults<FunctionDescriptor> {
+ val valueArguments = mutableListOf<ValueArgument>()
+
+ val dispatchReceiver = original.dispatchReceiver
+
+ val stableParamNames = candidate.hasStableParameterNames()
+
+ val possibleChildrenParameter =
+ candidate.valueParameters.possibleChildrenParameter
+
+ for (param in candidate.valueParameters) {
+ val name = param.name.asString()
+ val attr = attributes[name]
+ var arg: ValueArgument? = null
+
+ if (arg == null && param == possibleChildrenParameter) {
+ val childrenAttr = attributes[CHILDREN_KEY]
+ if (childrenAttr != null) {
+ usedAttributes.add(CHILDREN_KEY)
+
+ usedAttributeInfos.add(
+ TempParameterInfo(
+ attribute = childrenAttr,
+ descriptor = param
+ )
+ )
+
+ arg = childrenAttr.toValueArgument(name, stableParamNames)
+ }
+ }
+
+ if (arg == null && attr != null) {
+ usedAttributes.add(name)
+ usedAttributeInfos.add(
+ TempParameterInfo(
+ attribute = attr,
+ descriptor = param
+ )
+ )
+ context.trace.record(BindingContext.REFERENCE_TARGET, attr.key, param)
+ arg = attr.toValueArgument(attr.name, stableParamNames)
+ }
+
+ if (arg == null && isImplicitConstructorParam(param, candidate)) {
+ arg = ImplicitCtorValueArgument(param.type)
+ }
+
+ if (arg != null) {
+ valueArguments.add(arg)
+ } else if (!param.declaresDefaultValue()) {
+ // missing required parameter!
+ if (
+ dispatchReceiver?.type?.isExtensionFunctionType != true ||
+ param != candidate.valueParameters[0]
+ ) {
+ // if this isn't the case, the missing parameter is an extension parameter...
+ // and it will be provided implicitly. We don't want to diagnose this as a
+ // missing attribute. If it is missing, the call resolver will add a
+ // diagnostic anyway.
+ missingRequiredAttributes.add(param)
+ }
+ }
+ }
+
+ val call = makeCall(
+ original.callElement,
+ valueArguments = valueArguments,
+ calleeExpression = original.calleeExpression,
+ receiver = original.explicitReceiver,
+ dispatchReceiver = dispatchReceiver
+ )
+
+ val contextForVariable = BasicCallResolutionContext.create(
+ context,
+ call,
+ CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
+ DataFlowInfoForArgumentsImpl(context.dataFlowInfo, call)
+ )
+
+ return callResolver.resolveFunctionCall(contextForVariable)
+ }
+
+ private fun resolveReceiver(
+ expression: KtExpression,
+ context: ExpressionTypingContext
+ ): Receiver? {
+ if (expression !is KtQualifiedExpression) return null
+ val currentContext = context
+ .replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE)
+ .replaceContextDependency(ContextDependency.INDEPENDENT)
+
+ expression.elementChain(currentContext)
+
+ val receiverExpr = expression.receiverExpression
+
+ return context.trace.get(BindingContext.QUALIFIER, receiverExpr)
+ ?: ExpressionReceiver.create(
+ receiverExpr,
+ ErrorUtils.createErrorType("Type for " + receiverExpr.text),
+ context.trace.bindingContext
+ )
+ }
+
+ private fun makeValueArgument(
+ type: KotlinType,
+ context: ExpressionTypingContext,
+ forceType: Boolean = false
+ ): ValueArgument {
+ val fakeExpr = psiFactory.createSimpleName("tmpVar")
+
+ context.trace.record(
+ BindingContext.EXPRESSION_TYPE_INFO, fakeExpr, KotlinTypeInfo(
+ type = type,
+ dataFlowInfo = DataFlowInfo.EMPTY,
+ jumpOutPossible = false,
+ jumpFlowInfo = DataFlowInfo.EMPTY
+ )
+ )
+
+ if (forceType) {
+ context.trace.record(BindingContext.PROCESSED, fakeExpr, true)
+ }
+
+ return CallMaker.makeValueArgument(fakeExpr)
+ }
+
+ private fun resolveComposerEmit(
+ implicitCtorTypes: List<KotlinType>,
+ constructedType: KotlinType,
+ hasBody: Boolean,
+ expressionToReportErrorsOn: KtExpression,
+ context: ExpressionTypingContext
+ ): ResolvedCall<*>? {
+ return resolveSubstitutableComposerMethod(
+ KtxNameConventions.EMIT,
+ listOfNotNull(
+ builtIns.anyType,
+ functionType(
+ parameterTypes = implicitCtorTypes,
+ returnType = constructedType
+ ),
+ functionType(),
+ if (hasBody) functionType() else null
+ ),
+ constructedType,
+ expressionToReportErrorsOn,
+ context
+ )
+ }
+
+ private fun resolveSingleValidationCall(
+ expressionToReportErrorsOn: KtExpression,
+ receiverScope: KotlinType,
+ validationType: ValidationType,
+ checked: Boolean,
+ attrType: KotlinType,
+ lambdaArg: ValueArgument?,
+ context: ExpressionTypingContext
+ ): ResolvedCall<*>? {
+ val temporaryForVariable = TemporaryTraceAndCache.create(
+ context, "trace to resolve variable", expressionToReportErrorsOn
+ )
+ val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
+ val name = validationType.name.toLowerCase(Locale.ROOT).let {
+ if (!checked) (it + "Unchecked") else it
+ }
+ val calleeExpression = psiFactory.createSimpleName(name)
+ val call = makeCall(
+ callElement = calleeExpression,
+ calleeExpression = calleeExpression,
+ valueArguments = listOfNotNull(
+ makeValueArgument(attrType, contextToUse, forceType = true),
+ lambdaArg
+ ),
+ receiver = TransientReceiver(receiverScope)
+ )
+ val results = callResolver.resolveCallWithGivenName(
+ BasicCallResolutionContext.create(
+ contextToUse,
+ call,
+ CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
+ DataFlowInfoForArgumentsImpl(contextToUse.dataFlowInfo, call)
+ ),
+ call,
+ calleeExpression,
+ Name.identifier(name)
+ )
+
+ if (results.isSuccess) return results.resultingCall
+
+ if (results.resultCode == OverloadResolutionResults.Code.INCOMPLETE_TYPE_INFERENCE) {
+
+ // NOTE(lmr): We know the type of the attribute at this point, but it's possible for the validation call to require
+ // some help in order to do the type inference for the call. We are just guessing here that the type is going to be
+ // the attribute type, and not something more complicated. It is kind of a bummer that we need this and I wonder if
+ // there isn't a cleaner way to do this.
+
+ for (candidate in results.resultingCalls) {
+
+ val typeParam = candidate.typeArguments.keys.singleOrNull() ?: continue
+
+ if (!attrType.satisfiesConstraintsOf(typeParam)) continue
+
+ val nextTempTrace = TemporaryTraceAndCache.create(
+ context, "trace to resolve variable", expressionToReportErrorsOn
+ )
+
+ val nextContext = context
+ .replaceTraceAndCache(nextTempTrace)
+ .replaceCollectAllCandidates(false)
+
+ val substitutor = TypeSubstitutor.create(
+ mapOf(
+ typeParam.typeConstructor to attrType.asTypeProjection()
+ )
+ )
+
+ val nextCall = makeCall(
+ callElement = expressionToReportErrorsOn,
+ calleeExpression = calleeExpression,
+ receiver = TransientReceiver(composer.type),
+ valueArguments = candidate.candidateDescriptor.valueParameters.map {
+ makeValueArgument(it.type, nextContext)
+ }
+ )
+
+ val nextResults = callResolver.resolveCallWithKnownCandidate(
+ nextCall,
+ TracingStrategyImpl.create(calleeExpression, nextCall),
+ BasicCallResolutionContext.create(
+ nextContext,
+ nextCall,
+ CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
+ DataFlowInfoForArgumentsImpl(nextContext.dataFlowInfo, nextCall)
+ ),
+ ResolutionCandidate.create(
+ nextCall,
+ candidate.candidateDescriptor,
+ candidate.dispatchReceiver,
+ candidate.explicitReceiverKind,
+ substitutor
+ ),
+ DataFlowInfoForArgumentsImpl(nextContext.dataFlowInfo, nextCall)
+ )
+
+ if (nextResults.isSuccess) {
+ nextTempTrace.commit()
+ return nextResults.resultingCall
+ }
+ }
+ }
+
+ return null
+ }
+
+ private fun resolveValidationCall(
+ expressionToReportErrorsOn: KtExpression,
+ receiverScope: KotlinType,
+ assignmentReceiverScope: KotlinType?,
+ validationType: ValidationType,
+ attrType: KotlinType,
+ context: ExpressionTypingContext
+ ): Triple<ResolvedCall<*>?, ResolvedCall<*>?, FunctionDescriptor?> {
+ val temporaryForVariable = TemporaryTraceAndCache.create(
+ context, "trace to resolve variable", expressionToReportErrorsOn
+ )
+ val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
+
+ val includeLambda = validationType != ValidationType.CHANGED
+
+ // for call:
+ // ValidatorType.set(AttrType, (AttrType) -> Unit): Boolean
+ // ValidatorType.update(AttrType, (AttrType) -> Unit): Boolean
+ // ValidatorType.changed(AttrType): Boolean
+
+ // for emit:
+ // ValidatorType.set(AttrType, ElementType.(AttrType) -> Unit): Unit
+ // ValidatorType.update(AttrType, ElementType.(AttrType) -> Unit): Unit
+ // ValidatorType.changed(AttrType): Unit
+
+ val lambdaType = when {
+ includeLambda -> functionType(
+ parameterTypes = listOf(attrType),
+ receiverType = assignmentReceiverScope
+ )
+ else -> null
+ }
+ val lambdaArg = lambdaType?.let { makeValueArgument(it, contextToUse) }
+ val lambdaDescriptor = lambdaType?.let {
+ createFunctionDescriptor(
+ it,
+ contextToUse
+ )
+ }
+
+ val validationCall = resolveSingleValidationCall(
+ expressionToReportErrorsOn = expressionToReportErrorsOn,
+ receiverScope = receiverScope,
+ validationType = validationType,
+ checked = true,
+ attrType = attrType,
+ lambdaArg = lambdaArg,
+ context = context
+ )
+
+ val uncheckedValidationCall = resolveSingleValidationCall(
+ expressionToReportErrorsOn = expressionToReportErrorsOn,
+ receiverScope = receiverScope,
+ validationType = validationType,
+ checked = false,
+ attrType = attrType,
+ lambdaArg = lambdaArg,
+ context = context
+ )
+
+ return Triple(validationCall, uncheckedValidationCall, lambdaDescriptor)
+ }
+
+ private fun resolveSubstitutableComposerMethod(
+ methodName: Name,
+ argumentTypes: List<KotlinType>,
+ typeToSubstitute: KotlinType?,
+ expressionToReportErrorsOn: KtExpression,
+ context: ExpressionTypingContext
+ ): ResolvedCall<*>? {
+ val temporaryForVariable = TemporaryTraceAndCache.create(
+ context, "trace to resolve variable", expressionToReportErrorsOn
+ )
+ val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
+
+ val composerExpr = psiFactory.createSimpleName(methodName.asString())
+
+ val call = makeCall(
+ callElement = expressionToReportErrorsOn,
+ calleeExpression = composerExpr,
+ receiver = TransientReceiver(composer.type),
+ valueArguments = argumentTypes.map { makeValueArgument(it, contextToUse) }
+ )
+
+ val results = callResolver.resolveCallWithGivenName(
+ BasicCallResolutionContext.create(
+ contextToUse,
+ call,
+ CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
+ DataFlowInfoForArgumentsImpl(contextToUse.dataFlowInfo, call)
+ ),
+ call,
+ composerExpr,
+ methodName
+ )
+
+ if (results.isSuccess) return results.resultingCall
+
+ if (typeToSubstitute == null) return null
+
+ val candidates = if (context.collectAllCandidates)
+ results.allCandidates ?: emptyList()
+ else results.resultingCalls
+
+ for (candidate in candidates) {
+
+ val typeParam = candidate.candidateDescriptor.typeParameters.singleOrNull() ?: continue
+
+ if (!typeToSubstitute.satisfiesConstraintsOf(typeParam)) continue
+
+ val nextTempTrace = TemporaryTraceAndCache.create(
+ context, "trace to resolve variable", expressionToReportErrorsOn
+ )
+
+ val nextContext = context
+ .replaceTraceAndCache(nextTempTrace)
+ .replaceCollectAllCandidates(false)
+
+ val substitutor = TypeSubstitutor.create(
+ mapOf(
+ typeParam.typeConstructor to typeToSubstitute.asTypeProjection()
+ )
+ )
+
+ val nextCall = makeCall(
+ callElement = expressionToReportErrorsOn,
+ calleeExpression = composerExpr,
+ receiver = TransientReceiver(composer.type),
+ valueArguments = candidate.candidateDescriptor.valueParameters.map {
+ makeValueArgument(it.type, nextContext)
+ }
+ )
+
+ val nextResults = callResolver.resolveCallWithKnownCandidate(
+ nextCall,
+ TracingStrategyImpl.create(composerExpr, nextCall),
+ BasicCallResolutionContext.create(
+ nextContext,
+ nextCall,
+ CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
+ DataFlowInfoForArgumentsImpl(nextContext.dataFlowInfo, nextCall)
+ ),
+ ResolutionCandidate.create(
+ nextCall,
+ candidate.candidateDescriptor,
+ candidate.dispatchReceiver,
+ candidate.explicitReceiverKind,
+ substitutor
+ ),
+ DataFlowInfoForArgumentsImpl(nextContext.dataFlowInfo, nextCall)
+ )
+
+ if (nextResults.isSuccess) {
+ nextTempTrace.commit()
+ return nextResults.resultingCall
+ }
+ }
+
+ return if (context.collectAllCandidates) null
+ else resolveSubstitutableComposerMethod(
+ methodName,
+ argumentTypes,
+ typeToSubstitute,
+ expressionToReportErrorsOn,
+ context.replaceCollectAllCandidates(true)
+ )
+ }
+
+ private fun KtQualifiedExpression.elementChain(context: ExpressionTypingContext) {
+ val moduleDescriptor = context.scope.ownerDescriptor.module
+ val trace = context.trace
+ val scopeForFirstPart = context.scope
+
+ val path = asQualifierPartList()
+ val firstPart = path.first()
+ var currentDescriptor: DeclarationDescriptor? = scopeForFirstPart.findDescriptor(firstPart)
+ currentDescriptor =
+ currentDescriptor ?: moduleDescriptor.getPackage(FqName.topLevel(firstPart.name)).let {
+ if (it.isEmpty()) null else it
+ }
+
+ if (currentDescriptor == null) return
+ else storeSimpleNameExpression(firstPart.expression!!, currentDescriptor, trace)
+
+ // TODO(lmr): we need to add visibility checks into this function...
+ for (qualifierPartIndex in 1 until path.size) {
+ val qualifierPart = path[qualifierPartIndex]
+
+ val nextPackageOrClassDescriptor =
+ when (currentDescriptor) {
+ // TODO(lmr): i wonder if we could allow this for Ktx. Seems like a nice to have
+ // TODO type aliases as qualifiers? (would break some assumptions in TypeResolver)
+ is TypeAliasDescriptor -> null
+ is ClassDescriptor -> {
+ var next: DeclarationDescriptor? = null
+ next = next
+ ?: currentDescriptor.unsubstitutedInnerClassesScope.findDescriptor(
+ qualifierPart
+ )
+ if (currentDescriptor.kind == ClassKind.OBJECT) {
+ next = next
+ ?: currentDescriptor.unsubstitutedMemberScope.findDescriptor(
+ qualifierPart
+ )
+ }
+ val cod = currentDescriptor.companionObjectDescriptor
+ if (cod != null) {
+ next = next
+ ?: cod.unsubstitutedMemberScope.findDescriptor(qualifierPart)
+ }
+ next = next ?: currentDescriptor.staticScope.findDescriptor(qualifierPart)
+ next
+ }
+ is PackageViewDescriptor -> {
+ val packageView =
+ if (qualifierPart.typeArguments == null) {
+ moduleDescriptor.getPackage(
+ currentDescriptor.fqName.child(qualifierPart.name)
+ )
+ } else null
+ if (packageView != null && !packageView.isEmpty()) {
+ packageView
+ } else {
+ currentDescriptor.memberScope.findDescriptor(qualifierPart)
+ }
+ }
+ is VariableDescriptor -> {
+ currentDescriptor.type.memberScope.findDescriptor(qualifierPart)
+ }
+ else -> null
+ }
+
+ if (nextPackageOrClassDescriptor == null) return
+ else storeSimpleNameExpression(
+ qualifierPart.expression!!,
+ nextPackageOrClassDescriptor,
+ trace
+ )
+
+ currentDescriptor = nextPackageOrClassDescriptor
+ }
+ }
+
+ private fun storeSimpleNameExpression(
+ expression: KtSimpleNameExpression,
+ descriptor: DeclarationDescriptor,
+ trace: BindingTrace
+ ) {
+ trace.record(BindingContext.REFERENCE_TARGET, expression, descriptor)
+ UnderscoreUsageChecker.checkSimpleNameUsage(descriptor, expression, trace)
+
+ val qualifier = when (descriptor) {
+ is PackageViewDescriptor -> PackageQualifier(expression, descriptor)
+ is ClassDescriptor -> ClassQualifier(expression, descriptor)
+ is TypeParameterDescriptor -> TypeParameterQualifier(expression, descriptor)
+ is TypeAliasDescriptor -> descriptor.classDescriptor?.let {
+ TypeAliasQualifier(expression, descriptor, it)
+ }
+ else -> null
+ }
+
+ if (qualifier != null) {
+ trace.record(BindingContext.QUALIFIER, qualifier.expression, qualifier)
+ }
+ }
+}
+
+// general utils
+// ==============================
+
+private val builtIns = DefaultBuiltIns.Instance
+
+private fun functionType(
+ parameterTypes: List<KotlinType> = emptyList(),
+ annotations: Annotations = Annotations.EMPTY,
+ returnType: KotlinType = builtIns.unitType,
+ receiverType: KotlinType? = null
+): KotlinType = createFunctionType(
+ builtIns = builtIns,
+ annotations = annotations,
+ parameterNames = null,
+ parameterTypes = parameterTypes,
+ receiverType = receiverType,
+ returnType = returnType
+)
+
+private fun KotlinType.satisfiesConstraintsOf(T: TypeParameterDescriptor): Boolean {
+ return T.upperBounds.all { isSubtypeOf(it) }
+}
+
+// We want to return null in cases where types mismatch, so we use this heuristic to find out. I think there might be a more robust
+// way to find this out, but I'm not sure what it would be
+private fun BindingTrace.hasTypeMismatchErrorsOn(element: KtElement): Boolean =
+ bindingContext.diagnostics.forElement(element).any { it.severity == Severity.ERROR }
+
+private fun KtExpression.asQualifierPartList(): List<QualifiedExpressionResolver.QualifierPart> {
+ val result = SmartList<QualifiedExpressionResolver.QualifierPart>()
+
+ fun addQualifierPart(expression: KtExpression?): Boolean {
+ if (expression is KtSimpleNameExpression) {
+ result.add(
+ QualifiedExpressionResolver.ExpressionQualifierPart(
+ expression.getReferencedNameAsName(),
+ expression
+ )
+ )
+ return true
+ }
+ return false
+ }
+
+ var expression: KtExpression? = this
+ while (true) {
+ if (addQualifierPart(expression)) break
+ if (expression !is KtQualifiedExpression) break
+
+ addQualifierPart(expression.selectorExpression)
+
+ expression = expression.receiverExpression
+ }
+
+ return result.asReversed()
+}
+
+private fun HierarchicalScope.findDescriptor(
+ part: QualifiedExpressionResolver.QualifierPart
+): DeclarationDescriptor? {
+ return findFirstFromMeAndParent {
+ it.findVariable(part.name, part.location)
+ ?: it.findFunction(part.name, part.location)
+ ?: it.findClassifier(part.name, part.location)
+ }
+}
+
+private fun MemberScope.findDescriptor(
+ part: QualifiedExpressionResolver.QualifierPart
+): DeclarationDescriptor? {
+ return this.getContributedClassifier(part.name, part.location)
+ ?: getContributedFunctions(part.name, part.location).singleOrNull()
+ ?: getContributedVariables(part.name, part.location).singleOrNull()
+}
+
+private fun AttributeInfo.toValueArgument(name: String, named: Boolean): ValueArgument {
+ val argumentName = if (named) object : ValueArgumentName {
+ override val asName: Name
+ get() = Name.identifier(name)
+ override val referenceExpression: KtSimpleNameExpression?
+ get() = key
+ } else null
+ return object : ValueArgument {
+ override fun getArgumentExpression() = value
+ override fun getArgumentName() = argumentName
+ override fun isNamed() = named
+ override fun asElement(): KtElement = value
+ override fun getSpreadElement(): LeafPsiElement? = null
+ override fun isExternal() = true
+ }
+}
+
+/**
+ * This function was copied verbatim from descriptorUtils.kt from ide-common. For some reason, importing this method into our module
+ * would not work and would cause a NoClassDefFound exception.
+ */
+private fun descriptorsEqualWithSubstitution(
+ descriptor1: DeclarationDescriptor?,
+ descriptor2: DeclarationDescriptor?
+): Boolean {
+ if (descriptor1 == descriptor2) return true
+ if (descriptor1 == null || descriptor2 == null) return false
+ if (descriptor1 !is CallableDescriptor) return true
+ descriptor2 as CallableDescriptor
+
+ val typeChecker = KotlinTypeCheckerImpl.withAxioms(
+ object : KotlinTypeChecker.TypeConstructorEquality {
+ override fun equals(a: TypeConstructor, b: TypeConstructor): Boolean {
+ val typeParam1 = a.declarationDescriptor as? TypeParameterDescriptor
+ val typeParam2 = b.declarationDescriptor as? TypeParameterDescriptor
+ if (typeParam1 != null && typeParam2 != null &&
+ typeParam1.containingDeclaration == descriptor1 &&
+ typeParam2.containingDeclaration == descriptor2
+ ) {
+ return typeParam1.index == typeParam2.index
+ }
+
+ return a == b
+ }
+ }
+ )
+
+ if (!typeChecker.equalTypesOrNulls(descriptor1.returnType, descriptor2.returnType)) return false
+
+ val parameters1 = descriptor1.valueParameters
+ val parameters2 = descriptor2.valueParameters
+ if (parameters1.size != parameters2.size) return false
+ for ((param1, param2) in parameters1.zip(parameters2)) {
+ if (!typeChecker.equalTypes(param1.type, param2.type)) return false
+ }
+ // NOTE(lmr): edit
+ // this check was added
+ if (descriptor1.javaClass !== descriptor2.javaClass) return false
+ // NOTE(lmr): /end
+ return true
+}
+
+private val ResolvedCall<*>.semanticCall: ResolvedCall<*>
+ get() = when (this) {
+ is VariableAsFunctionResolvedCall -> variableCall
+ else -> this
+ }
+
+private val Collection<ValueParameterDescriptor>.possibleChildrenParameter:
+ ValueParameterDescriptor?
+ get() = maxBy { it.index }?.let { if (it.type.isFunctionType) it else null }
+
+// move these to naming conventions???
+const val CHILDREN_KEY = "<children>"
+const val TAG_KEY = "<tag>"
+
+fun makeCall(
+ callElement: KtElement,
+ calleeExpression: KtExpression? = null,
+ valueArguments: List<ValueArgument> = emptyList(),
+ receiver: Receiver? = null,
+ dispatchReceiver: ReceiverValue? = null
+): Call {
+ return object : Call {
+ override fun getDispatchReceiver(): ReceiverValue? = dispatchReceiver
+ override fun getValueArgumentList(): KtValueArgumentList? = null
+ override fun getTypeArgumentList(): KtTypeArgumentList? = null
+ override fun getExplicitReceiver(): Receiver? = receiver
+ override fun getCalleeExpression(): KtExpression? = calleeExpression
+ override fun getValueArguments(): List<ValueArgument> = valueArguments
+ override fun getCallElement(): KtElement = callElement
+ override fun getFunctionLiteralArguments(): List<LambdaArgument> = emptyList()
+ override fun getTypeArguments(): List<KtTypeProjection> = emptyList()
+ override fun getCallType(): Call.CallType = Call.CallType.DEFAULT
+ override fun getCallOperationNode(): ASTNode? = null
+ }
+}
+
+fun createFunctionDescriptor(
+ type: KotlinType,
+ context: ExpressionTypingContext
+): FunctionDescriptor {
+ return AnonymousFunctionDescriptor(
+ context.scope.ownerDescriptor,
+ Annotations.EMPTY,
+ CallableMemberDescriptor.Kind.SYNTHESIZED,
+ SourceElement.NO_SOURCE,
+ false
+ ).apply {
+ initialize(
+ type.getReceiverTypeFromFunctionType()?.let {
+ DescriptorFactory.createExtensionReceiverParameterForCallable(
+ this,
+ it,
+ Annotations.EMPTY)
+ },
+ null,
+ emptyList(),
+ type.getValueParameterTypesFromFunctionType().mapIndexed { i, t ->
+ ValueParameterDescriptorImpl(
+ containingDeclaration = this,
+ original = null,
+ index = i,
+ annotations = Annotations.EMPTY,
+ name = t.type.extractParameterNameFromFunctionTypeArgument()
+ ?: Name.identifier("p$i"),
+ outType = t.type,
+ declaresDefaultValue = false,
+ isCrossinline = false,
+ isNoinline = false,
+ varargElementType = null,
+ source = SourceElement.NO_SOURCE
+ )
+ },
+ type.getReturnTypeFromFunctionType(),
+ Modality.FINAL,
+ Visibilities.LOCAL,
+ null
+ )
+ isOperator = false
+ isInfix = false
+ isExternal = false
+ isInline = false
+ isTailrec = false
+ isSuspend = false
+ isExpect = false
+ isActual = false
+ }
+}
+
+fun KotlinType.satisfiesConstraintsOf(bounds: List<KotlinType>): Boolean {
+ return bounds.all { isSubtypeOf(it) }
+}
+
+fun KotlinType.upperBounds(): List<KotlinType> {
+ return if (isTypeParameter()) {
+ TypeUtils.getTypeParameterDescriptorOrNull(this)?.upperBounds ?: emptyList()
+ } else {
+ listOf(this)
+ }
+}
+
+// util classes
+// ========================
+class ImplicitCtorValueArgument(val type: KotlinType) : ValueArgument {
+ override fun getArgumentExpression(): KtExpression? = null
+ override fun getArgumentName(): ValueArgumentName? = null
+ override fun isNamed(): Boolean = false
+ override fun asElement(): KtElement = error("tried to get element")
+ override fun getSpreadElement(): LeafPsiElement? = null
+ override fun isExternal(): Boolean = true
+}
+
+class AttributeInfo(
+ val value: KtExpression,
+ val key: KtSimpleNameExpression?,
+ val name: String
+)
+
+class NoInterceptionCallResolver(private val callResolver: CallResolver) {
+ fun resolveCallWithGivenName(
+ context: ResolutionContext<*>,
+ call: Call,
+ functionReference: KtReferenceExpression,
+ name: Name
+ ): OverloadResolutionResults<FunctionDescriptor> {
+ context.trace.record(
+ ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION,
+ call,
+ true
+ )
+ return callResolver.resolveCallWithGivenName(
+ context,
+ call,
+ functionReference,
+ name
+ )
+ }
+
+ fun resolveCallWithKnownCandidate(
+ call: Call,
+ tracing: TracingStrategy,
+ context: ResolutionContext<*>,
+ candidate: ResolutionCandidate<FunctionDescriptor>,
+ dataFlowInfoForArguments: MutableDataFlowInfoForArguments?
+ ): OverloadResolutionResults<FunctionDescriptor> {
+ context.trace.record(
+ ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION,
+ call,
+ true
+ )
+ return callResolver.resolveCallWithKnownCandidate(
+ call,
+ tracing,
+ context,
+ candidate,
+ dataFlowInfoForArguments
+ )
+ }
+
+ fun resolveSimpleProperty(
+ context: BasicCallResolutionContext
+ ): OverloadResolutionResults<VariableDescriptor> {
+ return callResolver.resolveSimpleProperty(
+ context
+ )
+ }
+
+ fun resolveFunctionCall(
+ context: BasicCallResolutionContext
+ ): OverloadResolutionResults<FunctionDescriptor> {
+ context.trace.record(
+ ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION,
+ context.call,
+ true
+ )
+ return callResolver.resolveFunctionCall(
+ context
+ )
+ }
+
+ fun <T : CallableDescriptor> computeTasksAndResolveCall(
+ context: BasicCallResolutionContext,
+ name: Name,
+ referenceExpression: KtReferenceExpression,
+ kind: NewResolutionOldInference.ResolutionKind
+ ): OverloadResolutionResults<T> {
+ context.trace.record(
+ ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION,
+ context.call,
+ true
+ )
+ return callResolver.computeTasksAndResolveCall(
+ context,
+ name,
+ referenceExpression,
+ kind
+ )
+ }
+}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFlags.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFlags.kt
index 986ff92..ac6a98f 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFlags.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFlags.kt
@@ -19,5 +19,4 @@
object ComposeFlags {
var FRAMED_COMPONENTS = false
var FRAMED_MODEL_CLASSES = true
- var NEW_CALL_RESOLUTION_INTERCEPTION = false
}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFqNames.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFqNames.kt
index e00a888..e34cafc 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFqNames.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFqNames.kt
@@ -24,7 +24,6 @@
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.types.KotlinType
@@ -35,10 +34,7 @@
object ComposeFqNames {
val Composable = ComposeUtils.composeFqName("Composable")
val Pivotal = ComposeUtils.composeFqName("Pivotal")
- val Children = ComposeUtils.composeFqName("Children")
- val Stateful = ComposeUtils.composeFqName("Stateful")
val StableMarker = ComposeUtils.composeFqName("StableMarker")
- val Emittable = ComposeUtils.composeFqName("Emittable")
val HiddenAttribute = ComposeUtils.composeFqName("HiddenAttribute")
fun makeComposableAnnotation(module: ModuleDescriptor): AnnotationDescriptor =
@@ -69,31 +65,13 @@
annotations.findAnnotation(ComposeFqNames.Composable) != null
fun Annotated.hasPivotalAnnotation(): Boolean =
annotations.findAnnotation(ComposeFqNames.Pivotal) != null
-fun Annotated.hasChildrenAnnotation(): Boolean =
- annotations.findAnnotation(ComposeFqNames.Children) != null
-fun Annotated.hasStatefulAnnotation(): Boolean =
- annotations.findAnnotation(ComposeFqNames.Stateful) != null
-fun Annotated.hasEmittableAnnotation(): Boolean =
- annotations.findAnnotation(ComposeFqNames.Emittable) != null
fun Annotated.hasHiddenAttributeAnnotation(): Boolean =
annotations.findAnnotation(ComposeFqNames.HiddenAttribute) != null
-fun Annotated.isComposableFromChildrenAnnotation(): Boolean {
- val childrenAnnotation = annotations.findAnnotation(ComposeFqNames.Children) ?: return false
- return childrenAnnotation.isComposableChildrenAnnotation
-}
-
internal val KotlinType.isSpecialType: Boolean get() =
this === NO_EXPECTED_TYPE || this === UNIT_EXPECTED_TYPE
val AnnotationDescriptor.isComposableAnnotation: Boolean get() = fqName == ComposeFqNames.Composable
-val AnnotationDescriptor.isChildrenAnnotation: Boolean get() = fqName == ComposeFqNames.Children
-val AnnotationDescriptor.isComposableChildrenAnnotation: Boolean
- get() {
- if (fqName != ComposeFqNames.Children) return false
- val composableValueArgument = argumentValue("composable")?.value
- return composableValueArgument == null || composableValueArgument == true
- }
fun Annotations.hasStableMarker(): Boolean = any(AnnotationDescriptor::isStableMarker)
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeIrLoweringExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeIrLoweringExtension.kt
index 20e3a29..c766875 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeIrLoweringExtension.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeIrLoweringExtension.kt
@@ -20,7 +20,7 @@
import org.jetbrains.kotlin.backend.common.phaser.then
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.extensions.IrLoweringExtension
-import androidx.compose.plugins.kotlin.compiler.lower.ComposeFcsPatcher
+import androidx.compose.plugins.kotlin.compiler.lower.ComposableCallTransformer
import androidx.compose.plugins.kotlin.compiler.lower.ComposeObservePatcher
import androidx.compose.plugins.kotlin.frames.FrameIrTransformer
import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase
@@ -38,8 +38,8 @@
description = "Transform @Model classes into framed classes"
)
-val ComposeFcsPhase = makeIrModulePhase(
- ::ComposeFcsPatcher,
+val ComposeCallPhase = makeIrModulePhase(
+ ::ComposableCallTransformer,
name = "ComposeFcsPhase",
description = "Rewrite FCS descriptors to IR bytecode"
)
@@ -48,6 +48,9 @@
override fun interceptLoweringPhases(
phases: CompilerPhase<JvmBackendContext, IrModuleFragment, IrModuleFragment>
): CompilerPhase<JvmBackendContext, IrModuleFragment, IrModuleFragment> {
- return FrameClassGenPhase then ComposeObservePhase then ComposeFcsPhase then phases
+ return FrameClassGenPhase then
+ ComposeCallPhase then
+ ComposeObservePhase then
+ phases
}
}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxControlFlowExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxControlFlowExtension.kt
deleted file mode 100644
index 9c4156d..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxControlFlowExtension.kt
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import com.intellij.util.SmartFMap
-import org.jetbrains.kotlin.builtins.KotlinBuiltIns
-import org.jetbrains.kotlin.cfg.ControlFlowBuilder
-import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
-import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.AccessTarget
-import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue
-import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
-import org.jetbrains.kotlin.descriptors.VariableDescriptor
-import org.jetbrains.kotlin.extensions.KtxControlFlowExtension
-import org.jetbrains.kotlin.psi.KtCallExpression
-import org.jetbrains.kotlin.psi.KtDeclaration
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtIfExpression
-import org.jetbrains.kotlin.psi.KtPsiUtil
-import org.jetbrains.kotlin.psi.KtStatementExpression
-import org.jetbrains.kotlin.psi.KtTryExpression
-import org.jetbrains.kotlin.psi.KtVisitorVoid
-import org.jetbrains.kotlin.psi.KtWhenExpression
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.ValueArgument
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import org.jetbrains.kotlin.resolve.BindingTrace
-import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
-import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
-import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
-import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
-import org.jetbrains.kotlin.resolve.calls.tower.getFakeDescriptorForObject
-import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
-import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
-import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
-import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
-import java.util.ArrayList
-
-class ComposeKtxControlFlowExtension : KtxControlFlowExtension {
-
- private class ControlFlowVisitor(
- val builder: ControlFlowBuilder,
- val visitor: KtVisitorVoid,
- val trace: BindingTrace
- ) {
-
- private fun mark(element: KtElement) = builder.mark(element)
-
- private fun createSyntheticValue(
- instructionElement: KtElement,
- kind: MagicKind,
- vararg from: KtElement
- ): PseudoValue =
- builder.magic(
- instructionElement,
- null,
- elementsToValues(from.asList()), kind
- ).outputValue
-
- private fun createNonSyntheticValue(
- to: KtElement,
- from: List<KtElement?>,
- kind: MagicKind
- ): PseudoValue =
- builder.magic(to, to, elementsToValues(from), kind).outputValue
-
- private fun getBoundOrUnreachableValue(element: KtElement?): PseudoValue? {
- if (element == null) return null
-
- val value = builder.getBoundValue(element)
- return if (value != null || element is KtDeclaration) value
- else builder.newValue(element)
- }
-
- private fun elementsToValues(from: List<KtElement?>): List<PseudoValue> =
- from.mapNotNull { element -> getBoundOrUnreachableValue(element) }
-
- private fun instructionsToValues(from: List<InstructionWithValue>): List<PseudoValue> =
- from.mapNotNull { instruction -> instruction.outputValue }
-
- private fun generateInstructions(element: KtElement?) {
- if (element == null) return
- element.accept(visitor)
- checkNothingType(element)
- }
-
- private fun checkNothingType(element: KtElement) {
- if (element !is KtExpression) return
-
- val expression = KtPsiUtil.deparenthesize(element) ?: return
-
- if (expression is KtStatementExpression || expression is KtTryExpression ||
- expression is KtIfExpression || expression is KtWhenExpression
- ) {
- return
- }
-
- val type = trace.bindingContext.getType(expression)
- if (type != null && KotlinBuiltIns.isNothing(type)) {
- builder.jumpToError(expression)
- }
- }
-
- private fun checkAndGenerateCall(resolvedCall: ResolvedCall<*>?): Boolean {
- if (resolvedCall == null) return false
- generateCall(resolvedCall)
- return true
- }
-
- private fun generateCall(resolvedCall: ResolvedCall<*>): InstructionWithValue {
- val callElement = resolvedCall.call.callElement
-
- val receivers = getReceiverValues(resolvedCall)
-
- var parameterValues = SmartFMap.emptyMap<PseudoValue, ValueParameterDescriptor>()
- for (argument in resolvedCall.call.valueArguments) {
- val argumentMapping = resolvedCall.getArgumentMapping(argument)
- val argumentExpression = argument.getArgumentExpression()
- if (argumentMapping is ArgumentMatch) {
- parameterValues = generateValueArgument(
- argument, argumentMapping.valueParameter,
- parameterValues
- )
- } else if (argumentExpression != null) {
- generateInstructions(argumentExpression)
- createSyntheticValue(
- argumentExpression,
- MagicKind.VALUE_CONSUMER,
- argumentExpression
- )
- }
- }
-
- if (resolvedCall.resultingDescriptor is VariableDescriptor) {
- // If a callee of the call is just a variable (without 'invoke'), 'read variable' is generated.
- // todo : process arguments for such a case (KT-5387)
- val callExpression = callElement as? KtExpression
- ?: error("Variable-based call without callee expression: " + callElement.text)
- assert(parameterValues.isEmpty()) {
- "Variable-based call with non-empty argument list: " + callElement.text
- }
- return builder.readVariable(callExpression, resolvedCall, receivers)
- }
-
- mark(resolvedCall.call.callElement)
- return builder.call(callElement, resolvedCall, receivers, parameterValues)
- }
-
- private fun getReceiverValues(
- resolvedCall: ResolvedCall<*>
- ): Map<PseudoValue, ReceiverValue> {
- var varCallResult: PseudoValue? = null
- var explicitReceiver: ReceiverValue? = null
- if (resolvedCall is VariableAsFunctionResolvedCall) {
- varCallResult = generateCall(resolvedCall.variableCall).outputValue
-
- val kind = resolvedCall.explicitReceiverKind
- //noinspection EnumSwitchStatementWhichMissesCases
- when (kind) {
- ExplicitReceiverKind.DISPATCH_RECEIVER ->
- explicitReceiver = resolvedCall.dispatchReceiver
- ExplicitReceiverKind.EXTENSION_RECEIVER, ExplicitReceiverKind.BOTH_RECEIVERS ->
- explicitReceiver = resolvedCall.extensionReceiver
- ExplicitReceiverKind.NO_EXPLICIT_RECEIVER -> Unit
- }
- }
-
- var receiverValues = SmartFMap.emptyMap<PseudoValue, ReceiverValue>()
- if (explicitReceiver != null && varCallResult != null) {
- receiverValues = receiverValues.plus(varCallResult, explicitReceiver)
- }
- val callElement = resolvedCall.call.callElement
- receiverValues = getReceiverValues(
- callElement,
- resolvedCall.dispatchReceiver,
- receiverValues
- )
- receiverValues = getReceiverValues(
- callElement,
- resolvedCall.extensionReceiver,
- receiverValues
- )
- return receiverValues
- }
-
- private fun getReceiverValues(
- callElement: KtElement,
- receiver: ReceiverValue?,
- receiverValuesArg: SmartFMap<PseudoValue, ReceiverValue>
- ): SmartFMap<PseudoValue, ReceiverValue> {
- var receiverValues = receiverValuesArg
- if (receiver == null || receiverValues.containsValue(receiver)) return receiverValues
-
- when (receiver) {
- is ImplicitReceiver -> {
- if (callElement is KtCallExpression) {
- val declaration = receiver.declarationDescriptor
- if (declaration is ClassDescriptor) {
- val fakeDescriptor = getFakeDescriptorForObject(declaration)
- val calleeExpression = callElement.calleeExpression
- if (fakeDescriptor != null && calleeExpression != null) {
- builder.read(
- calleeExpression,
- AccessTarget.Declaration(fakeDescriptor),
- emptyMap()
- )
- }
- }
- }
- receiverValues = receiverValues.plus(
- createSyntheticValue(callElement, MagicKind.IMPLICIT_RECEIVER),
- receiver
- )
- }
- is ExpressionReceiver -> {
- val expression = receiver.expression
- if (builder.getBoundValue(expression) == null) {
- generateInstructions(expression)
- }
-
- val receiverPseudoValue = getBoundOrUnreachableValue(expression)
- if (receiverPseudoValue != null) {
- receiverValues = receiverValues.plus(receiverPseudoValue, receiver)
- }
- }
- is TransientReceiver -> {
- // Do nothing
- }
- else -> {
- throw IllegalArgumentException("Unknown receiver kind: $receiver")
- }
- }
-
- return receiverValues
- }
-
- private fun generateValueArgument(
- valueArgument: ValueArgument,
- parameterDescriptor: ValueParameterDescriptor,
- parameterValuesArg: SmartFMap<PseudoValue, ValueParameterDescriptor>
- ): SmartFMap<PseudoValue, ValueParameterDescriptor> {
- var parameterValues = parameterValuesArg
- val expression = valueArgument.getArgumentExpression()
- if (expression != null) {
- if (!valueArgument.isExternal()) {
- generateInstructions(expression)
- }
-
- val argValue = getBoundOrUnreachableValue(expression)
- if (argValue != null) {
- parameterValues = parameterValues.plus(argValue, parameterDescriptor)
- }
- }
- return parameterValues
- }
-
- private fun copyValue(from: KtElement?, to: KtElement) {
- getBoundOrUnreachableValue(from)?.let { builder.bindValue(it, to) }
- }
-
- fun visitComposerCallInfo(
- memoize: ComposerCallInfo,
- inputInstructions: MutableList<InstructionWithValue>
- ) {
- memoize.ctorCall?.let { inputInstructions.add(generateCall(it)) }
- memoize.composerCall?.let { inputInstructions.add(generateCall(it)) }
- memoize.validations.forEach { validation ->
- validation.validationCall?.let { inputInstructions.add(generateCall(it)) }
- validation.assignment?.let { inputInstructions.add(generateCall(it)) }
- }
- }
-
- fun visitKtxElement(element: KtxElement) {
- val inputExpressions = ArrayList<KtElement>()
- val inputInstructions = ArrayList<InstructionWithValue>()
- val tagExpr = element.simpleTagName ?: element.qualifiedTagName ?: return
-
- inputExpressions.add(tagExpr)
-
- for (attribute in element.attributes) {
- val valueExpr = attribute.value ?: attribute.key ?: break
- generateInstructions(valueExpr)
- mark(valueExpr)
- inputExpressions.add(valueExpr)
- }
-
- element.bodyLambdaExpression?.let {
- generateInstructions(it)
- inputExpressions.add(it)
- }
-
- val elementCall = trace.get(ComposeWritableSlices.RESOLVED_KTX_CALL, element) ?: return
- var node: EmitOrCallNode? = elementCall.emitOrCall
-
- elementCall.getComposerCall?.let { inputInstructions.add(generateCall(it)) }
-
- while (node != null) {
- when (node) {
- is MemoizedCallNode -> {
- visitComposerCallInfo(node.memoize, inputInstructions)
- node = node.call
- }
- is NonMemoizedCallNode -> {
- inputInstructions.add(generateCall(node.resolvedCall))
- node = node.nextCall
- }
- is EmitCallNode -> {
- visitComposerCallInfo(node.memoize, inputInstructions)
- node = null
- }
- is ErrorNode -> {
- node = null
- }
- }
- }
-
- builder.magic(
- instructionElement = element,
- valueElement = null,
- inputValues = elementsToValues(inputExpressions) +
- instructionsToValues(inputInstructions),
- kind = MagicKind.VALUE_CONSUMER
- )
- }
- }
-
- override fun visitKtxElement(
- element: KtxElement,
- builder: ControlFlowBuilder,
- visitor: KtVisitorVoid,
- trace: BindingTrace
- ) {
- ControlFlowVisitor(
- builder,
- visitor,
- trace
- ).visitKtxElement(element)
- }
-}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxParsingExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxParsingExtension.kt
deleted file mode 100644
index 0a1e5aa..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxParsingExtension.kt
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import com.intellij.lang.PsiBuilder
-import com.intellij.psi.tree.TokenSet
-import org.jetbrains.kotlin.KtNodeTypes.BLOCK
-import org.jetbrains.kotlin.KtNodeTypes.DOT_QUALIFIED_EXPRESSION
-import org.jetbrains.kotlin.KtNodeTypes.FUNCTION_LITERAL
-import org.jetbrains.kotlin.KtNodeTypes.KTX_ATTRIBUTE
-import org.jetbrains.kotlin.KtNodeTypes.KTX_BODY_LAMBDA
-import org.jetbrains.kotlin.KtNodeTypes.KTX_ELEMENT
-import org.jetbrains.kotlin.KtNodeTypes.REFERENCE_EXPRESSION
-import org.jetbrains.kotlin.KtNodeTypes.VALUE_PARAMETER_LIST
-import org.jetbrains.kotlin.lexer.KtTokens.ARROW
-import org.jetbrains.kotlin.lexer.KtTokens.COLON
-import org.jetbrains.kotlin.lexer.KtTokens.COMMA
-import org.jetbrains.kotlin.lexer.KtTokens.DIV
-import org.jetbrains.kotlin.lexer.KtTokens.DOT
-import org.jetbrains.kotlin.lexer.KtTokens.EQ
-import org.jetbrains.kotlin.lexer.KtTokens.GT
-import org.jetbrains.kotlin.lexer.KtTokens.IDENTIFIER
-import org.jetbrains.kotlin.lexer.KtTokens.LBRACE
-import org.jetbrains.kotlin.lexer.KtTokens.LPAR
-import org.jetbrains.kotlin.lexer.KtTokens.LT
-import org.jetbrains.kotlin.lexer.KtTokens.RBRACE
-import org.jetbrains.kotlin.lexer.KtTokens.SEMICOLON
-import org.jetbrains.kotlin.parsing.KotlinExpressionParsing
-import org.jetbrains.kotlin.parsing.KtxParsingExtension
-import org.jetbrains.kotlin.parsing.KtxStatementsParser
-import java.util.ArrayList
-
-private val EXPRESSION_FOLLOW_KTX =
- TokenSet.orSet(KotlinExpressionParsing.EXPRESSION_FOLLOW, TokenSet.create(DIV, GT))
-private val ATTRIBUTE_FOLLOW = TokenSet.create(DIV, GT, IDENTIFIER, EQ, LBRACE, RBRACE, SEMICOLON)
-
-class ComposeKtxParsingExtension : KtxParsingExtension {
-
- override fun atKtxStart(parser: KotlinExpressionParsing) = parser.at(LT)
-
- override fun parseKtxTag(parser: KotlinExpressionParsing) = parser.parseKtxTag()
-
- override fun parseKtxExpressionFollow() = EXPRESSION_FOLLOW_KTX
-
- override fun createStatementsParser(
- parser: KotlinExpressionParsing,
- closeTagToken: String?
- ): KtxStatementsParser {
- return object : KtxStatementsParser {
- val tags = ArrayList<KtxMarkerStart>()
- var shouldBreak = false
- var shouldContinue = false
-
- override fun handleTag() {
- if (parser.shouldExitKtxBodyBlock(tags, closeTagToken)) {
- shouldBreak = true
- shouldContinue = false
- } else {
- parser.handleKtx(tags)
- shouldBreak = false
- shouldContinue = true
- }
- }
-
- override fun shouldBreak() = shouldBreak
- override fun shouldContinue() = shouldContinue
-
- override fun finish() {
- // Close all open KTX tags
- for (i in tags.size - 1 downTo 0) {
- val marker = tags.removeAt(i)
- marker.bodyMarker!!.done(BLOCK)
- marker.functionLiteralMarker!!.done(FUNCTION_LITERAL)
- marker.lambdaExpressionMarker!!.done(KTX_BODY_LAMBDA)
- marker.close()
- parser.errorWithRecovery("Close tag expected",
- EXPRESSION_FOLLOW_KTX
- )
- }
- }
- }
- }
-}
-
-private fun KotlinExpressionParsing.shouldExitKtxBodyBlock(
- tags: List<KtxMarkerStart>,
- closeTagToken: String?
-): Boolean {
- val marker = mark()
- try {
- if (closeTagToken == null) return false
- if (!at(LT)) return false
- advance()
- if (!at(DIV)) return false
- advance()
- val tagName = parseKtxTagName()
- for (tag in tags)
- if (closeTagToken == tag.tagName)
- return false
- @Suppress("SENSELESS_COMPARISON")
- return tagName == null || closeTagToken == tagName
- } finally {
- marker.rollbackTo()
- }
-}
-
-private fun KotlinExpressionParsing.atCloseKtxTag(): Boolean {
- return lookahead(0) === LT && lookahead(1) === DIV
-}
-
-private fun KotlinExpressionParsing.handleKtx(markers: MutableList<KtxMarkerStart>) {
- // Determine if this is a open or close tag
- if (atCloseKtxTag()) {
- val startOfTag = mark()
- // Find the best match in terms of tags to close
- val tagName = parseKtxCloseTag()
- if (markers.size == 0) {
- startOfTag.drop()
- return
- }
- var index = markers.size - 1
- val tagsToClose = ArrayList<KtxMarkerStart>()
- if (tagName == null) {
- // We don't know what type of close tag it is, so just close something and try to recover as gracefully as possible.
- tagsToClose.add(markers.removeAt(index))
- } else {
- while (index >= 0) {
- if (tagName == markers[index].tagName) break
- index--
- }
- if (index >= 0 && !markers.isEmpty() && tagName == markers[index].tagName) {
- for (i in markers.size - 1 downTo index) {
- tagsToClose.add(markers.removeAt(i))
- }
- }
- }
- if (tagsToClose.size > 0) {
- startOfTag.rollbackTo()
- for (i in 0 until tagsToClose.size - 1) {
- val tag = tagsToClose[i]
- tag.bodyMarker!!.done(BLOCK)
- tag.functionLiteralMarker!!.done(FUNCTION_LITERAL)
- tag.lambdaExpressionMarker!!.done(KTX_BODY_LAMBDA)
- tag.close()
- }
- val tag = tagsToClose[tagsToClose.size - 1]
- tag.bodyMarker!!.done(BLOCK)
- tag.functionLiteralMarker!!.done(FUNCTION_LITERAL)
- tag.lambdaExpressionMarker!!.done(KTX_BODY_LAMBDA)
- parseKtxCloseTag()
- tag.close()
- } else {
- startOfTag.drop()
- }
- } else {
- // Open a new ktx tag
- val tag = parseKtxOpenTag()
- if (tag != null && !tag.isClosed) {
- tag.lambdaExpressionMarker = mark()
- tag.functionLiteralMarker = mark()
- parseKtxChildrenParameters()
- tag.bodyMarker = mark()
- markers.add(tag)
- }
- }
-}
-
-/*
- * KtxElement
- * ;
- */
-private fun KotlinExpressionParsing.parseKtxTag() {
- val startTag = parseKtxOpenTag()
- if (startTag == null || startTag.isClosed) return
-
- startTag.lambdaExpressionMarker = mark()
- startTag.functionLiteralMarker = mark()
- parseKtxChildrenParameters()
-
- startTag.bodyMarker = mark()
- parseStatements(false, startTag.tagName)
- startTag.bodyMarker!!.done(BLOCK)
- startTag.functionLiteralMarker!!.done(FUNCTION_LITERAL)
- startTag.lambdaExpressionMarker!!.done(KTX_BODY_LAMBDA)
-
- parseKtxCloseTag()
- startTag.close()
-}
-
-private fun KotlinExpressionParsing.parseKtxChildrenParameters() {
-
- myBuilder.enableNewlines()
-
- if (at(ARROW)) {
- // { -> ...}
- mark().done(VALUE_PARAMETER_LIST)
- advance() // ARROW
- } else if (at(IDENTIFIER) || at(COLON) || at(LPAR)) {
- // Try to parse a simple name list followed by an ARROW
- // {a -> ...}
- // {a, b -> ...}
- // {(a, b) -> ... }
- val rollbackMarker = mark()
- val nextToken = lookahead(1)
- val preferParamsToExpressions = nextToken === COMMA || nextToken === COLON
- parseFunctionLiteralParameterList()
-
- if (at(ARROW)) {
- advance() // ARROW
- rollbackMarker.drop()
- } else if (preferParamsToExpressions && atCloseKtxTag()) {
- rollbackMarker.drop()
- expect(ARROW, "An -> is expected")
- } else {
- rollbackMarker.rollbackTo()
- mark().done(VALUE_PARAMETER_LIST)
- }
- }
-}
-
-private fun KotlinExpressionParsing.parseKtxTagNameIdentifier(): String? {
- val ref = mark()
- return if (at(IDENTIFIER)) {
- val identifierText = myBuilder.getTokenText()
- advance() // IDENTIFIER
- ref.done(REFERENCE_EXPRESSION)
- identifierText
- } else {
- ref.error("Expected identifier as part of KTX tag name")
- ref.rollbackTo()
- null
- }
-}
-
-private fun KotlinExpressionParsing.parseKtxTagName(): String {
- var tagName = ""
- var expression: PsiBuilder.Marker = mark()
- tagName += parseKtxTagNameIdentifier()
- var firstExpressionParsed = true
-
- while (true) {
- if (interruptedWithNewLine()) {
- break
- } else if (at(DOT)) {
- advance() // DOT
- if (!firstExpressionParsed) {
- expression.drop()
- expression = mark()
- }
- tagName += "." + parseKtxTagNameIdentifier()
- if (firstExpressionParsed) {
- expression.done(DOT_QUALIFIED_EXPRESSION)
- } else {
- firstExpressionParsed = true
- continue
- }
- } else {
- break
- }
- expression = expression.precede()
- }
-
- expression.drop()
- return tagName
-}
-
-/*
- * KtxAttribute
- * ;
- */
-private fun KotlinExpressionParsing.parseKtxAttribute() {
- val marker = mark()
- if (at(IDENTIFIER)) {
- val attrKeyMark = mark()
- advance()
- attrKeyMark.done(REFERENCE_EXPRESSION)
- } else {
- errorWithRecovery("Expected attribute name",
- ATTRIBUTE_FOLLOW
- )
- }
-
- if (at(EQ)) {
- advance()
- parsePrefixExpression()
- }
- marker.done(KTX_ATTRIBUTE)
-}
-
-/*
- * KtxElement
- * ;
- */
-private fun KotlinExpressionParsing.parseKtxOpenTag(): KtxMarkerStart? {
-
- val marker = mark()
-
- if (at(LT)) {
- advance()
- } else {
- errorAndAdvance("Expecting a KTX Element")
- marker.drop()
- return null
- }
-
- val tagName = parseKtxTagName()
- val result = KtxMarkerStart(tagName, marker)
-
- while (at(IDENTIFIER)) {
- parseKtxAttribute()
- if (!atSet(ATTRIBUTE_FOLLOW)) {
- errorAndAdvance("Expecting an attribute")
- }
- }
-
- if (at(DIV)) {
- advance()
- return if (at(GT)) {
- advance()
- result.close()
- result
- } else {
- result.close()
- errorAndAdvance("Expecting `>` (end of KTX tag)")
- result
- }
- } else if (at(GT)) {
- advance()
- return result
- } else {
- errorWithRecovery("Invalid KTX tag", EXPRESSION_FOLLOW_KTX)
-
- if (at(DIV)) advance()
- if (at(GT)) advance()
- result.close()
-
- return result
- }
-}
-
-/*
- * KtxElement
- * ;
- */
-private fun KotlinExpressionParsing.parseKtxCloseTag(): String? {
- val marker = mark()
-
- if (at(LT)) {
- advance()
- } else {
- marker.error("Expected a KTX close tag")
- return null
- }
-
- if (at(DIV)) {
- advance()
- } else {
- marker.error("Expected tag to be a closing tag")
- return null
- }
-
- val tagName = parseKtxTagName()
-
- return if (at(GT)) {
- advance()
- marker.drop()
- tagName
- } else {
- marker.error("Expected `>` (end of KTX close tag)")
- tagName
- }
-}
-
-class KtxMarkerStart(tagName: String, private var tagMarker: PsiBuilder.Marker) {
- var tagName: String
- internal set
- internal var bodyMarker: PsiBuilder.Marker? = null
- internal var functionLiteralMarker: PsiBuilder.Marker? = null
- internal var lambdaExpressionMarker: PsiBuilder.Marker? = null
- internal var isClosed: Boolean = false
- private var closedAt: Throwable? = null
-
- init {
- this.tagName = tagName
- }
-
- fun close() {
- if (closedAt != null) {
- // throw new RuntimeException("Already closed at", closedAt);
- }
- closedAt = Throwable("Previously closed here")
- tagMarker.done(KTX_ELEMENT)
- isClosed = true
- }
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxTypeResolutionExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxTypeResolutionExtension.kt
deleted file mode 100644
index deda3b8..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeKtxTypeResolutionExtension.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import org.jetbrains.kotlin.extensions.KtxTypeResolutionExtension
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import org.jetbrains.kotlin.resolve.calls.CallResolver
-import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache
-import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
-import org.jetbrains.kotlin.types.expressions.ExpressionTypingFacade
-
-class ComposeKtxTypeResolutionExtension : KtxTypeResolutionExtension {
-
- override fun visitKtxElement(
- element: KtxElement,
- context: ExpressionTypingContext,
- facade: ExpressionTypingFacade,
- callResolver: CallResolver
- ) {
- val ktxCallResolver =
- KtxCallResolver(
- callResolver,
- facade,
- element.project,
- ComposableAnnotationChecker.get(element.project)
- )
-
- ktxCallResolver.initializeFromKtxElement(element, context)
-
- val temporaryForKtxCall =
- TemporaryTraceAndCache.create(context, "trace to resolve ktx call", element)
-
- val resolvedKtxElementCall = ktxCallResolver.resolveFromKtxElement(
- element,
- context.replaceTraceAndCache(temporaryForKtxCall)
- )
-
- temporaryForKtxCall.commit()
-
- context.trace.record(
- ComposeWritableSlices.RESOLVED_KTX_CALL,
- element,
- resolvedKtxElementCall
- )
- }
-}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
index b6e21a3..f7fffcb 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
@@ -76,36 +76,22 @@
companion object {
@Suppress("UNUSED_PARAMETER")
- fun registerProjectExtensions(project: Project, configuration: CompilerConfiguration) {
- StorageComponentContainerContributor.registerExtension(
- project,
- ComponentsClosedDeclarationChecker()
- )
+ fun registerProjectExtensions(
+ project: Project,
+ configuration: CompilerConfiguration
+ ) {
StorageComponentContainerContributor.registerExtension(
project,
ComposableAnnotationChecker()
)
StorageComponentContainerContributor.registerExtension(
project,
- ChildAnnotationChecker()
- )
- StorageComponentContainerContributor.registerExtension(
- project,
UnionAnnotationCheckerProvider()
)
StorageComponentContainerContributor.registerExtension(
project,
TryCatchComposableChecker()
)
- KtxParsingExtension.registerExtension(project,
- ComposeKtxParsingExtension()
- )
- KtxTypeResolutionExtension.registerExtension(project,
- ComposeKtxTypeResolutionExtension()
- )
- KtxControlFlowExtension.registerExtension(project,
- ComposeKtxControlFlowExtension()
- )
ComposeDiagnosticSuppressor.registerExtension(
project,
ComposeDiagnosticSuppressor()
@@ -124,7 +110,6 @@
project,
ComposeCallResolutionInterceptorExtension()
)
-
StorageComponentContainerContributor.registerExtension(project,
FrameModelChecker()
)
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeSyntheticIrExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeSyntheticIrExtension.kt
index 6d0e2e1..6e39e52 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeSyntheticIrExtension.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeSyntheticIrExtension.kt
@@ -16,102 +16,58 @@
package androidx.compose.plugins.kotlin
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.ParameterDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
-import org.jetbrains.kotlin.descriptors.ValueDescriptor
-import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
-import org.jetbrains.kotlin.ir.IrStatement
-import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
-import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
-import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
-import org.jetbrains.kotlin.ir.descriptors.IrTemporaryVariableDescriptorImpl
-import org.jetbrains.kotlin.ir.expressions.IrCall
-import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrExpression
-import org.jetbrains.kotlin.ir.expressions.IrStatementOriginImpl
-import org.jetbrains.kotlin.ir.expressions.IrReturn
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
-import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
+import org.jetbrains.kotlin.ir.expressions.IrStatementOriginImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
-import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
-import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
-import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
-import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
-import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl
-import org.jetbrains.kotlin.ir.expressions.putValueArgument
-import org.jetbrains.kotlin.ir.expressions.putTypeArguments
-import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
-import org.jetbrains.kotlin.ir.types.IrType
-import org.jetbrains.kotlin.ir.util.declareSimpleFunctionWithOverrides
-import org.jetbrains.kotlin.ir.util.referenceFunction
-import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtCallExpression
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtLambdaExpression
-import org.jetbrains.kotlin.psi.KtPsiUtil
+import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
-import org.jetbrains.kotlin.psi.KtxElement
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.startOffset
+import org.jetbrains.kotlin.psi.psiUtil.startOffsetSkippingComments
import org.jetbrains.kotlin.psi2ir.extensions.SyntheticIrExtension
+import org.jetbrains.kotlin.psi2ir.generators.CallGenerator
import org.jetbrains.kotlin.psi2ir.generators.ErrorExpressionGenerator
-import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
import org.jetbrains.kotlin.psi2ir.generators.StatementGenerator
-import org.jetbrains.kotlin.psi2ir.generators.generateReceiver
-import org.jetbrains.kotlin.psi2ir.generators.getSuperQualifier
-import org.jetbrains.kotlin.psi2ir.generators.getTypeArguments
-import org.jetbrains.kotlin.psi2ir.generators.pregenerateCallReceivers
-import org.jetbrains.kotlin.psi2ir.intermediate.AccessorPropertyLValue
-import org.jetbrains.kotlin.psi2ir.intermediate.AssignmentReceiver
-import org.jetbrains.kotlin.psi2ir.intermediate.CallReceiver
-import org.jetbrains.kotlin.psi2ir.intermediate.FieldPropertyLValue
-import org.jetbrains.kotlin.psi2ir.intermediate.IntermediateValue
-import org.jetbrains.kotlin.psi2ir.intermediate.PropertyLValueBase
-import org.jetbrains.kotlin.psi2ir.intermediate.SimpleCallReceiver
-import org.jetbrains.kotlin.psi2ir.intermediate.TransientReceiverValue
-import org.jetbrains.kotlin.psi2ir.unwrappedGetMethod
-import org.jetbrains.kotlin.psi2ir.unwrappedSetMethod
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import androidx.compose.plugins.kotlin.frames.buildWithScope
-import org.jetbrains.kotlin.descriptors.CallableDescriptor
-import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
-import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
-import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
-import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
-import org.jetbrains.kotlin.ir.expressions.typeParametersCount
-import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
-import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
-import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
+import org.jetbrains.kotlin.psi2ir.generators.generateCall
import org.jetbrains.kotlin.psi2ir.generators.getResolvedCall
+import org.jetbrains.kotlin.psi2ir.generators.pregenerateCall
+import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.util.OperatorNameConventions
class ComposeSyntheticIrExtension : SyntheticIrExtension {
- override fun visitKtxElement(
- statementGenerator: StatementGenerator,
- element: KtxElement
- ): IrStatement {
- val resolvedKtxCall = statementGenerator.context.bindingContext.get(
- ComposeWritableSlices.RESOLVED_KTX_CALL,
- element
- ) ?: error(
- "KTX Element encountered without a resolved KTX call. Something must have gone wrong " +
- "in type resolution."
- )
- val openTagName =
- element.simpleTagName ?: element.qualifiedTagName ?: error("malformed element")
- return statementGenerator.visitResolvedKtx(
- element.startOffset,
- element.endOffset,
- element.body,
- resolvedKtxCall,
- openTagName
+ private fun StatementGenerator.visitCallExpressionWithoutInterception(
+ expression: KtCallExpression
+ ): IrExpression {
+ val resolvedCall = getResolvedCall(expression)
+ ?: return ErrorExpressionGenerator(this)
+ .generateErrorCall(expression)
+
+ if (resolvedCall is VariableAsFunctionResolvedCall) {
+ val functionCall = pregenerateCall(resolvedCall.functionCall)
+ return CallGenerator(this)
+ .generateCall(expression, functionCall, IrStatementOrigin.INVOKE)
+ }
+
+ val calleeExpression = expression.calleeExpression
+ val origin =
+ if (resolvedCall.resultingDescriptor.name == OperatorNameConventions.INVOKE &&
+ calleeExpression !is KtSimpleNameExpression &&
+ calleeExpression !is KtQualifiedExpression
+ )
+ IrStatementOrigin.INVOKE
+ else
+ null
+
+ return CallGenerator(this).generateCall(
+ expression.startOffsetSkippingComments,
+ expression.endOffset,
+ pregenerateCall(resolvedCall),
+ origin
)
}
@@ -123,1115 +79,81 @@
?: return ErrorExpressionGenerator(statementGenerator).generateErrorCall(element)
val descriptor = resolvedCall.candidateDescriptor
- if (descriptor !is ComposeCallResolutionInterceptorExtension.ComposableInvocationDescriptor)
- return null
- val resolvedKtxCall = descriptor.ktxCall
- val openTagName = (element as? KtCallExpression)?.calleeExpression ?: element
- val body = (
- (resolvedKtxCall.emitOrCall as? EmitCallNode)?.inlineChildren as KtLambdaExpression?
- )?.bodyExpression?.statements
- return statementGenerator.visitResolvedKtx(
- element.startOffset,
- element.endOffset,
- body,
- resolvedKtxCall,
- openTagName
- )
+
+ if (descriptor is ComposableFunctionDescriptor) {
+ if (descriptor.isInline) {
+ return statementGenerator.visitCallExpressionWithoutInterception(element)
+ }
+ return statementGenerator.visitComposableCall(descriptor, element)
+ }
+ if (descriptor is ComposableEmitDescriptor) {
+ return statementGenerator.visitEmitCall(descriptor, element)
+ }
+
+ return null
}
- override fun visitSimpleNameExpression(
- statementGenerator: StatementGenerator,
- element: KtSimpleNameExpression
+ private fun StatementGenerator.visitEmitCall(
+ descriptor: ComposableEmitDescriptor,
+ expression: KtCallExpression
): IrExpression? {
- if (true) return null
- val resolvedCall = statementGenerator.getResolvedCall(element) ?: return null
-
- val descriptor = resolvedCall.candidateDescriptor
- if (descriptor !is
- ComposeCallResolutionInterceptorExtension.ComposableInvocationDescriptor) {
- return null
- }
- val resolvedKtxCall = descriptor.ktxCall
- val openTagName = (element as? KtCallExpression)?.calleeExpression ?: element
- val body = (
- (resolvedKtxCall.emitOrCall as? EmitCallNode)?.inlineChildren as KtLambdaExpression?
- )?.bodyExpression?.statements
- return statementGenerator.visitResolvedKtx(
- element.startOffset,
- element.endOffset,
- body,
- resolvedKtxCall,
- openTagName
- )
- }
-
- fun StatementGenerator.visitResolvedKtx(
- startOffset: Int,
- endOffset: Int,
- body: List<KtExpression>?,
- resolvedKtxCall: ResolvedKtxElementCall,
- openTagName: KtExpression
- ): IrExpression {
-
- val irBuiltIns = context.irBuiltIns
-
- val resolvedAttributes = resolvedKtxCall.usedAttributes.map { it.name to it }.toMap()
-
- val statements = mutableListOf<IrStatement>()
-
- val statementGenerator = this@visitResolvedKtx
-
- // Convert tag attribute expressions
- val attributeExpressions = resolvedAttributes.mapValues { (_, attribute) ->
- val valueExpression = attribute.expression
- val irValueExpression = KtPsiUtil.safeDeparenthesize(
- valueExpression
- ).accept(statementGenerator, null) as? IrExpression
- ?: error("attributes need to be expressions")
-
- if (irValueExpression is IrConst<*>) {
- // For constant expression don't create the temporary.
- ({ irValueExpression.copy() })
- } else {
- // Create a temporary variable to hold the value of the expression
- val attributeType = attribute.type
- val attributeVariable = statementGenerator.scope.createTemporaryVariable(
- irValueExpression,
- isMutable = false,
- type = attributeType,
- irType = attributeType.toIrType()
- )
-
- val attrVariableDeclaration =
- statementGenerator.context.symbolTable.declareVariable(
- valueExpression.startOffset, valueExpression.endOffset,
- IrDeclarationOrigin.DEFINED,
- attributeVariable.descriptor,
- attributeType.toIrType(),
- irValueExpression
- )
- // OUTPUT: var _el_attr_name = <attribute expression>
- statements.add(attrVariableDeclaration)
-
- ({
- IrGetValueImpl(
- valueExpression.startOffset, valueExpression.endOffset,
- statementGenerator.context.symbolTable.referenceVariable(
- attributeVariable.descriptor
- )
- )
- })
- }
- }.toMap()
-
- fun getAttribute(name: String) = (attributeExpressions[name]
- ?: error("Expected attribute $name"))()
-
- // TODO(lmr): if this is a "get local variable" will this still work?
- fun getComposer() = statementGenerator.getProperty(
- openTagName.startOffset, openTagName.endOffset,
- resolvedKtxCall.getComposerCall ?: error("Invalid KTX Call")
- )
-
- fun keyExpression(callInfo: ComposerCallInfo): IrExpression {
- val joinKeyCall = callInfo.joinKeyCall ?: error("Expected joinKeyCall to be non-null")
- val sourceKey = getKeyValue(
- statementGenerator.scopeOwner,
- startOffset
- )
- val keyValueExpressions = listOf<IrExpression>(
- IrConstImpl.int(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- irBuiltIns.intType,
- sourceKey
- )
- ) + callInfo.pivotals.map { getAttribute(it.name) }
- return keyValueExpressions
- .reduce { left, right ->
- statementGenerator
- .callMethod(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- joinKeyCall,
- getComposer()
- )
- .apply {
- putValueArgument(0, left)
- putValueArgument(1, right)
- }
- }
- }
-
- fun lambdaExpression(
- descriptor: FunctionDescriptor,
- kotlinType: KotlinType,
- origin: IrDeclarationOrigin,
- block: (statements: MutableList<IrStatement>) -> Unit
- ) = statementGenerator.lambdaExpression(
- openTagName.startOffset,
- openTagName.endOffset,
- descriptor,
- kotlinType,
- origin,
- block
- )
-
- fun generateEmitOrCallNode(
- callNode: EmitOrCallNode,
- container: DeclarationDescriptor,
- receiver: IrExpression? = null
- ): IrExpression {
- when (callNode) {
- is EmitCallNode -> {
- val memoize = callNode.memoize
- val composerCall =
- memoize.composerCall ?: error("Expected composerCall to be non-null")
-
- val composerCallParameters =
- composerCall.resultingDescriptor.valueParameters.map {
- it.name to it
- }.toMap()
- fun getComposerCallParameter(name: Name) = composerCallParameters[name]
- ?: error("Expected $name parameter to exist")
- fun getComposerCallParameterType(name: Name) =
- getComposerCallParameter(name).type
-
- return statementGenerator.callMethod(
- startOffset,
- endOffset,
- composerCall,
- getComposer()
- ).apply {
- // Place the key parameter
- putValueArgument(
- getComposerCallParameter(KtxNameConventions.EMIT_KEY_PARAMETER),
- keyExpression(memoize)
- )
-
- // Place the ctor parameter
- // NOTE: this is always non-null for EmitCallNode
- val ctor = memoize.ctorCall ?: error("Expected ctorCall to be non-null")
- val ctorLambdaDescriptor = memoize.emitCtorFnDescriptor
- ?: error("Expected emitCtorFnDescriptor to be non-null")
- val ctorValueParameters = ctorLambdaDescriptor.valueParameters
- val ctorParameterType =
- getComposerCallParameterType(KtxNameConventions.EMIT_CTOR_PARAMETER)
- val ctorLambda =
- lambdaExpression(
- ctorLambdaDescriptor,
- ctorParameterType,
- IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
- ) { statements ->
- val ctorCall = statementGenerator.buildCtorCall(
- openTagName.startOffset,
- openTagName.endOffset,
- ctor,
- ctor.resultingDescriptor.original as ClassConstructorDescriptor,
- memoize.ctorCall.valueArguments.size
- ).apply {
- putValueParameters(
- memoize.ctorParams,
- statementGenerator,
- ctorValueParameters
- ) {
- getAttribute(it)
- }
- }
- // OUTPUT: return <ctor>(<arguments>)
- val scopeOwner =
- statementGenerator.context.symbolTable.referenceFunction(
- ctorLambdaDescriptor
- )
- statements.add(
- IrReturnImpl(
- openTagName.startOffset, openTagName.endOffset,
- statementGenerator.context.irBuiltIns.nothingType,
- scopeOwner, ctorCall
- )
- )
- }
- putValueArgument(
- getComposerCallParameter(KtxNameConventions.EMIT_CTOR_PARAMETER),
- ctorLambda
- )
-
- // Place the updater parameter
- val updaterLambdaDescriptor = memoize.emitUpdaterFnDescriptor
- ?: error("Expected emitUpdaterFnDescriptor to be non-null")
- val updaterParameterType =
- getComposerCallParameterType(KtxNameConventions.EMIT_UPDATER_PARAMETER)
- val updaterLambda = lambdaExpression(
- updaterLambdaDescriptor,
- updaterParameterType,
- IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
- ) { statements ->
- statements.addAll(
- memoize.validations.map { validation ->
- statementGenerator.validationCall(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- memoizing = true,
- validation = validation,
- fnDescriptor = updaterLambdaDescriptor,
- assignmentReceiver = validation.assignmentLambda
- ?.extensionReceiverParameter
- ?: error("expected extension receiver")
- ) { name ->
- getAttribute(name)
- }
- }
- )
-
- statementGenerator.addReturn(statements, updaterLambdaDescriptor)
- }
- putValueArgument(
- getComposerCallParameter(KtxNameConventions.EMIT_UPDATER_PARAMETER),
- updaterLambda
- )
-
- val bodyStatements = body
- if (bodyStatements != null) {
- // Place the children parameter
- val bodyLambdaDescriptor = memoize.emitBodyFnDescriptor
- ?: error("Expected emitBodyFnDescriptor to be non-null")
- val bodyParameterType = getComposerCallParameterType(
- KtxNameConventions.EMIT_CHILDREN_PARAMETER
- )
- val bodyLambda = lambdaExpression(
- bodyLambdaDescriptor,
- bodyParameterType,
- IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
- ) { statements ->
- statements.addAll(
- bodyStatements.map { it.accept(statementGenerator, null) }
- )
- }
- putValueArgument(
- getComposerCallParameter(
- KtxNameConventions.EMIT_CHILDREN_PARAMETER
- ),
- bodyLambda
- )
- }
- }
- }
- is NonMemoizedCallNode -> {
- val resolvedCall =
- if (callNode.resolvedCall is VariableAsFunctionResolvedCall)
- callNode.resolvedCall.functionCall
- else callNode.resolvedCall
-
- var result: IrExpression = statementGenerator.buildCallOrCtor(
- startOffset,
- endOffset,
- resolvedCall = resolvedCall,
- descriptor = resolvedCall.resultingDescriptor as FunctionDescriptor,
- dispatchReceiver = receiver ?: attributeExpressions[TAG_KEY]?.invoke(),
- valueArgumentsCount = resolvedCall.valueArguments.size
- ).apply {
- putValueParameters(callNode.params, statementGenerator) {
- getAttribute(it)
- }
- }
-
- if (callNode.postAssignments.isNotEmpty()) {
- // Foo()
- // turns into
- // val x = Foo()
- // x.prop = value
- // x
- val elType = callNode.resolvedCall.resultingDescriptor.returnType
- ?: error("Expected returnType to be non-null")
-
- val elVarDescriptor = IrTemporaryVariableDescriptorImpl(
- container,
- Name.identifier("__el"),
- elType,
- false
- )
-
- val postAssignmentStatements = mutableListOf<IrStatement>()
-
- val elVarDecl = statementGenerator.context.symbolTable.declareVariable(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- IrDeclarationOrigin.DEFINED,
- elVarDescriptor,
- elType.toIrType(),
- result
- )
-
- postAssignmentStatements.add(elVarDecl)
-
- fun getEl() = IrGetValueImpl(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- statementGenerator.context.symbolTable.referenceVariable(
- elVarDescriptor
- )
- )
-
- for (assignment in callNode.postAssignments) {
- if (assignment.assignment == null) error("expected assignment")
-
- val statement = when (assignment.assignment.resultingDescriptor) {
- is PropertyDescriptor -> statementGenerator.assignmentReceiver(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- assignment.assignment,
- getEl(),
- elType
- ).assign(getAttribute(assignment.attribute.name))
-
- is FunctionDescriptor -> statementGenerator.callMethod(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- assignment.assignment,
- getEl()
- ).apply {
- putValueArgument(0, getAttribute(assignment.attribute.name))
- }
-
- else -> error("")
- }
-
- postAssignmentStatements.add(statement)
- }
- postAssignmentStatements.add(getEl())
-
- result = IrBlockImpl(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- type = elType.toIrType(),
- origin = null,
- statements = postAssignmentStatements
- )
- }
-
- // TODO(lmr): this doesn't work for what appears to be a broader compiler bug, but it is the more ideal
- // way to write this code. The above code does the same thing but we should switch it whenever the bug in
- // the compiler is fixed.
-// if (callNode.postAssignments.isNotEmpty()) {
-// if (callNode.applyCall == null) error("applyCall expected to be non-null")
-// if (callNode.applyLambdaDescriptor == null) error("applyLambdaDescriptor expected to be non-null")
-// if (callNode.applyLambdaType == null) error("applyLambdaType expected to be non-null")
-//
-// result = statementGenerator.buildCall(
-// element.startOffset,
-// element.endOffset,
-// resolvedCall = callNode.applyCall,
-// descriptor = callNode.applyCall.resultingDescriptor as FunctionDescriptor,
-// receiver = result
-// ).apply {
-// val applyLambda = lambdaExpression(callNode.applyLambdaDescriptor, callNode.applyLambdaType) { statements ->
-// val validator = statementGenerator.extensionReceiverOf(callNode.applyLambdaDescriptor)
-// ?: error("Expected extension receiver")
-//
-// for (assignment in callNode.postAssignments) {
-// if (assignment.assignment == null) error("expected assignmnet")
-//
-// val statement = statementGenerator.callMethod(
-// UNDEFINED_OFFSET, UNDEFINED_OFFSET,
-// assignment.assignment,
-// validator
-// ).apply {
-// putValueArgument(0, getAttribute(assignment.attribute.name))
-// }
-// statements.add(statement)
-// }
-// statementGenerator.addReturn(statements, callNode.applyLambdaDescriptor)
-// }
-// putValueArgument(0, applyLambda)
-// }
-// }
-
- val nextCall = callNode.nextCall
- if (nextCall != null) {
- result = generateEmitOrCallNode(nextCall, container, result)
- }
-
- return result
- }
- is MemoizedCallNode -> {
- val memoize = callNode.memoize
- val composerCall =
- memoize.composerCall ?: error("Expected composerCall to be non-null")
-
- val composerCallParameters =
- composerCall.resultingDescriptor.valueParameters.map {
- it.name to it
- }.toMap()
- fun getComposerCallParameter(name: Name) =
- composerCallParameters[name] ?: error("Expected $name parameter to exist")
- fun getComposerCallParameterType(name: Name) =
- getComposerCallParameter(name).type
-
- return statementGenerator.callMethod(
- startOffset,
- endOffset,
- composerCall,
- getComposer()
- ).apply {
- // Place the key parameter
- putValueArgument(
- getComposerCallParameter(KtxNameConventions.CALL_KEY_PARAMETER),
- keyExpression(memoize)
- )
-
- // Place the ctor parameter
- if (memoize.ctorCall != null) {
- val ctorLambdaDescriptor = memoize.callCtorFnDescriptor
- ?: error("Expected callCtorFnDescriptor to be non-null")
- val ctorParameterType =
- getComposerCallParameterType(KtxNameConventions.CALL_CTOR_PARAMETER)
- val ctorLambda = lambdaExpression(
- ctorLambdaDescriptor,
- ctorParameterType,
- IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
- ) { statements ->
- val ctorCall = statementGenerator.buildCtorCall(
- openTagName.startOffset,
- openTagName.endOffset,
- memoize.ctorCall,
- memoize.ctorCall.resultingDescriptor.original
- as ClassConstructorDescriptor,
- memoize.ctorCall.valueArguments.size
- ).apply {
- putValueParameters(memoize.ctorParams, statementGenerator) {
- getAttribute(it)
- }
- }
- // OUTPUT: return <ctor>(<arguments>)
- val scopeOwner =
- statementGenerator.context.symbolTable.referenceFunction(
- ctorLambdaDescriptor
- )
- statements.add(
- IrReturnImpl(
- openTagName.startOffset, openTagName.endOffset,
- statementGenerator.context.irBuiltIns.nothingType,
- scopeOwner, ctorCall
- )
- )
- }
- putValueArgument(
- getComposerCallParameter(KtxNameConventions.CALL_CTOR_PARAMETER),
- ctorLambda
- )
- }
-
- // Place the validation parameter
- val validateLambdaDescriptor = memoize.callInvalidFnDescriptor
- ?: error("Expected callInvalidFnDescriptor to be non-null")
- val validateParameterType =
- getComposerCallParameterType(KtxNameConventions.CALL_INVALID_PARAMETER)
- val memoizing = memoize.validations.all { it.attribute.isStable }
- val validations = memoize.validations
- val validateLambda =
- lambdaExpression(
- validateLambdaDescriptor,
- validateParameterType,
- IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
- ) { statements ->
- // all as one expression: a or b or c ... or z
-
- val validationCalls = validations
- .map { validation ->
- if (validation.validationType == ValidationType.CHANGED &&
- !memoizing)
- IrConstImpl.constTrue(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- irBuiltIns.booleanType
- )
- else
- statementGenerator.validationCall(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- memoizing = memoizing,
- validation = validation,
- fnDescriptor = validateLambdaDescriptor,
- assignmentReceiver = validateLambdaDescriptor
- .valueParameters.firstOrNull()
- ) { name ->
- getAttribute(name)
- }
- }
- when (validationCalls.size) {
- 0 -> if (!memoizing) {
- // If we are not memoizing we should always return true
- statements.add(IrConstImpl.constTrue(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- irBuiltIns.booleanType
- ))
- }
- 1 -> statements.add(validationCalls.single())
- else -> {
- statements.add(
- validationCalls.reduce { left, right ->
- when {
- left is IrConstImpl<*> -> right
- right is IrConstImpl<*> -> left
- else -> statementGenerator.callMethod(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- resolvedKtxCall.infixOrCall
- ?: error("Invalid KTX Call"),
- left
- ).apply {
- putValueArgument(0, right)
- }
- }
- }
- )
- }
- }
-
- statementGenerator.addReturn(statements, validateLambdaDescriptor)
- }
- putValueArgument(
- getComposerCallParameter(KtxNameConventions.CALL_INVALID_PARAMETER),
- validateLambda
- )
-
- // Place the last argument, which is where the "call" takes place
- val bodyLambdaDescriptor = memoize.callBlockFnDescriptor
- ?: error("Expected callBlockFnDescriptor to be non-null")
- val bodyParameterType =
- getComposerCallParameterType(KtxNameConventions.CALL_BLOCK_PARAMETER)
- val bodyLambda =
- lambdaExpression(
- bodyLambdaDescriptor, bodyParameterType,
- IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
- ) { statements ->
- val nextReceiver =
- bodyLambdaDescriptor.valueParameters.firstOrNull()?.let {
- val receiverValue =
- statementGenerator.context.symbolTable.referenceValue(it)
- IrGetValueImpl(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- it.type.toIrType(),
- receiverValue
- )
- }
- statements.add(
- generateEmitOrCallNode(
- callNode.call,
- bodyLambdaDescriptor,
- nextReceiver)
- )
- }
- putValueArgument(
- getComposerCallParameter(KtxNameConventions.CALL_BLOCK_PARAMETER),
- bodyLambda
- )
- }
- }
- is ErrorNode -> error("KTX element resolved to error node. Cannot generate.")
- }
- }
-
- statements.add(
- generateEmitOrCallNode(resolvedKtxCall.emitOrCall, statementGenerator.scopeOwner)
- )
-
+ // NOTE(lmr):
+ // we insert a block here with two calls:
+ // 1. a call to get the composer
+ // 2. the original call that would have been generated to the synthetic emit descriptor
+ //
+ // We need to do this here because this is the only place we can properly generate a
+ // resolved call in full generality, which we need to do for the composer. We then intercept
+ // this block in the lowering phase and generate the final code we want.
return IrBlockImpl(
- startOffset,
- endOffset,
- irBuiltIns.unitType,
- null,
- statements
- )
- }
-}
-
-private fun StatementGenerator.getProperty(
- startOffset: Int,
- endOffset: Int,
- property: ResolvedCall<*>
-): IrCall {
- val functionDescriptor = (property.resultingDescriptor as PropertyDescriptor).getter
- ?: error("Unexpected type resolution")
- return buildCall(
- startOffset,
- endOffset,
- property,
- functionDescriptor,
- irStatementOrigin = IrStatementOrigin.GET_PROPERTY
- )
-}
-
-private fun StatementGenerator.extensionReceiverOf(descriptor: FunctionDescriptor): IrExpression? {
- return descriptor.extensionReceiverParameter?.let {
- val receiverValue = context.symbolTable.referenceValue(it)
- IrGetValueImpl(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- it.type.toIrType(),
- receiverValue
- )
- }
-}
-
-private fun StatementGenerator.addReturn(
- statements: MutableList<IrStatement>,
- descriptor: FunctionDescriptor
-) {
- val count = statements.size
- if (count >= 1) {
- // Replace the last statement with a return
- val lastStatement = statements[count - 1]
- if (lastStatement !is IrReturn) {
- val scopeOwner = context.symbolTable.referenceFunction(descriptor)
- statements[count - 1] = IrReturnImpl(
- lastStatement.startOffset,
- lastStatement.endOffset,
- context.irBuiltIns.nothingType,
- scopeOwner,
- lastStatement as IrExpression
+ expression.startOffset,
+ expression.endOffset,
+ descriptor.returnType?.toIrType() ?: context.irBuiltIns.unitType,
+ COMPOSABLE_EMIT_OR_CALL,
+ listOf(
+ CallGenerator(this).generateCall(
+ expression.startOffsetSkippingComments,
+ expression.endOffset,
+ pregenerateCall(descriptor.composer),
+ COMPOSABLE_EMIT_OR_CALL
+ ),
+ visitCallExpressionWithoutInterception(expression)
)
- }
- }
-}
-
-private fun StatementGenerator.validationCall(
- startOffset: Int,
- endOffset: Int,
- memoizing: Boolean,
- validation: ValidatedAssignment,
- fnDescriptor: FunctionDescriptor,
- assignmentReceiver: ValueDescriptor?,
- getAttribute: (String) -> IrExpression
-): IrCall {
- val attribute = validation.attribute
- val name = attribute.name
- val attributeValue = getAttribute(name)
-
- val validator = extensionReceiverOf(fnDescriptor)
- ?: error("expected an extension receiver to validator lambda")
-
- // for emit, fnDescriptor is Validator.(Value) -> Unit or Validator.(Value, Element.(Value) -> Unit) -> Unit
- // for call, fnDescriptor is Validator.(Value) -> Boolean or Validator.(Value, (Value) -> Unit) -> Boolean
-
- // in emit, the element is passed through an extension parameter
- // in call, the element is passed through a capture scope
- val validationCall = (
- if (memoizing) validation.validationCall
- else validation.uncheckedValidationCall
- ) ?: error("Expected validationCall to be non-null")
- return callMethod(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET, validationCall,
- validator
- ).apply {
- putValueArgument(0, attributeValue)
- val assignment = validation.assignment
- if (assignment != null && validation.validationType != ValidationType.CHANGED) {
- val assignmentLambdaDescriptor = validation.assignmentLambda
- ?: error("Expected assignmentLambda to be non-null")
- val validationAssignment = lambdaExpression(
- startOffset, endOffset,
- assignmentLambdaDescriptor,
- validationCall.resultingDescriptor.valueParameters[1].type,
- IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
- ) { statements ->
- val parameterDefinition = validation.assignmentLambda.valueParameters.first()
- val parameterReference = context.symbolTable.referenceValueParameter(
- parameterDefinition
- )
- val receiver = assignmentReceiver?.let {
- val receiverValue = context.symbolTable.referenceValue(assignmentReceiver)
- IrGetValueImpl(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- assignmentReceiver.type.toIrType(),
- receiverValue
- )
- } ?: error("Expected an assignment receiver for validations that have assignments")
-
- val assignmentStatement = callMethod(
- startOffset,
- endOffset,
- assignment,
- receiver
- ).apply {
- putValueArgument(
- 0, IrGetValueImpl(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- parameterReference
- )
- )
- }
- statements.add(assignmentStatement)
- addReturn(statements, assignmentLambdaDescriptor)
- }
- putValueArgument(1, validationAssignment)
- }
- }
-}
-
-private fun IrMemberAccessExpression.putValueParameters(
- parameters: List<ValueNode>,
- statementGenerator: StatementGenerator,
- ctorValueParameters: List<ValueParameterDescriptor> = emptyList(),
- getAttribute: (String) -> IrExpression
-) {
- parameters.forEachIndexed { index, parameter ->
- when (parameter) {
- is DefaultValueNode -> {
- // do nothing
- }
- is ImplicitCtorValueNode -> {
- val implicitLambdaParameterCandidates = ctorValueParameters.filter {
- it.type.isSubtypeOf(parameter.type)
- }
- assert(implicitLambdaParameterCandidates.size == 1) {
- if (implicitLambdaParameterCandidates.isEmpty())
- "missing implicit constructor parameter ${parameter.name}: ${
- parameter.type
- }"
- else "ambiguous implicit constructor parameters ${parameter.name}: ${
- parameter.type
- }"
- }
- val implicitCtorParameter = implicitLambdaParameterCandidates.single()
- val implicitCtorParameterReference =
- statementGenerator.context.symbolTable.referenceValue(implicitCtorParameter)
- with(statementGenerator) {
- putValueArgument(
- index,
- IrGetValueImpl(
- UNDEFINED_OFFSET,
- UNDEFINED_OFFSET,
- implicitCtorParameter.type.toIrType(),
- implicitCtorParameterReference
- )
- )
- }
- }
- is AttributeNode -> {
- if (parameter.descriptor !is ValueParameterDescriptor) {
- error("Expected ValueParameterDescriptor. Found: ${parameter.descriptor}")
- }
- putValueArgument(parameter.descriptor.index, getAttribute(parameter.name))
- }
- }
- }
-}
-
-private fun StatementGenerator.callMethod(
- startOffset: Int,
- endOffset: Int,
- function: ResolvedCall<*>,
- dispatchReceiver: IrExpression
-): IrCall {
- val resultingDescriptor = function.resultingDescriptor
- val functionDescriptor = when (resultingDescriptor) {
- is PropertyDescriptor -> resultingDescriptor.setter!!
- is FunctionDescriptor -> resultingDescriptor
- else -> error("Expected function or property descriptor")
- }
-
- return buildCall(startOffset, endOffset, function, functionDescriptor, dispatchReceiver)
-}
-
-private fun StatementGenerator.assignmentReceiver(
- startOffset: Int,
- endOffset: Int,
- resolvedCall: ResolvedCall<*>,
- dispatchReceiver: IrExpression,
- dispatchReceiverType: KotlinType
-): AssignmentReceiver {
- val descriptor = resolvedCall.resultingDescriptor as PropertyDescriptor
-
- var dispatchReceiverValue = resolvedCall.dispatchReceiver?.let {
- generateReceiver(startOffset, endOffset, it)
- }
- var extensionReceiverValue = resolvedCall.extensionReceiver?.let {
- generateReceiver(startOffset, endOffset, it)
- }
-
- if (dispatchReceiverValue is TransientReceiverValue) {
- dispatchReceiverValue = object : IntermediateValue {
- override fun load(): IrExpression = dispatchReceiver
- override val type: IrType
- get() = dispatchReceiverType.toIrType()
- }
- }
- if (extensionReceiverValue is TransientReceiverValue) {
- extensionReceiverValue = object : IntermediateValue {
- override fun load(): IrExpression = dispatchReceiver
- override val type: IrType
- get() = dispatchReceiverType.toIrType()
- }
- }
-
- val propertyReceiver = SimpleCallReceiver(dispatchReceiverValue, extensionReceiverValue)
-
- val superQualifier = getSuperQualifier(resolvedCall)
-
- // TODO property imported from an object
- return createPropertyLValue(
- startOffset,
- endOffset,
- descriptor,
- propertyReceiver,
- getTypeArguments(resolvedCall),
- KTX_TAG_ORIGIN,
- superQualifier
- )
-}
-
-private fun StatementGenerator.createPropertyLValue(
- startOffset: Int,
- endOffset: Int,
- descriptor: PropertyDescriptor,
- propertyReceiver: CallReceiver,
- typeArgumentsMap: Map<TypeParameterDescriptor, KotlinType>?,
- origin: IrStatementOrigin?,
- superQualifier: ClassDescriptor?
-): PropertyLValueBase {
- val superQualifierSymbol = superQualifier?.let { context.symbolTable.referenceClass(it) }
-
- val getterDescriptor = descriptor.unwrappedGetMethod
- val setterDescriptor = descriptor.unwrappedSetMethod
-
- val getterSymbol = getterDescriptor?.let { context.symbolTable.referenceFunction(it.original) }
- val setterSymbol = setterDescriptor?.let { context.symbolTable.referenceFunction(it.original) }
-
- val propertyIrType = descriptor.type.toIrType()
- return if (getterSymbol != null || setterSymbol != null) {
- val typeArgumentsList =
- typeArgumentsMap?.let { typeArguments ->
- descriptor.original.typeParameters.map { typeArguments[it]!!.toIrType() }
- }
- AccessorPropertyLValue(
- context,
- scope,
- startOffset, endOffset, origin,
- propertyIrType,
- getterSymbol,
- getterDescriptor,
- setterSymbol,
- setterDescriptor,
- typeArgumentsList,
- propertyReceiver,
- superQualifierSymbol
)
- } else
- FieldPropertyLValue(
- context,
- scope,
- startOffset, endOffset, origin,
- context.symbolTable.referenceField(descriptor),
- propertyIrType,
- propertyReceiver,
- superQualifierSymbol
+ }
+
+ private fun StatementGenerator.visitComposableCall(
+ descriptor: ComposableFunctionDescriptor,
+ expression: KtCallExpression
+ ): IrExpression? {
+ // NOTE(lmr):
+ // we insert a block here with two calls:
+ // 1. a call to get the composer
+ // 2. the original call that would have been generated to the synthetic emit descriptor
+ //
+ // We need to do this here because this is the only place we can properly generate a
+ // resolved call in full generality, which we need to do for the composer. We then intercept
+ // this block in the lowering phase and generate the final code we want.
+ return IrBlockImpl(
+ expression.startOffset,
+ expression.endOffset,
+ descriptor.returnType?.toIrType() ?: context.irBuiltIns.unitType,
+ COMPOSABLE_EMIT_OR_CALL,
+ listOf(
+ CallGenerator(this).generateCall(
+ expression.startOffsetSkippingComments,
+ expression.endOffset,
+ pregenerateCall(descriptor.composerCall),
+ COMPOSABLE_EMIT_OR_CALL
+ ),
+ visitCallExpressionWithoutInterception(expression)
+ )
)
-}
-
-private fun StatementGenerator.buildCallOrCtor(
- startOffset: Int,
- endOffset: Int,
- resolvedCall: ResolvedCall<*>,
- descriptor: CallableDescriptor,
- valueArgumentsCount: Int,
- dispatchReceiver: IrExpression? = null
-) = when (descriptor) {
- is ClassConstructorDescriptor -> buildCtorCall(
- startOffset,
- endOffset,
- resolvedCall,
- descriptor,
- valueArgumentsCount,
- dispatchReceiver = dispatchReceiver
- )
- is FunctionDescriptor -> buildCall(
- startOffset,
- endOffset,
- resolvedCall,
- descriptor,
- dispatchReceiver = dispatchReceiver
- )
- else -> error("Unrecognized descriptor $descriptor")
-}
-
-private fun StatementGenerator.buildCall(
- startOffset: Int,
- endOffset: Int,
- resolvedCall: ResolvedCall<*>,
- descriptor: FunctionDescriptor,
- receiver: IrExpression? = null,
- dispatchReceiver: IrExpression? = null,
- extensionReceiver: IrExpression? = null,
- irStatementOrigin: IrStatementOrigin? = null
-): IrCall {
- val callBuilder = pregenerateCallReceivers(resolvedCall)
- return callBuilder.callReceiver.call { dispatchReceiverValue, extensionReceiverValue ->
- val returnType = descriptor.returnType!!
- val functionSymbol = context.symbolTable.referenceFunction(descriptor.original)
- IrCallImpl(
- startOffset, endOffset,
- returnType.toIrType(),
- functionSymbol,
- descriptor,
- resolvedCall.typeArguments.count(),
- irStatementOrigin,
- null
- ).apply {
- when (resolvedCall.explicitReceiverKind) {
- ExplicitReceiverKind.DISPATCH_RECEIVER -> {
- this.dispatchReceiver = dispatchReceiver ?: receiver
- ?: dispatchReceiverValue?.load()
- this.extensionReceiver = extensionReceiver ?: extensionReceiverValue?.load()
- }
- ExplicitReceiverKind.EXTENSION_RECEIVER -> {
- this.dispatchReceiver = dispatchReceiver ?: dispatchReceiverValue?.load()
- this.extensionReceiver = extensionReceiver ?: receiver
- ?: extensionReceiverValue?.load()
- }
- ExplicitReceiverKind.NO_EXPLICIT_RECEIVER -> {
- this.dispatchReceiver = dispatchReceiver ?: dispatchReceiverValue?.load()
- this.extensionReceiver = extensionReceiver ?: extensionReceiverValue?.load()
- }
- ExplicitReceiverKind.BOTH_RECEIVERS -> {
- this.dispatchReceiver = dispatchReceiver ?: dispatchReceiverValue?.load()
- this.extensionReceiver = extensionReceiver ?: extensionReceiverValue?.load()
- }
- }
- putTypeArguments(resolvedCall.typeArguments) { it.toIrType() }
- }
- } as IrCall
-}
-
-private fun StatementGenerator.buildCtorCall(
- startOffset: Int,
- endOffset: Int,
- resolvedCall: ResolvedCall<*>,
- descriptor: ClassConstructorDescriptor,
- valueArgumentsCount: Int,
- dispatchReceiver: IrExpression? = null,
- extensionReceiver: IrExpression? = null
-): IrConstructorCall {
- val callBuilder = pregenerateCallReceivers(resolvedCall)
- return (callBuilder.callReceiver.call { dispatchReceiverValue, extensionReceiverValue ->
- val returnType = descriptor.returnType
- val functionSymbol =
- context.symbolTable.referenceFunction(descriptor.original) as IrConstructorSymbol
- IrConstructorCallImpl(
- startOffset, endOffset,
- returnType.toIrType(),
- functionSymbol,
- descriptor,
- typeArgumentsCount = descriptor.typeParametersCount,
- constructorTypeArgumentsCount = 0,
- valueArgumentsCount = valueArgumentsCount
- ).apply {
- when (resolvedCall.explicitReceiverKind) {
- ExplicitReceiverKind.DISPATCH_RECEIVER -> {
- this.dispatchReceiver = dispatchReceiver ?: dispatchReceiverValue?.load()
- this.extensionReceiver = extensionReceiver ?: extensionReceiverValue?.load()
- }
- ExplicitReceiverKind.EXTENSION_RECEIVER -> {
- this.dispatchReceiver = dispatchReceiver ?: dispatchReceiverValue?.load()
- this.extensionReceiver = extensionReceiver ?: extensionReceiverValue?.load()
- }
- ExplicitReceiverKind.NO_EXPLICIT_RECEIVER -> {
- this.dispatchReceiver = dispatchReceiver ?: dispatchReceiverValue?.load()
- this.extensionReceiver = extensionReceiver ?: extensionReceiverValue?.load()
- }
- ExplicitReceiverKind.BOTH_RECEIVERS -> {
- this.dispatchReceiver = dispatchReceiver ?: dispatchReceiverValue?.load()
- this.extensionReceiver = extensionReceiver ?: extensionReceiverValue?.load()
- }
- }
- putTypeArguments(resolvedCall.typeArguments) { it.toIrType() }
- }
- }) as IrConstructorCall
+ }
}
internal fun getKeyValue(descriptor: DeclarationDescriptor, startOffset: Int): Int =
descriptor.fqNameSafe.toString().hashCode() xor startOffset
-private fun buildLambda(
- statementGenerator: StatementGenerator,
- context: GeneratorContext,
- startOffset: Int,
- endOffset: Int,
- descriptor: FunctionDescriptor,
- origin: IrDeclarationOrigin,
- body: (statements: MutableList<IrStatement>) -> Unit
-): IrSimpleFunction {
- return context.symbolTable.declareSimpleFunctionWithOverrides(
- startOffset = startOffset,
- endOffset = endOffset,
- origin = origin,
- descriptor = descriptor
- )
- .buildWithScope(context) { function ->
- with(statementGenerator) {
- fun declareParameter(descriptor: ParameterDescriptor) =
- context.symbolTable.declareValueParameter(
- startOffset = startOffset,
- endOffset = endOffset,
- origin = IrDeclarationOrigin.DEFINED,
- descriptor = descriptor,
- type = descriptor.type.toIrType()
- )
-
- function.dispatchReceiverParameter = descriptor.dispatchReceiverParameter?.let {
- declareParameter(it)
- }
- function.extensionReceiverParameter = descriptor.extensionReceiverParameter?.let {
- declareParameter(it)
- }
- descriptor.valueParameters.mapTo(function.valueParameters) { declareParameter(it) }
-
- val statements = mutableListOf<IrStatement>()
- body(statements)
- function.body = IrBlockBodyImpl(startOffset, endOffset, statements)
- function.returnType = descriptor.returnType?.toIrType()
- ?: error("unable to find return type")
- }
- }
-}
-
-private fun StatementGenerator.lambdaExpression(
- startOffset: Int,
- endOffset: Int,
- descriptor: FunctionDescriptor,
- kotlinType: KotlinType,
- origin: IrDeclarationOrigin,
- block: (statements: MutableList<IrStatement>) -> Unit
-): IrExpression {
- val declaration = buildLambda(
- this,
- context,
- startOffset,
- endOffset,
- descriptor,
- origin,
- block
- )
- val type = kotlinType.toIrType()
- return IrBlockImpl(
- startOffset = startOffset,
- endOffset = endOffset,
- type = type,
- // Important: The origin must be LAMBDA here or the JVM IR backend will create a class
- // for function when it is inlined
- origin = IrStatementOrigin.LAMBDA,
- statements = mutableListOf(
- declaration,
- IrFunctionReferenceImpl(
- startOffset = startOffset,
- endOffset = endOffset,
- type = type,
- symbol = declaration.symbol,
- descriptor = declaration.symbol.descriptor,
- typeArgumentsCount = 0,
- origin = IrStatementOrigin.LAMBDA
- )
- )
- )
-}
-
-private fun String.jvmSafeChars() = map {
- if (!it.isLetterOrDigit()) "${"$"}${it.toInt()}${"$"}" else "$it"
-}.joinToString(separator = "")
-
-internal val KTX_TAG_ORIGIN = object : IrStatementOriginImpl("KTX Tag") {}
\ No newline at end of file
+internal val COMPOSABLE_EMIT_OR_CALL =
+ object : IrStatementOriginImpl("Composable Emit Or Call") {}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeUtils.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeUtils.kt
index 0d23b0d..3ab9d97 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeUtils.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeUtils.kt
@@ -16,7 +16,19 @@
package androidx.compose.plugins.kotlin
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
+import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtFunction
+import org.jetbrains.kotlin.psi.KtFunctionLiteral
+import org.jetbrains.kotlin.psi.KtLambdaArgument
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
+import org.jetbrains.kotlin.resolve.descriptorUtil.module
object ComposeUtils {
@@ -38,4 +50,24 @@
// use !lower to capture non-alpha chars
return name.startsWith("set") && name.length > 3 && !name[3].isLowerCase()
}
+
+ fun isComposeComponent(descriptor: DeclarationDescriptor): Boolean {
+ if (descriptor !is ClassDescriptor) return false
+ val baseComponentDescriptor =
+ descriptor.module.findClassAcrossModuleDependencies(
+ ClassId.topLevel(
+ FqName(ComposeUtils.generateComposePackageName() + ".Component")
+ )
+ ) ?: return false
+ return descriptor.isSubclassOf(baseComponentDescriptor)
+ }
+}
+
+fun KtFunction.isEmitInline(bindingContext: BindingContext): Boolean {
+ if (this !is KtFunctionLiteral) return false
+ if (parent?.parent !is KtLambdaArgument) return false
+ val call = parent?.parent?.parent as? KtCallExpression
+ val resolvedCall = call?.getResolvedCall(bindingContext)
+ return resolvedCall != null &&
+ resolvedCall.candidateDescriptor is ComposableEmitDescriptor
}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerEmitMetadata.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerMetadata.kt
similarity index 89%
rename from compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerEmitMetadata.kt
rename to compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerMetadata.kt
index 1342e62..e5619e6 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerEmitMetadata.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerMetadata.kt
@@ -21,6 +21,7 @@
import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype
import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
@@ -37,7 +38,8 @@
import org.jetbrains.kotlin.types.typeUtil.isNothingOrNullableNothing
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
-class ComposerEmitMetadata(
+class ComposerMetadata(
+ val type: KotlinType,
// Set of valid upper bound types that were defined on the composer that can't have children
// For android, this should be [View]
private val emitSimpleUpperBoundTypes: Set<KotlinType>,
@@ -56,7 +58,8 @@
// fun <T : View> emit(key: Any, ctor: (context: Context) -> T, update: U<T>.() -> Unit)
//
// would produce a Pair of [View] to [Context]
- private val emittableTypeToImplicitCtorTypes: List<Pair<List<KotlinType>, Set<KotlinType>>>
+ private val emittableTypeToImplicitCtorTypes: List<Pair<List<KotlinType>, Set<KotlinType>>>,
+ val callDescriptors: List<FunctionDescriptor>
) {
companion object {
@@ -104,7 +107,7 @@
callResolver: CallResolver,
psiFactory: KtPsiFactory,
resolutionContext: BasicCallResolutionContext
- ): ComposerEmitMetadata {
+ ): ComposerMetadata {
val emitSimpleUpperBoundTypes = mutableSetOf<KotlinType>()
val emitCompoundUpperBoundTypes = mutableSetOf<KotlinType>()
val emittableTypeToImplicitCtorTypes =
@@ -118,6 +121,14 @@
psiFactory
)
+ val callCandidates = resolveComposerMethodCandidates(
+ KtxNameConventions.CALL,
+ resolutionContext,
+ composerType,
+ callResolver,
+ psiFactory
+ )
+
for (candidate in emitCandidates.map { it.candidateDescriptor }) {
if (candidate.name != KtxNameConventions.EMIT) continue
if (candidate !is SimpleFunctionDescriptor) continue
@@ -153,10 +164,16 @@
}
}
- return ComposerEmitMetadata(
+ val callDescriptors = callCandidates.mapNotNull {
+ it.candidateDescriptor as? FunctionDescriptor
+ }
+
+ return ComposerMetadata(
+ composerType,
emitSimpleUpperBoundTypes,
emitCompoundUpperBoundTypes,
- emittableTypeToImplicitCtorTypes
+ emittableTypeToImplicitCtorTypes,
+ callDescriptors
)
}
@@ -165,16 +182,15 @@
callResolver: CallResolver,
psiFactory: KtPsiFactory,
resolutionContext: BasicCallResolutionContext
- ): ComposerEmitMetadata {
+ ): ComposerMetadata {
val meta = resolutionContext.trace.bindingContext[
- ComposeWritableSlices.COMPOSER_EMIT_METADATA,
+ ComposeWritableSlices.COMPOSER_METADATA,
descriptor
]
return if (meta == null) {
- val built =
- build(descriptor.type, callResolver, psiFactory, resolutionContext)
+ val built = build(descriptor.type, callResolver, psiFactory, resolutionContext)
resolutionContext.trace.record(
- ComposeWritableSlices.COMPOSER_EMIT_METADATA,
+ ComposeWritableSlices.COMPOSER_METADATA,
descriptor,
built
)
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/GeneratedKtxChildrenLambdaClassDescriptor.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/GeneratedKtxChildrenLambdaClassDescriptor.kt
deleted file mode 100644
index 6607a5d..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/GeneratedKtxChildrenLambdaClassDescriptor.kt
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
-import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.ClassKind
-import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorVisitor
-import org.jetbrains.kotlin.descriptors.Modality
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
-import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
-import org.jetbrains.kotlin.descriptors.SourceElement
-import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
-import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
-import org.jetbrains.kotlin.descriptors.Visibilities
-import org.jetbrains.kotlin.descriptors.Visibility
-import org.jetbrains.kotlin.descriptors.annotations.Annotations
-import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
-import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.LazyClassReceiverParameterDescriptor
-import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.PropertySetterDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
-import org.jetbrains.kotlin.incremental.components.LookupLocation
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
-import org.jetbrains.kotlin.resolve.descriptorUtil.setSingleOverridden
-import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
-import org.jetbrains.kotlin.resolve.scopes.MemberScope
-import org.jetbrains.kotlin.storage.LockBasedStorageManager
-import org.jetbrains.kotlin.types.ClassTypeConstructorImpl
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.KotlinTypeFactory
-import org.jetbrains.kotlin.types.SimpleType
-import org.jetbrains.kotlin.types.TypeConstructor
-import org.jetbrains.kotlin.types.TypeProjection
-import org.jetbrains.kotlin.types.TypeSubstitution
-import org.jetbrains.kotlin.types.TypeSubstitutor
-import org.jetbrains.kotlin.types.TypeUtils
-import org.jetbrains.kotlin.utils.Printer
-
-open class GeneratedKtxChildrenLambdaClassDescriptor(
- private val module: ModuleDescriptor,
- private val containingDeclaration: DeclarationDescriptor,
- private val variablesToCapture: List<KotlinType>,
- functionType: KotlinType,
- private val parameters: List<ValueParameterDescriptor>
-) : ClassDescriptor {
-
- companion object {
- private var index = 1
- }
-
- private val internalName by lazy {
- Name.identifier("KTX_CHILD_${index++}")
- }
-
- override fun isInline(): Boolean = false
- override fun getName() = internalName
- override fun isExpect(): Boolean = false
- override fun isActual(): Boolean = false
- private val modality = Modality.FINAL
- private val kind = ClassKind.CLASS
- private val sourceElement = SourceElement.NO_SOURCE
- override val annotations = Annotations.EMPTY
- private lateinit var typeConstructor: TypeConstructor
- private lateinit var supertypes: Collection<KotlinType>
- private lateinit var defaultType: SimpleType
- private lateinit var declaredTypeParameters: List<TypeParameterDescriptor>
- private var unsubstitutedPrimaryConstructor: ClassConstructorDescriptor? = null
-
- init {
- initialize(emptyList(), listOf(functionType))
- }
-
- val capturedAccessesAsProperties by lazy {
- variablesToCapture.mapIndexed { index, type ->
- PropertyDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- Name.identifier("p$index"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE,
- false,
- false,
- true,
- true,
- false,
- false
- ).apply {
- setType(
- type,
- emptyList<TypeParameterDescriptor>(),
- thisAsReceiverParameter,
- null as ReceiverParameterDescriptor?
- )
- initialize(
- PropertyGetterDescriptorImpl(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- SourceElement.NO_SOURCE
- ).apply {
- initialize(type)
- },
- PropertySetterDescriptorImpl(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- SourceElement.NO_SOURCE
- ).apply {
- initializeDefault()
- }
- )
- }
- }
- }
-
- val invokeDescriptor by lazy {
- val functionNInterface =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("kotlin.jvm.functions.Function" + parameters.size))
- )!!
- val overridenMethod =
- functionNInterface.unsubstitutedMemberScope.getContributedFunctions(
- Name.identifier("invoke"),
- NoLookupLocation.FROM_BACKEND
- ).single()
- val newMethod = SimpleFunctionDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- Name.identifier("invoke"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE
- )
- newMethod.setSingleOverridden(overridenMethod)
-
- val parameters = parameters.map {
- ValueParameterDescriptorImpl(
- newMethod,
- it,
- it.index,
- it.annotations,
- it.name,
- it.type,
- false,
- false,
- false,
- null,
- it.source
- )
- }
-
- newMethod.initialize(
- null,
- this.thisAsReceiverParameter,
- emptyList(),
- parameters,
- builtIns.unitType,
- Modality.OPEN,
- Visibilities.PUBLIC
- )
- newMethod
- }
-
- private val thisAsReceiverParameter = LazyClassReceiverParameterDescriptor(this)
-
- fun initialize(
- declaredTypeParameters: List<TypeParameterDescriptor>,
- supertypes: Collection<KotlinType>
- ) {
- this.declaredTypeParameters = declaredTypeParameters
- this.supertypes = supertypes
- this.typeConstructor = ClassTypeConstructorImpl(
- this,
- declaredTypeParameters,
- supertypes,
- LockBasedStorageManager.NO_LOCKS
- )
- this.defaultType =
- TypeUtils.makeUnsubstitutedType(this, unsubstitutedMemberScope,
- KotlinTypeFactory.EMPTY_REFINED_TYPE_FACTORY)
- }
-
- override fun getCompanionObjectDescriptor(): ClassDescriptor? = null
- override fun getConstructors(): Collection<ClassConstructorDescriptor> =
- listOf(getUnsubstitutedPrimaryConstructor())
- override fun getContainingDeclaration() = containingDeclaration
- override fun getDeclaredTypeParameters(): List<TypeParameterDescriptor> = declaredTypeParameters
- override fun getKind(): ClassKind = kind
- override fun getSealedSubclasses(): Collection<ClassDescriptor> = emptyList()
-
- fun genScope(): MemberScope {
-
- return object : MemberScope {
-
- override fun getFunctionNames(): Set<Name> = setOf(Name.identifier("invoke"))
-
- override fun getVariableNames(): Set<Name> = emptySet()
-
- override fun getClassifierNames(): Set<Name>? = null
-
- override fun getContributedClassifier(
- name: Name,
- location: LookupLocation
- ): ClassifierDescriptor? = null
-
- override fun getContributedDescriptors(
- kindFilter: DescriptorKindFilter,
- nameFilter: (Name) -> Boolean
- ): Collection<DeclarationDescriptor> {
- return emptyList()
- }
-
- override fun getContributedVariables(
- name: Name,
- location: LookupLocation
- ): Collection<PropertyDescriptor> {
- return capturedAccessesAsProperties
- }
-
- override fun getContributedFunctions(
- name: Name,
- location: LookupLocation
- ): Collection<SimpleFunctionDescriptor> {
- if (name.identifier == "invoke") {
- return listOf(invokeDescriptor)
- }
- return emptyList()
- }
-
- override fun printScopeStructure(p: Printer) {
- p.println(this::class.java.simpleName)
- }
- }
- }
-
- override fun getMemberScope(typeArguments: MutableList<out TypeProjection>): MemberScope =
- genScope()
- override fun getMemberScope(typeSubstitution: TypeSubstitution): MemberScope = genScope()
- override fun getStaticScope(): MemberScope = genScope()
- override fun getUnsubstitutedInnerClassesScope(): MemberScope = genScope()
- override fun getUnsubstitutedMemberScope(): MemberScope = genScope()
-
- override fun getUnsubstitutedPrimaryConstructor(): ClassConstructorDescriptor {
- unsubstitutedPrimaryConstructor?.let { return it }
- val constructor = ClassConstructorDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- false,
- SourceElement.NO_SOURCE
- )
-
- val parameters = variablesToCapture.mapIndexed { index, parameterType ->
- ValueParameterDescriptorImpl(
- constructor,
- null, index, Annotations.EMPTY,
- Name.identifier("p$index"),
- parameterType,
- false,
- false,
- false, null, SourceElement.NO_SOURCE
- )
- }
-
- constructor.initialize(
- parameters,
- Visibilities.PUBLIC
- )
-
- constructor.apply {
- returnType = containingDeclaration.defaultType
- }
-
- unsubstitutedPrimaryConstructor = constructor
-
- return constructor
- }
-
- override fun substitute(substitutor: TypeSubstitutor): ClassDescriptor =
- error("Class $this can't be substituted")
- override fun getThisAsReceiverParameter(): ReceiverParameterDescriptor = thisAsReceiverParameter
- override fun getModality(): Modality = modality
- override fun getOriginal(): ClassDescriptor = this
- override fun getVisibility(): Visibility = Visibilities.PUBLIC
- override fun getSource(): SourceElement = sourceElement
- override fun getTypeConstructor(): TypeConstructor = typeConstructor
- override fun getDefaultType(): SimpleType = defaultType
- override fun isCompanionObject(): Boolean = false
- override fun isData(): Boolean = false
- override fun isInner(): Boolean = false
- override fun isExternal(): Boolean = false
-
- override fun <R : Any?, D : Any?> accept(
- visitor: DeclarationDescriptorVisitor<R, D>,
- data: D
- ): R {
- return visitor.visitClassDescriptor(this, data)
- }
-
- override fun acceptVoid(visitor: DeclarationDescriptorVisitor<Void, Void>) {
- visitor.visitClassDescriptor(this, null)
- }
-
- override fun toString(): String = "GeneratedChildrenLambdaClassDescriptor($fqNameUnsafe)"
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/GeneratedViewClassDescriptor.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/GeneratedViewClassDescriptor.kt
deleted file mode 100644
index 7f91b8c..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/GeneratedViewClassDescriptor.kt
+++ /dev/null
@@ -1,655 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
-import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.ClassKind
-import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorVisitor
-import org.jetbrains.kotlin.descriptors.Modality
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
-import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
-import org.jetbrains.kotlin.descriptors.SourceElement
-import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
-import org.jetbrains.kotlin.descriptors.Visibilities
-import org.jetbrains.kotlin.descriptors.Visibility
-import org.jetbrains.kotlin.descriptors.annotations.Annotations
-import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
-import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.LazyClassReceiverParameterDescriptor
-import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.PropertySetterDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
-import org.jetbrains.kotlin.incremental.components.LookupLocation
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
-import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
-import androidx.compose.plugins.kotlin.analysis.ComponentMetadata
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.psi.KtClass
-import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
-import org.jetbrains.kotlin.resolve.descriptorUtil.module
-import org.jetbrains.kotlin.resolve.descriptorUtil.setSingleOverridden
-import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
-import org.jetbrains.kotlin.resolve.scopes.MemberScope
-import org.jetbrains.kotlin.storage.LockBasedStorageManager
-import org.jetbrains.kotlin.types.ClassTypeConstructorImpl
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.KotlinTypeFactory
-import org.jetbrains.kotlin.types.SimpleType
-import org.jetbrains.kotlin.types.TypeConstructor
-import org.jetbrains.kotlin.types.TypeProjection
-import org.jetbrains.kotlin.types.TypeSubstitution
-import org.jetbrains.kotlin.types.TypeSubstitutor
-import org.jetbrains.kotlin.types.TypeUtils
-import org.jetbrains.kotlin.utils.Printer
-
-open class GeneratedViewClassDescriptor(val metadata: ComponentMetadata) : ClassDescriptor {
- private val module: ModuleDescriptor
- private val containingDeclaration: ClassDescriptor
-
- override fun isInline(): Boolean = false
- override fun getName() =
- Name.identifier(
- (metadata.descriptor.findPsi() as KtClass).fqName!!.shortName().identifier +
- "WrapperView"
- )
-
- override fun isExpect(): Boolean = false
- override fun isActual(): Boolean = false
- private val modality = Modality.FINAL
- private val kind = ClassKind.CLASS
- private val sourceElement = SourceElement.NO_SOURCE
- override val annotations = Annotations.EMPTY
- private lateinit var typeConstructor: TypeConstructor
- private lateinit var supertypes: Collection<KotlinType>
- private lateinit var defaultType: SimpleType
- private lateinit var declaredTypeParameters: List<TypeParameterDescriptor>
- private var unsubstitutedPrimaryConstructor: ClassConstructorDescriptor? = null
-
- val componentInstanceField by lazy {
- PropertyDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- true,
- Name.identifier("componentInstance"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE,
- false,
- false,
- true,
- true,
- false,
- false
- ).apply {
- setType(
- KotlinTypeFactory.simpleType(
- Annotations.EMPTY,
- metadata.descriptor.typeConstructor,
- emptyList<TypeProjection>(),
- true
- ),
- emptyList<TypeParameterDescriptor>(),
- thisAsReceiverParameter,
- null as ReceiverParameterDescriptor?
- )
- initialize(
- PropertyGetterDescriptorImpl(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- SourceElement.NO_SOURCE
- ).apply {
- initialize(metadata.descriptor.defaultType)
- },
- PropertySetterDescriptorImpl(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- SourceElement.NO_SOURCE
- ).apply {
- initializeDefault()
- }
- )
- }
- }
-
- val dirtyField by lazy {
- PropertyDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- true,
- Name.identifier("dirty"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE,
- false,
- false,
- true,
- true,
- false,
- false
- ).apply {
- setType(
- builtIns.booleanType,
- emptyList<TypeParameterDescriptor>(),
- thisAsReceiverParameter,
- null as ReceiverParameterDescriptor?
- )
- initialize(
- PropertyGetterDescriptorImpl(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- SourceElement.NO_SOURCE
- ).apply {
- initialize(builtIns.booleanType)
- },
- PropertySetterDescriptorImpl(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- SourceElement.NO_SOURCE
- ).apply {
- initializeDefault()
- }
- )
- }
- }
-
- val onAttachDescriptor by lazy {
- val newMethod = SimpleFunctionDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- Name.identifier("onAttachedToWindow"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE
- )
-
- newMethod.initialize(
- null,
- this.thisAsReceiverParameter,
- emptyList(),
- emptyList(),
- builtIns.unitType,
- Modality.OPEN,
- Visibilities.PUBLIC
- )
- newMethod
- }
-
- val onDetachDescriptor by lazy {
- val newMethod = SimpleFunctionDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- Name.identifier("onDetachedFromWindow"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE
- )
-
- newMethod.initialize(
- null,
- this.thisAsReceiverParameter,
- emptyList(),
- emptyList(),
- builtIns.unitType,
- Modality.OPEN,
- Visibilities.PUBLIC
- )
- newMethod
- }
-
- init {
- this.module = metadata.descriptor.module
- val linearLayout =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("android.widget.LinearLayout"))
- )!!.defaultType
- val viewTreeObserverDescriptor =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("android.view.ViewTreeObserver"))
- )!!
- val preDrawListener = (
- viewTreeObserverDescriptor.unsubstitutedInnerClassesScope.getContributedClassifier(
- Name.identifier("OnPreDrawListener"),
- NoLookupLocation.FROM_BACKEND
- ) as ClassDescriptor).defaultType
-
- initialize(emptyList(), listOf(linearLayout, preDrawListener))
- this.containingDeclaration = metadata.descriptor
- }
-
- val onPreDraw by lazy {
- val viewTreeObserverDescriptor =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("android.view.ViewTreeObserver"))
- )!!
- val preDrawListener =
- viewTreeObserverDescriptor.unsubstitutedInnerClassesScope.getContributedClassifier(
- Name.identifier("OnPreDrawListener"), NoLookupLocation.FROM_BACKEND
- ) as ClassDescriptor
-
- val overriddenMethod = preDrawListener.unsubstitutedMemberScope.getContributedFunctions(
- Name.identifier("onPreDraw"), NoLookupLocation.FROM_BACKEND
- ).single()
- val newMethod = SimpleFunctionDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- Name.identifier("onPreDraw"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE
- )
- newMethod.setSingleOverridden(overriddenMethod)
-
- newMethod.initialize(
- null,
- this.thisAsReceiverParameter,
- emptyList(),
- emptyList(),
- builtIns.booleanType,
- Modality.OPEN,
- Visibilities.PUBLIC
- )
- newMethod
- }
-
- val compositionContextField by lazy {
- val compositionContextDescriptor = module.findClassAcrossModuleDependencies(
- ClassId.topLevel(ComposeUtils.composeFqName("CompositionContext"))
- )!!
- PropertyDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- true,
- Name.identifier("compositionContext"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE,
- false,
- false,
- true,
- true,
- false,
- false
- ).apply {
- setType(
- compositionContextDescriptor.defaultType,
- emptyList<TypeParameterDescriptor>(), thisAsReceiverParameter,
- null as ReceiverParameterDescriptor?
- )
- initialize(
- PropertyGetterDescriptorImpl(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- SourceElement.NO_SOURCE
- ).apply {
- initialize(compositionContextDescriptor.defaultType)
- },
- PropertySetterDescriptorImpl(
- this,
- Annotations.EMPTY,
- Modality.FINAL,
- Visibilities.PRIVATE,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- SourceElement.NO_SOURCE
- ).apply {
- initializeDefault()
- }
- )
- }
- }
-
- val attributeSetters: Collection<SimpleFunctionDescriptor> by lazy {
-
- val output = mutableListOf<SimpleFunctionDescriptor>()
-
- for (attr in metadata.getAttributeDescriptors()) {
- val newMethod = SimpleFunctionDescriptorImpl.create(
- metadata.wrapperViewDescriptor,
- Annotations.EMPTY,
- Name.identifier(
- ComposeUtils.setterMethodFromPropertyName(
- attr.name.identifier
- )
- ),
- CallableMemberDescriptor.Kind.SYNTHESIZED, SourceElement.NO_SOURCE
- )
-
- newMethod.initialize(
- null,
- null,
- emptyList(),
- listOf(
- ValueParameterDescriptorImpl(
- newMethod,
- null,
- 0,
- Annotations.EMPTY,
- attr.name,
- attr.type,
- false,
- false,
- false,
- null,
- SourceElement.NO_SOURCE
- )
- ),
- builtIns.unitType,
- Modality.FINAL,
- Visibilities.PUBLIC
- )
-
- output.add(newMethod)
- }
- output
- }
-
- private val thisAsReceiverParameter = LazyClassReceiverParameterDescriptor(this)
-
- fun initialize(
- declaredTypeParameters: List<TypeParameterDescriptor>,
- supertypes: Collection<KotlinType>
- ) {
- this.declaredTypeParameters = declaredTypeParameters
- this.supertypes = supertypes
- this.typeConstructor =
- ClassTypeConstructorImpl(
- this,
- declaredTypeParameters,
- supertypes,
- LockBasedStorageManager.NO_LOCKS
- )
- this.defaultType =
- TypeUtils.makeUnsubstitutedType(this, unsubstitutedMemberScope,
- KotlinTypeFactory.EMPTY_REFINED_TYPE_FACTORY)
- }
-
- override fun getCompanionObjectDescriptor(): ClassDescriptor? = null
- override fun getConstructors(): Collection<ClassConstructorDescriptor> =
- listOf(getUnsubstitutedPrimaryConstructor())
- override fun getContainingDeclaration() = containingDeclaration
- override fun getDeclaredTypeParameters(): List<TypeParameterDescriptor> = declaredTypeParameters
- override fun getKind(): ClassKind = kind
- override fun getSealedSubclasses(): Collection<ClassDescriptor> = emptyList()
-
- fun genScope(): MemberScope {
-
- return object : MemberScope {
-
- override fun getFunctionNames(): Set<Name> {
- return setterMethodDescriptors.map { it.name }.toSet()
- }
-
- override fun getVariableNames(): Set<Name> {
- return emptySet()
- }
-
- override fun getClassifierNames(): Set<Name>? = null
-
- override fun getContributedClassifier(
- name: Name,
- location: LookupLocation
- ): ClassifierDescriptor? {
- return null
- }
-
- override fun getContributedDescriptors(
- kindFilter: DescriptorKindFilter,
- nameFilter: (Name) -> Boolean
- ): Collection<DeclarationDescriptor> {
- return emptyList()
- }
-
- override fun getContributedVariables(
- name: Name,
- location: LookupLocation
- ): Collection<PropertyDescriptor> {
- if (name.identifier == "componentInstance") {
- return listOf(componentInstanceField)
- }
- if (name.identifier == "compositionContext") {
- return listOf(compositionContextField)
- }
- if (name.identifier == "dirty") {
- return listOf(dirtyField)
- }
- return emptyList()
- }
-
- override fun getContributedFunctions(
- name: Name,
- location: LookupLocation
- ): Collection<SimpleFunctionDescriptor> {
- if (name.identifier == "onDetachedFromWindow") {
- return listOf(onDetachDescriptor)
- }
- if (name.identifier == "onAttachedToWindow") {
- return listOf(onAttachDescriptor)
- }
- if (name.identifier == "onPreDraw") {
- return listOf(onPreDraw)
- }
-
- return setterMethodDescriptors.filter { name == it.fqNameSafe.shortName() }
- }
-
- override fun printScopeStructure(p: Printer) {
- p.println(this::class.java.simpleName)
- }
- }
- }
-
- override fun getMemberScope(typeArguments: MutableList<out TypeProjection>): MemberScope =
- genScope()
- override fun getMemberScope(typeSubstitution: TypeSubstitution): MemberScope = genScope()
- override fun getStaticScope(): MemberScope = genScope()
- override fun getUnsubstitutedInnerClassesScope(): MemberScope = genScope()
- override fun getUnsubstitutedMemberScope(): MemberScope = genScope()
-
- override fun getUnsubstitutedPrimaryConstructor(): ClassConstructorDescriptor {
- unsubstitutedPrimaryConstructor?.let { return it }
- val constructor = ClassConstructorDescriptorImpl.create(
- this,
- Annotations.EMPTY,
- false,
- SourceElement.NO_SOURCE
- )
- val contextParameter = ValueParameterDescriptorImpl(
- constructor,
- null, 0, Annotations.EMPTY,
- Name.identifier("context"),
- containingDeclaration.module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("android.content.Context"))
- )!!.defaultType,
- false,
- false,
- false, null, SourceElement.NO_SOURCE)
- constructor.initialize(
- listOf(contextParameter),
- Visibilities.PUBLIC
- )
-
- constructor.apply {
- returnType = containingDeclaration.defaultType
- }
-
- unsubstitutedPrimaryConstructor = constructor
-
- return constructor
- }
-
- override fun substitute(substitutor: TypeSubstitutor): ClassDescriptor =
- error("Class $this can't be substituted")
-
- override fun getThisAsReceiverParameter(): ReceiverParameterDescriptor = thisAsReceiverParameter
-
- override fun getModality(): Modality = modality
- override fun getOriginal(): ClassDescriptor = this
- override fun getVisibility(): Visibility = Visibilities.PUBLIC
- override fun getSource(): SourceElement = sourceElement
- override fun getTypeConstructor(): TypeConstructor = typeConstructor
- override fun getDefaultType(): SimpleType = defaultType
-
- override fun isCompanionObject(): Boolean = false
- override fun isData(): Boolean = false
- override fun isInner(): Boolean = false
- override fun isExternal(): Boolean = false
-
- override fun <R : Any?, D : Any?> accept(
- visitor: DeclarationDescriptorVisitor<R, D>,
- data: D
- ): R {
- return visitor.visitClassDescriptor(this, data)
- }
-
- override fun acceptVoid(visitor: DeclarationDescriptorVisitor<Void, Void>) {
- visitor.visitClassDescriptor(this, null)
- }
-
- override fun toString(): String = "GeneratedViewClassDescriptor($fqNameUnsafe)"
-
- val setterMethodDescriptors: Collection<SimpleFunctionDescriptor> by lazy {
- val descriptors = ArrayList<SimpleFunctionDescriptor>()
- for (property in metadata.getAttributeDescriptors()) {
-
- val unitType: SimpleType = this.builtIns.unitType
- val methodName = "set" + property.name.identifier.substring(0, 1).toUpperCase() +
- property.name.identifier.substring(1)
- val newMethod = SimpleFunctionDescriptorImpl.create(
- this,
- annotations,
- Name.identifier(methodName),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE
- )
-
- val parameter = ValueParameterDescriptorImpl(
- newMethod,
- null, 0, Annotations.EMPTY,
- property.name,
- property.type,
- false,
- false,
- false, null, SourceElement.NO_SOURCE)
- newMethod.initialize(
- null,
- this.thisAsReceiverParameter,
- emptyList(),
- listOf(parameter),
- unitType,
- Modality.FINAL,
- Visibilities.PUBLIC
- )
- descriptors.add(newMethod)
- }
-
- descriptors
- }
-
- var instanceCreatorFunction: SimpleFunctionDescriptor? = null
- fun getInstanceCreatorFunction(
- componentClassDescriptor: ClassDescriptor
- ): SimpleFunctionDescriptor {
- if (instanceCreatorFunction != null) return instanceCreatorFunction!!
- val returnType: SimpleType = this.defaultType
-
- // JvmStatic annotation was removed because it causes illegal bytecode to be generated due to a bug in the compiler. Add it back once fixed.
- // TODO: Annotations.create(listOf(AnnotationDescriptorImpl(componentClassDescriptor.module.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("kotlin.jvm.JvmStatic")))!!.defaultType, HashMap<Name, ConstantValue<*>>(), SourceElement.NO_SOURCE)))
- val annotations = Annotations.EMPTY
-
- val newMethod = SimpleFunctionDescriptorImpl.create(
- componentClassDescriptor,
- annotations,
- Name.identifier("createInstance"),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE
- )
- val contextParameter = ValueParameterDescriptorImpl(
- newMethod,
- null, 0, Annotations.EMPTY,
- Name.identifier("context"),
- componentClassDescriptor.module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("android.content.Context"))
- )!!.defaultType,
- false,
- false,
- false, null, SourceElement.NO_SOURCE
- )
- newMethod.initialize(
- null,
- if (componentClassDescriptor.isCompanionObject)
- componentClassDescriptor.thisAsReceiverParameter
- else null,
- emptyList(),
- listOf(contextParameter),
- returnType,
- Modality.OPEN,
- Visibilities.PUBLIC
- )
- instanceCreatorFunction = newMethod
- return newMethod
- }
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt
deleted file mode 100644
index 2a67f56..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt
+++ /dev/null
@@ -1,3971 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import com.intellij.lang.ASTNode
-import com.intellij.openapi.project.Project
-import com.intellij.psi.impl.source.tree.LeafPsiElement
-import com.intellij.util.SmartList
-import org.jetbrains.kotlin.builtins.DefaultBuiltIns
-import org.jetbrains.kotlin.builtins.createFunctionType
-import org.jetbrains.kotlin.builtins.extractParameterNameFromFunctionTypeArgument
-import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
-import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
-import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
-import org.jetbrains.kotlin.builtins.isExtensionFunctionType
-import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype
-import org.jetbrains.kotlin.descriptors.CallableDescriptor
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
-import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.ClassKind
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
-import org.jetbrains.kotlin.descriptors.FieldDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.Modality
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor
-import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
-import org.jetbrains.kotlin.descriptors.ParameterDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.PropertySetterDescriptor
-import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
-import org.jetbrains.kotlin.descriptors.SourceElement
-import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
-import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
-import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
-import org.jetbrains.kotlin.descriptors.VariableDescriptor
-import org.jetbrains.kotlin.descriptors.Visibilities
-import org.jetbrains.kotlin.descriptors.annotations.Annotations
-import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
-import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
-import org.jetbrains.kotlin.diagnostics.Errors
-import org.jetbrains.kotlin.diagnostics.Severity
-import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce
-import org.jetbrains.kotlin.diagnostics.reportFromPlugin
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.psi.Call
-import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtLambdaArgument
-import org.jetbrains.kotlin.psi.KtPsiFactory
-import org.jetbrains.kotlin.psi.KtQualifiedExpression
-import org.jetbrains.kotlin.psi.KtReferenceExpression
-import org.jetbrains.kotlin.psi.KtSimpleNameExpression
-import org.jetbrains.kotlin.psi.KtTypeArgumentList
-import org.jetbrains.kotlin.psi.KtTypeProjection
-import org.jetbrains.kotlin.psi.KtValueArgumentList
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.LambdaArgument
-import org.jetbrains.kotlin.psi.ValueArgument
-import org.jetbrains.kotlin.psi.ValueArgumentName
-import androidx.compose.plugins.kotlin.analysis.ComposeDefaultErrorMessages
-import androidx.compose.plugins.kotlin.analysis.ComposeErrors
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices.IGNORE_COMPOSABLE_INTERCEPTION
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices.STABLE_TYPE
-import org.jetbrains.kotlin.builtins.KotlinBuiltIns
-import org.jetbrains.kotlin.builtins.isFunctionType
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.BindingTrace
-import org.jetbrains.kotlin.resolve.DescriptorFactory
-import org.jetbrains.kotlin.resolve.ObservableBindingTrace
-import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver
-import org.jetbrains.kotlin.resolve.TemporaryBindingTrace
-import org.jetbrains.kotlin.resolve.TraceEntryFilter
-import org.jetbrains.kotlin.resolve.calls.CallResolver
-import org.jetbrains.kotlin.resolve.calls.CallTransformer
-import org.jetbrains.kotlin.resolve.calls.checkers.UnderscoreUsageChecker
-import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
-import org.jetbrains.kotlin.resolve.calls.context.CallPosition
-import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
-import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
-import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
-import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache
-import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
-import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus
-import org.jetbrains.kotlin.resolve.calls.model.ArgumentUnmapped
-import org.jetbrains.kotlin.resolve.calls.model.DataFlowInfoForArgumentsImpl
-import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
-import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments
-import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
-import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
-import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
-import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil
-import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
-import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
-import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate
-import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
-import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategyForInvoke
-import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategyImpl
-import org.jetbrains.kotlin.resolve.calls.tower.NewResolutionOldInference
-import org.jetbrains.kotlin.resolve.calls.util.CallMaker
-import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
-import org.jetbrains.kotlin.resolve.descriptorUtil.module
-import org.jetbrains.kotlin.resolve.scopes.HierarchicalScope
-import org.jetbrains.kotlin.resolve.scopes.MemberScope
-import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier
-import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
-import org.jetbrains.kotlin.resolve.scopes.receivers.PackageQualifier
-import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
-import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
-import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
-import org.jetbrains.kotlin.resolve.scopes.receivers.TypeAliasQualifier
-import org.jetbrains.kotlin.resolve.scopes.receivers.TypeParameterQualifier
-import org.jetbrains.kotlin.resolve.scopes.receivers.expression
-import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
-import org.jetbrains.kotlin.resolve.scopes.utils.findFirstFromMeAndParent
-import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
-import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
-import org.jetbrains.kotlin.synthetic.isVisibleOutside
-import org.jetbrains.kotlin.types.ErrorUtils
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.TypeConstructor
-import org.jetbrains.kotlin.types.TypeSubstitutor
-import org.jetbrains.kotlin.types.TypeUtils
-import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
-import org.jetbrains.kotlin.types.checker.KotlinTypeCheckerImpl
-import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
-import org.jetbrains.kotlin.types.expressions.ExpressionTypingFacade
-import org.jetbrains.kotlin.types.expressions.KotlinTypeInfo
-import org.jetbrains.kotlin.types.isError
-import org.jetbrains.kotlin.types.isNullable
-import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
-import org.jetbrains.kotlin.types.typeUtil.equalTypesOrNulls
-import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
-import org.jetbrains.kotlin.types.typeUtil.isNothingOrNullableNothing
-import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
-import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
-import org.jetbrains.kotlin.types.typeUtil.isUnit
-import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
-import org.jetbrains.kotlin.types.typeUtil.supertypes
-import org.jetbrains.kotlin.util.OperatorNameConventions
-import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
-import java.util.Locale
-import java.util.concurrent.atomic.AtomicBoolean
-
-/**
- * This class is used to resolve a KTX Element to the corresponding set of calls on the composer, and the individual calls for
- * each attribute, etc.
- */
-class KtxCallResolver(
- callResolver: CallResolver,
- private val nullableFacade: ExpressionTypingFacade?,
- private val project: Project,
- private val composableAnnotationChecker: ComposableAnnotationChecker
-) {
- private val isDeprecatedKtxTagResolve: Boolean = nullableFacade != null
-
- private val facade: ExpressionTypingFacade
- get() = nullableFacade
- ?: error("Cannot use ExpressionTypingFacade in call interception mode")
-
- private val callResolver = NoInterceptionCallResolver(callResolver)
-
- private class TempResolveInfo(
- val valid: Boolean,
- val trace: TemporaryTraceAndCache,
- val unusedAttributes: Set<String>,
- val usedAttributes: Set<String>,
- val missingRequiredAttributes: List<DeclarationDescriptor>,
- val build: () -> EmitOrCallNode
- ) {
- val attributesLeft: Int
- get() = unusedAttributes.count { it != TAG_KEY }
- }
-
- private class TempParameterInfo(
- val attribute: AttributeInfo,
- val descriptor: DeclarationDescriptor,
- val type: KotlinType
- )
-
- // NOTE(lmr): I am unsure of the performance consequences of using KtPsiFactory. it appears to create a file for every
- // call, which seems like something we definitely do not want, but it's also used in resolving for(..) loops, so
- // maybe it's not terrible.
- private val psiFactory = KtPsiFactory(project, markGenerated = false)
-
- // The type of the `composer` variable in scope of the KTX tag
- private var composerType: KotlinType = builtIns.unitType
- // A ResolvedCall to "get" the composer variable in scope of the KTX element.
- private var composerResolvedCall: ResolvedCall<*>? = null
- // A ResolvedCall to the `joinKey(Any, Any?)` method on the composer in scope.
- private var joinKeyCall: ResolvedCall<*>? = null
-
- private var infixOrCall: ResolvedCall<*>? = null
-
- private lateinit var mainElement: KtElement
-
- private lateinit var tagExpressions: List<KtExpression>
-
- private lateinit var module: ModuleDescriptor
-
- // Set of valid upper bound types that were defined on the composer that can't have children
- // For android, this should be [View]
- private val emitSimpleUpperBoundTypes = mutableSetOf<KotlinType>()
-
- private fun KotlinType.isEmittable() =
- !isError && !isNothingOrNullableNothing() && emitSimpleUpperBoundTypes.any {
- isSubtypeOf(it)
- }
-
- // Set of valid upper bound types that were defined on the composer that can have children.
- // For android, this would be [ViewGroup]
- private val emitCompoundUpperBoundTypes = mutableSetOf<KotlinType>()
-
- private fun KotlinType.isCompoundEmittable() = !isError &&
- !this.isNothingOrNullableNothing() &&
- emitCompoundUpperBoundTypes.any {
- isSubtypeOf(it)
- }
-
- // The specification for `emit` on a composer allows for the `ctor` parameter to be a function type
- // with any number of parameters. We allow for these parameters to be used as parameters in the
- // Constructors that are emitted with a KTX tag. These parameters can be overridden with attributes
- // in the KTX tag, but if there are required parameters with a type that matches one declared in the
- // ctor parameter, we will resolve it automatically with the value passed in the `ctor` lambda.
- //
- // In order to do this resolution, we store a list of pairs of "upper bounds" to parameter types. For example,
- // the following emit call:
- //
- // fun <T : View> emit(key: Any, ctor: (context: Context) -> T, update: U<T>.() -> Unit)
- //
- // would produce a Pair of [View] to [Context]
- private val emittableTypeToImplicitCtorTypes =
- mutableListOf<Pair<List<KotlinType>, Set<KotlinType>>>()
-
- private fun isImplicitConstructorParam(
- param: ValueParameterDescriptor,
- fn: CallableDescriptor
- ): Boolean {
- val returnType = fn.returnType ?: return false
- val paramType = param.type
- for ((upperBounds, implicitTypes) in emittableTypeToImplicitCtorTypes) {
- if (!implicitTypes.any { it.isSubtypeOf(paramType) }) continue
- if (!returnType.satisfiesConstraintsOf(upperBounds)) continue
- return true
- }
- return false
- }
-
- fun initializeFromKtxElement(element: KtxElement, context: ExpressionTypingContext): Boolean {
-
- module = context.scope.ownerDescriptor.module
-
- // we want to report errors on the tag names (open and closing), and not the entire element, since
- // that would be too noisy
- tagExpressions = listOfNotNull(
- element.simpleTagName,
- element.simpleClosingTagName,
- element.qualifiedTagName,
- element.qualifiedClosingTagName
- )
-
- val result = resolveComposer(element, context)
- return result
- }
-
- fun initializeFromCall(call: Call, context: ExpressionTypingContext): Boolean {
- val callee = call.calleeExpression ?: error("Expected calleeExpression")
-
- module = context.scope.ownerDescriptor.module
-
- tagExpressions = listOf(callee)
-
- val result = resolveComposer(callee, context)
-
- return result
- }
-
- /**
- * KTX tags are defined to resolve a "composer" in the scope of the tag itself, and then the tag translates into a call
- * or a set of calls on that composer instance. This method should be called first, and will resolve the composer in scope
- * and record various pieces of metadata about the composer that will make resolving the tag possible. If it returns false
- * then something went wrong and you should not try and resolve the tag. If the method returns false, at least one
- * diagnostic will have been added to the tag somewhere to indicate that there was a problem.
- */
- private fun resolveComposer(element: KtExpression, context: ExpressionTypingContext): Boolean {
-
- // The composer is currently resolved as whatever is currently in scope with the name "composer".
- val resolvedComposer = resolveVar(KtxNameConventions.COMPOSER, element, context)
-
- if (!resolvedComposer.isSuccess) {
- ComposeErrors.NO_COMPOSER_FOUND.report(context, tagExpressions)
- return false
- }
-
- val composerResolvedCall = resolvedComposer.resultingCall.also { composerResolvedCall = it }
-
- val descriptor = composerResolvedCall.resultingDescriptor
-
- composerType = when (descriptor) {
- is PropertyDescriptor -> descriptor.type
- is VariableDescriptor -> descriptor.type
- // if composer isn't a property or variable, we don't currently know how to resolve it...
- else -> {
- ComposeErrors.NO_COMPOSER_FOUND.report(context, tagExpressions)
- return false
- }
- }
-
- val emitCandidates = resolveComposerMethodCandidates(
- element,
- KtxNameConventions.EMIT,
- context
- )
-
- for (candidate in emitCandidates.map { it.candidateDescriptor }) {
- if (candidate.name != KtxNameConventions.EMIT) continue
- if (candidate !is SimpleFunctionDescriptor) continue
- val params = candidate.valueParameters
- // NOTE(lmr): we could report diagnostics on some of these? it seems strange to emit diagnostics about a function
- // that is not necessarily being used though. I think it's probably better to just ignore them here.
-
- // the signature of emit that we are looking for has 3 or 4 parameters
- if (params.size < 3 || params.size > 4) continue
- val ctorParam = params.find { it.name == KtxNameConventions.EMIT_CTOR_PARAMETER }
- ?: continue
- if (!ctorParam.type.isFunctionTypeOrSubtype) continue
-
- // the return type from the ctor param is the "upper bound" of the node type. It will often be a generic type with constraints.
- val upperBounds = ctorParam.type.getReturnTypeFromFunctionType().upperBounds()
-
- // the ctor param can have parameters itself, which we interpret as implicit parameter types that the composer knows how to
- // automatically provide to the component. In the case of Android Views, this is how we automatically provide Context.
- val implicitParamTypes = ctorParam.type.getValueParameterTypesFromFunctionType().map {
- it.type
- }
-
- for (implicitType in implicitParamTypes) {
- emittableTypeToImplicitCtorTypes.add(upperBounds to implicitParamTypes.toSet())
- }
-
- emitSimpleUpperBoundTypes.addAll(upperBounds)
-
- if (params.any { it.name == KtxNameConventions.EMIT_CHILDREN_PARAMETER }) {
- emitCompoundUpperBoundTypes.addAll(upperBounds)
- }
- }
-
- if (emitSimpleUpperBoundTypes.isEmpty()) {
-
- // if the composer has no valid `emit` candidates, but *does* valid `call` methods, we will report no errors.
- // It's strange, but it's possible for a composer that only handles `call` to be useful. To be sure, we first
- // look up all of the call candidates.
- val callCandidates = resolveComposerMethodCandidates(
- element,
- KtxNameConventions.EMIT,
- context
- )
-
- if (callCandidates.isEmpty()) {
- ComposeErrors.INVALID_COMPOSER_IMPLEMENTATION.report(
- context,
- tagExpressions,
- composerType,
- "Couldn't find any valid `call(...)` or `emit(...)` methods"
- )
- }
- }
-
- val left = psiFactory.createSimpleName("a")
- val right = psiFactory.createSimpleName("b")
-
- context.trace.record(
- BindingContext.EXPRESSION_TYPE_INFO, left, KotlinTypeInfo(
- type = builtIns.booleanType,
- dataFlowInfo = DataFlowInfo.EMPTY,
- jumpOutPossible = false,
- jumpFlowInfo = DataFlowInfo.EMPTY
- )
- )
-
- context.trace.record(
- BindingContext.EXPRESSION_TYPE_INFO, right, KotlinTypeInfo(
- type = builtIns.booleanType,
- dataFlowInfo = DataFlowInfo.EMPTY,
- jumpOutPossible = false,
- jumpFlowInfo = DataFlowInfo.EMPTY
- )
- )
-
- infixOrCall = resolveInfixOr(context)
-
- joinKeyCall = resolveJoinKey(
- expressionToReportErrorsOn = tagExpressions.firstOrNull() ?: return false,
- context = context
- ) ?: run {
- ComposeErrors.INVALID_COMPOSER_IMPLEMENTATION.report(
- context,
- tagExpressions,
- composerType,
- "Couldn't find valid method 'fun joinKey(Any, Any?): Any'"
- )
- return false
- }
-
- return true
- }
-
- fun resolveFromKtxElement(
- element: KtxElement,
- context: ExpressionTypingContext
- ): ResolvedKtxElementCall {
- mainElement = element
-
- val openTagExpr = element.simpleTagName ?: element.qualifiedTagName
- ?: error("shouldn't happen")
- val closeTagExpr = element.simpleClosingTagName ?: element.qualifiedClosingTagName
- val attributes = element.attributes
-
- val tmpTraceAndCache = TemporaryTraceAndCache.create(
- context,
- "trace for ktx tag",
- element
- )
-
- val contextToUse = context.replaceTraceAndCache(tmpTraceAndCache)
-
- val attrInfos = mutableMapOf<String, AttributeInfo>()
-
- for (attr in attributes) {
- var key = attr.key
- var value = attr.value
- val name = key.getReferencedName()
- var isPunned = false
- if (attrInfos.contains(name)) {
- contextToUse.trace.reportFromPlugin(
- ComposeErrors.DUPLICATE_ATTRIBUTE.on(key),
- ComposeDefaultErrorMessages
- )
- }
- if (value == null && attr.equals != null) {
- // this isn't punning because there is an equal sign, but there is not yet a value expression. this happens often
- // when typing and with code completion. To deal with it, we just pass in a fake psi node as the value. This will
- // result in some diagnostics, but it's okay. We want to continue to pass the attribute in as it can affect which
- // target descriptor gets chosen.
- value = psiFactory.createSimpleName("_")
- }
- if (isDeprecatedKtxTagResolve && value == null && attr.equals == null) {
- // punning...
- // punning has a single expression that both acts as reference to the value and to the property/setter. As a result, we
- // need to save the descriptors that it targets into a different writable slice that we can surface later in a
- // reference so that users cam Cmd-Click to either target. Additionally, for the purposes of codegen it's more important
- // that the expression gets resolved to its *value*, and not its *target attribute*. In order to ensure this, we end up
- // running `getTypeInfo` on any attributes that are punned before doing any other resolution.
- val temporaryForPunning = TemporaryTraceAndCache.create(
- contextToUse, "trace to resolve reference for punning", key
- )
-
- facade.getTypeInfo(
- key,
- contextToUse.replaceTraceAndCache(temporaryForPunning)
- )
-
- temporaryForPunning.trace[BindingContext.REFERENCE_TARGET, key]?.let {
- // save the reference into a Set that we can use later in a custom reference
- temporaryForPunning.trace.recordAttributeKeyRef(key, it)
- }
-
- // in the case of a punned attribute, we create a fake psi node for the "key", or else some type resolution stuff will
- // get messed up (for example, smart casting).
- value = key
- key = psiFactory.createSimpleName(name)
- isPunned = true
-
- temporaryForPunning.commit()
- }
-
- attrInfos[name] = AttributeInfo(
- value = value ?: error("expected a value expression"),
- key = key,
- name = name,
- isPunned = isPunned
- )
- }
-
- // The tag expression and the body expression are both implicitly types of "attributes" for the tag, but they
- // aren't explicitly names. As a result, we put them into the `attrInfos` map with special keys
- element.bodyLambdaExpression?.let {
- attrInfos[CHILDREN_KEY] =
- AttributeInfo(
- value = it,
- key = null,
- name = CHILDREN_KEY,
- isPunned = false
- )
- }
-
- // we want to resolve all reference targets on the open tag on the closing tag as well, but we don't want
- // to have to execute the resolution code for both the open and close each time, so we create a binding
- // trace that will observe for traces on the open tag and copy them over to the closing tag if one
- // exists. We choose to only use this trace when we know that useful slices might show up on the tag.
- var traceForOpenClose: BindingTrace = tmpTraceAndCache.trace
- closeTagExpr?.let {
- traceForOpenClose = referenceCopyingTrace(
- openTagExpr,
- closeTagExpr,
- tmpTraceAndCache.trace
- )
- }
- val receiver = resolveReceiver(
- openTagExpr,
- contextToUse.replaceBindingTrace(traceForOpenClose)
- )
-
- attrInfos[TAG_KEY] =
- AttributeInfo(
- value = when (receiver) {
- is ExpressionReceiver -> receiver.expression
- else -> openTagExpr
- },
- key = null,
- name = TAG_KEY,
- isPunned = false
- )
-
- val result = resolve(
- openTagExpr,
- closeTagExpr,
- receiver,
- attrInfos,
- contextToUse
- )
-
- // Instead of committing the whole temp trace, we do so individually because there are some diagnostics that we are going
- // to switch out with others.
- tmpTraceAndCache.cache.commit()
-
- // if the tag target is non-namespaced and there is a closing tag as well, the default "Import" quick fix will not
- // work appropriately. To counteract this, we intercept UNRESOLVED_REFERENCE on the tag target specifically, and replace
- // it with an UNRESOLVED_TAG diagnostic. We have our own import quickfix that knows how to handle this properly.
- // Ideally in the long run we can fix this in a different / better way.
- val isSimpleTag = openTagExpr is KtSimpleNameExpression && closeTagExpr != null
-
- val diagnostics = tmpTraceAndCache.trace.bindingContext.diagnostics
- for (diagnostic in diagnostics) {
- if (isSimpleTag && diagnostic.psiElement in tagExpressions &&
- diagnostic.factory == Errors.UNRESOLVED_REFERENCE) {
- val refExpression = diagnostic.psiElement as KtReferenceExpression
- // here we want to swallow this diagnostic and replace it with UNRESOLVED_TAG, so that the quickfix works properly:
- context.trace.reportFromPlugin(
- ComposeErrors.UNRESOLVED_TAG.on(refExpression, refExpression),
- ComposeDefaultErrorMessages
- )
- } else {
- context.trace.reportDiagnosticOnce(diagnostic)
- }
- }
-
- // commit, but don't include diagnostics
- tmpTraceAndCache.trace.commit({ _, _ -> true }, false)
-
- return result
- }
-
- fun resolveFromCall(
- call: Call,
- context: ExpressionTypingContext
- ): ResolvedKtxElementCall {
- val callee = call.calleeExpression ?: error("Expected calleeExpression")
-
- mainElement = callee
-
- val openTagExpr = callee
- val closeTagExpr = null
-
- val tmpTraceAndCache = TemporaryTraceAndCache.create(
- context,
- "trace for ktx tag",
- callee
- )
-
- val contextToUse = context.replaceTraceAndCache(tmpTraceAndCache)
-
- val attrInfos = mutableMapOf<String, AttributeInfo>()
-
- for (arg in call.valueArguments) {
- if (arg is KtLambdaArgument) continue
- val argName = arg.getArgumentName()
-
- if (argName == null) TODO("indexed arguments not yet supported!")
-
- val key = argName.referenceExpression
- val value = arg.getArgumentExpression()
- val name = argName.asName.asString()
-
- // NOTE: We don't have to check for duplicate argument names, that will be done elsewhere.
-
- // NOTE: We don't have to deal with punning. punning isn't supported in FCS.
-
- attrInfos[name] = AttributeInfo(
- value = value ?: error("expected a value expression"),
- key = key,
- name = name,
- isPunned = false
- )
- }
-
- for (arg in call.functionLiteralArguments) {
- if (attrInfos.containsKey(CHILDREN_KEY))
- error("Only one children argument supported at a time")
- attrInfos[CHILDREN_KEY] =
- AttributeInfo(
- value = arg.getLambdaExpression() ?: error("expected a value expression"),
- key = null,
- name = CHILDREN_KEY,
- isPunned = false
- )
- }
-
- val receiver = resolveReceiver(openTagExpr, contextToUse)
-
- attrInfos[TAG_KEY] =
- AttributeInfo(
- value = when (receiver) {
- is ExpressionReceiver -> receiver.expression
- else -> openTagExpr
- },
- key = null,
- name = TAG_KEY,
- isPunned = false
- )
-
- val result = resolve(
- openTagExpr,
- closeTagExpr,
- receiver,
- attrInfos,
- contextToUse
- )
-
- tmpTraceAndCache.commit()
- return result
- }
-
- fun resolveStartRestartGroup(
- call: Call,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
- return resolveSubstitutableComposerMethod(
- KtxNameConventions.STARTRESTARTGROUP,
- listOf(builtIns.intType),
- null,
- call.calleeExpression ?: error("Could not find call expression"),
- context
- )
- }
-
- fun resolveEndRestartGroup(
- call: Call,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
- return resolveSubstitutableComposerMethod(
- KtxNameConventions.ENDRESTARTGROUP,
- emptyList(),
- null,
- call.calleeExpression ?: error("Could not find call expression"),
- context
- )
- }
-
- fun resolveComposerCall() = composerResolvedCall
-
- /**
- * This call is the main function of this class, and will take in a KtxElement and return an object with all of the information
- * necessary to generate the code for the KTX tag. This method will always return a result, but the result may contain errors
- * and it is the responsibility of the consumer of this class to handle that properly.
- */
- private fun resolve(
- openTagExpr: KtExpression,
- closeTagExpr: KtExpression?,
- receiver: Receiver?,
- attrInfos: Map<String, AttributeInfo>,
- context: ExpressionTypingContext
- ): ResolvedKtxElementCall {
-
- val usedAttributes = mutableSetOf<String>()
-
- val missingRequiredAttributes = mutableListOf<DeclarationDescriptor>()
-
- val emitOrCall = resolveChild(
- openTagExpr,
- ResolveStep.Root(openTagExpr, closeTagExpr),
- makeCall(
- openTagExpr,
- receiver = receiver,
- calleeExpression = when (openTagExpr) {
- is KtQualifiedExpression -> openTagExpr.selectorExpression
- is KtSimpleNameExpression -> openTagExpr
- else -> null
- }
- ),
- attrInfos,
- usedAttributes,
- missingRequiredAttributes,
- context,
- recurseOnUnresolved = false
- )
-
- // TODO(lmr): validate that if it bottoms out at an emit(...) that it doesn't have any call(...)s
-
- val errorNode = emitOrCall.errorNode()
-
- errorNode?.let { error ->
- when (error) {
- is ErrorNode.NonCallableRoot -> {
- if (isDeprecatedKtxTagResolve) {
- val type = facade.getTypeInfo(
- openTagExpr,
- context.withThrowawayTrace(openTagExpr)
- ).type
-
- if (type != null) {
- ComposeErrors.INVALID_TAG_TYPE.report(
- context,
- tagExpressions,
- type,
- emitSimpleUpperBoundTypes
- )
- } else {
- ComposeErrors.INVALID_TAG_DESCRIPTOR.report(
- context,
- tagExpressions,
- emitSimpleUpperBoundTypes
- )
- }
- }
- }
- is ErrorNode.NonEmittableNonCallable -> {
- // TODO(lmr): diagnostic
- // TODO(lmr): we should probably put more info here, saying the composerType and stuff like that
- // "ktx tag terminated with type "Foo", which is neither an emittable, nor callable
- ComposeErrors.INVALID_TAG_TYPE.report(
- context,
- tagExpressions,
- error.type,
- emitSimpleUpperBoundTypes
- )
- }
- is ErrorNode.RecursionLimitAmbiguousAttributesError -> {
- ComposeErrors.AMBIGUOUS_ATTRIBUTES_DETECTED.report(
- context,
- tagExpressions,
- error.attributes
- )
- }
- is ErrorNode.RecursionLimitError -> {
- ComposeErrors.CALLABLE_RECURSION_DETECTED.report(context, tagExpressions)
- }
- }
- }
-
- val constantChecker = ConstantExpressionEvaluator(
- project = project,
- module = context.scope.ownerDescriptor.module,
- languageVersionSettings = context.languageVersionSettings
- )
-
- val attributeNodes = emitOrCall
- .allAttributes()
- .mapNotNull { it as? AttributeNode }
- .groupBy { it.name }
-
- // we want to return a list of the used "AttributeNodes" to the top level call object that we return,
- // so we dig through all of the attributes of the AST and return a unique list. For efficiency, whether
- // or not an AttributeNode is "static" or not is not determined until now, since we don't want to have to
- // calculate it multiple times for the same attribute, so as we loop through this list, we calculate
- // whether it is static just once and then update all of the others as we go through them.
- val usedAttributeNodes = attributeNodes
- .mapValues { it.value.first() }
- .values
- .map { node ->
- val static = isStatic(
- node.expression,
- context,
- node.type,
- constantChecker
- )
-
- val stable = isStable(
- node.type,
- context
- )
-
- // update all of the nodes in the AST as "static"
- attributeNodes[node.name]?.forEach {
- it.isStatic = static
- it.isStable = stable
- }
-
- // return a node for the root of the AST that codegen can use
- AttributeNode(
- name = node.name,
- descriptor = node.descriptor,
- expression = node.expression,
- type = node.type,
- isStatic = static,
- isStable = stable
- )
- }
-
- // it's okay if the tag doesn't show up as used, so we remove it from this list
- val unusedAttributes = (attrInfos - usedAttributes - TAG_KEY).toMutableMap()
-
- if (isDeprecatedKtxTagResolve && unusedAttributes.isNotEmpty()) {
-
- // if we have some unused attributes, we want to provide some helpful diagnostics on them, so we grab
- // every possible attribute for the call. Note that we only want to run this (expensive) calculation in
- // cases where there *were* unused attributes, so the clean compile path should avoid this.
- val allPossibleAttributes = emitOrCall.allPossibleAttributes()
-
- loop@ for (attr in unusedAttributes.values) {
- when (attr.name) {
- CHILDREN_KEY -> {
- if (emitOrCall is EmitCallNode) {
- val type = emitOrCall.memoize.ctorCall?.resultingDescriptor?.returnType
- ?: error("expected a return type")
- if (!type.isCompoundEmittable()) {
- context.trace.reportFromPlugin(
- ComposeErrors.CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED.on(
- openTagExpr
- ),
- ComposeDefaultErrorMessages
- )
- } else {
- // this is a compound emittable, so we will interpret the children block as just code to execute
- unusedAttributes.remove(CHILDREN_KEY)
- }
- } else {
- facade.getTypeInfo(
- attr.value,
- context.replaceExpectedType(functionType().makeComposable(module))
- )
- val possibleChildren = allPossibleAttributes[CHILDREN_KEY]
- ?: emptyList()
- if (possibleChildren.isNotEmpty()) {
- context.trace.reportFromPlugin(
- ComposeErrors.UNRESOLVED_CHILDREN.on(
- openTagExpr,
- possibleChildren.map { it.type }
- ),
- ComposeDefaultErrorMessages
- )
- } else {
- context.trace.reportFromPlugin(
- ComposeErrors.CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED.on(
- openTagExpr
- ),
- ComposeDefaultErrorMessages
- )
- }
- }
- }
- else -> {
- val exprToReportOn = if (attr.isPunned) attr.value else (attr.key
- ?: error("expected non-null key expression"))
- val valueType = facade.getTypeInfo(attr.value, context).type
- // if the value expression is an unresolved reference, we don't need to put a diagnostic on the key
- val valueIsUnresolvedRef = attr.value is KtReferenceExpression &&
- context.trace[BindingContext.REFERENCE_TARGET, attr.value] != null
-
- val descriptors = emitOrCall.resolvedCalls().flatMap {
- listOfNotNull(
- it.resultingDescriptor,
- it.resultingDescriptor.returnType?.let { t ->
- if (t.isUnit()) null
- else t.constructor.declarationDescriptor
- }
- )
- }
-
- // since extension functions won't show up if we just traverse the member scope of the types in our call,
- // we might be giving inaccurate diagnostics around what types are accepted for this attribute. Since we have
- // the name, we can search for all possible candidates of attributes on a given node. This is expensive, but
- // since we are only in the erroneous path, it shouldn't be a big deal.
- val attrsOfSameKey = resolveAttributeCandidatesGivenNameAndNode(
- emitOrCall,
- attr.name,
- context.withThrowawayTrace(openTagExpr)
- )
-
- if (attrsOfSameKey.isNotEmpty()) {
- // NOTE(lmr): it would be great if we could record multiple possible types here instead of just one for
- // autocomplete
- context.trace.record(
- BindingContext.EXPECTED_EXPRESSION_TYPE,
- attr.value,
- attrsOfSameKey.first().type
- )
-
- // even if the type doesn't match the attribute, we should resolve it to something
- context.trace.record(
- BindingContext.REFERENCE_TARGET,
- attr.key,
- attrsOfSameKey.first().descriptor
- )
-
- // we can add all of the possible key targets here so that the user can Command+Click to see the list of
- // possible values
- attrsOfSameKey.forEach {
- context.trace.recordAttributeKeyRef(exprToReportOn, it.descriptor)
- }
- }
-
- val diagnostic = when {
- attrsOfSameKey.isNotEmpty() && valueType != null ->
- ComposeErrors.MISMATCHED_ATTRIBUTE_TYPE.on(
- exprToReportOn,
- valueType,
- attrsOfSameKey.map { it.type }.toSet()
- )
- attrsOfSameKey.isEmpty() && valueType != null ->
- ComposeErrors.UNRESOLVED_ATTRIBUTE_KEY.on(
- exprToReportOn,
- descriptors,
- attr.name,
- valueType
- )
- attrsOfSameKey.isNotEmpty() && valueType == null
- && !valueIsUnresolvedRef ->
- ComposeErrors.MISMATCHED_ATTRIBUTE_TYPE.on(
- exprToReportOn,
- ErrorUtils.createErrorType("???"),
- attrsOfSameKey.map { it.type }.toSet()
- )
- else ->
- ComposeErrors.UNRESOLVED_ATTRIBUTE_KEY_UNKNOWN_TYPE.on(
- exprToReportOn,
- descriptors,
- attr.name
- )
- }
-
- context.trace.reportFromPlugin(diagnostic,
- ComposeDefaultErrorMessages
- )
- }
- }
- }
- }
-
- if (missingRequiredAttributes.isNotEmpty()) {
- missingRequiredAttributes
- .filter { !it.hasChildrenAnnotation() }
- .ifNotEmpty {
- ComposeErrors.MISSING_REQUIRED_ATTRIBUTES.report(
- context,
- tagExpressions,
- this
- )
- }
- missingRequiredAttributes
- .filter { it.hasChildrenAnnotation() }
- .ifNotEmpty {
- ComposeErrors.MISSING_REQUIRED_CHILDREN.report(
- context,
- tagExpressions,
- first().typeAsAttribute()
- )
- }
- }
-
- // for each attribute we've consumed, we want to go through and call `checkType` so that the type system can flow through to
- // all of the attributes with the right type information (now that we know what types the attributes should have).
- if (isDeprecatedKtxTagResolve) {
- for (name in usedAttributes) {
- val expr = attrInfos[name]?.value ?: continue
- var type = usedAttributeNodes.find { it.name == name }?.type
- if (type == null && name == CHILDREN_KEY) {
- type = functionType().makeComposable(module)
- }
-
- facade.checkType(
- expr,
- context.replaceExpectedType(type)
- )
- }
- }
-
- return ResolvedKtxElementCall(
- usedAttributes = usedAttributeNodes,
- unusedAttributes = unusedAttributes.keys.toList(),
- emitOrCall = emitOrCall,
- getComposerCall = composerResolvedCall,
- emitSimpleUpperBoundTypes = emitSimpleUpperBoundTypes,
- emitCompoundUpperBoundTypes = emitCompoundUpperBoundTypes,
- infixOrCall = infixOrCall,
- attributeInfos = attrInfos
- )
- }
-
- private fun resolveAttributeCandidatesGivenNameAndNode(
- node: EmitOrCallNode,
- name: String,
- context: ExpressionTypingContext
- ): List<AttributeMeta> {
- val setterName = Name.identifier(
- ComposeUtils.setterMethodFromPropertyName(
- name
- )
- )
- val fakeSetterExpr = psiFactory.createSimpleName(setterName.asString())
- val fakePropertyExpr = psiFactory.createSimpleName(name)
- val contextToUse = context.replaceCollectAllCandidates(true)
- val resolvedCalls = node.resolvedCalls()
-
- val params = resolvedCalls
- .flatMap { it.resultingDescriptor.valueParameters }
- .filter { it.name.asString() == name }
- .mapNotNull {
- AttributeMeta(
- name = name,
- type = it.type,
- isChildren = it.hasChildrenAnnotation(),
- descriptor = it
- )
- }
-
- val types = resolvedCalls
- .mapNotNull { it.resultingDescriptor.returnType }
- .filter { !it.isUnit() }
-
- // setters, including extension setters
- val setters = types
- .flatMap { type ->
- val call = makeCall(
- callElement = fakeSetterExpr,
- calleeExpression = fakeSetterExpr,
- receiver = TransientReceiver(type)
- )
-
- callResolver.resolveCallWithGivenName(
- BasicCallResolutionContext.create(
- contextToUse,
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(contextToUse.dataFlowInfo, call)
- ),
- call,
- fakeSetterExpr,
- setterName
- ).allCandidates ?: emptyList()
- }
- .mapNotNull { it.resultingDescriptor as? SimpleFunctionDescriptor }
- .mapNotNull {
- when {
- it.valueParameters.size != 1 -> null
- it.returnType?.isUnit() == false -> null
- else -> AttributeMeta(
- name = name,
- type = it.valueParameters.first().type,
- isChildren = it.hasChildrenAnnotation(),
- descriptor = it
- )
- }
- }
-
- val properties = types
- .flatMap { type ->
- val call = CallMaker.makePropertyCall(
- TransientReceiver(type),
- null,
- fakePropertyExpr
- )
-
- val contextForVariable = BasicCallResolutionContext.create(
- contextToUse,
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS
- )
-
- callResolver.resolveSimpleProperty(contextForVariable).allCandidates ?: emptyList()
- }
- .mapNotNull { it.resultingDescriptor as? PropertyDescriptor }
- .map {
- AttributeMeta(
- name = name,
- type = it.type,
- isChildren = it.hasChildrenAnnotation(),
- descriptor = it
- )
- }
-
- return params + setters + properties
- }
-
- // pure. check where used
- private fun shouldMemoizeResult(resolvedCall: ResolvedCall<*>): Boolean {
- val descriptor = resolvedCall.resultingDescriptor
- val returnType = descriptor.returnType ?: return false
- return returnType.shouldMemoizeType() || descriptor.hasStatefulAnnotation()
- }
-
- private fun KotlinType.shouldMemoizeType(): Boolean {
- if (isUnit() || isAnyOrNullableAny()) return false
- val typeDescriptor = constructor.declarationDescriptor
- return hasStatefulAnnotation() ||
- supertypes().any { it.shouldMemoizeType() } ||
- (typeDescriptor?.hasStatefulAnnotation() ?: false)
- }
-
- private fun resolveTagValidations(
- kind: ComposerCallKind,
- step: ResolveStep,
- resolvedCall: ResolvedCall<*>,
- receiverScope: KotlinType,
- context: ExpressionTypingContext
- ): List<ValidatedAssignment> {
- if (step !is ResolveStep.Root) return emptyList()
- val descriptor = resolvedCall.resultingDescriptor
- when (resolvedCall.explicitReceiverKind) {
- ExplicitReceiverKind.DISPATCH_RECEIVER -> {
- val receiver = resolvedCall.dispatchReceiver as? ExpressionReceiver
- ?: return emptyList()
- val (validationCall, uncheckedValidationCall, _) = resolveValidationCall(
- kind = kind,
- validationType = ValidationType.CHANGED,
- attrType = receiver.type,
- expressionToReportErrorsOn = receiver.expression,
- receiverScope = receiverScope,
- assignmentReceiverScope = null,
- context = context
- )
- return listOf(
- ValidatedAssignment(
- validationType = ValidationType.CHANGED,
- validationCall = validationCall,
- uncheckedValidationCall = uncheckedValidationCall,
- assignment = null,
- assignmentLambda = null,
- attribute = AttributeNode(
- name = TAG_KEY,
- isStatic = false,
- isStable = false,
- type = receiver.type,
- expression = receiver.expression,
- descriptor = descriptor
- )
- )
- )
- }
- else -> return emptyList()
- }
- }
-
- // Loop through all of the validated assignments for the child call and create validations for the parent call.
- // The validations from the child call should be converted into CHANGED validations because if they were set/update we
- // don't want to do anything but we do want changes in them to invalidate the whole group.
- private fun collectValidations(
- kind: ComposerCallKind,
- current: List<ValidatedAssignment>,
- children: List<AttributeNode>,
- expression: KtExpression,
- invalidReceiverScope: KotlinType,
- attributes: Map<String, AttributeInfo>,
- context: ExpressionTypingContext
- ): List<ValidatedAssignment> {
- val result = mutableMapOf<String, ValidatedAssignment>()
-
- current.forEach {
- result[it.attribute.name] = it
- }
-
- children.forEach {
- if (result.containsKey(it.name)) return@forEach
- val attr = attributes[it.name] ?: error("did not find attribute")
- result[it.name] = it.asChangedValidatedAssignment(
- kind = kind,
- expressionToReportErrorsOn = attr.key ?: expression,
- receiverScope = invalidReceiverScope,
- context = context
- )
- }
-
- return result.values.toList()
- }
-
- private fun resolveChild(
- expression: KtExpression,
- resolveStep: ResolveStep,
- call: Call,
- attributes: Map<String, AttributeInfo>,
- usedAttributes: MutableSet<String>,
- missingRequiredAttributes: MutableList<DeclarationDescriptor>,
- context: ExpressionTypingContext,
- recurseOnUnresolved: Boolean
- ): EmitOrCallNode {
- if (!resolveStep.canRecurse()) {
- return when (resolveStep) {
- is ResolveStep.Root -> error("should never happen")
- is ResolveStep.Nested -> {
- resolveStep.constructNonMemoizedCallLinkedList()?.apply {
- nextCall = resolveStep.errorNode
- } ?: resolveStep.errorNode!!
- }
- }
- }
- val tmpForCandidates = TemporaryTraceAndCache.create(
- context, "trace to resolve ktx element", expression
- )
- val results = getCandidates(
- resolveStep,
- call,
- context.replaceTraceAndCache(tmpForCandidates)
- )
-
- if (results.allCandidates?.size == 0) {
- return when (resolveStep) {
- is ResolveStep.Root -> {
- // if the root tag failed to resolve to anything, then the tag isn't even callable. The call resolver will
- // add useful diagnostics in this case that we'd like to use, but it will only do it when we are not in
- // the "collectAllCandidates" mode. We just call `getCandidates` again to put all of the diagnostics on the element
- // that we want.
- getCandidates(resolveStep, call, context, collectAllCandidates = false)
- ErrorNode.NonCallableRoot()
- }
- is ResolveStep.Nested -> {
- val error = ErrorNode.NonEmittableNonCallable(resolveStep.calleeType)
- resolveStep.constructNonMemoizedCallLinkedList()?.apply { nextCall = error }
- ?: error
- }
- }
- }
-
- // TODO(lmr): we could have an optimization for results.isSuccess and attributes.size == 0 here
-
- val resolveInfos = results.allCandidates!!.mapNotNull { result ->
- val tmpForCandidate = TemporaryTraceAndCache.create(
- context, "trace to resolve ktx element", expression
- )
-
- var trace: BindingTrace = tmpForCandidate.trace
-
- if (resolveStep is ResolveStep.Root) {
- resolveStep.closeExpr?.let {
- trace = referenceCopyingTrace(
- resolveStep.openExpr,
- resolveStep.closeExpr,
- tmpForCandidate.trace
- )
- }
- // not sure why this is needed, but it is
- copyReferences(
- fromTrace = tmpForCandidate.trace,
- toTrace = trace,
- element = resolveStep.openExpr
- )
- }
-
- val candidateContext = context
- .replaceTraceAndCache(tmpForCandidate)
- .replaceBindingTrace(trace)
-
- val attrsUsedInCall = mutableSetOf<String>()
-
- val attrsUsedInSets = mutableSetOf<String>()
-
- val subMissingRequiredAttributes = mutableListOf<DeclarationDescriptor>()
-
- val usedAttributeInfos = mutableListOf<TempParameterInfo>()
-
- val candidateResults = resolveCandidate(
- resolveStep,
- result,
- call,
- attributes,
- attrsUsedInCall,
- usedAttributeInfos,
- subMissingRequiredAttributes,
- candidateContext
- )
-
- if (candidateResults.isNothing)
- return@mapNotNull TempResolveInfo(
- false,
- tmpForCandidate,
- (attributes - attrsUsedInCall).keys,
- attrsUsedInCall,
- subMissingRequiredAttributes
- ) {
- ErrorNode.ResolveError(results)
- }
-
- val resolvedCall = candidateResults.resultingCalls.first()
-
- if (!candidateResults.isSuccess) {
- when (candidateResults.resultCode) {
- OverloadResolutionResults.Code.SINGLE_CANDIDATE_ARGUMENT_MISMATCH -> {
- resolvedCall.call.valueArguments.map {
- resolvedCall.getArgumentMapping(it)
- }.forEach {
- when (it) {
- is ArgumentMatch -> {
- when (it.status) {
- ArgumentMatchStatus.TYPE_MISMATCH -> {
- if (!isDeprecatedKtxTagResolve) return@forEach
- val attr =
- attributes[it.valueParameter.name.asString()]
- ?: return@forEach
- val key = attr.key ?: return@forEach
- val type =
- facade.getTypeInfo(
- attr.value,
- candidateContext
- ).type ?: return@forEach
- candidateContext.trace.reportFromPlugin(
- ComposeErrors.MISMATCHED_ATTRIBUTE_TYPE.on(
- key,
- type,
- listOfNotNull(it.valueParameter.type)
- ),
- ComposeDefaultErrorMessages
- )
- }
- ArgumentMatchStatus.MATCH_MODULO_UNINFERRED_TYPES -> {
- if (!isDeprecatedKtxTagResolve) return@forEach
- val attr = attributes[it.valueParameter.name.asString()]
- ?: return@forEach
- val key = attr.key ?: return@forEach
- val type =
- facade.getTypeInfo(
- attr.value,
- candidateContext
- ).type ?: return@forEach
-
- candidateContext.trace.reportFromPlugin(
- ComposeErrors.MISMATCHED_INFERRED_ATTRIBUTE_TYPE.on(
- key,
- type,
- listOfNotNull(it.valueParameter.type)
- ),
- ComposeDefaultErrorMessages
- )
- }
- ArgumentMatchStatus.UNKNOWN -> {
- // NOTE(lmr): This can happen with the implicit constructor params. ignore it
- }
- ArgumentMatchStatus.SUCCESS -> {
- // do nothing
- }
- ArgumentMatchStatus.ARGUMENT_HAS_NO_TYPE -> {
- // NOTE(lmr): This can happen when the attribute has no value expression, since
- // we pass in a fake psi node for the value in that case.
- }
- }
- }
- is ArgumentUnmapped -> {
- if (!recurseOnUnresolved) {
- return@mapNotNull TempResolveInfo(
- false,
- tmpForCandidate,
- (attributes - attrsUsedInCall).keys,
- attrsUsedInCall,
- subMissingRequiredAttributes
- ) {
- ErrorNode.ResolveError(
- results
- )
- }
- }
- }
- }
- }
- }
- OverloadResolutionResults.Code.INCOMPLETE_TYPE_INFERENCE,
- OverloadResolutionResults.Code.MANY_FAILED_CANDIDATES,
- OverloadResolutionResults.Code.CANDIDATES_WITH_WRONG_RECEIVER -> {
- if (!recurseOnUnresolved) {
- return@mapNotNull TempResolveInfo(
- false,
- tmpForCandidate,
- (attributes - attrsUsedInCall).keys,
- attrsUsedInCall,
- subMissingRequiredAttributes
- ) {
- ErrorNode.ResolveError(results)
- }
- } else {
- candidateContext.trace.recordFailedCandidates(
- mainElement,
- results.allCandidates
- )
- }
- }
- else -> {
- error("new kind of resolution problem. figure out why this happened...")
- }
- }
- }
-
- if (
- !descriptorsEqualWithSubstitution(
- resolvedCall.semanticCall.candidateDescriptor.original,
- result.semanticCall.candidateDescriptor.original,
- false
- )
- ) {
- // if this happens, we've likely resolved the candidate to one of the other candidates, and we don't want that.
- // we fail in this case, since it will succeed once we actually process the candidate that this resolved to.
- return@mapNotNull TempResolveInfo(
- false,
- tmpForCandidate,
- (attributes - attrsUsedInCall).keys,
- attrsUsedInCall,
- subMissingRequiredAttributes
- ) {
- ErrorNode.ResolveError(results)
- }
- }
-
- val returnType = resolvedCall.resultingDescriptor.returnType ?: builtIns.unitType
-
- val isStaticCall = isStaticTag(
- resolveStep,
- resolvedCall,
- candidateContext
- )
-
- val shouldMemoizeCtor = returnType.isEmittable() || shouldMemoizeResult(resolvedCall)
-
- if (returnType.isUnit()) {
- // bottomed out
-
- // it is important to pass in "result" here and not "resolvedCall" since "result" is the one that will have
- // the composable annotation on it in the case of lambda invokes
- if (
- !composableAnnotationChecker.shouldInvokeAsTag(
- candidateContext.trace, result
- )
- ) {
- candidateContext.trace.reportFromPlugin(
- ComposeErrors.NON_COMPOSABLE_INVOCATION.on(
- expression,
- "Lambda variable",
- resolvedCall.semanticCall.resultingDescriptor
- ),
- ComposeDefaultErrorMessages
- )
- }
-
- return@mapNotNull TempResolveInfo(
- true, // TODO(lmr): valid
- tmpForCandidate,
- (attributes - attrsUsedInCall).keys,
- attrsUsedInCall,
- subMissingRequiredAttributes
- ) {
- val composerCall = resolveComposerCall(
- constructedType = null, // or should we pass in Unit here?
- expressionToReportErrorsOn = expression,
- context = candidateContext
- )
-
- val invalidReceiverScope = composerCall
- ?.resultingDescriptor
- ?.valueParameters
- ?.first { it.name == KtxNameConventions.CALL_INVALID_PARAMETER }
- ?.type
- ?.getReceiverTypeFromFunctionType()
- ?: ErrorUtils.createErrorType("???")
-
- val tagValidations = resolveTagValidations(
- kind = ComposerCallKind.CALL,
- step = resolveStep,
- resolvedCall = resolvedCall,
- receiverScope = invalidReceiverScope,
- context = candidateContext
- )
-
- val pivotals = resolvePivotalAttributes(
- attributes,
- attrsUsedInCall,
- usedAttributeInfos,
- emptyList(),
- returnType
- )
-
- val nonMemoizedCall = NonMemoizedCallNode(
- resolvedCall = resolvedCall,
- params = resolvedCall.buildParamsFromAttributes(attributes),
- applyCall = null,
- applyLambdaDescriptor = null,
- applyLambdaType = null,
- postAssignments = emptyList(),
- nextCall = null
- )
-
- if (resolveStep is ResolveStep.Root) {
- MemoizedCallNode(
- memoize = ComposerCallInfo(
- context = candidateContext,
- composerCall = composerCall,
- pivotals = pivotals,
- joinKeyCall = joinKeyCall,
- ctorCall = null,
- ctorParams = emptyList(),
- validations = collectValidations(
- kind = ComposerCallKind.CALL,
- current = tagValidations,
- children = nonMemoizedCall.consumedAttributes(),
- expression = expression,
- attributes = attributes,
- invalidReceiverScope = invalidReceiverScope,
- context = candidateContext
- )
- ),
- call = resolveStep.constructNonMemoizedCallLinkedList(nonMemoizedCall)
- )
- } else {
- resolveStep.constructNonMemoizedCallLinkedList(nonMemoizedCall)
- }
- }
- }
-
- var inlineChildren: KtExpression? = null
- if (returnType.isEmittable() == true) {
-
- val composerCall = resolveComposerEmit(
- constructedType = returnType,
- hasBody = attributes.contains(CHILDREN_KEY),
- implicitCtorTypes = resolvedCall.call.valueArguments.mapNotNull {
- (it as? ImplicitCtorValueArgument)?.type
- },
- expressionToReportErrorsOn = expression,
- context = candidateContext
- )
- ?: return@mapNotNull TempResolveInfo(
- false,
- tmpForCandidate,
- (attributes - attrsUsedInCall - attrsUsedInSets).keys,
- attrsUsedInCall + attrsUsedInSets,
- subMissingRequiredAttributes
- ) {
- ErrorNode.ResolveError(results)
- }
-
- if (attributes.contains(CHILDREN_KEY) && returnType.isCompoundEmittable()) {
- attrsUsedInSets.add(CHILDREN_KEY)
- inlineChildren = attributes.get(CHILDREN_KEY)!!.value
- }
-
- val updateReceiverScope = composerCall
- .resultingDescriptor
- .valueParameters
- .first { it.name == KtxNameConventions.EMIT_UPDATER_PARAMETER }
- .type
- .getReceiverTypeFromFunctionType() ?: error("Expected receiver type")
-
- val setterValidations = resolveAllSetAttributes(
- kind = ComposerCallKind.EMIT,
- expressionToReportErrorsOn = expression,
- receiverScope = updateReceiverScope,
- type = returnType,
- attributes = attributes,
- attributesUsedInCall = attrsUsedInCall,
- consumedAttributes = attrsUsedInSets,
- missingRequiredAttributes = subMissingRequiredAttributes,
- shouldMemoizeCtor = shouldMemoizeCtor,
- context = candidateContext
- )
-
- val pivotals = resolvePivotalAttributes(
- attributes,
- attrsUsedInCall,
- usedAttributeInfos,
- setterValidations,
- returnType
- )
-
- return@mapNotNull TempResolveInfo(
- true,
- tmpForCandidate,
- (attributes - attrsUsedInCall - attrsUsedInSets).keys,
- attrsUsedInCall + attrsUsedInSets,
- subMissingRequiredAttributes
- ) {
- EmitCallNode(
- memoize = ComposerCallInfo(
- context = candidateContext,
- composerCall = composerCall,
- pivotals = pivotals,
- joinKeyCall = joinKeyCall,
- ctorCall = resolvedCall,
- ctorParams = resolvedCall.buildParamsFromAttributes(attributes),
- validations = setterValidations
- ),
- inlineChildren = inlineChildren
- )
- }
- }
-
- val composerCall = resolveComposerCall(
- constructedType = if (shouldMemoizeCtor) returnType else null,
- expressionToReportErrorsOn = expression,
- context = candidateContext
- )
-
- // the "invalid" lambda is at a different argument index depending on whether or not there is a "ctor" param.
- val invalidReceiverScope = composerCall
- ?.resultingDescriptor
- ?.valueParameters
- ?.first { it.name == KtxNameConventions.CALL_INVALID_PARAMETER }
- ?.type
- ?.getReceiverTypeFromFunctionType()
- ?: ErrorUtils.createErrorType("???")
-
- val tagValidations = resolveTagValidations(
- kind = ComposerCallKind.CALL,
- step = resolveStep,
- resolvedCall = resolvedCall,
- receiverScope = invalidReceiverScope,
- context = candidateContext
- )
-
- val setterValidations = resolveAllSetAttributes(
- kind = ComposerCallKind.CALL,
- expressionToReportErrorsOn = expression,
- receiverScope = invalidReceiverScope,
- type = returnType,
- attributes = attributes,
- attributesUsedInCall = attrsUsedInCall,
- consumedAttributes = attrsUsedInSets,
- missingRequiredAttributes = subMissingRequiredAttributes,
- shouldMemoizeCtor = shouldMemoizeCtor,
- context = candidateContext
- )
-
- val pivotals = resolvePivotalAttributes(
- attributes,
- attrsUsedInCall,
- usedAttributeInfos,
- setterValidations,
- returnType
- )
-
- val attrsUsedInFollowingCalls = mutableSetOf<String>()
-
- // TODO(lmr): clean this up a little bit
- val postAssignments = if (!shouldMemoizeCtor) {
- (tagValidations + setterValidations)
- .filter { it.validationType != ValidationType.CHANGED }
- } else emptyList()
-
- val allValidations = if (!shouldMemoizeCtor) {
- (tagValidations + setterValidations).map { validation ->
- when (validation.validationType) {
- ValidationType.CHANGED -> validation
- ValidationType.UPDATE,
- ValidationType.SET -> resolveValidationCall(
- kind = ComposerCallKind.CALL,
- validationType = ValidationType.CHANGED,
- attrType = validation.attribute.type,
- expressionToReportErrorsOn = expression,
- receiverScope = invalidReceiverScope,
- assignmentReceiverScope = null,
- context = context
- ).let {
- val (validationCall, uncheckedValidationCall, _) = it
- ValidatedAssignment(
- validationType = ValidationType.CHANGED,
- validationCall = validationCall,
- uncheckedValidationCall = uncheckedValidationCall,
- assignment = null,
- attribute = validation.attribute,
- assignmentLambda = null
- )
- }
- }
- }
- } else tagValidations + setterValidations
-
- val applyCall = if (!shouldMemoizeCtor) {
- resolveApplyCallForType(returnType, candidateContext)
- } else null
-
- val applyLambdaType = if (!shouldMemoizeCtor)
- functionType(receiverType = returnType)
- else null
- val applyLambdaDescriptor = if (!shouldMemoizeCtor) {
- createFunctionDescriptor(
- applyLambdaType!!,
- candidateContext
- )
- } else null
-
- val nonMemoizedCall = NonMemoizedCallNode(
- resolvedCall = resolvedCall,
- params = resolvedCall.buildParamsFromAttributes(attributes),
- applyCall = applyCall,
- applyLambdaDescriptor = applyLambdaDescriptor,
- postAssignments = postAssignments,
- applyLambdaType = applyLambdaType,
- nextCall = null
- )
-
- val childCall = resolveChild(
- expression,
- resolveStep.recurse(
- calleeType = returnType,
- nonMemoizedCall = if (shouldMemoizeCtor) null else nonMemoizedCall,
- isStaticCall = isStaticCall,
- resolvedCall = resolvedCall,
- specifiedAttributes = attrsUsedInCall + attrsUsedInSets
- ),
- makeCall(
- psiFactory.createSimpleName("invoke"),
- dispatchReceiver = TransientReceiver(returnType)
- ),
- attributes,
- attrsUsedInFollowingCalls,
- subMissingRequiredAttributes,
- candidateContext,
- recurseOnUnresolved
- )
-
- val subUsedAttributes = attrsUsedInCall + attrsUsedInSets + attrsUsedInFollowingCalls
-
- return@mapNotNull TempResolveInfo(
- true, // TODO(lmr): valid
- tmpForCandidate,
- (attributes - subUsedAttributes).keys,
- subUsedAttributes,
- subMissingRequiredAttributes
- ) {
- if (shouldMemoizeCtor || resolveStep is ResolveStep.Root) {
- MemoizedCallNode(
- memoize = ComposerCallInfo(
- context = candidateContext,
- composerCall = composerCall,
- pivotals = pivotals,
- joinKeyCall = joinKeyCall,
- ctorCall = if (shouldMemoizeCtor)
- nonMemoizedCall.resolvedCall
- else null,
- ctorParams = if (shouldMemoizeCtor)
- nonMemoizedCall.params
- else emptyList(),
- validations = collectValidations(
- kind = ComposerCallKind.CALL,
- current = allValidations,
- children = childCall.consumedAttributes(),
- expression = expression,
- attributes = attributes,
- invalidReceiverScope = invalidReceiverScope,
- context = candidateContext
- )
- ),
- call = childCall
- )
- } else {
- childCall
- }
- }
- }
- .sortedWith(Comparator { a, b ->
- // TODO(lmr): should we also use missingRequiredAttributes to sort here?
- if (a.attributesLeft != b.attributesLeft) {
- a.attributesLeft - b.attributesLeft
- } else {
- (if (a.valid) 0 else 1) - (if (b.valid) 0 else 1)
- }
- }).toList()
-
- val result = resolveInfos.first()
-
- if (!recurseOnUnresolved && result.attributesLeft > 0 &&
- !results.allCandidates.isNullOrEmpty()) {
- // Looking up all possible combinations of resolved calls to satisfy a KTX element can be expensive, so normally we try to do as
- // few lookups as possible. But if we failed (ie, there is for sure an error), then we go ahead and run it again with a flag
- // set that explores all options so that we can provide the most meaningful diagnostics.
- return resolveChild(
- expression,
- resolveStep,
- call,
- attributes,
- usedAttributes,
- missingRequiredAttributes,
- context,
- recurseOnUnresolved = true
- )
- }
-
- val resultNode = result.build()
- usedAttributes.addAll(result.usedAttributes)
- missingRequiredAttributes.addAll(result.missingRequiredAttributes)
- result.trace.commit()
-
- if (resolveInfos.size > 1) {
- val nextBest = resolveInfos[1]
- if (result.attributesLeft == 0 && result.attributesLeft
- == nextBest.attributesLeft && nextBest.valid) {
- val nextNode = nextBest.build()
- val primaryCalls = listOfNotNull(
- resultNode.primaryCall,
- nextNode.primaryCall
- ).distinctBy { it.semanticCall.resultingDescriptor }
- if (primaryCalls.size > 1) {
- ComposeErrors.AMBIGUOUS_KTX_CALL.report(
- context,
- tagExpressions,
- primaryCalls
- )
- }
- }
- }
- return resultNode
- }
-
- private fun resolvePivotalAttributes(
- attributes: Map<String, AttributeInfo>,
- attrsUsedInCall: Set<String>,
- callParamInfos: List<TempParameterInfo>,
- validations: List<ValidatedAssignment>,
- returnType: KotlinType?
- ): List<AttributeNode> {
- val result = mutableListOf<AttributeNode>()
-
- if (returnType == null || returnType.isUnit()) {
- return callParamInfos
- .filter { it.descriptor.hasPivotalAnnotation() }
- .map {
- AttributeNode(
- name = it.attribute.name,
- descriptor = it.descriptor,
- type = it.type,
- expression = it.attribute.value,
- isStatic = false,
- isStable = false
- )
- }
- }
-
- val validationSet = validations.map { it.attribute.name }.toSet()
-
- // if you were in the ctor call but not in the sets, you *have* to be pivotal
- for (info in callParamInfos) {
- if (validationSet.contains(info.attribute.name)) continue
- val attribute = attributes[info.attribute.name] ?: continue
- result.add(
- AttributeNode(
- name = info.attribute.name,
- descriptor = info.descriptor,
- type = info.type,
- expression = attribute.value,
- isStatic = false,
- isStable = false
- )
- )
- }
-
- // There are additional cases where attributes can be pivotal:
- // 1. It is annotated as @Pivotal
- // 2. It is a `val` ctor parameter
- for (assignment in validations) {
- val attribute = assignment.attribute
- val name = attribute.name
- val descriptor = attribute.descriptor
-
- if (descriptor.hasPivotalAnnotation()) {
- result.add(
- AttributeNode(
- name = name,
- descriptor = descriptor,
- type = attribute.type,
- expression = attribute.expression,
- isStatic = false,
- isStable = false
- )
- )
- continue
- }
- if (descriptor is PropertyDescriptor &&
- attrsUsedInCall.contains(name) && !descriptor.isVar) {
- result.add(
- AttributeNode(
- name = name,
- descriptor = descriptor,
- type = attribute.type,
- expression = attribute.expression,
- isStatic = false,
- isStable = false
- )
- )
- continue
- }
- }
-
- return result
- }
-
- private fun ResolvedCall<*>.buildParamsFromAttributes(
- attributes: Map<String, AttributeInfo>
- ): List<ValueNode> {
- val possbileChildrenParam = valueArguments.keys.possibleChildrenParameter
- return valueArguments.map { (param, value) ->
- val name = param.name.asString()
- var type = param.type
- var attr = attributes[name]
-
- if (param.hasChildrenAnnotation() || (param == possbileChildrenParam && attr == null)) {
- val childrenAttr = attributes[CHILDREN_KEY]
- if (childrenAttr != null) {
- attr = childrenAttr
- }
- if (param.isComposableFromChildrenAnnotation()) {
- type = type.makeComposable(module)
- }
- }
-
- if (value is DefaultValueArgument) {
- return@map DefaultValueNode(
- name = name,
- descriptor = param,
- type = type
- )
- }
-
- if (attr == null && isImplicitConstructorParam(param, resultingDescriptor)) {
- return@map ImplicitCtorValueNode(
- name = name,
- descriptor = param,
- type = type
- )
- }
-
- if (attr == null) {
- error("Couldn't find attribute but expected to. param=$param name=$name")
- }
-
- AttributeNode(
- name = attr.name,
- isStatic = false,
- isStable = false,
- descriptor = param,
- type = type,
- expression = attr.value
- )
- }
- }
-
- // pure, can be moved out
- private fun AttributeNode.asChangedValidatedAssignment(
- kind: ComposerCallKind,
- expressionToReportErrorsOn: KtExpression,
- receiverScope: KotlinType,
- context: ExpressionTypingContext
- ): ValidatedAssignment {
- val (validationCall, uncheckedValidationCall, _) = resolveValidationCall(
- kind = kind,
- validationType = ValidationType.CHANGED,
- attrType = type,
- expressionToReportErrorsOn = expressionToReportErrorsOn,
- receiverScope = receiverScope,
- assignmentReceiverScope = null,
- context = context
- )
-
- return ValidatedAssignment(
- validationType = ValidationType.CHANGED,
- validationCall = validationCall,
- uncheckedValidationCall = uncheckedValidationCall,
- attribute = this,
- assignment = null,
- assignmentLambda = null
- )
- }
-
- private fun resolveAllSetAttributes(
- kind: ComposerCallKind,
- expressionToReportErrorsOn: KtExpression,
- receiverScope: KotlinType,
- type: KotlinType?,
- attributes: Map<String, AttributeInfo>,
- attributesUsedInCall: Set<String>,
- consumedAttributes: MutableSet<String>,
- missingRequiredAttributes: MutableList<DeclarationDescriptor>,
- shouldMemoizeCtor: Boolean,
- context: ExpressionTypingContext
- ): List<ValidatedAssignment> {
- if (type == null) return emptyList()
- val results = mutableListOf<ValidatedAssignment>()
- var children: AttributeInfo? = null
-
- val tempForAttributes = TemporaryTraceAndCache.create(
- context, "temp for attributes", expressionToReportErrorsOn
- )
-
- val tempForValidations = TemporaryTraceAndCache.create(
- context, "temp for validations", expressionToReportErrorsOn
- )
-
- for ((name, attribute) in attributes) {
- if (name == TAG_KEY) continue
- if (name == CHILDREN_KEY) {
- children = attribute
- continue
- }
- val keyExpr = attribute.key ?: error("key expected")
-
- val expectedTypes = mutableListOf<KotlinType>()
-
- var resolvedCall: ResolvedCall<*>? = null
-
- // NOTE(lmr): A ktx element that has access (like its a recursive call or a nested class) to the private property
- // of the tag will be able to set it as an attribute... I'm not sure if that'a s good thing or not, but unless we
- // do something extra, that is indeed possible. Perhaps it's something we should look into.
-
- if (resolvedCall == null) {
- resolvedCall = resolveAttributeAsSetter(
- type,
- attribute.name,
- keyExpr,
- attribute.value,
- attribute.isPunned,
- expectedTypes,
- context.replaceTraceAndCache(tempForAttributes)
- )
- }
-
- if (resolvedCall == null) {
- resolvedCall = resolveAttributeAsProperty(
- type,
- keyExpr,
- attribute.value,
- attribute.isPunned,
- expectedTypes,
- context.replaceTraceAndCache(tempForAttributes)
- )
- }
-
- if (resolvedCall != null) {
-
- val validationType = when {
- !shouldMemoizeCtor && attributesUsedInCall.contains(name) ->
- ValidationType.CHANGED
- attributesUsedInCall.contains(name) -> ValidationType.UPDATE
- else -> ValidationType.SET
- }
-
- val descriptor = resolvedCall.resultingDescriptor
-
- val attrType = when (descriptor) {
- is FunctionDescriptor -> descriptor.valueParameters.firstOrNull()?.type
- is PropertyDescriptor -> descriptor.type
- else -> null
- } ?: continue
-
- val (validationCall, uncheckedValidationCall, lambdaDescriptor) =
- resolveValidationCall(
- kind = kind,
- expressionToReportErrorsOn = expressionToReportErrorsOn,
- receiverScope = receiverScope,
- assignmentReceiverScope = type,
- validationType = validationType,
- attrType = attrType,
- context = context.replaceTraceAndCache(tempForValidations)
- )
-
- results.add(
- ValidatedAssignment(
- validationType = validationType,
- assignment = resolvedCall,
- assignmentLambda = lambdaDescriptor,
- attribute = AttributeNode(
- name = name,
- expression = attribute.value,
- type = attrType,
- descriptor = resolvedCall.resultingDescriptor,
- isStatic = false,
- isStable = false
- ),
- validationCall = validationCall,
- uncheckedValidationCall = uncheckedValidationCall
- )
- )
- consumedAttributes.add(name)
- }
- }
-
- if (children != null) {
- val childrenExpr = children.value
-
- var resolvedCall: ResolvedCall<*>? = null
-
- for (descriptor in getChildrenDescriptors(type, attributesUsedInCall, context)) {
- if (resolvedCall != null) break
-
- when (descriptor) {
- is PropertyDescriptor -> {
- resolvedCall = resolveChildrenAsProperty(
- type,
- descriptor,
- childrenExpr,
- context.replaceTraceAndCache(tempForAttributes),
- attributesUsedInCall.contains(CHILDREN_KEY)
- )
- }
- is SimpleFunctionDescriptor -> {
- resolvedCall = resolveChildrenAsSetter(
- type,
- descriptor,
- childrenExpr,
- context.replaceTraceAndCache(tempForAttributes)
- )
- }
- }
- }
- if (resolvedCall != null) {
-
- val descriptor = resolvedCall.resultingDescriptor
-
- val validationType = when {
- attributesUsedInCall.contains(CHILDREN_KEY) -> ValidationType.UPDATE
- else -> ValidationType.SET
- }
-
- val attrName = when (descriptor) {
- is SimpleFunctionDescriptor ->
- ComposeUtils.propertyNameFromSetterMethod(
- descriptor.name.asString()
- )
- is PropertySetterDescriptor -> descriptor.correspondingProperty.name.asString()
- else -> descriptor.name.asString()
- }
-
- attributes[attrName]?.let {
- if (attributesUsedInCall.contains(attrName) &&
- attributesUsedInCall.contains(CHILDREN_KEY)) {
- // if they were both already used in the call, then this diagnostic will already have been reported, and we
- // can ignore it
- } else {
- // they are providing a named attribute for a @Children attribute while also providing a children
- // body. This is illegal.
- context.trace.reportFromPlugin(
- ComposeErrors.CHILDREN_ATTR_USED_AS_BODY_AND_KEYED_ATTRIBUTE.on(
- it.key!!,
- attrName
- ),
- ComposeDefaultErrorMessages
- )
- }
- consumedAttributes.add(attrName)
- }
-
- val attrType = when (descriptor) {
- is FunctionDescriptor -> descriptor.valueParameters.firstOrNull()?.type
- ?: error("Expected single parameter setter")
- is PropertyDescriptor -> descriptor.type
- else -> error("Unknown callable type encountered")
- }
-
- val (validationCall, uncheckedValidationCall, lambdaDescriptor) =
- resolveValidationCall(
- kind = kind,
- expressionToReportErrorsOn = expressionToReportErrorsOn,
- receiverScope = receiverScope,
- assignmentReceiverScope = type,
- validationType = validationType,
- attrType = attrType,
- context = context.replaceTraceAndCache(tempForValidations)
- )
-
- results.add(
- ValidatedAssignment(
- validationType = when {
- attributesUsedInCall.contains(CHILDREN_KEY) -> ValidationType.UPDATE
- else -> ValidationType.SET
- },
- assignment = resolvedCall,
- assignmentLambda = lambdaDescriptor,
- attribute = AttributeNode(
- name = CHILDREN_KEY,
- expression = children.value,
- type = attrType,
- descriptor = resolvedCall.resultingDescriptor,
- isStatic = false,
- isStable = false
- ),
- validationCall = validationCall,
- uncheckedValidationCall = uncheckedValidationCall
- )
- )
- consumedAttributes.add(CHILDREN_KEY)
- }
- }
-
- if (!type.isUnit()) {
- val cls = type.constructor.declarationDescriptor as? ClassDescriptor
- ?: error("unexpected classifier descriptor")
- val requiredAttributes = cls.unsubstitutedMemberScope
- .getContributedDescriptors()
- .mapNotNull { it as? PropertyDescriptor }
- // NOTE(lmr): I think we should consider not marking lateinit properties as required. It would maybe align
- // ourselves more with the language semantic of `lateinit`
- .filter {
- it.isLateInit && it.visibility.isVisibleOutside() &&
- !Visibilities.isPrivate(it.visibility)
- }
- .filter { !it.hasHiddenAttributeAnnotation() }
-
- requiredAttributes
- .filter { !consumedAttributes.contains(it.name.asString()) }
- .filter { !it.hasChildrenAnnotation() }
- .ifNotEmpty { missingRequiredAttributes.addAll(this) }
-
- requiredAttributes
- .filter { it.hasChildrenAnnotation() }
- .filter { !consumedAttributes.contains(it.name.asString()) &&
- !consumedAttributes.contains(CHILDREN_KEY) }
- .ifNotEmpty { missingRequiredAttributes.addAll(this) }
- }
-
- tempForAttributes.commit()
- tempForValidations.commit()
- return results
- }
-
- // pure, can be moved out. used in resolveAllSetAttrs
- private fun getChildrenDescriptors(
- type: KotlinType,
- attributesUsedInCall: Set<String>,
- context: ExpressionTypingContext
- ): List<DeclarationDescriptor> {
- val descriptor = type.constructor.declarationDescriptor
- return when (descriptor) {
- is ClassDescriptor -> {
- // We have to be careful here because if the property was defined as a constructor parameter, then the @Children
- // annotation will be on the parameter descriptor, *not* on the property descriptor. These aren't linked in any real
- // sense at the descriptor level, so we have to find the primary constructor and just use the convention of the
- // parameter named of the constructor being the same as the property.
- val ctorParameters = descriptor.unsubstitutedPrimaryConstructor?.valueParameters
- ?: emptyList()
- val shouldIncludeCtorParam = attributesUsedInCall.contains(CHILDREN_KEY)
- fun DeclarationDescriptor.isChildrenCtorParam(): Boolean {
- if (!shouldIncludeCtorParam) return false
- if (this !is PropertyDescriptor) return false
- val param = ctorParameters.firstOrNull { p -> name == p.name } ?: return false
- return param.hasChildrenAnnotation()
- }
- descriptor
- .unsubstitutedMemberScope
- .getContributedDescriptors()
- .filter { it.hasChildrenAnnotation() || it.isChildrenCtorParam() }
- .filter {
- // if it's a property descriptor, we are only interested if it has a setter
- if (it is PropertyDescriptor) it.setter != null else true
- }
- .filter {
- // we want to make sure the descriptor is actually visible from the callsite
- it is DeclarationDescriptorWithVisibility &&
- Visibilities.isVisibleIgnoringReceiver(
- it,
- context.scope.ownerDescriptor
- )
- }
- }
- else -> emptyList()
- }
- }
-
- private fun resolveAttributeAsSetter(
- instanceType: KotlinType,
- name: String,
- keyExpr: KtReferenceExpression,
- valueExpr: KtExpression,
- isPunned: Boolean,
- expectedTypes: MutableCollection<KotlinType>,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
- val setterName = Name.identifier(
- ComposeUtils.setterMethodFromPropertyName(
- name
- )
- )
- val receiver = TransientReceiver(instanceType)
-
- val call = makeCall(
- keyExpr,
- calleeExpression = keyExpr,
- valueArguments = listOf(CallMaker.makeValueArgument(valueExpr)),
- receiver = receiver
- )
-
- val temporaryForFunction = TemporaryTraceAndCache.create(
- context, "trace to resolve as function call", keyExpr
- )
-
- val results = callResolver.computeTasksAndResolveCall<FunctionDescriptor>(
- BasicCallResolutionContext.create(
- context.replaceTraceAndCache(temporaryForFunction),
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(context.dataFlowInfo, call)
- ),
- setterName,
- keyExpr,
- NewResolutionOldInference.ResolutionKind.Function
- )
-
- if (results.isNothing) {
- return null
- }
-
- if (results.isAmbiguity || temporaryForFunction.trace.hasTypeMismatchErrorsOn(valueExpr)) {
- expectedTypes.addAll(
- results.resultingCalls.mapNotNull {
- it.resultingDescriptor.valueParameters.firstOrNull()
- }.map { it.type }
- )
- return null
- }
-
- val resolvedCall = OverloadResolutionResultsUtil.getResultingCall(results, context)
- ?: return null
-
- if (isPunned) {
- context.trace.recordAttributeKeyRef(valueExpr, resolvedCall.resultingDescriptor)
- }
- temporaryForFunction.commit()
-
- return resolvedCall
- }
-
- private fun resolveAttributeAsProperty(
- instanceType: KotlinType,
- keyExpr: KtSimpleNameExpression,
- valueExpr: KtExpression,
- isPunned: Boolean,
- expectedTypes: MutableCollection<KotlinType>,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
-
- // NOTE(lmr): I'm not sure what the consequences are of using the tagExpr as the receiver...
- val receiver = TransientReceiver(instanceType)
-
- val temporaryForVariable = TemporaryTraceAndCache.create(
- context, "trace to resolve as local variable or property", keyExpr
- )
-
- val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
-
- val call = CallMaker.makePropertyCall(receiver, null, keyExpr)
-
- val contextForVariable = BasicCallResolutionContext.create(
- contextToUse,
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS
- )
-
- val results = callResolver.resolveSimpleProperty(contextForVariable)
-
- if (results.isNothing) {
- return null
- }
-
- val resolvedCall = OverloadResolutionResultsUtil.getResultingCall(results, contextToUse)
- ?: return null
-
- val descriptor = resolvedCall.resultingDescriptor as PropertyDescriptor
- val expectedType = descriptor.type
-
- if (isDeprecatedKtxTagResolve) {
- facade.getTypeInfo(
- valueExpr,
- contextToUse
- .replaceExpectedType(expectedType)
- .replaceCallPosition(CallPosition.PropertyAssignment(keyExpr))
- )
- }
-
- if (temporaryForVariable.trace.hasTypeMismatchErrorsOn(valueExpr)) {
- expectedTypes.add(expectedType)
- return null
- }
-
- if (descriptor.setter == null) {
- // NOTE(lmr): we probably shouldn't do this. if this ends up being a cross-module compile, then the
- // setter will be gone. I'm not sure the right way to figure out whether or not a property is
- // settable or not...
- return null
- }
-
- if (isPunned) {
- context.trace.recordAttributeKeyRef(valueExpr, resolvedCall.resultingDescriptor)
- }
-
- temporaryForVariable.commit()
-
- return resolvedCall
- }
-
- private fun resolveChildrenAsSetter(
- instanceType: KotlinType,
- childrenDescriptor: SimpleFunctionDescriptor,
- childrenExpr: KtExpression,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
- val setterName = childrenDescriptor.name
-
- val valueArguments = listOf(CallMaker.makeValueArgument(childrenExpr))
- val receiver = TransientReceiver(instanceType)
- val call = makeCall(
- childrenExpr,
- valueArguments = valueArguments,
- receiver = receiver,
- calleeExpression = childrenExpr // NOTE(lmr): this seems wrong
- )
-
- val temporaryForFunction = TemporaryTraceAndCache.create(
- context, "trace to resolve as function call", childrenExpr
- )
-
- val refExpr = psiFactory.createSimpleName(setterName.identifier)
-
- val results = callResolver.computeTasksAndResolveCall<FunctionDescriptor>(
- BasicCallResolutionContext.create(
- context.replaceTraceAndCache(temporaryForFunction),
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(context.dataFlowInfo, call)
- ),
- setterName,
- refExpr,
- NewResolutionOldInference.ResolutionKind.Function
- )
-
- if (results.isNothing) {
- return null
- }
-
- if (temporaryForFunction.trace.hasTypeMismatchErrorsOn(childrenExpr)) {
- return null
- }
- // TODO(lmr): should we check isSuccess here or anything like that?
-
- val resolvedCall = OverloadResolutionResultsUtil.getResultingCall(results, context)
- ?: return null
-
- temporaryForFunction.commit()
-
- if (isDeprecatedKtxTagResolve) {
- facade.getTypeInfo(
- childrenExpr,
- context.replaceExpectedType(
- resolvedCall.resultingDescriptor.valueParameters.first().type
- )
- )
- }
- return resolvedCall
- }
-
- private fun resolveChildrenAsProperty(
- instanceType: KotlinType,
- propertyDescriptor: PropertyDescriptor,
- childrenExpr: KtExpression,
- context: ExpressionTypingContext,
- shouldIncludeCtorParam: Boolean
- ): ResolvedCall<*>? {
- val temporaryForVariable = TemporaryTraceAndCache.create(
- context, "trace to resolve as local variable or property", childrenExpr
- )
-
- val receiver = TransientReceiver(instanceType)
-
- val call = makeCall(
- childrenExpr,
- calleeExpression = childrenExpr,
- receiver = receiver
- )
-
- val contextForVariable = BasicCallResolutionContext.create(
- context.replaceTraceAndCache(temporaryForVariable),
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS
- )
-
- val results = callResolver.computeTasksAndResolveCall<PropertyDescriptor>(
- contextForVariable,
- propertyDescriptor.name,
- TracingStrategy.EMPTY,
- NewResolutionOldInference.ResolutionKind.Variable
- )
-
- if (results.isNothing) {
- return null
- }
-
- val resolvedCall = OverloadResolutionResultsUtil.getResultingCall(results, context)
- ?: return null
-
- val composableFromChildrenAnnotation = (propertyDescriptor.containingDeclaration
- as? ClassDescriptor)?.let { containingDeclaration ->
- val ctorParameters =
- containingDeclaration.unsubstitutedPrimaryConstructor?.valueParameters
- ?: emptyList()
- fun DeclarationDescriptor.isComposableChildrenCtorParam(): Boolean {
- if (!shouldIncludeCtorParam) return false
- if (this !is PropertyDescriptor) return false
- val param = ctorParameters.firstOrNull { p -> name == p.name } ?: return false
- return param.isComposableFromChildrenAnnotation()
- }
- resolvedCall.resultingDescriptor.isComposableFromChildrenAnnotation() ||
- resolvedCall.resultingDescriptor.isComposableChildrenCtorParam()
- } ?: false
-
- val expectedType =
- if (composableFromChildrenAnnotation)
- (resolvedCall.resultingDescriptor).type.makeComposable(module)
- else (resolvedCall.resultingDescriptor).type
-
- if (isDeprecatedKtxTagResolve) {
- facade.getTypeInfo(
- childrenExpr,
- context
- .replaceTraceAndCache(temporaryForVariable)
- .replaceExpectedType(expectedType)
- .replaceCallPosition(CallPosition.PropertyAssignment(null))
- )
- }
-
- if (temporaryForVariable.trace.hasTypeMismatchErrorsOn(childrenExpr)) {
- return null
- }
-
- temporaryForVariable.commit()
-
- return resolvedCall
- }
-
- private fun resolveCandidate(
- step: ResolveStep,
- candidate: ResolvedCall<FunctionDescriptor>,
- original: Call,
- attributes: Map<String, AttributeInfo>,
- usedAttributes: MutableSet<String>,
- usedAttributeInfos: MutableList<TempParameterInfo>,
- missingRequiredAttributes: MutableList<DeclarationDescriptor>,
- context: ExpressionTypingContext
- ): OverloadResolutionResults<FunctionDescriptor> {
- val valueArguments = mutableListOf<ValueArgument>()
-
- val referencedDescriptor = candidate.semanticCall.resultingDescriptor
-
- val dispatchReceiver = candidate.semanticCall.dispatchReceiver ?: candidate.dispatchReceiver
-
- val stableParamNames = referencedDescriptor.hasStableParameterNames()
-
- val possibleChildrenParameter =
- candidate.resultingDescriptor.valueParameters.possibleChildrenParameter
-
- for (param in candidate.resultingDescriptor.valueParameters) {
- val name = param.name.asString()
- val attr = attributes[name]
- var arg: ValueArgument? = null
-
- if (arg == null && (param.hasChildrenAnnotation() ||
- param == possibleChildrenParameter)) {
- val childrenAttr = attributes[CHILDREN_KEY]
- if (childrenAttr != null) {
- usedAttributes.add(CHILDREN_KEY)
- var type = param.type
- if (param.isComposableFromChildrenAnnotation()) {
- type = type.makeComposable(module)
- }
-
- usedAttributeInfos.add(
- TempParameterInfo(
- attribute = childrenAttr,
- descriptor = param,
- type = type
- )
- )
-
- arg = childrenAttr.toValueArgument(name, stableParamNames)
-
- if (attr != null) {
- // they are providing a named attribute for a @Children attribute while also providing a children
- // body. This is illegal.
- context.trace.reportFromPlugin(
- ComposeErrors.CHILDREN_ATTR_USED_AS_BODY_AND_KEYED_ATTRIBUTE.on(
- attr.key!!,
- attr.name
- ),
- ComposeDefaultErrorMessages
- )
- usedAttributes.add(attr.name)
- }
- }
- }
-
- if (arg == null && attr != null) {
- usedAttributes.add(name)
- usedAttributeInfos.add(
- TempParameterInfo(
- attribute = attr,
- descriptor = param,
- type = param.type
- )
- )
- context.trace.record(BindingContext.REFERENCE_TARGET, attr.key, param)
- if (attr.isPunned) {
- context.trace.recordAttributeKeyRef(attr.value, param)
- }
- arg = attr.toValueArgument(attr.name, stableParamNames)
- }
-
- if (arg == null && isImplicitConstructorParam(param, referencedDescriptor)) {
- arg = ImplicitCtorValueArgument(param.type)
- }
-
- if (arg != null) {
- valueArguments.add(arg)
- } else if (!param.declaresDefaultValue()) {
- // missing required parameter!
- if (dispatchReceiver?.type?.isExtensionFunctionType == true &&
- param == candidate.resultingDescriptor.valueParameters[0]) {
- // in this case, the missing parameter is an extension parameter... and it will be provided implicitly. We don't want
- // to diagnose this as a missing attribute. If it is missing, the call resolver will add a diagnostic anyway.
- } else {
- missingRequiredAttributes.add(param)
- }
- }
- }
-
- val call = makeCall(
- original.callElement,
- valueArguments = valueArguments,
- calleeExpression = original.calleeExpression,
- receiver = original.explicitReceiver,
- dispatchReceiver = dispatchReceiver
- )
-
- val contextForVariable = BasicCallResolutionContext.create(
- context,
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(context.dataFlowInfo, call)
- )
-
- return when (step) {
- is ResolveStep.Root -> callResolver.resolveFunctionCall(contextForVariable)
- is ResolveStep.Nested -> forceResolveCallForInvoke(step.calleeType, contextForVariable)
- }
- }
-
- private fun getCandidates(
- step: ResolveStep,
- call: Call,
- context: ExpressionTypingContext,
- collectAllCandidates: Boolean = true
- ): OverloadResolutionResults<FunctionDescriptor> {
- val contextForVariable = BasicCallResolutionContext.create(
- context,
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(context.dataFlowInfo, call)
- ).replaceCollectAllCandidates(collectAllCandidates)
-
- return when (step) {
- is ResolveStep.Root -> callResolver.resolveFunctionCall(contextForVariable)
- is ResolveStep.Nested -> forceResolveCallForInvoke(step.calleeType, contextForVariable)
- }
- }
-
- private fun resolveReceiver(
- expression: KtExpression,
- context: ExpressionTypingContext
- ): Receiver? {
- if (expression !is KtQualifiedExpression) return null
- val currentContext = context
- .replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE)
- .replaceContextDependency(ContextDependency.INDEPENDENT)
-
- expression.elementChain(currentContext)
-
- val receiverExpr = expression.receiverExpression
-
- val qualifier = context.trace.get(BindingContext.QUALIFIER, receiverExpr)
-
- val receiverTypeInfo = when {
- !isDeprecatedKtxTagResolve -> error("in here")
- qualifier == null -> facade.getTypeInfo(receiverExpr, currentContext)
- else -> KotlinTypeInfo(null, currentContext.dataFlowInfo)
- }
-
- // TODO(lmr): inspect jumps and nullability. We cant allow tags that can be null or return early
- val receiverType = receiverTypeInfo.type
- ?: ErrorUtils.createErrorType("Type for " + receiverExpr.text)
-
- return context.trace.get(BindingContext.QUALIFIER, receiverExpr)
- ?: ExpressionReceiver.create(receiverExpr, receiverType, context.trace.bindingContext)
- }
-
- private fun makeValueArgument(
- type: KotlinType,
- context: ExpressionTypingContext,
- forceType: Boolean = false
- ): ValueArgument {
- val fakeExpr = psiFactory.createSimpleName("tmpVar")
-
- context.trace.record(
- BindingContext.EXPRESSION_TYPE_INFO, fakeExpr, KotlinTypeInfo(
- type = type,
- dataFlowInfo = DataFlowInfo.EMPTY,
- jumpOutPossible = false,
- jumpFlowInfo = DataFlowInfo.EMPTY
- )
- )
-
- if (forceType) {
- context.trace.record(BindingContext.PROCESSED, fakeExpr, true)
- }
-
- return CallMaker.makeValueArgument(fakeExpr)
- }
-
- private fun resolveJoinKey(
- expressionToReportErrorsOn: KtExpression,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
-
- return resolveSubstitutableComposerMethod(
- KtxNameConventions.JOINKEY,
- listOf(
- builtIns.anyType,
- builtIns.anyType
- ),
- null,
- expressionToReportErrorsOn,
- context
- )
- }
-
- private fun resolveInfixOr(context: ExpressionTypingContext): ResolvedCall<*> {
- val orName = Name.identifier("or")
- val left = psiFactory.createSimpleName("a")
- val right = psiFactory.createSimpleName("b")
- val operator = psiFactory.createSimpleName(orName.identifier)
-
- context.trace.record(
- BindingContext.EXPRESSION_TYPE_INFO, left, KotlinTypeInfo(
- type = builtIns.booleanType,
- dataFlowInfo = DataFlowInfo.EMPTY,
- jumpOutPossible = false,
- jumpFlowInfo = DataFlowInfo.EMPTY
- )
- )
-
- context.trace.record(
- BindingContext.EXPRESSION_TYPE_INFO, right, KotlinTypeInfo(
- type = builtIns.booleanType,
- dataFlowInfo = DataFlowInfo.EMPTY,
- jumpOutPossible = false,
- jumpFlowInfo = DataFlowInfo.EMPTY
- )
- )
-
- return callResolver.resolveCallWithGivenName(
- context,
- makeCall(
- callElement = left,
- calleeExpression = operator,
- receiver = ExpressionReceiver.create(
- left,
- builtIns.booleanType,
- context.trace.bindingContext
- ),
- valueArguments = listOf(CallMaker.makeValueArgument(right))
- ),
- operator,
- Name.identifier("or")
- ).resultingCall
- }
-
- private fun resolveApplyCallForType(
- type: KotlinType,
- context: ExpressionTypingContext
- ): ResolvedCall<*> {
- val apply = psiFactory.createSimpleName("apply")
-
- val results = callResolver.resolveCallWithGivenName(
- context,
- makeCall(
- callElement = apply,
- calleeExpression = apply,
- receiver = TransientReceiver(type),
- valueArguments = listOf(
- makeValueArgument(
- functionType(receiverType = type),
- context
- )
- )
- ),
- apply,
- Name.identifier("apply")
- )
- return results.resultingCall
- }
-
- private fun resolveComposerEmit(
- implicitCtorTypes: List<KotlinType>,
- constructedType: KotlinType,
- hasBody: Boolean,
- expressionToReportErrorsOn: KtExpression,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
- return resolveSubstitutableComposerMethod(
- KtxNameConventions.EMIT,
- listOfNotNull(
- builtIns.anyType,
- functionType(
- parameterTypes = implicitCtorTypes,
- returnType = constructedType
- ),
- functionType(),
- if (hasBody) functionType() else null
- ),
- constructedType,
- expressionToReportErrorsOn,
- context
- )
- }
-
- private fun resolveComposerMethodCandidates(
- element: KtExpression,
- name: Name,
- context: ExpressionTypingContext
- ): Collection<ResolvedCall<*>> {
- val calleeExpression = psiFactory.createSimpleName(name.asString())
-
- val methodCall = makeCall(
- callElement = element,
- calleeExpression = calleeExpression,
- receiver = TransientReceiver(composerType)
- )
-
- val contextForVariable = BasicCallResolutionContext.create(
- context,
- methodCall,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(context.dataFlowInfo, methodCall)
- )
-
- val results = callResolver.resolveCallWithGivenName(
- // it's important that we use "collectAllCandidates" so that extension functions get included
- contextForVariable.replaceCollectAllCandidates(true),
- methodCall,
- calleeExpression,
- name
- )
-
- return results.allCandidates ?: emptyList()
- }
-
- private fun resolveComposerCall(
- constructedType: KotlinType?,
- expressionToReportErrorsOn: KtExpression,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
-
- // call signature is:
- // ==================
- // key: Any, invalid: V.() -> Boolean, block: () -> Unit
- // key: Any, ctor: () -> T, invalid: V.(T) -> Boolean, block: (T) -> Unit
-
- return resolveSubstitutableComposerMethod(
- KtxNameConventions.CALL,
- listOfNotNull(
- builtIns.anyType,
- constructedType?.let {
- functionType(returnType = constructedType)
- },
- functionType(
- parameterTypes = listOfNotNull(constructedType),
- returnType = builtIns.booleanType
- ),
- functionType(
- parameterTypes = listOfNotNull(
- constructedType
- )
- )
- ),
- constructedType,
- expressionToReportErrorsOn,
- context
- )
- }
-
- private fun resolveSingleValidationCall(
- expressionToReportErrorsOn: KtExpression,
- receiverScope: KotlinType,
- validationType: ValidationType,
- checked: Boolean,
- attrType: KotlinType,
- lambdaArg: ValueArgument?,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
- val temporaryForVariable = TemporaryTraceAndCache.create(
- context, "trace to resolve variable", expressionToReportErrorsOn
- )
- val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
- val name = validationType.name.toLowerCase(Locale.ROOT).let {
- if (!checked) (it + "Unchecked") else it
- }
- val calleeExpression = psiFactory.createSimpleName(name)
- val call = makeCall(
- callElement = calleeExpression,
- calleeExpression = calleeExpression,
- valueArguments = listOfNotNull(
- makeValueArgument(attrType, contextToUse, forceType = true),
- lambdaArg
- ),
- receiver = TransientReceiver(receiverScope)
- )
- val results = callResolver.resolveCallWithGivenName(
- BasicCallResolutionContext.create(
- contextToUse,
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(contextToUse.dataFlowInfo, call)
- ),
- call,
- calleeExpression,
- Name.identifier(name)
- )
-
- if (results.isSuccess) return results.resultingCall
-
- if (results.resultCode == OverloadResolutionResults.Code.INCOMPLETE_TYPE_INFERENCE) {
-
- // NOTE(lmr): We know the type of the attribute at this point, but it's possible for the validation call to require
- // some help in order to do the type inference for the call. We are just guessing here that the type is going to be
- // the attribute type, and not something more complicated. It is kind of a bummer that we need this and I wonder if
- // there isn't a cleaner way to do this.
-
- for (candidate in results.resultingCalls) {
-
- val typeParam = candidate.typeArguments.keys.singleOrNull() ?: continue
-
- if (!attrType.satisfiesConstraintsOf(typeParam)) continue
-
- val nextTempTrace = TemporaryTraceAndCache.create(
- context, "trace to resolve variable", expressionToReportErrorsOn
- )
-
- val nextContext = context
- .replaceTraceAndCache(nextTempTrace)
- .replaceCollectAllCandidates(false)
-
- val substitutor = TypeSubstitutor.create(
- mapOf(
- typeParam.typeConstructor to attrType.asTypeProjection()
- )
- )
-
- val nextCall = makeCall(
- callElement = expressionToReportErrorsOn,
- calleeExpression = calleeExpression,
- receiver = TransientReceiver(composerType),
- valueArguments = candidate.candidateDescriptor.valueParameters.map {
- makeValueArgument(it.type, nextContext)
- }
- )
-
- val nextResults = callResolver.resolveCallWithKnownCandidate(
- nextCall,
- TracingStrategyImpl.create(calleeExpression, nextCall),
- BasicCallResolutionContext.create(
- nextContext,
- nextCall,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(nextContext.dataFlowInfo, nextCall)
- ),
- ResolutionCandidate.create(
- nextCall,
- candidate.candidateDescriptor,
- candidate.dispatchReceiver,
- candidate.explicitReceiverKind,
- substitutor
- ),
- DataFlowInfoForArgumentsImpl(nextContext.dataFlowInfo, nextCall)
- )
-
- if (nextResults.isSuccess) {
- nextTempTrace.commit()
- return nextResults.resultingCall
- }
- }
- }
-
- return null
- }
-
- private fun resolveValidationCall(
- kind: ComposerCallKind,
- expressionToReportErrorsOn: KtExpression,
- receiverScope: KotlinType,
- assignmentReceiverScope: KotlinType?,
- validationType: ValidationType,
- attrType: KotlinType,
- context: ExpressionTypingContext
- ): Triple<ResolvedCall<*>?, ResolvedCall<*>?, FunctionDescriptor?> {
- val temporaryForVariable = TemporaryTraceAndCache.create(
- context, "trace to resolve variable", expressionToReportErrorsOn
- )
- val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
-
- val includeLambda = validationType != ValidationType.CHANGED
-
- // for call:
- // ValidatorType.set(AttrType, (AttrType) -> Unit): Boolean
- // ValidatorType.update(AttrType, (AttrType) -> Unit): Boolean
- // ValidatorType.changed(AttrType): Boolean
-
- // for emit:
- // ValidatorType.set(AttrType, ElementType.(AttrType) -> Unit): Unit
- // ValidatorType.update(AttrType, ElementType.(AttrType) -> Unit): Unit
- // ValidatorType.changed(AttrType): Unit
-
- val lambdaType = when {
- includeLambda && kind == ComposerCallKind.EMIT -> functionType(
- parameterTypes = listOf(attrType),
- receiverType = assignmentReceiverScope
- )
- includeLambda && kind == ComposerCallKind.CALL -> functionType(
- parameterTypes = listOf(attrType)
- )
- else -> null
- }
- val lambdaArg = lambdaType?.let { makeValueArgument(it, contextToUse) }
- val lambdaDescriptor = lambdaType?.let {
- createFunctionDescriptor(
- it,
- contextToUse
- )
- }
-
- val validationCall = resolveSingleValidationCall(
- expressionToReportErrorsOn = expressionToReportErrorsOn,
- receiverScope = receiverScope,
- validationType = validationType,
- checked = true,
- attrType = attrType,
- lambdaArg = lambdaArg,
- context = context
- )
-
- val uncheckedValidationCall = resolveSingleValidationCall(
- expressionToReportErrorsOn = expressionToReportErrorsOn,
- receiverScope = receiverScope,
- validationType = validationType,
- checked = false,
- attrType = attrType,
- lambdaArg = lambdaArg,
- context = context
- )
-
- return Triple(validationCall, uncheckedValidationCall, lambdaDescriptor)
- }
-
- private fun resolveSubstitutableComposerMethod(
- methodName: Name,
- argumentTypes: List<KotlinType>,
- typeToSubstitute: KotlinType?,
- expressionToReportErrorsOn: KtExpression,
- context: ExpressionTypingContext
- ): ResolvedCall<*>? {
- val temporaryForVariable = TemporaryTraceAndCache.create(
- context, "trace to resolve variable", expressionToReportErrorsOn
- )
- val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
-
- val composerExpr = psiFactory.createSimpleName(methodName.asString())
-
- val call = makeCall(
- callElement = expressionToReportErrorsOn,
- calleeExpression = composerExpr,
- receiver = TransientReceiver(composerType),
- valueArguments = argumentTypes.map { makeValueArgument(it, contextToUse) }
- )
-
- val results = callResolver.resolveCallWithGivenName(
- BasicCallResolutionContext.create(
- contextToUse,
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(contextToUse.dataFlowInfo, call)
- ),
- call,
- composerExpr,
- methodName
- )
-
- if (results.isSuccess) return results.resultingCall
-
- if (typeToSubstitute == null) return null
-
- val candidates = if (context.collectAllCandidates)
- results.allCandidates ?: emptyList()
- else results.resultingCalls
-
- for (candidate in candidates) {
-
- val typeParam = candidate.candidateDescriptor.typeParameters.singleOrNull() ?: continue
-
- if (!typeToSubstitute.satisfiesConstraintsOf(typeParam)) continue
-
- val nextTempTrace = TemporaryTraceAndCache.create(
- context, "trace to resolve variable", expressionToReportErrorsOn
- )
-
- val nextContext = context
- .replaceTraceAndCache(nextTempTrace)
- .replaceCollectAllCandidates(false)
-
- val substitutor = TypeSubstitutor.create(
- mapOf(
- typeParam.typeConstructor to typeToSubstitute.asTypeProjection()
- )
- )
-
- val nextCall = makeCall(
- callElement = expressionToReportErrorsOn,
- calleeExpression = composerExpr,
- receiver = TransientReceiver(composerType),
- valueArguments = candidate.candidateDescriptor.valueParameters.map {
- makeValueArgument(it.type, nextContext)
- }
- )
-
- val nextResults = callResolver.resolveCallWithKnownCandidate(
- nextCall,
- TracingStrategyImpl.create(composerExpr, nextCall),
- BasicCallResolutionContext.create(
- nextContext,
- nextCall,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(nextContext.dataFlowInfo, nextCall)
- ),
- ResolutionCandidate.create(
- nextCall,
- candidate.candidateDescriptor,
- candidate.dispatchReceiver,
- candidate.explicitReceiverKind,
- substitutor
- ),
- DataFlowInfoForArgumentsImpl(nextContext.dataFlowInfo, nextCall)
- )
-
- if (nextResults.isSuccess) {
- nextTempTrace.commit()
- return nextResults.resultingCall
- }
- }
-
- return if (context.collectAllCandidates) null
- else resolveSubstitutableComposerMethod(
- methodName,
- argumentTypes,
- typeToSubstitute,
- expressionToReportErrorsOn,
- context.replaceCollectAllCandidates(true)
- )
- }
-
- private fun resolveVar(
- name: Name,
- expr: KtExpression,
- context: ExpressionTypingContext
- ): OverloadResolutionResults<CallableDescriptor> {
- val temporaryForVariable = TemporaryTraceAndCache.create(
- context, "trace to resolve variable", expr
- )
- val call = makeCall(expr)
- val contextForVariable = BasicCallResolutionContext.create(
- context.replaceTraceAndCache(temporaryForVariable),
- call,
- CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
- DataFlowInfoForArgumentsImpl(context.dataFlowInfo, call)
- )
- return callResolver.computeTasksAndResolveCall<CallableDescriptor>(
- contextForVariable,
- name,
- TracingStrategy.EMPTY,
- NewResolutionOldInference.ResolutionKind.Variable
- )
- }
-
- private fun KtQualifiedExpression.elementChain(context: ExpressionTypingContext) {
- val moduleDescriptor = context.scope.ownerDescriptor.module
- val trace = context.trace
- val scopeForFirstPart = context.scope
-
- val path = asQualifierPartList()
- val firstPart = path.first()
- var currentDescriptor: DeclarationDescriptor? = scopeForFirstPart.findDescriptor(firstPart)
- currentDescriptor =
- currentDescriptor ?: moduleDescriptor.getPackage(FqName.topLevel(firstPart.name)).let {
- if (it.isEmpty()) null else it
- }
-
- if (currentDescriptor == null) return
- else storeSimpleNameExpression(firstPart.expression!!, currentDescriptor, trace)
-
- // TODO(lmr): we need to add visibility checks into this function...
- for (qualifierPartIndex in 1 until path.size) {
- val qualifierPart = path[qualifierPartIndex]
-
- val nextPackageOrClassDescriptor =
- when (currentDescriptor) {
- // TODO(lmr): i wonder if we could allow this for Ktx. Seems like a nice to have
- // TODO type aliases as qualifiers? (would break some assumptions in TypeResolver)
- is TypeAliasDescriptor -> null
- is ClassDescriptor -> {
- var next: DeclarationDescriptor? = null
- next = next
- ?: currentDescriptor.unsubstitutedInnerClassesScope.findDescriptor(
- qualifierPart
- )
- if (currentDescriptor.kind == ClassKind.OBJECT) {
- next = next
- ?: currentDescriptor.unsubstitutedMemberScope.findDescriptor(
- qualifierPart
- )
- }
- val cod = currentDescriptor.companionObjectDescriptor
- if (cod != null) {
- next = next
- ?: cod.unsubstitutedMemberScope.findDescriptor(qualifierPart)
- }
- next = next ?: currentDescriptor.staticScope.findDescriptor(qualifierPart)
- next
- }
- is PackageViewDescriptor -> {
- val packageView =
- if (qualifierPart.typeArguments == null) {
- moduleDescriptor.getPackage(
- currentDescriptor.fqName.child(qualifierPart.name)
- )
- } else null
- if (packageView != null && !packageView.isEmpty()) {
- packageView
- } else {
- currentDescriptor.memberScope.findDescriptor(qualifierPart)
- }
- }
- is VariableDescriptor -> {
- currentDescriptor.type.memberScope.findDescriptor(qualifierPart)
- }
- else -> null
- }
-
- if (nextPackageOrClassDescriptor == null) return
- else storeSimpleNameExpression(
- qualifierPart.expression!!,
- nextPackageOrClassDescriptor,
- trace
- )
-
- currentDescriptor = nextPackageOrClassDescriptor
- }
- }
-
- private fun storeSimpleNameExpression(
- expression: KtSimpleNameExpression,
- descriptor: DeclarationDescriptor,
- trace: BindingTrace
- ) {
- trace.record(BindingContext.REFERENCE_TARGET, expression, descriptor)
- UnderscoreUsageChecker.checkSimpleNameUsage(descriptor, expression, trace)
-
- val qualifier = when (descriptor) {
- is PackageViewDescriptor -> PackageQualifier(expression, descriptor)
- is ClassDescriptor -> ClassQualifier(expression, descriptor)
- is TypeParameterDescriptor -> TypeParameterQualifier(expression, descriptor)
- is TypeAliasDescriptor -> descriptor.classDescriptor?.let {
- TypeAliasQualifier(expression, descriptor, it)
- }
- else -> null
- }
-
- if (qualifier != null) {
- trace.record(BindingContext.QUALIFIER, qualifier.expression, qualifier)
- }
- }
-
- // call resolver extension
- private fun forceResolveCallForInvoke(
- calleeType: KotlinType,
- context: BasicCallResolutionContext
- ): OverloadResolutionResults<FunctionDescriptor> {
- val fake = psiFactory.createSimpleName(OperatorNameConventions.INVOKE.asString())
- val expressionReceiver = ExpressionReceiver.create(
- fake,
- calleeType,
- context.trace.bindingContext
- )
- val call = CallTransformer.CallForImplicitInvoke(
- context.call.explicitReceiver, expressionReceiver, context.call,
- false
- )
- val tracingForInvoke = TracingStrategyForInvoke(fake, call, calleeType)
- return resolveCallForInvoke(context.replaceCall(call), tracingForInvoke)
- }
-
- // callResolver extension
- private fun resolveCallForInvoke(
- context: BasicCallResolutionContext,
- tracing: TracingStrategy
- ): OverloadResolutionResults<FunctionDescriptor> {
- return callResolver.computeTasksAndResolveCall<FunctionDescriptor>(
- context, OperatorNameConventions.INVOKE, tracing,
- NewResolutionOldInference.ResolutionKind.Invoke
- )
- }
-}
-
-// move these to naming conventions???
-const val CHILDREN_KEY = "<children>"
-const val TAG_KEY = "<tag>"
-
-// general utils
-// ==============================
-
-private fun ExpressionTypingContext.withThrowawayTrace(expression: KtExpression) =
- replaceTraceAndCache(
- TemporaryTraceAndCache.create(
- this,
- "Throwaway trace and cache",
- expression
- )
- )
-
-private val builtIns = DefaultBuiltIns.Instance
-
-fun makeCall(
- callElement: KtElement,
- calleeExpression: KtExpression? = null,
- valueArguments: List<ValueArgument> = emptyList(),
- receiver: Receiver? = null,
- dispatchReceiver: ReceiverValue? = null
-): Call {
- return object : Call {
- override fun getDispatchReceiver(): ReceiverValue? = dispatchReceiver
- override fun getValueArgumentList(): KtValueArgumentList? = null
- override fun getTypeArgumentList(): KtTypeArgumentList? = null
- override fun getExplicitReceiver(): Receiver? = receiver
- override fun getCalleeExpression(): KtExpression? = calleeExpression
- override fun getValueArguments(): List<ValueArgument> = valueArguments
- override fun getCallElement(): KtElement = callElement
- override fun getFunctionLiteralArguments(): List<LambdaArgument> = emptyList()
- override fun getTypeArguments(): List<KtTypeProjection> = emptyList()
- override fun getCallType(): Call.CallType = Call.CallType.DEFAULT
- override fun getCallOperationNode(): ASTNode? = null
- }
-}
-
-private fun functionType(
- parameterTypes: List<KotlinType> = emptyList(),
- annotations: Annotations = Annotations.EMPTY,
- returnType: KotlinType = builtIns.unitType,
- receiverType: KotlinType? = null
-): KotlinType = createFunctionType(
- builtIns = builtIns,
- annotations = annotations,
- parameterNames = null,
- parameterTypes = parameterTypes,
- receiverType = receiverType,
- returnType = returnType
-)
-
-fun createFunctionDescriptor(
- type: KotlinType,
- context: ExpressionTypingContext
-): FunctionDescriptor {
- return AnonymousFunctionDescriptor(
- context.scope.ownerDescriptor,
- Annotations.EMPTY,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- SourceElement.NO_SOURCE,
- false
- ).apply {
- initialize(
- type.getReceiverTypeFromFunctionType()?.let {
- DescriptorFactory.createExtensionReceiverParameterForCallable(
- this,
- it,
- Annotations.EMPTY)
- },
- null,
- emptyList(),
- type.getValueParameterTypesFromFunctionType().mapIndexed { i, t ->
- ValueParameterDescriptorImpl(
- containingDeclaration = this,
- original = null,
- index = i,
- annotations = Annotations.EMPTY,
- name = t.type.extractParameterNameFromFunctionTypeArgument()
- ?: Name.identifier("p$i"),
- outType = t.type,
- declaresDefaultValue = false,
- isCrossinline = false,
- isNoinline = false,
- varargElementType = null,
- source = SourceElement.NO_SOURCE
- )
- },
- type.getReturnTypeFromFunctionType(),
- Modality.FINAL,
- Visibilities.LOCAL,
- null
- )
- isOperator = false
- isInfix = false
- isExternal = false
- isInline = false
- isTailrec = false
- isSuspend = false
- isExpect = false
- isActual = false
- }
-}
-
-private fun KotlinType.satisfiesConstraintsOf(T: TypeParameterDescriptor): Boolean {
- return T.upperBounds.all { isSubtypeOf(it) }
-}
-
-fun KotlinType.satisfiesConstraintsOf(bounds: List<KotlinType>): Boolean {
- return bounds.all { isSubtypeOf(it) }
-}
-
-// We want to return null in cases where types mismatch, so we use this heuristic to find out. I think there might be a more robust
-// way to find this out, but I'm not sure what it would be
-private fun BindingTrace.hasTypeMismatchErrorsOn(element: KtElement): Boolean =
- bindingContext.diagnostics.forElement(element).any { it.severity == Severity.ERROR }
-
-private fun KtExpression.asQualifierPartList(): List<QualifiedExpressionResolver.QualifierPart> {
- val result = SmartList<QualifiedExpressionResolver.QualifierPart>()
-
- fun addQualifierPart(expression: KtExpression?): Boolean {
- if (expression is KtSimpleNameExpression) {
- result.add(
- QualifiedExpressionResolver.ExpressionQualifierPart(
- expression.getReferencedNameAsName(),
- expression
- )
- )
- return true
- }
- return false
- }
-
- var expression: KtExpression? = this
- while (true) {
- if (addQualifierPart(expression)) break
- if (expression !is KtQualifiedExpression) break
-
- addQualifierPart(expression.selectorExpression)
-
- expression = expression.receiverExpression
- }
-
- return result.asReversed()
-}
-
-private fun HierarchicalScope.findDescriptor(
- part: QualifiedExpressionResolver.QualifierPart
-): DeclarationDescriptor? {
- return findFirstFromMeAndParent {
- it.findVariable(part.name, part.location)
- ?: it.findFunction(part.name, part.location)
- ?: it.findClassifier(part.name, part.location)
- }
-}
-
-private fun MemberScope.findDescriptor(
- part: QualifiedExpressionResolver.QualifierPart
-): DeclarationDescriptor? {
- return this.getContributedClassifier(part.name, part.location)
- ?: getContributedFunctions(part.name, part.location).singleOrNull()
- ?: getContributedVariables(part.name, part.location).singleOrNull()
-}
-
-private fun KtExpression?.refExpressions(): List<KtReferenceExpression> = when (this) {
- is KtReferenceExpression -> listOf(this)
- is KtDotQualifiedExpression ->
- selectorExpression.refExpressions() + receiverExpression.refExpressions()
- else -> emptyList()
-}
-
-fun KotlinType.upperBounds(): List<KotlinType> {
- return if (isTypeParameter()) {
- TypeUtils.getTypeParameterDescriptorOrNull(this)?.upperBounds ?: emptyList()
- } else {
- listOf(this)
- }
-}
-
-private fun AttributeInfo.toValueArgument(name: String, named: Boolean): ValueArgument {
- val argumentName = if (named) object : ValueArgumentName {
- override val asName: Name
- get() = Name.identifier(name)
- override val referenceExpression: KtSimpleNameExpression?
- get() = key
- } else null
- return object : ValueArgument {
- override fun getArgumentExpression() = value
- override fun getArgumentName() = argumentName
- override fun isNamed() = named
- override fun asElement(): KtElement = value
- override fun getSpreadElement(): LeafPsiElement? = null
- override fun isExternal() = true
- }
-}
-
-private fun DeclarationDescriptor.typeAsAttribute() = when (this) {
- is PropertyDescriptor -> type
- is ParameterDescriptor -> type
- is SimpleFunctionDescriptor -> valueParameters.first().type
- else -> error("unknown descriptor type")
-}
-
-/**
- * This function was copied verbatim from descriptorUtils.kt from ide-common. For some reason, importing this method into our module
- * would not work and would cause a NoClassDefFound exception.
- */
-private fun descriptorsEqualWithSubstitution(
- descriptor1: DeclarationDescriptor?,
- descriptor2: DeclarationDescriptor?,
- checkOriginals: Boolean = true
-): Boolean {
- if (descriptor1 == descriptor2) return true
- if (descriptor1 == null || descriptor2 == null) return false
- if (checkOriginals && descriptor1.original != descriptor2.original) return false
- if (descriptor1 !is CallableDescriptor) return true
- descriptor2 as CallableDescriptor
-
- val typeChecker = KotlinTypeCheckerImpl.withAxioms(
- object : KotlinTypeChecker.TypeConstructorEquality {
- override fun equals(a: TypeConstructor, b: TypeConstructor): Boolean {
- val typeParam1 = a.declarationDescriptor as? TypeParameterDescriptor
- val typeParam2 = b.declarationDescriptor as? TypeParameterDescriptor
- if (typeParam1 != null && typeParam2 != null &&
- typeParam1.containingDeclaration == descriptor1 &&
- typeParam2.containingDeclaration == descriptor2
- ) {
- return typeParam1.index == typeParam2.index
- }
-
- return a == b
- }
- }
- )
-
- if (!typeChecker.equalTypesOrNulls(descriptor1.returnType, descriptor2.returnType)) return false
-
- val parameters1 = descriptor1.valueParameters
- val parameters2 = descriptor2.valueParameters
- if (parameters1.size != parameters2.size) return false
- for ((param1, param2) in parameters1.zip(parameters2)) {
- if (!typeChecker.equalTypes(param1.type, param2.type)) return false
- }
- // NOTE(lmr): edit
- // this check was added
- if (descriptor1.javaClass !== descriptor2.javaClass) return false
- // NOTE(lmr): /end
- return true
-}
-
-// trace util
-// ========================
-private fun referenceCopyingTrace(
- from: KtExpression,
- to: KtExpression,
- trace: TemporaryBindingTrace
-): BindingTrace {
- val openTagExpressions = from.refExpressions()
- val closeTagExpressions = to.refExpressions()
-
- if (openTagExpressions.size != closeTagExpressions.size) return trace
-
- val elMap = openTagExpressions.zip(closeTagExpressions).toMap()
-
- val observableTrace = ObservableBindingTrace(trace)
-
- observableTrace.addHandler(BindingContext.REFERENCE_TARGET) { _, key, value ->
- val otherRefExpr = elMap[key]
- if (otherRefExpr != null) {
- trace.record(
- BindingContext.REFERENCE_TARGET,
- otherRefExpr,
- value
- )
- }
- }
-
- return observableTrace
-}
-
-private fun copyReferences(
- fromTrace: TemporaryBindingTrace,
- toTrace: BindingTrace,
- element: KtExpression
-) {
- val references = element.refExpressions()
- val filter = TraceEntryFilter { slice, key ->
- slice === BindingContext.REFERENCE_TARGET && key in references
- }
- fromTrace.addOwnDataTo(toTrace, filter, false)
-}
-
-// util classes
-// ========================
-private class ImplicitCtorValueArgument(val type: KotlinType) : ValueArgument {
- override fun getArgumentExpression(): KtExpression? = null
- override fun getArgumentName(): ValueArgumentName? = null
- override fun isNamed(): Boolean = false
- override fun asElement(): KtElement = error("tried to get element")
- override fun getSpreadElement(): LeafPsiElement? = null
- override fun isExternal(): Boolean = true
-}
-
-class AttributeInfo(
- val value: KtExpression,
- val key: KtSimpleNameExpression?,
- val name: String,
- val isPunned: Boolean
-)
-
-private sealed class ResolveStep(
- private val attributes: Set<String>,
- private val isValid: Boolean,
- private val trail: IntArray,
- val errorNode: ErrorNode?
-) {
- class Root(
- val openExpr: KtExpression,
- val closeExpr: KtExpression?
- ) : ResolveStep(emptySet(), true, intArrayOf(1, 1, 1), null)
-
- class Nested(
- val calleeType: KotlinType,
- val nonMemoizedCall: NonMemoizedCallNode?,
- val isStaticCall: Boolean,
- val parent: ResolveStep,
- attributes: Set<String>,
- isValid: Boolean,
- trail: IntArray,
- errorNode: ErrorNode?
- ) : ResolveStep(attributes, isValid, trail, errorNode) {
- fun constructNonMemoizedCallLinkedList(): NonMemoizedCallNode? {
- return nonMemoizedCall?.let { parent.constructNonMemoizedCallLinkedList(it) }
- }
- }
-
- fun recurse(
- calleeType: KotlinType,
- nonMemoizedCall: NonMemoizedCallNode?,
- isStaticCall: Boolean,
- resolvedCall: ResolvedCall<*>,
- specifiedAttributes: Set<String>
- ): ResolveStep {
- val possibleAttributes = resolvedCall.resultingDescriptor.valueParameters.map {
- it.name.asString()
- }.toSet()
- var errorNode: ErrorNode? = null
- // steps in the recursion cannot define attributes that conflict with previous steps
- val intersection = attributes.intersect(possibleAttributes)
- val hasDuplicates = intersection.isNotEmpty()
-
- if (hasDuplicates) {
- // TODO(lmr): it would be nice if we also grabbed the descriptors that these attributes were on
- errorNode = ErrorNode.RecursionLimitAmbiguousAttributesError(intersection)
- }
-
- // we require that at least one of the last three steps has had an attribute that was used.
- // we could tweak this. Potentially the "hasDuplicates" test is more than enough to prevent
- // infinite recursion.
- val nextTrail = intArrayOf(trail[1], trail[2], specifiedAttributes.size)
- val trailIsValid = nextTrail.sum() > 0
-
- if (!trailIsValid) {
- errorNode = ErrorNode.RecursionLimitError()
- }
-
- return Nested(
- calleeType = calleeType,
- nonMemoizedCall = nonMemoizedCall,
- isStaticCall = isStaticCall,
- parent = this,
- attributes = attributes + possibleAttributes,
- isValid = !hasDuplicates && trailIsValid,
- trail = nextTrail,
- errorNode = errorNode
- )
- }
-
- fun constructNonMemoizedCallLinkedList(
- nonMemoizedCall: NonMemoizedCallNode
- ): NonMemoizedCallNode {
- var call = nonMemoizedCall
- var node = this
- while (node is Nested) {
- val prevCall = node.nonMemoizedCall ?: break
- node = node.parent
- call = NonMemoizedCallNode(
- resolvedCall = prevCall.resolvedCall,
- params = prevCall.params,
- applyCall = prevCall.applyCall,
- applyLambdaDescriptor = prevCall.applyLambdaDescriptor,
- postAssignments = prevCall.postAssignments,
- applyLambdaType = prevCall.applyLambdaType,
- nextCall = call
- )
- }
- return call
- }
-
- fun canRecurse(): Boolean = isValid
-}
-
-// static checking
-// ==========================
-private fun isStatic(
- expression: KtExpression,
- context: ExpressionTypingContext,
- expectedType: KotlinType?,
- constantChecker: ConstantExpressionEvaluator
-): Boolean {
- val constValue = constantChecker.evaluateExpression(expression, context.trace, expectedType)
- return constValue != null
-}
-
-private fun isStaticTag(
- step: ResolveStep,
- resolvedCall: ResolvedCall<*>,
- context: ExpressionTypingContext
-): Boolean {
- return when (step) {
- is ResolveStep.Root -> when (step.openExpr) {
- is KtQualifiedExpression -> {
- val parts = step.openExpr.asQualifierPartList()
- val targets = parts
- .mapNotNull { it.expression }
- .mapNotNull { context.trace[BindingContext.REFERENCE_TARGET, it] }
-
- if (parts.size != targets.size) return false
-
- val first = targets.first()
-
- if (!first.isRoot()) return false
-
- for (target in targets) {
- val isValid = isValidStaticQualifiedPart(target)
- if (!isValid)
- return false
- }
- // TODO(lmr): is there more we need to do here?
- return true
- }
- is KtSimpleNameExpression -> {
- when (resolvedCall) {
- is VariableAsFunctionResolvedCall -> {
- val variableDescriptor = resolvedCall.variableCall.candidateDescriptor
- if (variableDescriptor.isVar) return false
- if (variableDescriptor.isConst) return true
- val isRoot = variableDescriptor.isRoot()
- when (variableDescriptor) {
- is PropertyDescriptor ->
- (variableDescriptor.getter?.isDefault ?: false) && isRoot
- else -> false
- }
- }
- else -> true
- }
- }
- else -> false
- }
- is ResolveStep.Nested -> step.isStaticCall
- }
-}
-
-private fun isValidStaticQualifiedPart(target: DeclarationDescriptor): Boolean {
- return when (target) {
- is ClassDescriptor -> when {
- target.kind == ClassKind.OBJECT -> true
- target.isCompanionObject -> true
- else -> false
- }
- is ClassConstructorDescriptor -> true
- is PropertyDescriptor -> when {
- target.isVar -> false
- target.isConst -> true
- target.getter?.isDefault == true -> true
- else -> false
- }
- is FieldDescriptor -> isValidStaticQualifiedPart(target.correspondingProperty)
- is SimpleFunctionDescriptor -> true
- else -> {
- false
- }
- }
-}
-
-private fun isStable(type: KotlinType?, context: ExpressionTypingContext): Boolean {
- return type?.let {
- val trace = context.trace
- val calculated = trace.get(STABLE_TYPE, it)
- if (calculated == null) {
- val isStable = !it.isError && !it.isSpecialType && (
- KotlinBuiltIns.isPrimitiveType(it) ||
- it.isFunctionType ||
- it.isMarkedStable() ||
- (type.isNullable() && isStable(it.makeNotNullable(), context)))
- trace.record(STABLE_TYPE, it, isStable)
- isStable
- } else calculated
- } ?: false
-}
-
-private fun DeclarationDescriptor.isRoot() =
- containingDeclaration?.containingDeclaration is ModuleDescriptor
-
-private fun BindingTrace.recordAttributeKeyRef(expr: KtExpression?, value: DeclarationDescriptor) {
- val key = expr as? KtReferenceExpression ?: error("expected a KtReferenceExpression")
- val prev = get(ComposeWritableSlices.ATTRIBUTE_KEY_REFERENCE_TARGET, key) ?: emptySet()
- record(ComposeWritableSlices.ATTRIBUTE_KEY_REFERENCE_TARGET, key, prev + value)
-}
-
-private fun BindingTrace.recordFailedCandidates(
- expr: KtElement,
- results: Collection<ResolvedCall<FunctionDescriptor>>?
-) {
- if (results == null) return
- val prev = get(ComposeWritableSlices.FAILED_CANDIDATES, expr) ?: emptyList()
- record(ComposeWritableSlices.FAILED_CANDIDATES, expr, prev + results)
-}
-
-enum class ComposerCallKind { CALL, EMIT }
-
-private val ResolvedCall<*>.semanticCall: ResolvedCall<*>
- get() = when (this) {
- is VariableAsFunctionResolvedCall -> variableCall
- else -> this
- }
-
-private val Collection<ValueParameterDescriptor>.possibleChildrenParameter:
- ValueParameterDescriptor?
- get() = maxBy { it.index }?.let { if (it.type.isFunctionType) it else null }
-
-class NoInterceptionCallResolver(private val callResolver: CallResolver) {
- fun resolveCallWithGivenName(
- context: ResolutionContext<*>,
- call: Call,
- name: Name,
- tracing: TracingStrategy
- ): OverloadResolutionResults<FunctionDescriptor> {
- context.trace.record(IGNORE_COMPOSABLE_INTERCEPTION, call, true)
- return callResolver.resolveCallWithGivenName(
- context,
- call,
- name,
- tracing
- )
- }
-
- fun resolveCallWithGivenName(
- context: ResolutionContext<*>,
- call: Call,
- functionReference: KtReferenceExpression,
- name: Name
- ): OverloadResolutionResults<FunctionDescriptor> {
- context.trace.record(IGNORE_COMPOSABLE_INTERCEPTION, call, true)
- return callResolver.resolveCallWithGivenName(
- context,
- call,
- functionReference,
- name
- )
- }
-
- fun resolveCallWithKnownCandidate(
- call: Call,
- tracing: TracingStrategy,
- context: ResolutionContext<*>,
- candidate: ResolutionCandidate<FunctionDescriptor>,
- dataFlowInfoForArguments: MutableDataFlowInfoForArguments?
- ): OverloadResolutionResults<FunctionDescriptor> {
- context.trace.record(IGNORE_COMPOSABLE_INTERCEPTION, call, true)
- return callResolver.resolveCallWithKnownCandidate(
- call,
- tracing,
- context,
- candidate,
- dataFlowInfoForArguments
- )
- }
-
- fun resolveSimpleProperty(
- context: BasicCallResolutionContext
- ): OverloadResolutionResults<VariableDescriptor> {
- return callResolver.resolveSimpleProperty(
- context
- )
- }
-
- fun resolveFunctionCall(
- context: BasicCallResolutionContext
- ): OverloadResolutionResults<FunctionDescriptor> {
- context.trace.record(IGNORE_COMPOSABLE_INTERCEPTION, context.call, true)
- return callResolver.resolveFunctionCall(
- context
- )
- }
-
- fun <T : CallableDescriptor> computeTasksAndResolveCall(
- context: BasicCallResolutionContext,
- name: Name,
- tracing: TracingStrategy,
- kind: NewResolutionOldInference.ResolutionKind
- ): OverloadResolutionResults<T> {
- context.trace.record(IGNORE_COMPOSABLE_INTERCEPTION, context.call, true)
- return callResolver.computeTasksAndResolveCall(
- context,
- name,
- tracing,
- kind
- )
- }
-
- fun <T : CallableDescriptor> computeTasksAndResolveCall(
- context: BasicCallResolutionContext,
- name: Name,
- referenceExpression: KtReferenceExpression,
- kind: NewResolutionOldInference.ResolutionKind
- ): OverloadResolutionResults<T> {
- context.trace.record(IGNORE_COMPOSABLE_INTERCEPTION, context.call, true)
- return callResolver.computeTasksAndResolveCall(
- context,
- name,
- referenceExpression,
- kind
- )
- }
-}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt
deleted file mode 100644
index 5c0eb78..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import org.jetbrains.kotlin.backend.common.push
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.renderer.ClassifierNamePolicy
-import org.jetbrains.kotlin.renderer.DescriptorRenderer
-import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
-import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
-import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
-
-sealed class ValueNode(
- val name: String,
- val type: KotlinType,
- val descriptor: DeclarationDescriptor
-)
-
-class DefaultValueNode(
- name: String,
- type: KotlinType,
- descriptor: DeclarationDescriptor
-) : ValueNode(name, type, descriptor)
-
-class ImplicitCtorValueNode(
- name: String,
- type: KotlinType,
- descriptor: DeclarationDescriptor
-) : ValueNode(name, type, descriptor) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as ImplicitCtorValueNode
-
- if (name != other.name) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- return name.hashCode()
- }
-}
-
-class AttributeNode(
- name: String,
- var isStatic: Boolean,
- var isStable: Boolean,
- val expression: KtExpression,
- type: KotlinType,
- descriptor: DeclarationDescriptor
-) : ValueNode(name, type, descriptor) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as AttributeNode
-
- if (name != other.name) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- return name.hashCode()
- }
-}
-
-enum class ValidationType {
- CHANGED,
- SET,
- UPDATE
-}
-
-class ValidatedAssignment(
- val validationType: ValidationType,
- val validationCall: ResolvedCall<*>?,
- val uncheckedValidationCall: ResolvedCall<*>?,
- val assignment: ResolvedCall<*>?,
- val assignmentLambda: FunctionDescriptor?,
- val attribute: AttributeNode
-)
-
-private fun getParamFnDescriptorFromCall(
- name: Name,
- call: ResolvedCall<*>?,
- context: ExpressionTypingContext
-): FunctionDescriptor? {
- val param = call?.resultingDescriptor?.valueParameters?.firstOrNull {
- it.name == name
- } ?: return null
- return createFunctionDescriptor(param.type, context)
-}
-
-class ComposerCallInfo(
- context: ExpressionTypingContext,
- val composerCall: ResolvedCall<*>?,
- val pivotals: List<AttributeNode>,
- val joinKeyCall: ResolvedCall<*>?,
- val ctorCall: ResolvedCall<*>?,
- val ctorParams: List<ValueNode>,
- val validations: List<ValidatedAssignment>
-) {
- val emitCtorFnDescriptor =
- getParamFnDescriptorFromCall(
- KtxNameConventions.EMIT_CTOR_PARAMETER,
- composerCall,
- context
- )
- val emitUpdaterFnDescriptor =
- getParamFnDescriptorFromCall(
- KtxNameConventions.EMIT_UPDATER_PARAMETER,
- composerCall,
- context
- )
- val emitBodyFnDescriptor =
- getParamFnDescriptorFromCall(
- KtxNameConventions.EMIT_CHILDREN_PARAMETER,
- composerCall,
- context
- )
- val callCtorFnDescriptor =
- getParamFnDescriptorFromCall(
- KtxNameConventions.CALL_CTOR_PARAMETER,
- composerCall,
- context
- )
- val callInvalidFnDescriptor =
- getParamFnDescriptorFromCall(
- KtxNameConventions.CALL_INVALID_PARAMETER,
- composerCall,
- context
- )
- val callBlockFnDescriptor =
- getParamFnDescriptorFromCall(
- KtxNameConventions.CALL_BLOCK_PARAMETER,
- composerCall,
- context
- )
-
- fun allAttributes(): List<ValueNode> =
- ctorParams.filter { it !is ImplicitCtorValueNode } + validations.map { it.attribute }
-}
-
-class AttributeMeta(
- val name: String,
- val type: KotlinType,
- val isChildren: Boolean,
- val descriptor: DeclarationDescriptor
-)
-
-sealed class EmitOrCallNode {
- abstract fun allAttributes(): List<ValueNode>
- abstract fun print(): String
-
- inline fun <T> collect(visitor: (EmitOrCallNode) -> T?): List<T> {
- val results = mutableListOf<T>()
- var node: EmitOrCallNode? = this
- while (node != null) {
- visitor(node)?.let { results.add(it) }
- node = when (node) {
- is MemoizedCallNode -> node.call
- is NonMemoizedCallNode -> node.nextCall
- is EmitCallNode -> null
- is ErrorNode -> null
- }
- }
- return results
- }
-
- fun resolvedCalls(): List<ResolvedCall<*>> {
- return when (this) {
- is MemoizedCallNode -> listOfNotNull(memoize.ctorCall) + call.resolvedCalls()
- is NonMemoizedCallNode ->
- listOf(resolvedCall) + (nextCall?.resolvedCalls() ?: emptyList())
- is EmitCallNode -> listOfNotNull(memoize.ctorCall)
- is ErrorNode.ResolveError -> results.allCandidates?.toList() ?: emptyList()
- is ErrorNode -> emptyList()
- }
- }
-
- fun allPossibleAttributes(): Map<String, List<AttributeMeta>> {
- val collector = mutableMapOf<String, MutableList<AttributeMeta>>()
- collectPossibleAttributes(collector)
- return collector
- }
-
- private fun collectPossibleAttributes(
- collector: MutableMap<String, MutableList<AttributeMeta>>
- ) {
- when (this) {
- is ErrorNode -> Unit
- is MemoizedCallNode -> {
- collectPossibleAttributes(
- memoize.ctorCall?.resultingDescriptor as? FunctionDescriptor, collector
- )
- call.collectPossibleAttributes(collector)
- }
- is NonMemoizedCallNode -> {
- collectPossibleAttributes(
- resolvedCall.resultingDescriptor as? FunctionDescriptor, collector
- )
- nextCall?.collectPossibleAttributes(collector)
- }
- is EmitCallNode -> {
- collectPossibleAttributes(
- memoize.ctorCall?.resultingDescriptor as? FunctionDescriptor, collector
- )
- }
- }
- }
-
- private fun collectPossibleAttributes(
- callDescriptor: FunctionDescriptor?,
- collector: MutableMap<String, MutableList<AttributeMeta>>
- ) {
- callDescriptor?.let {
- it.valueParameters.forEachIndexed { index, param ->
- collector.multiPut(
- AttributeMeta(
- name = param.name.asString(),
- type = param.type,
- descriptor = param,
- isChildren = param.hasChildrenAnnotation() ||
- it.valueParameters.size - 1 == index
- )
- )
- }
- }
- callDescriptor?.returnType?.let { type ->
- val cls = type.constructor.declarationDescriptor as? ClassDescriptor ?: return
- cls.unsubstitutedMemberScope.getContributedDescriptors().forEach {
- when (it) {
- is SimpleFunctionDescriptor -> {
- if (ComposeUtils.isSetterMethodName(
- it.name.asString()
- ) && it.valueParameters.size == 1) {
- val name = ComposeUtils.propertyNameFromSetterMethod(it.name.asString())
- collector.multiPut(
- AttributeMeta(
- name = name,
- type = it.valueParameters.first().type,
- descriptor = it,
- isChildren = it.hasChildrenAnnotation()
- )
- )
- }
- }
- is PropertyDescriptor -> {
- collector.multiPut(
- AttributeMeta(
- name = it.name.asString(),
- type = it.type,
- descriptor = it,
- isChildren = it.hasChildrenAnnotation()
- )
- )
- }
- }
- }
- }
- }
-
- fun errorNode(): ErrorNode? {
- return when (this) {
- is ErrorNode -> this
- is MemoizedCallNode -> call.errorNode()
- is NonMemoizedCallNode -> nextCall?.errorNode()
- is EmitCallNode -> null
- }
- }
-}
-
-sealed class CallNode : EmitOrCallNode()
-
-sealed class ErrorNode : EmitOrCallNode() {
- override fun allAttributes(): List<ValueNode> = emptyList()
- override fun print(): String = "<ERROR:${javaClass.simpleName}>"
-
- class NonEmittableNonCallable(val type: KotlinType) : ErrorNode()
- class RecursionLimitAmbiguousAttributesError(val attributes: Set<String>) : ErrorNode()
- class RecursionLimitError : ErrorNode()
- class NonCallableRoot : ErrorNode()
- class ResolveError(val results: OverloadResolutionResults<FunctionDescriptor>) : ErrorNode()
-}
-
-class NonMemoizedCallNode(
- val resolvedCall: ResolvedCall<*>,
- val params: List<ValueNode>,
- val postAssignments: List<ValidatedAssignment>,
- val applyCall: ResolvedCall<*>?,
- val applyLambdaDescriptor: FunctionDescriptor?,
- val applyLambdaType: KotlinType?,
- var nextCall: EmitOrCallNode?
-) : CallNode() {
- override fun allAttributes(): List<ValueNode> = params
- override fun print(): String = buildString {
- self("NonMemoizedCallNode")
- attr("resolvedCall", resolvedCall) { it.print() }
- attr("params", params) { it.print() }
- list("postAssignments", postAssignments) { it.print() }
- attr("nextCall", nextCall) { it.print() }
- }
-}
-
-class MemoizedCallNode(
- val memoize: ComposerCallInfo,
- val call: EmitOrCallNode
-) : CallNode() {
- override fun allAttributes(): List<ValueNode> = call.allAttributes() + memoize.allAttributes()
- override fun print(): String = buildString {
- self("MemoizedCallNode")
- attr("memoize", memoize) { it.print() }
- attr("call", call) { it.print() }
- }
-}
-
-class EmitCallNode(
- val memoize: ComposerCallInfo,
- val inlineChildren: KtExpression?
-) : EmitOrCallNode() {
- override fun allAttributes(): List<ValueNode> = memoize.allAttributes()
- override fun print() = buildString {
- self("EmitCallNode")
- attr("memoize", memoize) { it.print() }
- }
-}
-
-class ResolvedKtxElementCall(
- val usedAttributes: List<AttributeNode>,
- val unusedAttributes: List<String>,
- val emitOrCall: EmitOrCallNode,
- val getComposerCall: ResolvedCall<*>?,
- val emitSimpleUpperBoundTypes: Set<KotlinType>,
- val emitCompoundUpperBoundTypes: Set<KotlinType>,
- val infixOrCall: ResolvedCall<*>?,
- val attributeInfos: Map<String, AttributeInfo>
-)
-
-class ResolvedRestartCalls(
- val composer: ResolvedCall<*>,
- val startRestartGroup: ResolvedCall<*>,
- val endRestartGroup: ResolvedCall<*>
-)
-
-fun ComposerCallInfo?.consumedAttributes(): List<AttributeNode> {
- if (this == null) return emptyList()
- return pivotals +
- ctorParams.mapNotNull { it as? AttributeNode } +
- validations.map { it.attribute }
-}
-
-fun EmitOrCallNode?.consumedAttributes(): List<AttributeNode> {
- return when (this) {
- is MemoizedCallNode -> memoize.consumedAttributes() + call.consumedAttributes()
- is NonMemoizedCallNode ->
- params.mapNotNull { it as? AttributeNode } + nextCall.consumedAttributes()
- is EmitCallNode -> memoize.consumedAttributes()
- is ErrorNode -> emptyList()
- null -> emptyList()
- }
-}
-
-val EmitOrCallNode.primaryCall: ResolvedCall<*>?
- get() = when (this) {
- is MemoizedCallNode -> memoize.ctorCall ?: call.primaryCall
- is NonMemoizedCallNode -> resolvedCall
- is EmitCallNode -> memoize.ctorCall
- is ErrorNode -> null
- }
-
-fun List<ValueNode>.print(): String {
- return if (isEmpty()) "<empty>"
- else joinToString(", ") { it.print() }
-}
-
-fun ValueNode.print(): String = when (this) {
- is AttributeNode -> name
- is ImplicitCtorValueNode -> "(implicit)$name"
- is DefaultValueNode -> "(default)$name"
-}
-
-fun ResolvedKtxElementCall.print() = buildString {
- self("ResolvedKtxElementCall")
- attr("emitOrCall", emitOrCall) { it.print() }
- attr("usedAttributes", usedAttributes) { it.print() }
- attr("unusedAttributes", unusedAttributes) {
- it.joinToString(separator = ", ").let { s -> if (s.isBlank()) "<empty>" else s }
- }
-}
-
-fun ComposerCallInfo.print() = buildString {
- self("ComposerCallInfo")
- attr("composerCall", composerCall) { it.print() }
- attr("pivotals", pivotals) { it.print() }
- attr("joinKeyCall", joinKeyCall) { it.print() }
- attr("ctorCall", ctorCall) { it.print() }
- attr("ctorParams", ctorParams) { it.print() }
- list("validations", validations) { it.print() }
-}
-
-fun AttributeNode.print() = name
-fun ResolvedCall<*>.print() = DESC_RENDERER.render(resultingDescriptor)
-fun ValidatedAssignment.print() = buildString {
- self("ValidatedAssignment(${validationType.name})")
- attr("validationCall", validationCall) { it.print() }
- attr("assignment", assignment) { it.print() }
- attr("attribute", attribute) { it.print() }
-}
-
-val DESC_RENDERER = DescriptorRenderer.COMPACT_WITHOUT_SUPERTYPES.withOptions {
- parameterNamesInFunctionalTypes = false
- renderConstructorKeyword = false
- classifierNamePolicy = ClassifierNamePolicy.SHORT
- includeAdditionalModifiers = false
- unitReturnType = false
- withoutTypeParameters = true
- parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
- defaultParameterValueRenderer = null
- renderUnabbreviatedType = false
-}
-
-fun StringBuilder.self(name: String) {
- append(name)
- appendln(":")
-}
-
-fun <T> StringBuilder.attr(name: String, obj: T?, printer: (T) -> String) {
- append(" ")
- append(name)
- append(" = ")
- if (obj == null) {
- appendln("<null>")
- } else {
- appendln(printer(obj).indentExceptFirstLine(" ").trim())
- }
-}
-
-fun String.indentExceptFirstLine(indent: String = " "): String =
- lineSequence()
- .drop(1)
- .map {
- when {
- it.isBlank() -> {
- when {
- it.length < indent.length -> indent
- else -> it
- }
- }
- else -> indent + it
- }
- }
- .joinToString("\n", prefix = lineSequence().first() + "\n")
-
-fun <T> StringBuilder.list(name: String, obj: Collection<T>, printer: (T) -> String) {
- append(" ")
- append(name)
- if (obj.isEmpty()) {
- appendln(" = <empty>")
- } else {
- appendln(" =")
- obj.forEach {
- append(" - ")
- appendln(printer(it).indentExceptFirstLine(" ").trim())
- }
- }
-}
-
-private fun <T, V> MutableMap<T, MutableList<V>>.multiPut(key: T, value: V) {
- val current = get(key)
- if (current != null) {
- current.push(value)
- } else {
- put(key, mutableListOf(value))
- }
-}
-
-private fun MutableMap<String, MutableList<AttributeMeta>>.multiPut(value: AttributeMeta) {
- multiPut(value.name, value)
- if (value.isChildren) {
- multiPut(CHILDREN_KEY, value)
- }
-}
-
-/*
-
-fun ResolvedCall<*>.printCode(params: List<AttributeNode>): String {
- return ""
-}
-
-fun ValidatedAssignment.printCode(): String {
- val name = attribute.name
- var result = "${validationType.name.toLowerCase()}($name)"
- when (validationType) {
- ValidationType.UPDATE -> {
- result += " { $name = it }"
- }
- ValidationType.SET -> {
- result += " { $name = it }"
- }
- ValidationType.CHANGED -> Unit
- }
- return result
-}
-
-fun ComposerCallInfo.printCode(methodName: String, itName: String, block: StringBuilder.() -> Unit) = buildString {
- append(methodName)
- append("(")
- val joinKey = (listOf("#k") + pivotals.map { it.name }).joinToString(
- separator = ",",
- prefix = "jk(",
- postfix = ")"
- )
- val ctor = if (ctorCall == null) "null"
- else "{ ${ctorCall.printCode(ctorParams)} }"
-
- val invalid = validations.joinToString(
- separator = " + ",
- prefix = "{ ",
- postfix = " }",
- transform = ValidatedAssignment::printCode
- )
-
- appendln("call($joinKey, $ctor, $invalid) { $itName ->")
- val sb = StringBuilder()
- sb.block()
- appendln(sb.toString().prependIndent(" ").trim())
- appendln("}")
-
-}
-
-fun ResolvedKtxElementCall.printCode() = buildString {
- when (emitOrCall) {
- is MemoizedCallNode -> {
- append(emitOrCall.memoize?.printCode("call", "f") {
- append("f()")
- })
- }
- is NonMemoizedCallNode -> {
-
- }
- is EmitCallNode -> {
-
- }
- }
-}
-
-*/
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Stateful.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedRestartCalls.kt
similarity index 70%
rename from compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Stateful.kt
rename to compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedRestartCalls.kt
index 49d29f8..9bf1eaa 100644
--- a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Stateful.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedRestartCalls.kt
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package androidx.compose
+package androidx.compose.plugins.kotlin
-/**
- * @suppress
- */
-@Target(
- AnnotationTarget.FUNCTION,
- AnnotationTarget.CLASS,
- AnnotationTarget.ANNOTATION_CLASS
-)
-annotation class Stateful
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+
+class ResolvedRestartCalls(
+ val composer: ResolvedCall<*>,
+ val startRestartGroup: ResolvedCall<*>,
+ val endRestartGroup: ResolvedCall<*>
+)
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/StaticWrapperCreatorFunctionResolveExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/StaticWrapperCreatorFunctionResolveExtension.kt
deleted file mode 100644
index add0f33..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/StaticWrapperCreatorFunctionResolveExtension.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
-import org.jetbrains.kotlin.name.Name
-import androidx.compose.plugins.kotlin.analysis.ComponentMetadata
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
-import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
-import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider
-
-class StaticWrapperCreatorFunctionResolveExtension() : SyntheticResolveExtension {
-
- override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor): Name? {
- return if (ComponentMetadata.isComposeComponent(thisDescriptor))
- Name.identifier("R4HStaticRenderCompanion")
- else null
- }
-
- override fun generateSyntheticClasses(
- thisDescriptor: ClassDescriptor,
- name: Name,
- ctx: LazyClassContext,
- declarationProvider: ClassMemberDeclarationProvider,
- result: MutableSet<ClassDescriptor>
- ) {
- super.generateSyntheticClasses(thisDescriptor, name, ctx, declarationProvider, result)
-
- if (ComponentMetadata.isComposeComponent(thisDescriptor)) {
- val wrapperViewDescriptor =
- ComponentMetadata.fromDescriptor(thisDescriptor).wrapperViewDescriptor
- if (wrapperViewDescriptor.name == name) result.add(wrapperViewDescriptor)
- }
- }
-
- override fun generateSyntheticMethods(
- thisDescriptor: ClassDescriptor,
- name: Name,
- bindingContext: BindingContext,
- fromSupertypes: List<SimpleFunctionDescriptor>,
- result: MutableCollection<SimpleFunctionDescriptor>
- ) {
- if (!ComponentMetadata.isComponentCompanion(thisDescriptor)) return
- if (name != Name.identifier("createInstance")) return
-
- val containingClass = thisDescriptor.containingDeclaration as? ClassDescriptor ?: return
- val wrapperView = ComponentMetadata.fromDescriptor(containingClass).wrapperViewDescriptor
- result.add(wrapperView.getInstanceCreatorFunction(thisDescriptor))
- }
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/TryCatchComposableChecker.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/TryCatchComposableChecker.kt
index 3466b42..9cc10a1 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/TryCatchComposableChecker.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/TryCatchComposableChecker.kt
@@ -16,15 +16,14 @@
package androidx.compose.plugins.kotlin
+import androidx.compose.plugins.kotlin.analysis.ComposeDefaultErrorMessages
+import androidx.compose.plugins.kotlin.analysis.ComposeErrors
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.reportFromPlugin
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
-import org.jetbrains.kotlin.psi.KtFunction
-import androidx.compose.plugins.kotlin.analysis.ComposeDefaultErrorMessages
-import androidx.compose.plugins.kotlin.analysis.ComposeErrors
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.psi.KtTryExpression
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ValidatedAssignment.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ValidatedAssignment.kt
new file mode 100644
index 0000000..2272ab9
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ValidatedAssignment.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 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.plugins.kotlin
+
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.types.KotlinType
+
+class ValidatedAssignment(
+ val validationType: ValidationType,
+ val validationCall: ResolvedCall<*>?,
+ val uncheckedValidationCall: ResolvedCall<*>?,
+ val assignment: ResolvedCall<*>?,
+ val assignmentLambda: FunctionDescriptor?, // needed?
+ val type: KotlinType,
+ val name: String,
+ val descriptor: DeclarationDescriptor
+)
+
+enum class ValidationType {
+ CHANGED,
+ SET,
+ UPDATE
+}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/WrapperViewSettersGettersResolveExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/WrapperViewSettersGettersResolveExtension.kt
deleted file mode 100644
index 08c84dd..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/WrapperViewSettersGettersResolveExtension.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
-import org.jetbrains.kotlin.name.Name
-import androidx.compose.plugins.kotlin.analysis.ComponentMetadata
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
-
-class WrapperViewSettersGettersResolveExtension : SyntheticResolveExtension {
- override fun generateSyntheticMethods(
- thisDescriptor: ClassDescriptor,
- name: Name,
- bindingContext: BindingContext,
- fromSupertypes: List<SimpleFunctionDescriptor>,
- result: MutableCollection<SimpleFunctionDescriptor>
- ) {
- if (!ComponentMetadata.isWrapperView(thisDescriptor)) return
-
- result.addAll((thisDescriptor as GeneratedViewClassDescriptor).attributeSetters)
- }
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComponentMetadata.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComponentMetadata.kt
deleted file mode 100644
index f9355d4..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComponentMetadata.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package androidx.compose.plugins.kotlin.analysis
-
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.Visibilities
-import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import androidx.compose.plugins.kotlin.GeneratedViewClassDescriptor
-import androidx.compose.plugins.kotlin.ComposeUtils
-import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
-import org.jetbrains.kotlin.resolve.descriptorUtil.module
-
-/**
- * ComponentMetadata takes in a DeclarationDescriptor and interprets it as a component.
- * All assumptions about the Component API (public and private) should reside in this class.
- *
- * A ComponentMetadata can be used to get Compose synthetic method descriptors on a component,
- * as well as descriptors for the various synthetic helper classes.
- */
-class ComponentMetadata(val descriptor: ClassDescriptor) {
- // TODO(lmr): ClassDescriptor won't handle SFCs. Consider how to refactor.
-
- val wrapperViewDescriptor by lazy {
- GeneratedViewClassDescriptor(this)
- }
-
- init {
- if (!isComposeComponent(
- descriptor
- )
- )
- throw IllegalArgumentException("Not a component: " + descriptor::class)
- }
-
- companion object {
- // Use DeclarationDescriptor as the primary key (instead of PSI), because the PSI doesn't
- // exist for synthetic descriptors
- private val cache = HashMap<DeclarationDescriptor, ComponentMetadata>()
-
- fun isComponentCompanion(cls: ClassDescriptor): Boolean {
- if (!cls.isCompanionObject) return false
- if (!cls.name.identifier.contains("R4HStaticRenderCompanion")) return false
- val containingClass = cls.containingDeclaration as? ClassDescriptor ?: return false
- if (!isComposeComponent(
- containingClass
- )
- ) return false
- return true
- }
-
- fun isComposeComponent(descriptor: DeclarationDescriptor): Boolean {
- if (descriptor !is ClassDescriptor) return false
- val baseComponentDescriptor =
- descriptor.module.findClassAcrossModuleDependencies(
- ClassId.topLevel(
- FqName(ComposeUtils.generateComposePackageName() + ".Component")
- )
- ) ?: return false
- return descriptor.isSubclassOf(baseComponentDescriptor)
- }
-
- fun isWrapperView(descriptor: DeclarationDescriptor): Boolean {
- return descriptor is GeneratedViewClassDescriptor
- }
-
- fun fromDescriptor(descriptor: ClassDescriptor): ComponentMetadata {
- if (!cache.containsKey(descriptor)) cache[descriptor] =
- ComponentMetadata(descriptor)
- return cache[descriptor]!!
- }
- }
-
- fun getAttributeDescriptors(): List<PropertyDescriptor> {
- return descriptor.unsubstitutedMemberScope.getContributedDescriptors()
- .mapNotNull { it as? PropertyDescriptor }
- .filter {
- it.containingDeclaration == descriptor && !Visibilities.isPrivate(it.visibility) &&
- it.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE
- }
- .sortedBy { it.name }
- }
-}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
index 26917c0..baa2c2b 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
@@ -31,27 +31,6 @@
"Model objects do not support inheritance"
)
MAP.put(
- ComposeErrors.MISMATCHED_ATTRIBUTE_TYPE,
- "<html>Type Mismatch.<br/>Required: {1}<br/>Found: {0}</html>",
- Renderers.RENDER_TYPE,
- Renderers.commaSeparated(Renderers.RENDER_TYPE)
- )
- MAP.put(
- ComposeErrors.MISMATCHED_INFERRED_ATTRIBUTE_TYPE,
- "<html>Type Mismatch.<br/>Required: {1}<br/>Found: {0}</html>",
- Renderers.RENDER_TYPE,
- Renderers.commaSeparated(Renderers.RENDER_TYPE)
- )
- MAP.put(
- ComposeErrors.CALLABLE_RECURSION_DETECTED,
- "Recursion detected"
- )
- MAP.put(
- ComposeErrors.AMBIGUOUS_ATTRIBUTES_DETECTED,
- "KTX call targets resulted in ambiguous attributes: {0}",
- Renderers.commaSeparated(Renderers.STRING)
- )
- MAP.put(
ComposeErrors.INVALID_COMPOSER_IMPLEMENTATION,
"Composer of type ''{0}'' was found to be an invalid Composer implementation. " +
"Reason: {1}",
@@ -59,40 +38,6 @@
Renderers.STRING
)
MAP.put(
- ComposeErrors.UNRESOLVED_CHILDREN,
- "<html>Mismatched children body type.<br/>Expected: {0}</html>",
- Renderers.commaSeparated(Renderers.RENDER_TYPE)
- )
- MAP.put(
- ComposeErrors.UNRESOLVED_ATTRIBUTE_KEY,
- "No valid attribute on ''{0}'' found with key ''{1}'' and type ''{2}''",
- Renderers.commaSeparated(Renderers.COMPACT),
- Renderers.STRING,
- Renderers.RENDER_TYPE
- )
- MAP.put(
- ComposeErrors.UNRESOLVED_ATTRIBUTE_KEY_UNKNOWN_TYPE,
- "No valid attribute on ''{0}'' found with key ''{1}''",
- Renderers.commaSeparated(Renderers.COMPACT),
- Renderers.STRING
- )
- MAP.put(
- ComposeErrors.MISMATCHED_ATTRIBUTE_TYPE_NO_SINGLE_PARAM_SETTER_FNS,
- "Setters with multiple arguments are currently unsupported. Found: ''{0}''",
- Renderers.COMPACT
- )
- MAP.put(
- ComposeErrors.MISSING_REQUIRED_ATTRIBUTES,
- "Missing required attributes: {0}",
- Renderers.commaSeparated(Renderers.COMPACT)
- )
- MAP.put(
- ComposeErrors.INVALID_TAG_TYPE,
- "Invalid KTX tag type. Found ''{0}'', Expected ''{1}''",
- Renderers.RENDER_TYPE,
- Renderers.commaSeparated(Renderers.RENDER_TYPE)
- )
- MAP.put(
ComposeErrors.SUSPEND_FUNCTION_USED_AS_SFC,
"Suspend functions are not allowed to be used as Components"
)
@@ -101,69 +46,23 @@
"Only Unit-returning functions are allowed to be used as Components"
)
MAP.put(
- ComposeErrors.INVALID_TAG_DESCRIPTOR,
- "Invalid KTX tag type. Expected ''{0}''",
- Renderers.commaSeparated(Renderers.RENDER_TYPE)
- )
- MAP.put(
ComposeErrors.SVC_INVOCATION,
"Stateless Functional Components (SFCs) should not be invoked, use <{0} /> " +
"syntax instead",
Renderers.STRING
)
MAP.put(
- ComposeErrors.NON_COMPOSABLE_INVOCATION,
- "{0} `{1}` must be marked as @Composable in order to be used as a KTX tag",
- Renderers.STRING,
- Renderers.COMPACT
- )
- MAP.put(
- ComposeErrors.KTX_IN_NON_COMPOSABLE,
- "Functions containing KTX Tags should be marked with the @Composable annotation"
- )
- MAP.put(
ComposeErrors.COMPOSABLE_INVOCATION_IN_NON_COMPOSABLE,
"Functions which invoke @Composable functions must be marked with the @Composable " +
"annotation"
)
MAP.put(
- ComposeErrors.UNRESOLVED_TAG,
- "Unresolved reference: {0}",
- Renderers.ELEMENT_TEXT
- )
- MAP.put(
- ComposeErrors.CHILDREN_ATTR_USED_AS_BODY_AND_KEYED_ATTRIBUTE,
- "Attribute {0} is provided as the body of the element and therefore can't " +
- "simultaneously be provided as a keyed attribute.",
- Renderers.STRING
- )
- MAP.put(
- ComposeErrors.CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED,
- "Element has a children body provided, but no @Children declarations were found"
- )
- MAP.put(
- ComposeErrors.MISSING_REQUIRED_CHILDREN,
- "A children body of type '{0}' is required",
- Renderers.RENDER_TYPE
- )
- MAP.put(
ComposeErrors.ILLEGAL_ASSIGN_TO_UNIONTYPE,
"Value of type {0} can't be assigned to union type {1}.",
RENDER_COLLECTION_OF_TYPES,
RENDER_COLLECTION_OF_TYPES
)
MAP.put(
- ComposeErrors.AMBIGUOUS_KTX_CALL,
- "Ambiguous targets. {0}",
- Renderers.AMBIGUOUS_CALLS
- )
- MAP.put(
- ComposeErrors.CHILDREN_MUST_BE_LAST,
- "Children annotation must only occur on last parameter. This annotation is " +
- "deprecated (move children to be last parameter, make it @Composable, and " +
- "remove the @Children annotation.)."
- )
- MAP.put(
ComposeErrors.ILLEGAL_TRY_CATCH_AROUND_COMPOSABLE,
"Try catch is not supported around composable function invocations."
)
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
index 810ce18..c54e713 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
@@ -44,54 +44,18 @@
DiagnosticFactory0<PsiElement> OPEN_MODEL = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> UNSUPPORTED_MODEL_INHERITANCE = DiagnosticFactory0.create(ERROR);
- DiagnosticFactory3<KtElement, Collection<DeclarationDescriptor>, String, KotlinType>
- UNRESOLVED_ATTRIBUTE_KEY = DiagnosticFactory3.create(ERROR);
- DiagnosticFactory2<KtElement, Collection<DeclarationDescriptor>, String>
- UNRESOLVED_ATTRIBUTE_KEY_UNKNOWN_TYPE = DiagnosticFactory2.create(ERROR);
- DiagnosticFactory2<KtElement, KotlinType, Collection<KotlinType>>
- MISMATCHED_ATTRIBUTE_TYPE = DiagnosticFactory2.create(ERROR);
- DiagnosticFactory2<KtElement, KotlinType, Collection<KotlinType>>
- MISMATCHED_INFERRED_ATTRIBUTE_TYPE = DiagnosticFactory2.create(ERROR);
- DiagnosticFactory1<KtElement, Collection<KotlinType>>
- UNRESOLVED_CHILDREN = DiagnosticFactory1.create(ERROR);
- DiagnosticFactory1<KtElement, SimpleFunctionDescriptor>
- MISMATCHED_ATTRIBUTE_TYPE_NO_SINGLE_PARAM_SETTER_FNS = DiagnosticFactory1.create(ERROR);
- DiagnosticFactory1<KtElement, Collection<DeclarationDescriptor>>
- MISSING_REQUIRED_ATTRIBUTES = DiagnosticFactory1.create(ERROR);
- DiagnosticFactory2<KtElement, KotlinType, Collection<KotlinType>>
- INVALID_TAG_TYPE = DiagnosticFactory2.create(ERROR);
- DiagnosticFactory0<KtElement>
- CALLABLE_RECURSION_DETECTED = DiagnosticFactory0.create(ERROR);
- DiagnosticFactory1<KtElement, Collection<String>>
- AMBIGUOUS_ATTRIBUTES_DETECTED = DiagnosticFactory1.create(ERROR);
- DiagnosticFactory1<KtElement, Collection<KotlinType>>
- INVALID_TAG_DESCRIPTOR = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<KtElement, String>
SVC_INVOCATION = DiagnosticFactory1.create(ERROR);
- DiagnosticFactory2<KtElement, String, DeclarationDescriptor>
- NON_COMPOSABLE_INVOCATION = DiagnosticFactory2.create(WARNING);
DiagnosticFactory0<KtElement>
SUSPEND_FUNCTION_USED_AS_SFC = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement>
- KTX_IN_NON_COMPOSABLE = DiagnosticFactory0.create(WARNING);
- DiagnosticFactory0<PsiElement>
COMPOSABLE_INVOCATION_IN_NON_COMPOSABLE = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<KtElement>
INVALID_TYPE_SIGNATURE_SFC = DiagnosticFactory0.create(ERROR);
- DiagnosticFactory1<KtElement, KtReferenceExpression>
- UNRESOLVED_TAG = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<KtElement>
NO_COMPOSER_FOUND = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<KtElement, KotlinType, String>
INVALID_COMPOSER_IMPLEMENTATION = DiagnosticFactory2.create(ERROR);
- DiagnosticFactory1<KtElement, Collection<ResolvedCall<?>>>
- AMBIGUOUS_KTX_CALL = DiagnosticFactory1.create(ERROR);
- DiagnosticFactory1<KtElement, String>
- CHILDREN_ATTR_USED_AS_BODY_AND_KEYED_ATTRIBUTE = DiagnosticFactory1.create(ERROR);
- DiagnosticFactory0<KtElement>
- CHILDREN_PROVIDED_BUT_NO_CHILDREN_DECLARED = DiagnosticFactory0.create(ERROR);
- DiagnosticFactory1<KtElement, KotlinType>
- MISSING_REQUIRED_CHILDREN = DiagnosticFactory1.create(ERROR);
DiagnosticFactory2<KtExpression, Collection<KotlinType>, Collection<KotlinType>>
ILLEGAL_ASSIGN_TO_UNIONTYPE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory0<KtElement>
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
index 4b96dd0..b2df5aa 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
@@ -1,11 +1,11 @@
package androidx.compose.plugins.kotlin.analysis
import androidx.compose.plugins.kotlin.ComposableAnnotationChecker
-import androidx.compose.plugins.kotlin.ComposerEmitMetadata
-import androidx.compose.plugins.kotlin.ResolvedKtxElementCall
+import androidx.compose.plugins.kotlin.ComposerMetadata
import androidx.compose.plugins.kotlin.ResolvedRestartCalls
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.psi.Call
@@ -20,13 +20,6 @@
object ComposeWritableSlices {
val COMPOSABLE_ANALYSIS: WritableSlice<KtElement, ComposableAnnotationChecker.Composability> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
- val RESOLVED_KTX_CALL: WritableSlice<KtElement, ResolvedKtxElementCall> =
- BasicWritableSlice(RewritePolicy.DO_NOTHING)
- val ATTRIBUTE_KEY_REFERENCE_TARGET:
- WritableSlice<KtReferenceExpression, Set<DeclarationDescriptor>> =
- BasicWritableSlice(REWRITES_ALLOWED)
- val FAILED_CANDIDATES: WritableSlice<KtElement, Collection<ResolvedCall<FunctionDescriptor>>> =
- BasicWritableSlice(REWRITES_ALLOWED)
val FCS_CALL_WITHIN_COMPOSABLE: WritableSlice<KtElement, Boolean> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
val FCS_RESOLVEDCALL_COMPOSABLE: WritableSlice<KtElement, Boolean> =
@@ -39,10 +32,12 @@
BasicWritableSlice(RewritePolicy.DO_NOTHING)
val RESTART_CALLS: WritableSlice<SimpleFunctionDescriptor, ResolvedRestartCalls> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
- val COMPOSER_EMIT_METADATA: WritableSlice<VariableDescriptor, ComposerEmitMetadata> =
+ val COMPOSER_METADATA: WritableSlice<VariableDescriptor, ComposerMetadata> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
val IGNORE_COMPOSABLE_INTERCEPTION: WritableSlice<Call, Boolean> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
+ val IS_EMIT_CHILDREN_PARAMETER: WritableSlice<ParameterDescriptor, Boolean> =
+ BasicWritableSlice(RewritePolicy.DO_NOTHING)
}
private val REWRITES_ALLOWED = object : RewritePolicy {
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/ir/IrKtxAttribute.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/ir/IrKtxAttribute.kt
deleted file mode 100644
index 2aad490..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/ir/IrKtxAttribute.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package androidx.compose.plugins.kotlin.compiler.ir
-
-import org.jetbrains.kotlin.ir.IrKtxStatement
-import org.jetbrains.kotlin.ir.expressions.IrExpression
-import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
-import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
-import org.jetbrains.kotlin.psi.KtxAttribute
-import org.jetbrains.kotlin.psi.psiUtil.endOffset
-import org.jetbrains.kotlin.psi.psiUtil.startOffset
-
-class IrKtxAttribute(
- val element: KtxAttribute,
- val value: IrExpression
-) : IrKtxStatement {
- override val startOffset = element.startOffset
- override val endOffset = element.endOffset
- override fun <R, D> accept(visitor: IrElementVisitor<R, D>, data: D): R =
- visitor.visitKtxStatement(this, data)
- override fun <D> acceptChildren(visitor: IrElementVisitor<Unit, D>, data: D) =
- value.accept(visitor, data)
- override fun <D> transformChildren(transformer: IrElementTransformer<D>, data: D) {
- throw UnsupportedOperationException(
- "This temporary placeholder node must be manually transformed, not visited"
- )
- }
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/ir/Utilities.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/ir/Utilities.kt
deleted file mode 100644
index 43ee538..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/ir/Utilities.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package androidx.compose.plugins.kotlin.compiler.ir
-
-import org.jetbrains.kotlin.ir.IrElement
-import org.jetbrains.kotlin.ir.IrKtxStatement
-import org.jetbrains.kotlin.ir.declarations.IrDeclaration
-import org.jetbrains.kotlin.ir.util.withScope
-import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
-import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
-
-fun IrElement.find(filter: (descriptor: IrElement) -> Boolean): Collection<IrElement> {
- val elements: MutableList<IrElement> = mutableListOf()
- accept(object : IrElementVisitorVoid {
- override fun visitElement(element: IrElement) {
- if (filter(element)) elements.add(element)
- element.acceptChildren(this, null)
- }
- override fun visitKtxStatement(expression: IrKtxStatement, data: Nothing?) {
- expression.acceptChildren(this, null)
- }
- }, null)
- return elements
-}
-
-inline fun <T : IrDeclaration> T.buildWithScope(
- context: GeneratorContext,
- builder: (T) -> Unit
-): T =
- also { irDeclaration ->
- context.symbolTable.withScope(irDeclaration.descriptor) {
- builder(irDeclaration)
- }
- }
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposableCallTransformer.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposableCallTransformer.kt
new file mode 100644
index 0000000..dfb430e
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposableCallTransformer.kt
@@ -0,0 +1,869 @@
+package androidx.compose.plugins.kotlin.compiler.lower
+
+import androidx.compose.plugins.kotlin.COMPOSABLE_EMIT_OR_CALL
+import androidx.compose.plugins.kotlin.ComposableEmitDescriptor
+import androidx.compose.plugins.kotlin.ComposableFunctionDescriptor
+import androidx.compose.plugins.kotlin.EmitChildrenValueParameterDescriptor
+import androidx.compose.plugins.kotlin.KtxNameConventions
+import androidx.compose.plugins.kotlin.ValidatedAssignment
+import androidx.compose.plugins.kotlin.ValidationType
+import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
+import androidx.compose.plugins.kotlin.hasPivotalAnnotation
+import androidx.compose.plugins.kotlin.isMarkedStable
+import androidx.compose.plugins.kotlin.isSpecialType
+import org.jetbrains.kotlin.backend.common.FileLoweringPass
+import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
+import org.jetbrains.kotlin.backend.common.peek
+import org.jetbrains.kotlin.backend.common.pop
+import org.jetbrains.kotlin.backend.common.push
+import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.builtins.extractParameterNameFromFunctionTypeArgument
+import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
+import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
+import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
+import org.jetbrains.kotlin.builtins.isFunctionType
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.Modality
+import org.jetbrains.kotlin.descriptors.ParameterDescriptor
+import org.jetbrains.kotlin.descriptors.PropertyDescriptor
+import org.jetbrains.kotlin.descriptors.SourceElement
+import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
+import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
+import org.jetbrains.kotlin.descriptors.Visibilities
+import org.jetbrains.kotlin.descriptors.annotations.Annotations
+import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
+import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
+import org.jetbrains.kotlin.ir.IrStatement
+import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
+import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder
+import org.jetbrains.kotlin.ir.builders.IrBlockBuilder
+import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
+import org.jetbrains.kotlin.ir.builders.irBlock
+import org.jetbrains.kotlin.ir.builders.irBlockBody
+import org.jetbrains.kotlin.ir.builders.irCall
+import org.jetbrains.kotlin.ir.builders.irFalse
+import org.jetbrains.kotlin.ir.builders.irGet
+import org.jetbrains.kotlin.ir.builders.irInt
+import org.jetbrains.kotlin.ir.builders.irReturn
+import org.jetbrains.kotlin.ir.builders.irReturnUnit
+import org.jetbrains.kotlin.ir.builders.irTemporary
+import org.jetbrains.kotlin.ir.builders.irTrue
+import org.jetbrains.kotlin.ir.builders.parent
+import org.jetbrains.kotlin.ir.declarations.IrDeclaration
+import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
+import org.jetbrains.kotlin.ir.declarations.IrFile
+import org.jetbrains.kotlin.ir.declarations.IrFunction
+import org.jetbrains.kotlin.ir.declarations.getIrValueParameter
+import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
+import org.jetbrains.kotlin.ir.declarations.impl.IrTypeParameterImpl
+import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
+import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
+import org.jetbrains.kotlin.ir.expressions.IrBlock
+import org.jetbrains.kotlin.ir.expressions.IrCall
+import org.jetbrains.kotlin.ir.expressions.IrConst
+import org.jetbrains.kotlin.ir.expressions.IrExpression
+import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
+import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
+import org.jetbrains.kotlin.ir.expressions.IrGetValue
+import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
+import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
+import org.jetbrains.kotlin.ir.expressions.copyTypeArgumentsFrom
+import org.jetbrains.kotlin.ir.expressions.getValueArgument
+import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
+import org.jetbrains.kotlin.ir.expressions.putTypeArguments
+import org.jetbrains.kotlin.ir.expressions.putValueArgument
+import org.jetbrains.kotlin.ir.expressions.typeParametersCount
+import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
+import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl
+import org.jetbrains.kotlin.ir.types.IrType
+import org.jetbrains.kotlin.ir.types.toKotlinType
+import org.jetbrains.kotlin.ir.util.ConstantValueGenerator
+import org.jetbrains.kotlin.ir.util.TypeTranslator
+import org.jetbrains.kotlin.ir.util.endOffset
+import org.jetbrains.kotlin.ir.util.referenceFunction
+import org.jetbrains.kotlin.ir.util.startOffset
+import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
+import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
+import org.jetbrains.kotlin.js.translate.callTranslator.getReturnType
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi2ir.findFirstFunction
+import org.jetbrains.kotlin.resolve.DescriptorFactory
+import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.isError
+import org.jetbrains.kotlin.types.isNullable
+import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
+
+class ComposableCallTransformer(val context: JvmBackendContext) :
+ IrElementTransformerVoid(),
+ FileLoweringPass {
+
+ private val typeTranslator =
+ TypeTranslator(
+ context.ir.symbols.externalSymbolTable,
+ context.state.languageVersionSettings,
+ context.builtIns
+ ).apply {
+ constantValueGenerator = ConstantValueGenerator(
+ context.state.module,
+ context.ir.symbols.externalSymbolTable
+ )
+ constantValueGenerator.typeTranslator = this
+ }
+
+ private val builtIns = context.irBuiltIns
+
+ private val orFunctionDescriptor = builtIns.builtIns.booleanType.memberScope
+ .findFirstFunction("or") { it is FunctionDescriptor && it.isInfix }
+
+ private val jvmContext get() = context
+
+ private val symbolTable get() = context.ir.symbols.externalSymbolTable
+
+ private fun KotlinType?.isStable(): Boolean {
+ if (this == null) return false
+
+ val trace = jvmContext.state.bindingTrace
+ val calculated = trace.get(ComposeWritableSlices.STABLE_TYPE, this)
+ return if (calculated == null) {
+ val isStable = !isError &&
+ !isSpecialType &&
+ (
+ KotlinBuiltIns.isPrimitiveType(this) ||
+ isFunctionType ||
+ isMarkedStable() ||
+ (
+ isNullable() &&
+ makeNotNullable().isStable()
+ )
+ )
+ trace.record(ComposeWritableSlices.STABLE_TYPE, this, isStable)
+ isStable
+ } else calculated
+ }
+
+ override fun lower(irFile: IrFile) {
+ irFile.transformChildrenVoid(this)
+ }
+
+ val declarationStack = mutableListOf<IrFunction>()
+
+ override fun visitFunction(declaration: IrFunction): IrStatement {
+ try {
+ declarationStack.push(declaration)
+ return super.visitFunction(declaration)
+ } finally {
+ declarationStack.pop()
+ }
+ }
+
+ override fun visitBlock(expression: IrBlock): IrExpression {
+ if (expression.origin != COMPOSABLE_EMIT_OR_CALL) {
+ return super.visitBlock(expression)
+ }
+
+ assert(expression.statements.size == 2)
+ // the first statement should represent the call to get the composer
+ // the second statement should represent the composable call or emit
+ val (first, second) = expression.statements
+ val composerCall = when {
+ first is IrCall -> first
+ // the psi->IR phase seems to generate coercions to UNIT, so we won't just find the
+ // bare call here.
+ first is IrTypeOperatorCall &&
+ first.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT &&
+ first.argument is IrCall -> first.argument as IrCall
+ else -> error("Couldn't find composer call in block")
+ }
+
+ val emitOrCall = when {
+ second is IrCall -> second
+ // the psi -> IR phase seems to generate argument reordering blocks when calls are
+ // made with named arguments out of order. In this case we need to find the last
+ // statement
+ second is IrBlock &&
+ second.origin == IrStatementOrigin.ARGUMENTS_REORDERING_FOR_CALL &&
+ second.statements.last() is IrCall -> second.statements.last() as IrCall
+ else -> error("Couldn't find composable call in block")
+ }
+
+ val descriptor = emitOrCall.descriptor
+
+ return when {
+ descriptor.isInline -> expression
+ descriptor is ComposableFunctionDescriptor -> {
+ val transformedComposerCall = composerCall.transformChildren()
+ val transformed = emitOrCall.transformChildren()
+
+ context.createIrBuilder(declarationStack.last().symbol).irBlock {
+ +irComposableCall(transformedComposerCall, transformed, descriptor)
+ }
+ }
+ descriptor is ComposableEmitDescriptor -> {
+ val transformedComposerCall = composerCall.transformChildren()
+ val transformed = emitOrCall.transformChildren()
+ context.createIrBuilder(declarationStack.last().symbol).irBlock {
+ +irComposableEmit(transformedComposerCall, transformed, descriptor)
+ }
+ }
+ else -> error("dont know what to do")
+ }
+ }
+
+ private fun IrExpression.isReorderTemporaryVariable(): Boolean {
+ return this is IrGetValue &&
+ symbol.owner.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE
+ }
+
+ private fun IrExpression.unwrapReorderTemporaryVariable(): IrExpression {
+ val getValue = this as IrGetValue
+ val variable = getValue.symbol.owner as IrVariableImpl
+ return variable.initializer!!
+ }
+
+ private fun IrBlockBuilder.irComposableCall(
+ composerCall: IrCall,
+ original: IrCall,
+ descriptor: ComposableFunctionDescriptor
+ ): IrExpression {
+ val composerTemp = irTemporary(composerCall)
+
+ /*
+
+ Foo(text="foo")
+
+ // transforms into
+
+ val attr_text = "foo"
+ composer.call(
+ key = 123,
+ invalid = { changed(attr_text) },
+ block = { Foo(attr_text) }
+ )
+ */
+ val composer = descriptor.composerCall.resultingDescriptor as PropertyDescriptor
+
+ // TODO(lmr): the way we grab temporaries here feels wrong. We should investigate the right
+ // way to do this. Additionally, we are creating temporary vars for variables which is
+ // causing larger stack space than needed in our generated code.
+
+ val irGetArguments = original
+ .descriptor
+ .valueParameters
+ .map {
+ val arg = original.getValueArgument(it)
+ it to getParameterExpression(it, arg)
+ }
+
+ val tmpDispatchReceiver = original.dispatchReceiver?.let { irTemporary(it) }
+ val tmpExtensionReceiver = original.extensionReceiver?.let { irTemporary(it) }
+
+ // TODO(lmr): come up with a better way to find this?
+ val callDescriptor = descriptor
+ .composerMetadata
+ .callDescriptors
+ .first { it.typeParametersCount == 0 }
+
+ val joinKeyDescriptor = composer
+ .type
+ .memberScope
+ .findFirstFunction(KtxNameConventions.JOINKEY.identifier) {
+ it.valueParameters.size == 2
+ }
+
+ val callParameters = callDescriptor.valueParameters
+ .map { it.name to it }
+ .toMap()
+
+ fun getCallParameter(name: Name) = callParameters[name]
+ ?: error("Expected $name parameter to exist")
+
+ return irCall(
+ callee = symbolTable.referenceFunction(callDescriptor),
+ type = builtIns.unitType // TODO(lmr): refactor call(...) to return a type
+ ).apply {
+ dispatchReceiver = irGet(composerTemp)
+
+ putValueArgument(
+ getCallParameter(KtxNameConventions.CALL_KEY_PARAMETER),
+ irGroupKey(
+ original = original,
+ getComposer = { irGet(composerTemp) },
+ joinKey = joinKeyDescriptor,
+ pivotals = irGetArguments.mapNotNull { (param, getExpr) ->
+ val expr = getExpr()
+ if (expr == null) null
+ else if (!param.hasPivotalAnnotation()) null
+ else expr
+ }
+ )
+ )
+
+ val invalidParameter = getCallParameter(KtxNameConventions.CALL_INVALID_PARAMETER)
+
+ val validatorType = invalidParameter.type.getReceiverTypeFromFunctionType()
+ ?: error("Expected validator type to be on receiver of the invalid lambda")
+
+ val changedDescriptor = validatorType
+ .memberScope
+ .findFirstFunction("changed") { it.typeParametersCount == 1 }
+
+ val validatedArguments: List<IrExpression> =
+ irGetArguments.mapNotNull { (_, getExpr) -> getExpr() } +
+ listOfNotNull(
+ tmpDispatchReceiver?.let { irGet(it) },
+ tmpExtensionReceiver?.let { irGet(it) }
+ )
+
+ val isSkippable = validatedArguments.all { it.type.toKotlinType().isStable() }
+
+ putValueArgument(
+ invalidParameter,
+ irLambdaExpression(
+ original.startOffset,
+ original.endOffset,
+ descriptor = createFunctionDescriptor(
+ type = invalidParameter.type,
+ owner = descriptor.containingDeclaration
+ ),
+ type = invalidParameter.type.toIrType()
+ ) { fn ->
+ if (!isSkippable) {
+ // if it's not skippable, we don't validate any arguments.
+ +irReturn(irTrue())
+ } else {
+ val validationCalls = validatedArguments
+ .map {
+ irChangedCall(
+ changedDescriptor = changedDescriptor,
+ receiver = irGet(fn.extensionReceiverParameter!!),
+ attributeValue = it
+ )
+ }
+
+ // all as one expression: a or b or c ... or z
+ +irReturn(when (validationCalls.size) {
+ 0 -> irFalse()
+ 1 -> validationCalls.single()
+ else -> validationCalls.reduce { accumulator, value ->
+ when {
+ // if it is a constant, the value is `false`
+ accumulator is IrConst<*> -> value
+ value is IrConst<*> -> accumulator
+ else -> irOr(accumulator, value)
+ }
+ }
+ })
+ }
+ }
+ )
+
+ val blockParameter = getCallParameter(KtxNameConventions.CALL_BLOCK_PARAMETER)
+
+ putValueArgument(
+ blockParameter,
+ irLambdaExpression(
+ original.startOffset,
+ original.endOffset,
+ descriptor = createFunctionDescriptor(
+ type = blockParameter.type,
+ owner = descriptor.containingDeclaration
+ ),
+ type = blockParameter.type.toIrType()
+ ) {
+ +irCall(
+ callee = symbolTable.referenceFunction(descriptor.underlyingDescriptor),
+ type = original.type
+ ).apply {
+ copyTypeArgumentsFrom(original)
+
+ dispatchReceiver = tmpDispatchReceiver?.let { irGet(it) }
+ extensionReceiver = tmpExtensionReceiver?.let { irGet(it) }
+
+ irGetArguments.forEach { (param, getExpr) ->
+ putValueArgument(param, getExpr())
+ }
+ }
+ }
+ )
+ }
+ }
+
+ private fun isChildrenParameter(desc: ValueParameterDescriptor, expr: IrExpression): Boolean {
+ return expr is IrFunctionExpression &&
+ expr.origin == IrStatementOrigin.LAMBDA &&
+ desc is EmitChildrenValueParameterDescriptor
+ }
+
+ private fun IrBlockBuilder.getParameterExpression(
+ desc: ValueParameterDescriptor,
+ expr: IrExpression?
+ ): () -> IrExpression? {
+ if (expr == null)
+ return { null }
+ if (expr.isReorderTemporaryVariable()) {
+ return getParameterExpression(desc, expr.unwrapReorderTemporaryVariable())
+ }
+ return when {
+ expr is IrConst<*> ->
+ ({ expr.copy() })
+ isChildrenParameter(desc, expr) ->
+ ({ expr })
+ else -> {
+ val temp = irTemporary(
+ expr,
+ typeHint = desc.type,
+ irType = desc.type.toIrType()
+ )
+ ({ irGet(temp) })
+ }
+ }
+ }
+
+ private fun IrBlockBuilder.irComposableEmit(
+ composerCall: IrCall,
+ original: IrCall,
+ descriptor: ComposableEmitDescriptor
+ ): IrExpression {
+ val composerTemp = irTemporary(composerCall)
+ /*
+
+ TextView(text="foo")
+
+ // transforms into
+
+ val attr_text = "foo"
+ composer.emit(
+ key = 123,
+ ctor = { context -> TextView(context) },
+ update = { set(attr_text) { text -> this.text = text } }
+ )
+ */
+
+ val parametersByName = descriptor
+ .valueParameters
+ .mapNotNull { desc ->
+ original.getValueArgument(desc)?.let { desc to it }
+ }
+ .map { (desc, expr) ->
+ desc.name.asString() to getParameterExpression(desc, expr)
+ }
+ .toMap()
+
+ val emitCall = descriptor.emitCall
+ val emitFunctionDescriptor = emitCall.candidateDescriptor
+
+ val emitParameters = emitFunctionDescriptor.valueParameters
+ .map { it.name to it }
+ .toMap()
+
+ fun getEmitParameter(name: Name) = emitParameters[name]
+ ?: error("Expected $name parameter to exist")
+
+ val emitFunctionSymbol = symbolTable.referenceFunction(emitFunctionDescriptor)
+
+ val joinKeyDescriptor = descriptor.composer
+ .getReturnType()
+ .memberScope
+ .findFirstFunction(KtxNameConventions.JOINKEY.identifier) {
+ it.valueParameters.size == 2
+ }
+
+ fun irGetParameter(name: String): IrExpression = parametersByName[name]?.invoke()
+ ?: error("No parameter found with name $name")
+
+ return irCall(
+ callee = emitFunctionSymbol,
+ type = builtIns.unitType
+ ).apply {
+ dispatchReceiver = irGet(composerTemp)
+ // TODO(lmr): extensionReceiver.
+ // We would want to do this to enable "emit" and "call" implementations that are
+ // extensions on the composer
+
+ putTypeArguments(emitCall.typeArguments) { it.toIrType() }
+
+ putValueArgument(
+ getEmitParameter(KtxNameConventions.EMIT_KEY_PARAMETER),
+ irGroupKey(
+ original = original,
+ getComposer = { irGet(composerTemp) },
+ joinKey = joinKeyDescriptor,
+ pivotals = descriptor.pivotals.map { irGetParameter(it) }
+ )
+ )
+
+ val ctorParam = getEmitParameter(KtxNameConventions.EMIT_CTOR_PARAMETER)
+
+ val ctorLambdaDescriptor = createFunctionDescriptor(ctorParam.type)
+
+ putValueArgument(
+ ctorParam,
+ irLambdaExpression(
+ original.startOffset,
+ original.endOffset,
+ descriptor = ctorLambdaDescriptor,
+ type = ctorParam.type.toIrType()
+ ) { fn ->
+
+ val ctorCall = descriptor.ctorCall
+
+ val ctorCallSymbol = symbolTable.referenceConstructor(
+ ctorCall.candidateDescriptor as ClassConstructorDescriptor
+ )
+
+ +irReturn(irCall(ctorCallSymbol).apply {
+ putTypeArguments(ctorCall.typeArguments) { it.toIrType() }
+ ctorLambdaDescriptor.valueParameters.zip(
+ ctorCall
+ .candidateDescriptor!!
+ .valueParameters
+ ) { a, b ->
+ putValueArgument(
+ b,
+ irGet(fn.getIrValueParameter(a))
+ )
+ }
+ descriptor.ctorParams.forEach { name ->
+ val param = ctorCall
+ .candidateDescriptor
+ .valueParameters
+ .firstOrNull { it.name.identifier == name }
+ if (param != null) {
+ putValueArgument(
+ param,
+ irGetParameter(name)
+ )
+ }
+ }
+ })
+ }
+ )
+
+ val updateParam = getEmitParameter(KtxNameConventions
+ .EMIT_UPDATER_PARAMETER)
+
+ val updateLambdaDescriptor = createFunctionDescriptor(updateParam.type)
+
+ putValueArgument(
+ updateParam,
+ irLambdaExpression(
+ original.startOffset,
+ original.endOffset,
+ descriptor = updateLambdaDescriptor,
+ type = updateParam.type.toIrType()
+ ) { fn ->
+ descriptor.validations.forEach {
+ // set(attr_text) { text -> this.text = text }
+ val arg = irGetParameter(it.name)
+ +irValidatedAssignment(
+ arg.startOffset,
+ arg.endOffset,
+ memoizing = true,
+ validation = it,
+ receiver = irGet(fn.extensionReceiverParameter!!),
+ attributeValue = arg
+ )
+ }
+ +irReturnUnit()
+ }
+ )
+
+ if (descriptor.hasChildren) {
+ val bodyParam = getEmitParameter(KtxNameConventions.EMIT_CHILDREN_PARAMETER)
+
+ val childrenExpr = irGetParameter("\$CHILDREN")
+
+ putValueArgument(
+ bodyParam,
+ childrenExpr
+ )
+ }
+ }
+ }
+
+ private fun IrBuilderWithScope.irGroupKey(
+ original: IrCall,
+ joinKey: FunctionDescriptor,
+ getComposer: () -> IrExpression,
+ pivotals: List<IrExpression>
+ ): IrExpression {
+ val keyValueExpression = irInt(original.sourceLocationHash())
+ return if (pivotals.isEmpty()) keyValueExpression
+ else (listOf(keyValueExpression) + pivotals).reduce { accumulator, value ->
+ irCall(
+ callee = symbolTable.referenceFunction(joinKey),
+ type = joinKey.returnType!!.toIrType()
+ ).apply {
+ dispatchReceiver = getComposer()
+ putValueArgument(0, accumulator)
+ putValueArgument(1, value)
+ }
+ }
+ }
+
+ private fun IrCall.sourceLocationHash(): Int {
+ return descriptor.fqNameSafe.toString().hashCode() xor startOffset
+ }
+
+ private fun IrBuilderWithScope.irChangedCall(
+ changedDescriptor: FunctionDescriptor,
+ receiver: IrExpression,
+ attributeValue: IrExpression
+ ): IrExpression {
+ // TODO(lmr): make it so we can use the "changed" overloads with primitive types.
+ // Right now this is causing a lot of boxing/unboxing for primitives
+ return if (attributeValue is IrConst<*>) irFalse()
+ else irCall(
+ callee = symbolTable.referenceFunction(changedDescriptor),
+ type = changedDescriptor.returnType?.toIrType()!!
+ ).apply {
+ putTypeArgument(0, attributeValue.type)
+ dispatchReceiver = receiver
+ putValueArgument(0, attributeValue)
+ }
+ }
+
+ private fun IrBuilderWithScope.irOr(
+ left: IrExpression,
+ right: IrExpression
+ ): IrExpression {
+ return irCall(
+ callee = symbolTable.referenceFunction(orFunctionDescriptor),
+ type = builtIns.booleanType
+ ).apply {
+ dispatchReceiver = left
+ putValueArgument(0, right)
+ }
+ }
+
+ private fun IrBuilderWithScope.irValidatedAssignment(
+ startOffset: Int,
+ endOffset: Int,
+ memoizing: Boolean,
+ validation: ValidatedAssignment,
+ receiver: IrExpression,
+ attributeValue: IrExpression
+ ): IrExpression {
+ // for emit, fnDescriptor is Validator.(Value) -> Unit or Validator.(Value, Element.(Value) -> Unit) -> Unit
+ // for call, fnDescriptor is Validator.(Value) -> Boolean or Validator.(Value, (Value) -> Unit) -> Boolean
+
+ // in emit, the element is passed through an extension parameter
+ // in call, the element is passed through a capture scope
+ val validationCall =
+ if (memoizing) validation.validationCall
+ else validation.uncheckedValidationCall
+
+ if (validationCall == null) error("Expected validationCall to be non-null")
+
+ val validationCallDescriptor = validationCall.candidateDescriptor as FunctionDescriptor
+
+ return irCall(
+ callee = symbolTable.referenceFunction(validationCallDescriptor),
+ type = validationCallDescriptor.returnType?.toIrType()!!
+ ).apply {
+
+ dispatchReceiver = receiver
+ // TODO(lmr): extensionReceiver.
+ // This might be something we want to be able to do in the cases where we want to
+ // build extension `changed(...)` or `set(..) { ... }` methods
+
+ putTypeArguments(validationCall.typeArguments) { it.toIrType() }
+
+ putValueArgument(0, attributeValue)
+ val assignment = validation.assignment
+ if (assignment != null && validation.validationType != ValidationType.CHANGED) {
+ val assignmentLambdaDescriptor = validation.assignmentLambda
+ ?: error("Expected assignmentLambda to be non-null")
+ val assignmentDescriptor = assignment.candidateDescriptor.original
+
+ val assignmentSymbol = when (assignmentDescriptor) {
+ is PropertyDescriptor -> symbolTable.referenceFunction(
+ assignmentDescriptor.setter!!
+ )
+ else -> symbolTable.referenceFunction(assignmentDescriptor)
+ }
+ val assignmentValueParameterDescriptor = assignmentLambdaDescriptor
+ .valueParameters[0]
+
+ putValueArgument(
+ 1,
+ irLambdaExpression(
+ startOffset,
+ endOffset,
+ assignmentLambdaDescriptor,
+ validationCallDescriptor.valueParameters[1].type.toIrType()
+ ) { fn ->
+ +irReturn(
+ irCall(
+ callee = assignmentSymbol,
+ type = builtIns.unitType
+ ).apply {
+ putTypeArguments(assignment.typeArguments) { it.toIrType() }
+ when (assignment.explicitReceiverKind) {
+ ExplicitReceiverKind.DISPATCH_RECEIVER -> {
+ dispatchReceiver = irGet(fn.extensionReceiverParameter!!)
+ }
+ ExplicitReceiverKind.EXTENSION_RECEIVER -> {
+ extensionReceiver = irGet(fn.extensionReceiverParameter!!)
+ }
+ ExplicitReceiverKind.BOTH_RECEIVERS -> {
+ // NOTE(lmr): This should not be possible. This would have
+ // to be an extension method on the ComposerUpdater class
+ // itself for the emittable type.
+ error("Extension instance methods are not allowed for " +
+ "assignments")
+ }
+ ExplicitReceiverKind.NO_EXPLICIT_RECEIVER -> {
+ // NOTE(lmr): This is not possible
+ error("Static methods are invalid for assignments")
+ }
+ }
+ putValueArgument(
+ 0,
+ irGet(
+ fn.getIrValueParameter(assignmentValueParameterDescriptor)
+ )
+ )
+ }
+ )
+ }
+ )
+ }
+ }
+ }
+
+ private fun IrBuilderWithScope.irLambdaExpression(
+ startOffset: Int,
+ endOffset: Int,
+ descriptor: FunctionDescriptor,
+ type: IrType,
+ body: IrBlockBodyBuilder.(IrFunction) -> Unit
+ ): IrExpression {
+ val symbol = IrSimpleFunctionSymbolImpl(descriptor)
+
+ val returnType = descriptor.returnType!!.toIrType()
+
+ val lambda = IrFunctionImpl(
+ startOffset, endOffset,
+ IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA,
+ symbol,
+ returnType
+ ).also {
+ it.parent = scope.getLocalDeclarationParent()
+ it.createParameterDeclarations()
+ it.body = jvmContext
+ .createIrBuilder(symbol)
+ .irBlockBody { body(it) }
+ }
+
+ return irBlock(
+ startOffset = startOffset,
+ endOffset = endOffset,
+ origin = IrStatementOrigin.LAMBDA,
+ resultType = type
+ ) {
+ +lambda
+ +IrFunctionReferenceImpl(
+ startOffset = startOffset,
+ endOffset = endOffset,
+ type = type,
+ symbol = symbol,
+ descriptor = descriptor,
+ typeArgumentsCount = descriptor.typeParametersCount,
+ origin = IrStatementOrigin.LAMBDA
+ )
+ }
+ }
+
+ private fun IrFunction.createParameterDeclarations() {
+ fun ParameterDescriptor.irValueParameter() = IrValueParameterImpl(
+ this.startOffset ?: UNDEFINED_OFFSET,
+ this.endOffset ?: UNDEFINED_OFFSET,
+ IrDeclarationOrigin.DEFINED,
+ this,
+ type.toIrType(),
+ (this as? ValueParameterDescriptor)?.varargElementType?.toIrType()
+ ).also {
+ it.parent = this@createParameterDeclarations
+ }
+
+ fun TypeParameterDescriptor.irTypeParameter() = IrTypeParameterImpl(
+ this.startOffset ?: UNDEFINED_OFFSET,
+ this.endOffset ?: UNDEFINED_OFFSET,
+ IrDeclarationOrigin.DEFINED,
+ IrTypeParameterSymbolImpl(this)
+ ).also {
+ it.parent = this@createParameterDeclarations
+ }
+
+ dispatchReceiverParameter = descriptor.dispatchReceiverParameter?.irValueParameter()
+ extensionReceiverParameter = descriptor.extensionReceiverParameter?.irValueParameter()
+
+ assert(valueParameters.isEmpty())
+ descriptor.valueParameters.mapTo(valueParameters) { it.irValueParameter() }
+
+ assert(typeParameters.isEmpty())
+ descriptor.typeParameters.mapTo(typeParameters) { it.irTypeParameter() }
+ }
+
+ private fun KotlinType.toIrType(): IrType = typeTranslator.translateType(this)
+
+ private fun IrBuilderWithScope.createFunctionDescriptor(
+ type: KotlinType,
+ owner: DeclarationDescriptor = scope.scopeOwner
+ ): FunctionDescriptor {
+ return AnonymousFunctionDescriptor(
+ owner,
+ Annotations.EMPTY,
+ CallableMemberDescriptor.Kind.SYNTHESIZED,
+ SourceElement.NO_SOURCE,
+ false
+ ).apply {
+ initialize(
+ type.getReceiverTypeFromFunctionType()?.let {
+ DescriptorFactory.createExtensionReceiverParameterForCallable(
+ this,
+ it,
+ Annotations.EMPTY)
+ },
+ null,
+ emptyList(),
+ type.getValueParameterTypesFromFunctionType().mapIndexed { i, t ->
+ ValueParameterDescriptorImpl(
+ containingDeclaration = this,
+ original = null,
+ index = i,
+ annotations = Annotations.EMPTY,
+ name = t.type.extractParameterNameFromFunctionTypeArgument()
+ ?: Name.identifier("p$i"),
+ outType = t.type,
+ declaresDefaultValue = false,
+ isCrossinline = false,
+ isNoinline = false,
+ varargElementType = null,
+ source = SourceElement.NO_SOURCE
+ )
+ },
+ type.getReturnTypeFromFunctionType(),
+ Modality.FINAL,
+ Visibilities.LOCAL,
+ null
+ )
+ isOperator = false
+ isInfix = false
+ isExternal = false
+ isInline = false
+ isTailrec = false
+ isSuspend = false
+ isExpect = false
+ isActual = false
+ }
+ }
+}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeFcsPatcher.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeFcsPatcher.kt
deleted file mode 100644
index dd567cb..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeFcsPatcher.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package androidx.compose.plugins.kotlin.compiler.lower
-
-import org.jetbrains.kotlin.backend.common.FileLoweringPass
-import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
-import org.jetbrains.kotlin.ir.declarations.IrFile
-import org.jetbrains.kotlin.ir.expressions.IrCall
-import org.jetbrains.kotlin.ir.expressions.IrExpression
-import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
-import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
-import androidx.compose.plugins.kotlin.ComposeCallResolutionInterceptorExtension
-
-class ComposeFcsPatcher(val context: JvmBackendContext) :
- IrElementTransformerVoid(),
- FileLoweringPass {
-
- override fun lower(irFile: IrFile) {
- irFile.transformChildrenVoid(this)
- }
-
- override fun visitCall(expression: IrCall): IrExpression {
- if (expression.descriptor is
- ComposeCallResolutionInterceptorExtension.ComposableInvocationDescriptor) {
- // System.out.println("******"+expression.descriptor.name)
- // return IrBlockImpl(expression.startOffset, expression.endOffset, context.irBuiltIns.unitType, null, emptyList())
- TODO("Implement me")
- }
- return super.visitCall(expression)
- }
-}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt
index 3640d89..1697900 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposeObservePatcher.kt
@@ -54,6 +54,7 @@
import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl
import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
import androidx.compose.plugins.kotlin.getKeyValue
+import androidx.compose.plugins.kotlin.isEmitInline
import org.jetbrains.kotlin.backend.common.lower.irIfThen
import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
@@ -109,13 +110,16 @@
super.visitFunction(declaration)
// Only insert observe scopes in non-empty composable function
- if (declaration.body == null) return declaration
- if (!isComposable(declaration)) return declaration
+ if (declaration.body == null)
+ return declaration
+ if (!isComposable(declaration))
+ return declaration
val descriptor = declaration.descriptor
// Do not insert observe scope in an inline function
- if (descriptor.isInline) return declaration
+ if (descriptor.isInline)
+ return declaration
// Do not insert an observe scope in an inline composable lambda
descriptor.findPsi()?.let { psi ->
@@ -123,15 +127,19 @@
if (InlineUtil.isInlinedArgument(
it,
context.state.bindingContext,
- true
+ false
)
)
return declaration
+ if (it.isEmitInline(context.state.bindingContext)) {
+ return declaration
+ }
}
}
// Do not insert an observe scope if the funciton has a return result
- if (descriptor.returnType.let { it == null || !it.isUnit() }) return declaration
+ if (descriptor.returnType.let { it == null || !it.isUnit() })
+ return declaration
// Check if the descriptor has restart scope calls resolved
val bindingContext = context.state.bindingContext
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/WrapperViewGenerator.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/WrapperViewGenerator.kt
deleted file mode 100644
index b2a8571..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/WrapperViewGenerator.kt
+++ /dev/null
@@ -1,435 +0,0 @@
-package androidx.compose.plugins.kotlin.compiler.lower
-/*
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
-import org.jetbrains.kotlin.ir.IrStatement
-import org.jetbrains.kotlin.ir.declarations.*
-import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
-import org.jetbrains.kotlin.ir.expressions.impl.*
-import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
-import org.jetbrains.kotlin.ir.util.createParameterDeclarations
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
-import androidx.compose.plugins.kotlin.ComposeUtils
-import androidx.compose.plugins.kotlin.ComponentMetadata
-import androidx.compose.plugins.kotlin.buildWithScope
-import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
-
-fun generateWrapperView(context: GeneratorContext, componentMetadata: ComponentMetadata): IrClass {
- val syntheticClassDescriptor = componentMetadata.wrapperViewDescriptor
- val wrapperViewIrClass = context.symbolTable.declareClass(-1, -1, IrDeclarationOrigin.DEFINED, syntheticClassDescriptor)
-
- wrapperViewIrClass.createParameterDeclarations()
- val wrapperViewReceiverSymbol = wrapperViewIrClass.thisReceiver!!
- wrapperViewIrClass.declarations.add(generateConstructor(wrapperViewReceiverSymbol, context, componentMetadata))
- wrapperViewIrClass.declarations.addAll(generateProperties(context, componentMetadata))
- wrapperViewIrClass.declarations.add(generateOnAttachFunction(wrapperViewReceiverSymbol, context, componentMetadata))
- wrapperViewIrClass.declarations.add(generateOnDetachFunction(wrapperViewReceiverSymbol, context, componentMetadata))
- wrapperViewIrClass.declarations.add(generateOnPreDrawFunction(wrapperViewReceiverSymbol, context, componentMetadata))
- wrapperViewIrClass.declarations.addAll(generateAttributeSetterFunctions(context, componentMetadata))
-
- return wrapperViewIrClass
-}
-
-private fun generateConstructor(wrapperViewAsThisReceiver: IrValueParameter, context: GeneratorContext, componentMetadata: ComponentMetadata): IrConstructor {
- val syntheticClassDescriptor = componentMetadata.wrapperViewDescriptor
- return context.symbolTable.declareConstructor(
- -1,
- -1,
- IrDeclarationOrigin.DEFINED,
- syntheticClassDescriptor.unsubstitutedPrimaryConstructor
- )
- .buildWithScope(context) { constructor ->
- constructor.createParameterDeclarations()
- context.symbolTable.introduceValueParameter(wrapperViewAsThisReceiver)
- val getThisExpr = IrGetValueImpl(-1, -1, wrapperViewAsThisReceiver.symbol)
-
- val statements = mutableListOf<IrStatement>()
- val superConstructor =
- context.symbolTable.referenceConstructor(componentMetadata.wrapperViewDescriptor.getSuperClassNotAny()!!.constructors.single { it.valueParameters.size == 1 })
- val superCall = IrDelegatingConstructorCallImpl(-1, -1, superConstructor, superConstructor.descriptor, 0).apply {
- putValueArgument(0, IrGetValueImpl(-1, -1, constructor.valueParameters[0].symbol))
- }
-
- statements.add(superCall)
-
-
- val linearLayoutClass =
- context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("android.widget.LinearLayout")))!!
- val linearLayoutParamsClass = linearLayoutClass.unsubstitutedMemberScope.getContributedClassifier(
- Name.identifier("LayoutParams"),
- NoLookupLocation.FROM_BACKEND
- )!! as ClassDescriptor
- val linearLayoutParamsConstructor =
- context.symbolTable.referenceConstructor(linearLayoutParamsClass.constructors.single { it.valueParameters.size == 2 && it.valueParameters[0].type == context.moduleDescriptor.builtIns.intType }!!)
-
- val layoutParams = IrCallImpl(
- -1, -1, linearLayoutParamsClass.defaultType,
- linearLayoutParamsConstructor,
- linearLayoutParamsConstructor.descriptor, null
- )
- layoutParams.putValueArgument(
- 0, IrGetFieldImpl(
- -1, -1, context.symbolTable.referenceField(
- linearLayoutParamsClass.staticScope.getContributedVariables(
- Name.identifier("MATCH_PARENT"), NoLookupLocation.FROM_BACKEND
- ).single()
- )
- )
- )
- layoutParams.putValueArgument(
- 1, IrGetFieldImpl(
- -1, -1, context.symbolTable.referenceField(
- linearLayoutParamsClass.staticScope.getContributedVariables(
- Name.identifier("WRAP_CONTENT"), NoLookupLocation.FROM_BACKEND
- ).single()
- )
- )
- )
-
- val setLayoutParamsFunction = linearLayoutClass.unsubstitutedMemberScope.getContributedFunctions(
- Name.identifier("setLayoutParams"),
- NoLookupLocation.FROM_BACKEND
- ).single()
- statements.add(IrInstanceInitializerCallImpl(-1, -1, context.symbolTable.referenceClass(syntheticClassDescriptor)))
- val setLayoutParamsCall = org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl(
- -1, -1, context.moduleDescriptor.builtIns.unitType,
- context.symbolTable.referenceSimpleFunction(setLayoutParamsFunction),
- setLayoutParamsFunction, null
- )
- setLayoutParamsCall.dispatchReceiver = getThisExpr
- setLayoutParamsCall.putValueArgument(0, layoutParams)
- statements.add(setLayoutParamsCall)
-
-
- val componentInstanceProperty = syntheticClassDescriptor.componentInstanceField
- val componentConstructorDescriptor = componentMetadata.descriptor.unsubstitutedPrimaryConstructor!!
- val componentConstructor = context.symbolTable.referenceConstructor(componentConstructorDescriptor)
- val componentConstructorCall = IrCallImpl(
- -1, -1, componentMetadata.descriptor.defaultType,
- componentConstructor,
- componentConstructorDescriptor, null
- )
- // this.componentInstance = ComponentClass()
- statements.add(
- IrSetFieldImpl(
- -1,
- -1,
- context.symbolTable.referenceField(componentInstanceProperty),
- getThisExpr,
- componentConstructorCall
- )
- )
-
- // this.dirty = true
- statements.add(
- IrSetFieldImpl(
- -1, -1,
- context.symbolTable.referenceField(syntheticClassDescriptor.dirtyField),
- getThisExpr,
- IrConstImpl.boolean(-1, -1, context.builtIns.booleanType, true)
- )
- )
-
- val ccClass =
- context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(ComposeUtils.composeFqName("CompositionContext")))!!
-
- val ccCreateDescriptor = ccClass
- .companionObjectDescriptor
- ?.unsubstitutedMemberScope
- ?.getContributedFunctions(Name.identifier("create"), NoLookupLocation.FROM_BACKEND)
- ?.single()!!
-
- val ccCreateCall = IrCallImpl(
- -1, -1,
- context.symbolTable.referenceSimpleFunction(ccCreateDescriptor),
- ccCreateDescriptor
- )
-
- ccCreateCall.dispatchReceiver = IrGetObjectValueImpl(
- -1, -1,
- ccClass.companionObjectDescriptor!!.defaultType,
- context.symbolTable.referenceClass(ccClass.companionObjectDescriptor!!)
- )
-
- // context (Context)
- ccCreateCall.putValueArgument(0, IrGetValueImpl(-1, -1, constructor.valueParameters[0].symbol))
-
- // this (ViewGroup)
- ccCreateCall.putValueArgument(1, getThisExpr)
-
- // this.componentInstance (Component)
- ccCreateCall.putValueArgument(
- 2, IrGetFieldImpl(
- -1, -1,
- context.symbolTable.referenceField(componentInstanceProperty),
- getThisExpr
- )
- )
-
- // this.compositionContext = CompositionContext.create(context, this, instance)
- statements.add(
- IrSetFieldImpl(
- -1, -1,
- context.symbolTable.referenceField(syntheticClassDescriptor.compositionContextField),
- getThisExpr,
- ccCreateCall
- )
- )
-
- constructor.body = IrBlockBodyImpl(-1, -1, statements)
- }
-}
-
-private fun generateOnAttachFunction(wrapperViewAsThisReceiver: IrValueParameter, context: GeneratorContext, componentMetadata: ComponentMetadata): IrFunction {
- val syntheticClassDescriptor = componentMetadata.wrapperViewDescriptor
- val functionDescriptor = syntheticClassDescriptor.onAttachDescriptor
- val viewDescriptor = context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("android.view.View")))!!
- val viewGroupDescriptor =
- context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("android.view.ViewGroup")))!!
- val viewTreeObserverDescriptor =
- context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("android.view.ViewTreeObserver")))!!
- return context.symbolTable.declareSimpleFunction(-1, -1, IrDeclarationOrigin.DEFINED, functionDescriptor)
- .buildWithScope(context) { irFunction ->
- irFunction.createParameterDeclarations()
- context.symbolTable.introduceValueParameter(wrapperViewAsThisReceiver)
- val getThisExpr = IrGetValueImpl(-1, -1, wrapperViewAsThisReceiver.symbol)
-
- val superFunction = viewGroupDescriptor.unsubstitutedMemberScope.getContributedFunctions(
- Name.identifier("onAttachedToWindow"),
- NoLookupLocation.FROM_BACKEND
- ).single()
- val superCall = IrCallImpl(
- -1, -1,
- context.moduleDescriptor.builtIns.unitType,
- context.symbolTable.referenceSimpleFunction(superFunction),
- superFunction
- , null, null, context.symbolTable.referenceClass(syntheticClassDescriptor.getSuperClassNotAny()!!)
- )
- superCall.dispatchReceiver = getThisExpr
-
- val getViewTreeObserverFunction = viewDescriptor
- .unsubstitutedMemberScope
- .getContributedFunctions(Name.identifier("getViewTreeObserver"), NoLookupLocation.FROM_BACKEND)
- .single()
- val getViewTreeObserverCall = IrCallImpl(
- -1, -1,
- context.symbolTable.referenceSimpleFunction(getViewTreeObserverFunction)
- )
- getViewTreeObserverCall.dispatchReceiver = getThisExpr
-
- val addOnPreDrawListenerFunction = viewTreeObserverDescriptor
- .unsubstitutedMemberScope
- .getContributedFunctions(Name.identifier("addOnPreDrawListener"), NoLookupLocation.FROM_BACKEND)
- .single()
-
- val addOnPreDrawListenerCall = IrCallImpl(
- -1, -1,
- context.symbolTable.referenceSimpleFunction(addOnPreDrawListenerFunction)
- )
-
- addOnPreDrawListenerCall.dispatchReceiver = getViewTreeObserverCall
- addOnPreDrawListenerCall.putValueArgument(0, getThisExpr)
-
- irFunction.body = IrBlockBodyImpl(-1, -1, listOf(superCall, addOnPreDrawListenerCall))
- }
-}
-
-private fun generateOnDetachFunction(wrapperViewAsThisReceiver: IrValueParameter, context: GeneratorContext, componentMetadata: ComponentMetadata): IrFunction {
- val syntheticClassDescriptor = componentMetadata.wrapperViewDescriptor
- val functionDescriptor = syntheticClassDescriptor.onDetachDescriptor
- val viewDescriptor = context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("android.view.View")))!!
- val viewGroupDescriptor =
- context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("android.view.ViewGroup")))!!
- val viewTreeObserverDescriptor =
- context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("android.view.ViewTreeObserver")))!!
- return context.symbolTable.declareSimpleFunction(-1, -1, IrDeclarationOrigin.DEFINED, functionDescriptor)
- .buildWithScope(context) { irFunction ->
- irFunction.createParameterDeclarations()
- context.symbolTable.introduceValueParameter(wrapperViewAsThisReceiver)
- val getThisExpr = IrGetValueImpl(-1, -1, wrapperViewAsThisReceiver.symbol)
-
- val superFunction = viewGroupDescriptor.unsubstitutedMemberScope.getContributedFunctions(
- Name.identifier("onDetachedFromWindow"),
- NoLookupLocation.FROM_BACKEND
- ).single()
- val superCall = IrCallImpl(
- -1, -1,
- context.moduleDescriptor.builtIns.unitType,
- context.symbolTable.referenceSimpleFunction(superFunction),
- superFunction
- , null, null, context.symbolTable.referenceClass(syntheticClassDescriptor.getSuperClassNotAny()!!)
- )
- superCall.dispatchReceiver = getThisExpr
-
- val getViewTreeObserverFunction = viewDescriptor
- .unsubstitutedMemberScope
- .getContributedFunctions(Name.identifier("getViewTreeObserver"), NoLookupLocation.FROM_BACKEND)
- .single()
- val getViewTreeObserverCall = IrCallImpl(
- -1, -1,
- context.symbolTable.referenceSimpleFunction(getViewTreeObserverFunction)
- )
- getViewTreeObserverCall.dispatchReceiver = getThisExpr
-
- val removeOnPreDrawListenerFunction = viewTreeObserverDescriptor
- .unsubstitutedMemberScope
- .getContributedFunctions(Name.identifier("removeOnPreDrawListener"), NoLookupLocation.FROM_BACKEND)
- .single()
-
- val removeOnPreDrawListenerCall = IrCallImpl(
- -1, -1,
- context.symbolTable.referenceSimpleFunction(removeOnPreDrawListenerFunction)
- )
-
- removeOnPreDrawListenerCall.dispatchReceiver = getViewTreeObserverCall
- removeOnPreDrawListenerCall.putValueArgument(0, getThisExpr)
-
- irFunction.body = IrBlockBodyImpl(-1, -1, listOf(superCall, removeOnPreDrawListenerCall))
- }
-}
-
-private fun generateOnPreDrawFunction(wrapperViewAsThisReceiver: IrValueParameter, context: GeneratorContext, componentMetadata: ComponentMetadata): IrFunction {
- val syntheticClassDescriptor = componentMetadata.wrapperViewDescriptor
- val functionDescriptor = syntheticClassDescriptor.onPreDraw
- val compositionContextDescriptor =
- context.moduleDescriptor.findClassAcrossModuleDependencies(ClassId.topLevel(ComposeUtils.composeFqName("CompositionContext")))!!
- return context.symbolTable.declareSimpleFunction(-1, -1, IrDeclarationOrigin.DEFINED, functionDescriptor)
- .buildWithScope(context) { irFunction ->
- irFunction.createParameterDeclarations()
- context.symbolTable.introduceValueParameter(wrapperViewAsThisReceiver)
- val getThisExpr = IrGetValueImpl(-1, -1, wrapperViewAsThisReceiver.symbol)
-
-
- val getDirtyCall = IrGetFieldImpl(
- -1, -1,
- context.symbolTable.referenceField(syntheticClassDescriptor.dirtyField),
- getThisExpr
- )
-
- val setDirtyFalseCall = IrSetFieldImpl(
- -1, -1,
- context.symbolTable.referenceField(syntheticClassDescriptor.dirtyField),
- getThisExpr,
- IrConstImpl.boolean(-1, -1, context.builtIns.booleanType, false)
- )
-
- val getCompositionContextCall = IrGetFieldImpl(
- -1, -1,
- context.symbolTable.referenceField(syntheticClassDescriptor.compositionContextField),
- getThisExpr
- )
-
- val recomposeFromRootFunction = compositionContextDescriptor
- .unsubstitutedMemberScope
- .getContributedFunctions(Name.identifier("recompose"), NoLookupLocation.FROM_BACKEND)
- .single()
- val recomposeFromRootExpr = IrCallImpl(
- -1, -1,
- context.symbolTable.referenceFunction(recomposeFromRootFunction)
- ).apply {
- dispatchReceiver = getCompositionContextCall
- putValueArgument(0, IrGetFieldImpl(
- -1,
- -1,
- context.symbolTable.referenceField(syntheticClassDescriptor.componentInstanceField),
- getThisExpr
- ))
- }
-
- val ifDirtyExpr = IrIfThenElseImpl(
- -1, -1,
- context.builtIns.unitType,
- getDirtyCall,
-
- IrBlockImpl(
- -1, -1,
- context.builtIns.unitType,
- null,
- listOf(recomposeFromRootExpr, setDirtyFalseCall)
- )
- )
-
- val returnTrue = IrReturnImpl(-1, -1, irFunction.symbol, IrConstImpl.boolean(-1, -1, context.builtIns.booleanType, true))
-
- irFunction.body = IrBlockBodyImpl(-1, -1, listOf(ifDirtyExpr, returnTrue))
- }
-}
-
-private fun generateAttributeSetterFunctions(context: GeneratorContext, componentMetadata: ComponentMetadata): Collection<IrFunction> {
- val output = mutableListOf<IrFunction>()
- for (functionDescriptor in componentMetadata.wrapperViewDescriptor.setterMethodDescriptors) {
- val irFunction = context.symbolTable.declareSimpleFunction(-1, -1, IrDeclarationOrigin.DEFINED, functionDescriptor)
- .buildWithScope(context) { irFunction ->
- irFunction.createParameterDeclarations()
- val wrapperViewAsThisReceiver = irFunction.dispatchReceiverParameter!!.symbol
-
- val componentInstanceField = componentMetadata.wrapperViewDescriptor.componentInstanceField
- context.symbolTable.introduceValueParameter(irFunction.valueParameters[0])
-
- val componentAttribute = componentMetadata.getAttributeDescriptors()
- .single({ it.name.identifier == ComposeUtils.propertyNameFromSetterMethod(irFunction.name.identifier) })
-
- val componentInstance = IrGetFieldImpl(
- -1,
- -1,
- context.symbolTable.referenceField(componentInstanceField),
- IrGetValueImpl(-1, -1, wrapperViewAsThisReceiver)
- )
-
- val newAttributeValue =
- IrGetValueImpl(-1, -1, context.symbolTable.referenceValueParameter(functionDescriptor.valueParameters[0]))
-
- val setAttributeOnComponentInstruction =
- if (componentAttribute.setter != null) {
- val setAttributeOnComponentCall = IrSetterCallImpl(
- -1,
- -1,
- context.symbolTable.referenceFunction(componentAttribute.setter!!),
- componentAttribute.setter!!,
- 0
- )
- setAttributeOnComponentCall.dispatchReceiver = componentInstance
- setAttributeOnComponentCall.putValueArgument(0, newAttributeValue)
- setAttributeOnComponentCall
- } else {
- IrSetFieldImpl(-1, -1, context.symbolTable.referenceField(componentAttribute), componentInstance, newAttributeValue)
- }
-
- // TODO: Invalidate the WrapperView using Leland's new WrapperView API (pending commit of that API)
-
- irFunction.body = IrBlockBodyImpl(-1, -1, listOf(setAttributeOnComponentInstruction /*, invalidateCall */))
- }
- output.add(irFunction)
- }
- return output
-}
-
-
-private fun generateProperties(context: GeneratorContext, componentMetadata: ComponentMetadata) = listOf(
- context.symbolTable.declareField(
- -1, -1,
- IrDeclarationOrigin.DEFINED,
- componentMetadata
- .wrapperViewDescriptor
- .componentInstanceField
- ),
- context.symbolTable.declareField(
- -1, -1,
- IrDeclarationOrigin.DEFINED,
- componentMetadata
- .wrapperViewDescriptor
- .compositionContextField
- ),
- context.symbolTable.declareField(
- -1, -1,
- IrDeclarationOrigin.DEFINED,
- componentMetadata
- .wrapperViewDescriptor
- .dirtyField
- )
-)
- */
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/diagnosticsUtils.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/diagnosticsUtils.kt
deleted file mode 100644
index d6fd0f9..0000000
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/diagnosticsUtils.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2019 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.plugins.kotlin
-
-import com.intellij.psi.PsiElement
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory3
-import org.jetbrains.kotlin.diagnostics.reportFromPlugin
-import androidx.compose.plugins.kotlin.analysis.ComposeDefaultErrorMessages
-import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
-
-fun <E : PsiElement> DiagnosticFactory0<E>.report(
- context: ExpressionTypingContext,
- elements: Collection<E>
-) {
- elements.forEach {
- context.trace.reportFromPlugin(
- on(it),
- ComposeDefaultErrorMessages
- )
- }
-}
-
-fun <E : PsiElement, T1> DiagnosticFactory1<E, T1>.report(
- context: ExpressionTypingContext,
- elements: Collection<E>,
- value1: T1
-) {
- elements.forEach {
- context.trace.reportFromPlugin(
- on(it, value1),
- ComposeDefaultErrorMessages
- )
- }
-}
-
-fun <E : PsiElement, T1, T2> DiagnosticFactory2<E, T1, T2>.report(
- context: ExpressionTypingContext,
- elements: Collection<E>,
- value1: T1,
- value2: T2
-) {
- elements.forEach {
- context.trace.reportFromPlugin(
- on(it, value1, value2),
- ComposeDefaultErrorMessages
- )
- }
-}
-
-fun <E : PsiElement, T1, T2, T3> DiagnosticFactory3<E, T1, T2, T3>.report(
- context: ExpressionTypingContext,
- elements: Collection<E>,
- value1: T1,
- value2: T2,
- value3: T3
-) {
- elements.forEach {
- context.trace.reportFromPlugin(
- on(it, value1, value2, value3),
- ComposeDefaultErrorMessages
- )
- }
-}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/frames/FrameIrTransformer.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/frames/FrameIrTransformer.kt
index ad5f7a65..a46fc4e 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/frames/FrameIrTransformer.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/frames/FrameIrTransformer.kt
@@ -48,7 +48,6 @@
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.IrElement
-import org.jetbrains.kotlin.ir.IrKtxStatement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder
@@ -628,10 +627,6 @@
if (filter(element)) elements.add(element)
element.acceptChildren(this, null)
}
-
- override fun visitKtxStatement(expression: IrKtxStatement, data: Nothing?) {
- expression.acceptChildren(this, null)
- }
}, null)
return elements
}
@@ -648,11 +643,6 @@
}
}
}
-
- override fun visitKtxStatement(expression: IrKtxStatement, data: Nothing?) {
- if (!result)
- expression.acceptChildren(this, null)
- }
}, null)
return result
}
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/AttributeInfoExtractor.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/AttributeInfoExtractor.kt
deleted file mode 100644
index 0971090..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/AttributeInfoExtractor.kt
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright 2019 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.plugins.idea
-
-import org.jetbrains.kotlin.builtins.extractParameterNameFromFunctionTypeArgument
-import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
-import org.jetbrains.kotlin.descriptors.CallableDescriptor
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
-import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
-import org.jetbrains.kotlin.descriptors.VariableDescriptor
-import org.jetbrains.kotlin.descriptors.annotations.Annotated
-import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
-import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
-import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
-import org.jetbrains.kotlin.idea.caches.resolve.util.getResolveScope
-import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
-import org.jetbrains.kotlin.idea.inspections.collections.isFunctionOfAnyKind
-import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.isChildOf
-import org.jetbrains.kotlin.psi.KtDeclaration
-import org.jetbrains.kotlin.psi.KtFile
-import androidx.compose.plugins.kotlin.ComposeUtils
-import androidx.compose.plugins.kotlin.ResolvedKtxElementCall
-import androidx.compose.plugins.kotlin.hasHiddenAttributeAnnotation
-import org.jetbrains.kotlin.resolve.DescriptorUtils
-import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
-import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
-import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.TypeProjection
-import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
-import org.jetbrains.kotlin.types.typeUtil.isUnit
-import org.jetbrains.kotlin.types.typeUtil.supertypes
-
-data class AttributeInfo(
- val name: String,
- val type: KotlinType
-) {
- var isImmediate: Boolean = false
- var isRequired: Boolean = false
- var isExtension: Boolean = false
- var isChildren: Boolean = false
- var isPivotal: Boolean = false
- var isImported: Boolean = false
- var descriptor: DeclarationDescriptor? = null
- var contributingDescriptor: ClassifierDescriptor? = null
- var extensionType: KotlinType? = null
-}
-
-class AttributeInfoExtractor(
- file: KtFile,
- // TODO(jdemeulenaere): Remove those params and try to share more code instead.
- private val visibilityFilter: (DeclarationDescriptor) -> Boolean,
- declarationTranslator: (KtDeclaration) -> KtDeclaration? = { it },
- private val ktxCall: ResolvedKtxElementCall? = null
-) {
- private val module = file.findModuleDescriptor()
- private val composeComponentDescriptor =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(ComposeUtils.composeFqName("Component"))
- )
- private val androidViewDescriptor =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("android.view.View"))
- )
- private val indicesHelper = KotlinIndicesHelper(
- file.getResolutionFacade(),
- getResolveScope(file),
- visibilityFilter,
- declarationTranslator,
- file = file
- )
-
- private val ktxCallResolvedCalls by lazy { ktxCall?.emitOrCall?.resolvedCalls() ?: emptyList() }
- private val referrableDescriptors by lazy {
- ktxCallResolvedCalls
- .map {
- val resultingDescriptor = it.resultingDescriptor
- val result: DeclarationDescriptor = when {
- it is VariableAsFunctionResolvedCall -> it.variableCall.candidateDescriptor
- resultingDescriptor is ConstructorDescriptor ->
- resultingDescriptor.constructedClass
- else -> resultingDescriptor
- }
- result
- }
- }
-
- private val instanceTypes by lazy {
- ktxCallResolvedCalls
- .mapNotNull { it.resultingDescriptor.returnType }
- .filter { !it.isUnit() }
- }
-
- private fun isDescriptorReferredTo(descriptor: DeclarationDescriptor?): Boolean {
- return descriptor in referrableDescriptors
- }
-
- fun extract(tagDescriptor: DeclarationDescriptor, receiver: (Sequence<AttributeInfo>) -> Unit) {
- when (tagDescriptor) {
- is ClassDescriptor -> {
- receiver(
- tagDescriptor
- .constructors
- .filter { visibilityFilter(it) }
- .flatMap { it.valueParameters }
- .asSequence()
- .mapNotNull { it.constructAttributeInfo(isImported = true) }
- )
-
- receiver(
- tagDescriptor
- .unsubstitutedMemberScope
- .getContributedDescriptors()
- .filter { it.isValidAttribute() }
- .filter { visibilityFilter(it) }
- .asSequence()
- .mapNotNull { it.constructAttributeInfo(isImported = true) }
- )
-
- val defaultType = tagDescriptor.defaultType
- receiver(
- indicesHelper
- .getCallableTopLevelExtensions(
- callTypeAndReceiver = CallTypeAndReceiver.DEFAULT,
- receiverTypes = listOf(defaultType),
- nameFilter = { true },
- declarationFilter = { true }
- )
- .filter { it.isValidAttribute() }
- // We don't `.filter { visibilityFilter(it) }` as KotlinIndicesHelper was instantiated with that filter already.
- .asSequence()
- .mapNotNull { it.constructAttributeInfo(isImported = true) }
- )
- }
- is FunctionDescriptor -> {
- receiver(
- tagDescriptor
- .valueParameters
- .asSequence()
- .mapNotNull { it.constructAttributeInfo(isImported = true) }
- )
- }
- is VariableDescriptor -> {
- if (tagDescriptor.type.isFunctionOfAnyKind()) {
- receiver(
- tagDescriptor.type
- .getValueParameterTypesFromFunctionType()
- .asSequence()
- .mapNotNull { it.constructAttributeInfo() }
- )
- }
- }
- }
- }
-
- private fun DeclarationDescriptor.isValidAttribute(): Boolean {
- return when (this) {
- is FunctionDescriptor -> {
- when {
- valueParameters.size != 1 -> false
- returnType?.isUnit() != true -> false
- // only void setters are allowed
- else -> ComposeUtils.isSetterMethodName(name.identifier)
- }
- }
- is PropertyDescriptor -> isVar
- else -> false
- }
- }
-
- // TODO(jdemeulenaere): This kind of code is duplicated multiple times in different files. Extract and refactor in a single extension.
- private val composeChildrenAnnotationFqName = ComposeUtils.composeFqName("Children")
- private fun Annotated.hasChildrenAnnotation() =
- annotations.hasAnnotation(composeChildrenAnnotationFqName)
-
- private fun TypeProjection.constructAttributeInfo(): AttributeInfo? {
- return AttributeInfo(
- name = type.extractParameterNameFromFunctionTypeArgument()?.asString() ?: return null,
- type = type
- ).also {
- it.descriptor = null
- it.isChildren = type.hasChildrenAnnotation()
- it.isRequired = true
- it.isExtension = false
- it.isImmediate = true
- it.isImported = true
- it.isPivotal = false
- it.contributingDescriptor = null
- it.extensionType = null
- }
- }
-
- private fun DeclarationDescriptor.constructAttributeInfo(isImported: Boolean): AttributeInfo? {
- if (shouldFilter(this)) return null
-
- return AttributeInfo(
- name = when (this) {
- is FunctionDescriptor -> ComposeUtils.propertyNameFromSetterMethod(name.asString())
- else -> name.asString()
- },
- type = when (this) {
- is FunctionDescriptor -> valueParameters.firstOrNull()?.type ?: return null
- is PropertyDescriptor -> type
- is ValueParameterDescriptor -> type
- else -> return null
- }
- ).also {
- it.descriptor = this
- it.isChildren = hasChildrenAnnotation()
- it.isRequired = when (this) {
- is PropertyDescriptor -> {
- isLateInit || !isVar
- }
- is FunctionDescriptor -> false
- is ValueParameterDescriptor -> {
- !hasDefaultValue()
- }
- else -> false
- }
- it.isExtension = isExtension
- it.isImmediate = when {
- this is CallableMemberDescriptor && kind ==
- CallableMemberDescriptor.Kind.FAKE_OVERRIDE -> false
- isDescriptorReferredTo(containingDeclaration) -> true
- containingDeclaration is ConstructorDescriptor &&
- isDescriptorReferredTo(containingDeclaration?.containingDeclaration) -> true
- else -> false
- }
- it.isImported = isImported
- it.isPivotal = when (this) {
- is PropertyDescriptor -> !isVar && !isExtension
- is ValueParameterDescriptor -> {
- // TODO(lmr): decide if we still want/need this with new type resolution
- false // in this case there is no component instance, so nothing is pivotal
- }
- else -> false
- }
- it.contributingDescriptor = when (this) {
- is FunctionDescriptor -> getContributingDescriptor(this)
- is PropertyDescriptor -> getContributingDescriptor(this)
- else -> null
- }
- it.extensionType = when (this) {
- is FunctionDescriptor -> getExtensionType(this)
- is PropertyDescriptor -> getExtensionType(this)
- else -> null
- }
- }
- }
-
- private fun getContributingDescriptor(d: CallableMemberDescriptor): ClassifierDescriptor? {
- return DescriptorUtils.unwrapFakeOverride(d).containingDeclaration as? ClassifierDescriptor
- }
-
- private fun getExtensionType(d: CallableDescriptor): KotlinType? {
- val receiverParameter = d.extensionReceiverParameter ?: return null
-
- if (receiverParameter.value.original.type.isTypeParameter()) {
- return receiverParameter.value.original.type.supertypes().firstOrNull()
- }
- return receiverParameter.type
- }
-
- private val kotlinxAndroidSyntheticFqname = FqName("kotlinx.android.synthetic")
- private fun shouldFilter(d: DeclarationDescriptor): Boolean {
- if (d.fqNameSafe.isChildOf(kotlinxAndroidSyntheticFqname)) return true
- if (d.hasHiddenAttributeAnnotation()) return true
- val name = d.name.asString()
-
- when (name) {
- "_firstFrameRecord",
- "setRecompose" -> {
- if (d is CallableMemberDescriptor) {
- val realDescriptor = getContributingDescriptor(d)
- if (realDescriptor != null && realDescriptor != d &&
- realDescriptor == composeComponentDescriptor) {
- return true
- }
- }
- }
- }
-
- // NOTE(lmr): View subclasses all have constructors with these params. We handle view creation somewhat automatically, and
- // we don't want these params to show up in the autocomplete despite people technically being able to use them. This is
- // kind of a gross way of removing them but seems to be a reasonably effective strategy.
- when (name) {
- "context",
- "attrs",
- "defStyle",
- "defStyleRes",
- "defStyleAttr" -> {
- if (
- d is ValueParameterDescriptor &&
- d.containingDeclaration is ConstructorDescriptor
- ) {
- val cd = d.containingDeclaration.containingDeclaration as? ClassDescriptor
- if (
- cd != null &&
- androidViewDescriptor != null &&
- cd.isSubclassOf(androidViewDescriptor)
- ) {
- return true
- }
- }
- }
- }
-
- return false
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/ComposeBundle.java b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/ComposeBundle.java
deleted file mode 100644
index 25702d5..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/ComposeBundle.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2019 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.plugins.idea;
-
-import com.intellij.CommonBundle;
-
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.PropertyKey;
-
-import java.lang.ref.Reference;
-import java.lang.ref.SoftReference;
-import java.util.ResourceBundle;
-
-/**
- * Compose resource bundle
- */
-public class ComposeBundle {
- private static Reference<ResourceBundle> sOurBundle;
-
- @NonNls
- private static final String BUNDLE = "androidx.compose.plugins.idea.ComposeBundle";
-
- private ComposeBundle() {
- }
-
- /**
- * Get a message by key
- * @param key The key for the message
- * @param params Substitution parameters
- * @return the localized message
- */
- @NotNull
- public static String message(
- @NonNls @PropertyKey(resourceBundle = BUNDLE) String key,
- Object... params
- ) {
- return CommonBundle.message(getBundle(), key, params);
- }
-
- private static ResourceBundle getBundle() {
- ResourceBundle bundle = null;
- if (sOurBundle != null) bundle = sOurBundle.get();
- if (bundle == null) {
- bundle = ResourceBundle.getBundle(BUNDLE);
- sOurBundle = new SoftReference<ResourceBundle>(bundle);
- }
- return bundle;
- }
-}
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/Utils.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/Utils.kt
deleted file mode 100644
index 7140099..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/Utils.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2019 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.plugins.idea
-
-import com.intellij.psi.PsiComment
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiErrorElement
-import com.intellij.psi.PsiWhiteSpace
-import org.jetbrains.kotlin.psi.KtBlockExpression
-import org.jetbrains.kotlin.psi.psiUtil.nextLeaf
-import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
-
-internal inline fun <reified T : PsiElement> PsiElement.parentOfType(): T? {
- var node: PsiElement? = this
- while (node != null) {
- if (node is T) return node
- node = node.parent
- }
- return null
-}
-
-private val nonWhiteSpaceFilter = { it: PsiElement ->
- when (it) {
- is PsiWhiteSpace -> false
- is PsiComment -> false
- is KtBlockExpression -> false
- is PsiErrorElement -> false
- else -> true
- }
-}
-internal fun PsiElement.getNextLeafIgnoringWhitespace(includeSelf: Boolean = false): PsiElement? =
- if (includeSelf && nonWhiteSpaceFilter(this)) this else nextLeaf(
- nonWhiteSpaceFilter
- )
-
-internal fun PsiElement.getPrevLeafIgnoringWhitespace(includeSelf: Boolean = false): PsiElement? =
- if (includeSelf && nonWhiteSpaceFilter(this)) this else prevLeaf(
- nonWhiteSpaceFilter
- )
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/BaseComposeCompletionSession.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/BaseComposeCompletionSession.kt
deleted file mode 100644
index d27a59a..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/BaseComposeCompletionSession.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-package androidx.compose.plugins.idea.completion
-
-import com.intellij.codeInsight.completion.CompletionParameters
-import com.intellij.codeInsight.completion.CompletionResultSet
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiClass
-import org.jetbrains.kotlin.builtins.extractParameterNameFromFunctionTypeArgument
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
-import org.jetbrains.kotlin.descriptors.VariableDescriptor
-import org.jetbrains.kotlin.descriptors.annotations.Annotated
-import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
-import org.jetbrains.kotlin.idea.completion.CompletionSession
-import org.jetbrains.kotlin.idea.completion.CompletionSessionConfiguration
-import org.jetbrains.kotlin.idea.completion.ToFromOriginalFileMapper
-import org.jetbrains.kotlin.idea.core.ExpectedInfo
-import org.jetbrains.kotlin.idea.core.isExcludedFromAutoImport
-import org.jetbrains.kotlin.idea.core.isVisible
-import org.jetbrains.kotlin.idea.inspections.collections.isFunctionOfAnyKind
-import org.jetbrains.kotlin.idea.util.getResolutionScope
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtExpression
-import androidx.compose.plugins.kotlin.ComposeUtils
-import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
-import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
-import org.jetbrains.kotlin.types.KotlinType
-
-abstract class BaseComposeCompletionSession(
- configuration: CompletionSessionConfiguration,
- parameters: CompletionParameters,
- toFromOriginalFileMapper: ToFromOriginalFileMapper,
- resultSet: CompletionResultSet
-) : CompletionSession(configuration, parameters, toFromOriginalFileMapper, resultSet) {
- override val descriptorKindFilter: DescriptorKindFilter?
- get() = TODO("not implemented")
- override val expectedInfos: Collection<ExpectedInfo>
- get() = emptyList()
-
- protected val cursorElement = expression ?: error("expression cannot be null")
- protected val module = resolutionFacade.moduleDescriptor
-
- protected val scope = cursorElement.getResolutionScope(bindingContext, resolutionFacade)
-
- protected val composeComponentDescriptor =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(ComposeUtils.composeFqName("Component"))
- )
- protected val androidViewDescriptor =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("android.view.View"))
- )
- protected val androidViewGroupDescriptor =
- module.findClassAcrossModuleDependencies(
- ClassId.topLevel(FqName("android.view.ViewGroup"))
- )
-
- protected val composeChildrenAnnotationFqName = ComposeUtils.composeFqName("Children")
- protected val composeComposableAnnotationFqName = ComposeUtils.composeFqName("Composable")
-
- protected val psiFacade = JavaPsiFacade.getInstance(project)
-
- protected val androidViewPsiClass = psiFacade.findClass("android.view.View", searchScope)
- protected val androidViewGroupPsiClass =
- psiFacade.findClass("android.view.ViewGroup", searchScope)
-
- protected fun DeclarationDescriptor.isVisibleDescriptor(): Boolean {
- if (this is TypeParameterDescriptor && !isTypeParameterVisible(this))
- return false
-
- if (this is DeclarationDescriptorWithVisibility) {
- return isVisible(
- cursorElement,
- callTypeAndReceiver.receiver as? KtExpression,
- bindingContext,
- resolutionFacade
- )
- }
-
- if (isExcludedFromAutoImport(file.project, file)) return false
-
- return true
- }
-
- private fun isTypeParameterVisible(typeParameter: TypeParameterDescriptor): Boolean {
- val owner = typeParameter.containingDeclaration
- var parent: DeclarationDescriptor? = inDescriptor
- while (parent != null) {
- if (parent == owner) return true
- if (parent is ClassDescriptor && !parent.isInner) return false
- parent = parent.containingDeclaration
- }
- return true
- }
-
- protected fun Annotated.hasChildrenAnnotation() =
- annotations.hasAnnotation(composeChildrenAnnotationFqName)
- protected fun Annotated.hasComposableAnnotation() =
- annotations.hasAnnotation(composeComposableAnnotationFqName)
-
- protected fun DeclarationDescriptor.allowsChildren(): Boolean {
- return when (this) {
- is ClassDescriptor -> when {
- androidViewGroupDescriptor != null && isSubclassOf(androidViewGroupDescriptor) ->
- true
- unsubstitutedMemberScope.getContributedDescriptors().any {
- it.hasChildrenAnnotation()
- } -> true
- unsubstitutedPrimaryConstructor?.valueParameters?.any {
- it.hasChildrenAnnotation()
- } ?: false -> true
- else -> false
- }
- is FunctionDescriptor -> when {
- valueParameters.any { it.hasChildrenAnnotation() } -> true
- else -> false
- }
- is VariableDescriptor -> when {
- type.isFunctionOfAnyKind() && type.arguments.any {
- it.type.hasChildrenAnnotation()
- } -> true
- else -> false
- }
- else -> false
- }
- }
-
- protected fun PsiClass.allowsChildren(): Boolean {
- // TODO(lmr): if it's a subclass of ViewGroup is a reasonable first order approximation of whether or not it
- // accepts children or not, but it is not a perfect test by any means. Many "leaf" views subclass ViewGroups
- // and handle laying out their own children. Perhaps we should have a manually curated list of these?
- return isSubclassOf(androidViewGroupPsiClass)
- }
-
- protected fun PsiClass.isSubclassOf(other: PsiClass?): Boolean {
- if (other == null) return false
- var cls: PsiClass? = this
- while (cls != null) {
- if (cls == other) return true
- cls = cls.superClass
- }
- return false
- }
-
- protected fun KotlinType.functionParameterNames(): List<String> =
- if (isFunctionOfAnyKind()) arguments.mapNotNull {
- it.type.extractParameterNameFromFunctionTypeArgument()?.asString()
- } else emptyList()
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeAttributeCompletionSession.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeAttributeCompletionSession.kt
deleted file mode 100644
index 51a5616..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeAttributeCompletionSession.kt
+++ /dev/null
@@ -1,234 +0,0 @@
-package androidx.compose.plugins.idea.completion
-
-import com.intellij.codeInsight.AutoPopupController
-import com.intellij.codeInsight.completion.CompletionParameters
-import com.intellij.codeInsight.completion.CompletionResultSet
-import com.intellij.codeInsight.completion.CompletionSorter
-import com.intellij.codeInsight.completion.InsertHandler
-import com.intellij.codeInsight.lookup.LookupElement
-import com.intellij.codeInsight.lookup.LookupElementBuilder
-import com.intellij.codeInsight.lookup.LookupElementPresentation
-import com.intellij.codeInsight.lookup.LookupElementRenderer
-import com.intellij.openapi.util.Key
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.ui.JBColor
-import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.idea.completion.CompletionSessionConfiguration
-import org.jetbrains.kotlin.idea.completion.ToFromOriginalFileMapper
-import org.jetbrains.kotlin.idea.completion.isArtificialImportAliasedDescriptor
-import org.jetbrains.kotlin.idea.util.ImportInsertHelper
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtxAttribute
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.KtxLambdaExpression
-import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import androidx.compose.plugins.idea.AttributeInfo
-import androidx.compose.plugins.idea.AttributeInfoExtractor
-import androidx.compose.plugins.idea.parentOfType
-import org.jetbrains.kotlin.renderer.DescriptorRenderer
-import org.jetbrains.kotlin.resolve.DescriptorUtils
-import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
-import org.jetbrains.kotlin.types.typeUtil.isUnit
-
-class ComposeAttributeCompletionSession(
- configuration: CompletionSessionConfiguration,
- parameters: CompletionParameters,
- toFromOriginalFileMapper: ToFromOriginalFileMapper,
- resultSet: CompletionResultSet
-) : BaseComposeCompletionSession(configuration, parameters, toFromOriginalFileMapper, resultSet) {
-
- private val elementExpr = cursorElement.parentOfType<KtxElement>()
- ?: error("no ktx element found")
- private val usedAttributes = elementExpr.getChildrenOfType<KtxAttribute>()
- private val hasChildrenLambda =
- elementExpr.getChildrenOfType<KtxLambdaExpression>().isNotEmpty()
- private val usedAttributesNameSet = usedAttributes.mapNotNull {
- attr -> attr.key?.getIdentifier()?.text
- }.toSet()
-
- fun isValid() = nullableKtxCall != null
-
- private val nullableKtxCall =
- bindingContext.get(ComposeWritableSlices.RESOLVED_KTX_CALL, elementExpr)
-
- private val ktxCall get() = nullableKtxCall
- ?: error("no tag info found on element. Call isValid() before using this class")
-
- private val ktxCallResolvedCalls by lazy {
- val failedCandidates = bindingContext.get(
- ComposeWritableSlices.FAILED_CANDIDATES,
- elementExpr
- ) ?: emptyList()
- val resolvedCalls = ktxCall.emitOrCall.resolvedCalls()
- resolvedCalls + failedCandidates
- }
- private val referrableDescriptors by lazy {
- ktxCallResolvedCalls
- .map {
- val resultingDescriptor = it.resultingDescriptor
- val result: DeclarationDescriptor = when {
- it is VariableAsFunctionResolvedCall -> it.variableCall.candidateDescriptor
- resultingDescriptor is ConstructorDescriptor ->
- resultingDescriptor.constructedClass
- else -> resultingDescriptor
- }
- result
- }
- }
-
- private val instanceTypes by lazy {
- ktxCallResolvedCalls
- .mapNotNull { it.resultingDescriptor.returnType }
- .filter { !it.isUnit() }
- }
-
- private val attributeInfoExtractor = AttributeInfoExtractor(
- file = file,
- visibilityFilter = { it.isVisibleDescriptor() },
- declarationTranslator = { toFromOriginalFileMapper.toSyntheticFile(it) },
- ktxCall = nullableKtxCall
- )
-
- private fun AttributeInfo.constructLookupElement(): LookupElement? {
- val extensionType = extensionType
- val contributingDescriptor = contributingDescriptor
-
- if (usedAttributesNameSet.contains(name)) return null
-
- // if the user has defined a chilren lambda already, there is no need to add a children attribute to the
- // autocomplete list
- if (hasChildrenLambda && isChildren) return null
-
- // NOTE(lmr): if the attribute is a setter function, the parameter name can often be useful. we should consider using its
- // name somewhere in here: (attr.descriptor as SimpleFunctionDescriptor).valueParameters.single().name
-
- var isImmediate = isImmediate
-
- var myTailText = "=..."
-
- if (isExtension && extensionType != null) {
- if (extensionType in instanceTypes) {
- isImmediate = true
- }
- val klass = SHORT_NAMES_RENDERER.renderType(extensionType)
- myTailText += " (extension on $klass)"
- }
-
- if (!isImmediate && !isExtension && contributingDescriptor != null) {
- val klass = SHORT_NAMES_RENDERER.renderClassifierName(contributingDescriptor)
- myTailText += " (from $klass)"
- }
-
- if (isRequired) {
- myTailText += " (required)"
- }
- if (isPivotal) {
- myTailText += " (pivotal)"
- }
-
- val renderer = object : LookupElementRenderer<LookupElement>() {
- override fun renderElement(
- element: LookupElement?,
- presentation: LookupElementPresentation?
- ) {
- if (presentation == null) return
- with(presentation) {
- itemText = name
- isItemTextBold = when {
- isImmediate || isRequired -> true
- else -> false
- }
- itemTextForeground = when {
- isPivotal -> JBColor.RED
- isRequired -> JBColor.BLUE
- isImmediate -> JBColor.BLACK
- else -> JBColor.darkGray
- }
- typeText = SHORT_NAMES_RENDERER.renderType(type)
- if (isExtension) {
- appendTailTextItalic(myTailText, true)
- } else {
- appendTailText(myTailText, true)
- }
- }
- }
- }
-
- return LookupElementBuilder
- .create(this, name)
- .withRenderer(renderer)
- .withInsertHandler(attributeInsertionHandler)
- .apply {
- putUserData(DESCRIPTOR, descriptor)
- putUserData(Weighers.ATTRIBUTE_INFO_KEY, this@constructLookupElement)
- }
- }
-
- override fun doComplete() {
- for (descriptor in referrableDescriptors) {
- attributeInfoExtractor.extract(descriptor) { attributeInfos ->
- attributeInfos.forEach {
- // TODO(lmr): use it.getPossibleValues(project) here to get some of the int enum values as well
- it.constructLookupElement()?.let { collector.addElement(it) }
- }
- flushToResultSet()
- }
- }
- }
-
- override fun createSorter(): CompletionSorter {
- return super.createSorter()
- .weighAfter(
- "prefix",
- Weighers.IsRequiredAttribute,
- Weighers.IsImmediateAttribute,
- Weighers.IsImported,
- Weighers.IsChildAttribute
- )
- }
-
- private val attributeInsertionHandler = InsertHandler<LookupElement> { context, item ->
- val document = context.document
- val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
- psiDocumentManager.commitAllDocuments()
- item.getUserData(DESCRIPTOR)?.let { descriptor ->
- if (DescriptorUtils.isTopLevelDeclaration(descriptor) &&
- !descriptor.isArtificialImportAliasedDescriptor) {
- ImportInsertHelper.getInstance(context.project)
- .importDescriptor(context.file as KtFile, descriptor)
- }
- }
- psiDocumentManager.doPostponedOperationsAndUnblockDocument(document)
-
- val token = context.file.findElementAt(context.startOffset) ?: return@InsertHandler
- val ktxAttribute = token.parent.parent as? KtxAttribute ?: return@InsertHandler
-
- // If the attribute has an equals sign already, then we don't need to insert it...
- if (ktxAttribute.node.findChildByType(KtTokens.EQ) != null) return@InsertHandler
-
- // NOTE: If we introduce attribute "punning", we will need to do the right thing here...
-
- val tailOffset = context.tailOffset
- val moveCaret = context.editor.caretModel.offset == tailOffset
- val textToInsert = "="
- document.insertString(tailOffset, textToInsert)
-
- if (moveCaret) {
- // move after the '='
- context.editor.caretModel.moveToOffset(tailOffset + 1)
-
- // since they just created a new attribute and need to fill out the expression, we might as well open up the autocomplete!
- AutoPopupController.getInstance(context.project)?.scheduleAutoPopup(context.editor)
- }
- }
-
- companion object {
- val DESCRIPTOR = Key<DeclarationDescriptor>("compose.descriptor")
- private val SHORT_NAMES_RENDERER = DescriptorRenderer.SHORT_NAMES_IN_TYPES.withOptions {
- parameterNamesInFunctionalTypes = false
- }
- }
-}
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeCompletionContributor.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeCompletionContributor.kt
deleted file mode 100644
index e1576ae..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeCompletionContributor.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package androidx.compose.plugins.idea.completion
-
-import com.intellij.codeInsight.completion.CompletionContributor
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiWhiteSpace
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.psi.KtPsiUtil
-import org.jetbrains.kotlin.psi.KtxAttribute
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.psiUtil.endOffset
-import androidx.compose.plugins.idea.parentOfType
-
-class ComposeCompletionContributor : CompletionContributor() {
-
- @Suppress("OverridingDeprecatedMember")
- override fun invokeAutoPopup(position: PsiElement, typeChar: Char): Boolean {
- when (typeChar) {
- ' ' -> {
- if (position.parent?.parent is KtxAttribute) {
- // user hit space immediately after an attribute value
- return true
- }
- val ktxElement = position.parentOfType<KtxElement>() ?: return false
- val openTag = ktxElement.simpleTagName ?: ktxElement.qualifiedTagName
- ?: return false
- // user hit space immediately after the open tag
- if (position.endOffset == openTag.endOffset) return true
- if (position is PsiWhiteSpace && position.parent == ktxElement) {
- val gt = ktxElement.node.findChildByType(KtTokens.GT) ?: return false
- // user hit space in between attributes
- if (position.endOffset < gt.startOffset) return true
- }
- }
- '<' -> {
- if (position.parent is KtxElement) {
- // this also happens inside of a KTX Body in some cases
- return true
- }
- if (KtPsiUtil.isStatementContainer(position.parent)) {
- // if the user types a bracket inside of a block, there is a high probability that they are
- // starting a ktx tag
- return true
- }
- }
- }
- @Suppress("DEPRECATION")
- return super.invokeAutoPopup(position, typeChar)
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeCompletionExtension.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeCompletionExtension.kt
deleted file mode 100644
index 3dcd7888..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeCompletionExtension.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package androidx.compose.plugins.idea.completion
-
-import com.intellij.codeInsight.completion.CompletionParameters
-import com.intellij.codeInsight.completion.CompletionResultSet
-import org.jetbrains.kotlin.idea.completion.CompletionSessionConfiguration
-import org.jetbrains.kotlin.idea.completion.KotlinCompletionExtension
-import org.jetbrains.kotlin.idea.completion.ToFromOriginalFileMapper
-import org.jetbrains.kotlin.idea.editor.fixers.range
-import org.jetbrains.kotlin.lexer.KtTokens.EQ
-import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
-import org.jetbrains.kotlin.psi.KtxAttribute
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.idea.parentOfType
-
-class ComposeCompletionExtension : KotlinCompletionExtension() {
- override fun perform(parameters: CompletionParameters, result: CompletionResultSet): Boolean {
- val expr = parameters.position
- val superParent = expr.parent?.parent
-
- when (superParent) {
- is KtDotQualifiedExpression -> {
- val ktxEl = superParent.parentOfType<KtxElement>()
- if (ktxEl != null && ktxEl.qualifiedTagName?.range?.contains(expr.range) == true) {
- return performKtxTagCompletion(parameters, result)
- }
- }
- is KtxElement -> {
- return performKtxTagCompletion(parameters, result)
- }
- is KtxAttribute -> {
- if (expr.parent?.prevSibling?.node?.elementType == EQ) {
- // we are inside of a ktx expression value... use normal autocomplete
- return false
- }
- return performKtxAttributeCompletion(parameters, result)
- }
- }
-
- return false
- }
-
- private fun performKtxAttributeCompletion(
- params: CompletionParameters,
- result: CompletionResultSet
- ): Boolean {
- val session = ComposeAttributeCompletionSession(
- CompletionSessionConfiguration(params),
- params,
- ToFromOriginalFileMapper.create(params),
- result
- )
-
- if (!session.isValid()) return false
-
- session.complete()
-
- return true
- }
-
- private fun performKtxTagCompletion(
- params: CompletionParameters,
- result: CompletionResultSet
- ): Boolean {
- ComposeTagCompletionSession(
- CompletionSessionConfiguration(params),
- params,
- ToFromOriginalFileMapper.create(params),
- result
- ).complete()
-
- return true
- }
-}
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeTagCompletionSession.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeTagCompletionSession.kt
deleted file mode 100644
index 837c6e3..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/ComposeTagCompletionSession.kt
+++ /dev/null
@@ -1,716 +0,0 @@
-package androidx.compose.plugins.idea.completion
-
-import com.intellij.codeInsight.AutoPopupController
-import com.intellij.codeInsight.completion.AllClassesGetter
-import com.intellij.codeInsight.completion.CompletionParameters
-import com.intellij.codeInsight.completion.CompletionResultSet
-import com.intellij.codeInsight.completion.CompletionSorter
-import com.intellij.codeInsight.completion.InsertHandler
-import com.intellij.codeInsight.completion.InsertionContext
-import com.intellij.codeInsight.lookup.LookupElement
-import com.intellij.codeInsight.lookup.LookupElementBuilder
-import com.intellij.codeInsight.lookup.LookupElementPresentation
-import com.intellij.codeInsight.lookup.LookupElementRenderer
-import com.intellij.lang.jvm.JvmModifier
-import com.intellij.openapi.util.Iconable
-import com.intellij.openapi.util.Key
-import com.intellij.psi.PsiClass
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.psi.PsiLiteral
-import com.intellij.psi.PsiWhiteSpace
-import com.intellij.ui.JBColor
-import com.intellij.util.PlatformIcons
-import org.jetbrains.kotlin.asJava.classes.KtLightClass
-import org.jetbrains.kotlin.builtins.DefaultBuiltIns
-import org.jetbrains.kotlin.descriptors.CallableDescriptor
-import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.ClassKind
-import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.Modality
-import org.jetbrains.kotlin.descriptors.ParameterDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.VariableDescriptor
-import org.jetbrains.kotlin.idea.KotlinDescriptorIconProvider
-import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor
-import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
-import org.jetbrains.kotlin.idea.completion.CompletionSessionConfiguration
-import org.jetbrains.kotlin.idea.completion.KindWeigher
-import org.jetbrains.kotlin.idea.completion.ToFromOriginalFileMapper
-import org.jetbrains.kotlin.idea.completion.asNameFilter
-import org.jetbrains.kotlin.idea.completion.isArtificialImportAliasedDescriptor
-import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
-import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
-import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly
-import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil
-import org.jetbrains.kotlin.idea.util.ImportInsertHelper
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.load.java.JvmAnnotationNames
-import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.psiUtil.endOffset
-import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
-import org.jetbrains.kotlin.psi.psiUtil.nextLeaf
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import androidx.compose.plugins.idea.parentOfType
-import org.jetbrains.kotlin.renderer.render
-import org.jetbrains.kotlin.resolve.DescriptorUtils
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
-import org.jetbrains.kotlin.resolve.scopes.utils.collectAllFromMeAndParent
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes
-import org.jetbrains.kotlin.types.typeUtil.isUnit
-import org.jetbrains.kotlin.util.OperatorNameConventions.INVOKE
-
-class ComposeTagCompletionSession(
- configuration: CompletionSessionConfiguration,
- parameters: CompletionParameters,
- toFromOriginalFileMapper: ToFromOriginalFileMapper,
- resultSet: CompletionResultSet
-) : BaseComposeCompletionSession(configuration, parameters, toFromOriginalFileMapper, resultSet) {
-
- private val elementExpr = cursorElement.parentOfType<KtxElement>()
- ?: error("no ktx element found")
- private val nullableKtxCall =
- bindingContext.get(ComposeWritableSlices.RESOLVED_KTX_CALL, elementExpr)
-
- private val emitUpperBounds by lazy {
- if (nullableKtxCall == null || nullableKtxCall.getComposerCall == null)
- listOf(DefaultBuiltIns.Instance.any)
- else nullableKtxCall.emitSimpleUpperBoundTypes
- .mapNotNull { it.constructor.declarationDescriptor as? ClassDescriptor }
- }
-
- private val emitUpperBoundsPsi by lazy {
- emitUpperBounds.mapNotNull { psiFacade.findClass(it.fqNameSafe.asString(), searchScope) }
- }
-
- private val indiceshelper = KotlinIndicesHelper(
- resolutionFacade,
- searchScope,
- { true },
- filterOutPrivate = true,
- declarationTranslator = { toFromOriginalFileMapper.toSyntheticFile(it) },
- file = file
- )
-
- private fun PsiClass.isEmittable(): Boolean {
- return when {
- isSyntheticKotlinClass() -> false
- isAnnotationType -> false
- isInterface -> false
- isEnum -> false
- hasModifier(JvmModifier.ABSTRACT) -> false
- emitUpperBoundsPsi.any { isSubclassOf(it) } -> true
- else -> false
- }
- }
-
- private fun PsiClass.isSyntheticKotlinClass(): Boolean {
- if ('$' !in name!!) return false // optimization to not analyze annotations of all classes
- val metadata = modifierList?.findAnnotation(JvmAnnotationNames.METADATA_FQ_NAME.asString())
- return (metadata?.findAttributeValue(JvmAnnotationNames.KIND_FIELD_NAME) as?
- PsiLiteral)?.value == KotlinClassHeader.Kind.SYNTHETIC_CLASS.id
- }
-
- private fun DeclarationDescriptor.childrenParameterNames(): List<String> {
- when (this) {
- is ClassDescriptor -> {
- val descriptor =
- unsubstitutedMemberScope.getContributedDescriptors().firstOrNull {
- it.hasChildrenAnnotation()
- } ?: unsubstitutedPrimaryConstructor?.valueParameters?.firstOrNull {
- it.hasChildrenAnnotation()
- } ?: return emptyList()
-
- return when (descriptor) {
- is ParameterDescriptor -> descriptor.type.functionParameterNames()
- is PropertyDescriptor -> descriptor.type.functionParameterNames()
- is FunctionDescriptor ->
- descriptor.valueParameters.firstOrNull()?.type?.functionParameterNames()
- ?: emptyList()
- else -> emptyList()
- }
- }
- is FunctionDescriptor -> {
- val descriptor = valueParameters.firstOrNull { it.hasChildrenAnnotation() }
- ?: return emptyList()
- return descriptor.type.functionParameterNames()
- }
- is VariableDescriptor -> {
- val childTypeArg = type.arguments.firstOrNull { it.type.hasChildrenAnnotation() }
- ?: return emptyList()
- return childTypeArg.type.functionParameterNames()
- }
- }
-
- return emptyList()
- }
-
- private fun DeclarationDescriptor.constructLookupElement(): LookupElement? {
- val isVisible = isVisibleDescriptor()
- val parentFqName = fqNameSafe.parent()
-
- val fileFqName = file.packageFqName
-
- val packageText = if (fileFqName != parentFqName) parentFqName.render() else ""
-
- val tailText = if (packageText.isNotBlank()) " ($packageText)" else ""
-
- val allowsChildren = allowsChildren()
- val parameterNames = if (allowsChildren) childrenParameterNames() else emptyList()
-
- val presentableText =
- if (allowsChildren && parameterNames.isNotEmpty())
- "<${name.asString()}> ${parameterNames.joinToString()} -> ... </${name.asString()}>"
- else if (allowsChildren)
- "<${name.asString()}>...</${name.asString()}>"
- else
- "<${name.asString()} />"
-
- val descriptor = this
- val psiDecl = when (descriptor) {
- is ConstructorDescriptor -> descriptor.containingDeclaration
- else -> descriptor
- }
-
- val renderer = object : LookupElementRenderer<LookupElement>() {
- override fun renderElement(
- element: LookupElement?,
- presentation: LookupElementPresentation?
- ) {
- if (presentation == null) return
- with(presentation) {
- val psiElement = DescriptorToSourceUtilsIde.getAnyDeclaration(project, psiDecl)
- itemText = presentableText
- isItemTextBold = isVisible
- icon = KotlinDescriptorIconProvider.getIcon(
- descriptor,
- psiElement,
- Iconable.ICON_FLAG_VISIBILITY
- )
- appendTailText(tailText, true)
- }
- }
- }
-
- return LookupElementBuilder.create(this, name.asString())
- .withRenderer(renderer)
- .withInsertHandler(tagInsertionHandler)
- .apply {
- putUserData(ALLOWS_CHILDREN, allowsChildren)
- putUserData(DESCRIPTOR, this@constructLookupElement)
- putUserData(Weighers.IS_IMPORTED_KEY, isVisible)
- }
- }
-
- private fun PsiClass.constructLookupElement(): LookupElement? {
- val parentFqName = getKotlinFqName()?.parent() ?: return null
-
- val name = name ?: return null
-
- val fileFqName = file.packageFqName
-
- val packageText = if (fileFqName != parentFqName) parentFqName.render() else ""
-
- val tailText = if (packageText.isNotBlank()) " ($packageText)" else ""
-
- val allowsChildren = allowsChildren()
- // TODO: we should look for parameter names in the cases where views have @Children properties that have parameters
- val parameterNames = emptyList<String>()
-
- val presentableText =
- if (allowsChildren && parameterNames.isNotEmpty())
- "<$name> ${parameterNames.joinToString()} -> ... </$name>"
- else if (allowsChildren)
- "<$name>...</$name>"
- else
- "<$name />"
-
- val psiClass = this
-
- val renderer = object : LookupElementRenderer<LookupElement>() {
- override fun renderElement(
- element: LookupElement?,
- presentation: LookupElementPresentation?
- ) {
- if (presentation == null) return
- with(presentation) {
- itemText = presentableText
- isItemTextBold = false
- icon = psiClass.getIcon(Iconable.ICON_FLAG_VISIBILITY)
- appendTailText(tailText, true)
- }
- }
- }
-
- return LookupElementBuilder.create(this, name)
- .withRenderer(renderer)
- .withInsertHandler(psiClassTagInsertionHandler)
- .apply {
- putUserData(ALLOWS_CHILDREN, allowsChildren)
- putUserData(PSI_CLASS, this@constructLookupElement)
- putUserData(Weighers.IS_IMPORTED_KEY, false)
- }
- }
-
- private fun FqName.constructLookupElement(): LookupElement? {
- val renderer = object : LookupElementRenderer<LookupElement>() {
- override fun renderElement(
- element: LookupElement?,
- presentation: LookupElementPresentation?
- ) {
- with(presentation ?: return) {
- itemText = "<${this@constructLookupElement}.* />"
- itemTextForeground = JBColor.lightGray
- icon = PlatformIcons.PACKAGE_ICON
- }
- }
- }
-
- return LookupElementBuilder.create(pathSegments().last().asString())
- .withRenderer(renderer)
- .withInsertHandler(packageInsertionHandler)
- .apply {
- putUserData(Weighers.IS_IMPORTED_KEY, false)
- }
- }
-
- private enum class ComposableKind(val valid: Boolean) {
- // This is used internally. public functions should never return this as a result.
- UNKNOWN(false),
- // This type cannot be used in a tag because it is either uninvokable, or invokable and returning a type which is itself "INVALID"
- INVALID(false),
- // This type can be used in a tag but the "terminal" invoke (the one returning Unit) is not marked as @Composable
- UNIT_TERMINAL(true),
- // This type can be used in a tag because it is either invokable and marked as @Composable, or invokable and returning a type
- // which is itself "COMPOSABLE"
- COMPOSABLE(true)
- }
-
- private inner class ValidTagHelper {
- private val index = mutableMapOf<KotlinType, ComposableKind>()
- private val invokeFns = mutableMapOf<KotlinType, MutableList<KotlinType>>()
-
- fun getComposability(type: KotlinType?): ComposableKind {
- if (type == null) return ComposableKind.INVALID
- val kind = index[type] ?: ComposableKind.UNKNOWN
- return when (kind) {
- ComposableKind.UNKNOWN -> {
- // temporarily set the composability of this type to INVALID, so we don't get into a cycle
- index[type] =
- ComposableKind.INVALID
-
- // there might be an invoke function that goes to another type, in which case
- // we need to check the composability of the return type
- var max = invokeFns[type]
- ?.map { getComposability(it) }
- ?.maxBy { it.ordinal }
- ?: ComposableKind.INVALID
-
- // if we are still invalid, we check to see if any invoke functions are defined on the
- // type. This is needed because the indicesHelper doesn't seem to go through classes in
- // other modules
- if (max == ComposableKind.INVALID &&
- type.memberScope.getFunctionNames().contains(INVOKE)) {
- type
- .memberScope
- .getContributedFunctions(INVOKE, NoLookupLocation.FROM_IDE)
- .filter { it.name == INVOKE && it.isOperator }
- .forEach { invoke ->
- addInvoke(type, invoke.returnType
- ?: DefaultBuiltIns.Instance.unitType, invoke)
- }
-
- index[type]?.let { max = it }
- }
-
- // It's possible we have new invokeFns now, so we check again
- if (max == ComposableKind.INVALID) {
- max = invokeFns[type]
- ?.map { getComposability(it) }
- ?.maxBy { it.ordinal }
- ?: ComposableKind.INVALID
- }
-
- // if we are still invalid, we want to check the composability of our immediate
- // super-types
- if (max == ComposableKind.INVALID) {
- max = type
- .immediateSupertypes()
- .map { getComposability(it) }
- .maxBy { it.ordinal }
- ?: ComposableKind.INVALID
- }
-
- // at this point we've exhausted all options, so we should set the composability
- // of this type
- index[type] = max
- max
- }
- ComposableKind.INVALID,
- ComposableKind.UNIT_TERMINAL,
- ComposableKind.COMPOSABLE -> kind
- }
- }
-
- fun addInvoke(type: KotlinType, returnType: KotlinType, descriptor: FunctionDescriptor) {
- val isUnit = returnType.isUnit()
- val isComposable = descriptor.hasComposableAnnotation()
-
- index[type] = when {
- isComposable -> ComposableKind.COMPOSABLE
- isUnit -> ComposableKind.UNIT_TERMINAL
- else -> ComposableKind.UNKNOWN
- }
- if (!isUnit) {
- invokeFns.multiPut(type, returnType)
- }
- }
- }
-
- private fun ClassDescriptor.isEmittable(): Boolean {
- return emitUpperBounds.any { isSubclassOf(it) }
- }
-
- private fun CallableDescriptor.isImmediatelyComposable(): Boolean {
- return when (this) {
- is FunctionDescriptor -> returnType?.isUnit() == true && hasComposableAnnotation()
- is VariableDescriptor -> hasComposableAnnotation() || type.hasComposableAnnotation() ||
- hasChildrenAnnotation() || type.hasChildrenAnnotation()
- else -> false
- }
- }
-
- private fun collectComposable(
- d: DeclarationDescriptor,
- type: KotlinType?,
- tagHelper: ValidTagHelper
- ) {
- val kind = tagHelper.getComposability(type)
- if (kind == ComposableKind.INVALID) {
- // at this point we can just throw it on the floor?
- } else {
- val element = d.constructLookupElement()
- if (element != null) collector.addElement(element)
- }
- }
-
- private fun collectDescriptor(d: DeclarationDescriptor, tagHelper: ValidTagHelper) {
- when (d) {
- is ClassDescriptor -> {
- if (d.modality == Modality.ABSTRACT) return
- if (d.kind == ClassKind.INTERFACE) return
- if (!d.isVisibleDescriptor()) return
- if (d.isEmittable()) {
- val element = d.constructLookupElement()
- if (element != null) collector.addElement(element)
- } else {
- collectComposable(d, d.defaultType, tagHelper)
- }
- }
- is CallableDescriptor -> {
- if (d.isImmediatelyComposable()) {
- val element = d.constructLookupElement()
- if (element != null) collector.addElement(element)
- } else {
- val type = when (d) {
- is FunctionDescriptor -> d.returnType
- is VariableDescriptor -> d.type
- else -> null
- }
- collectComposable(d, type, tagHelper)
- }
- }
- else -> {
- // TODO(lmr): are there valid cases here?
- }
- }
- }
-
- override fun doComplete() {
- val receiverText = callTypeAndReceiver.receiver?.text
- val receiverFqName = receiverText?.let { FqName(it) }
-
- val tagHelper = ValidTagHelper()
-
- indiceshelper.getMemberOperatorsByName(INVOKE.identifier).forEach { x ->
- val dispatchReceiver = x.dispatchReceiverParameter
- val extensionReceiver = x.extensionReceiverParameter
- if (extensionReceiver == null && dispatchReceiver != null) {
- tagHelper.addInvoke(
- dispatchReceiver.type,
- x.returnType ?: DefaultBuiltIns.Instance.unitType,
- x
- )
- }
- }
-
- indiceshelper.getTopLevelExtensionOperatorsByName(INVOKE.identifier).forEach { x ->
- val dispatchReceiver = x.dispatchReceiverParameter
- val extensionReceiver = x.extensionReceiverParameter
- if (extensionReceiver != null && dispatchReceiver == null) {
- tagHelper.addInvoke(
- extensionReceiver.type,
- x.returnType ?: DefaultBuiltIns.Instance.unitType,
- x
- )
- }
- }
-
- if (receiverTypes != null && receiverTypes.isNotEmpty()) {
- receiverTypes
- .flatMap { it.type.memberScope.getContributedDescriptors() }
- .asSequence()
- .forEach { collectDescriptor(it, tagHelper) }
- flushToResultSet()
- }
-
- if (callTypeAndReceiver.receiver != null) {
- if (receiverFqName != null) {
- module
- .getPackage(receiverFqName)
- .memberScope
- .getContributedDescriptors()
- .forEach { collectDescriptor(it, tagHelper) }
- flushToResultSet()
- }
- } else {
- doSimpleCompleteNew(tagHelper)
- }
-
- doPackageNameCompletion()
- }
-
- private fun doSimpleCompleteNew(tagHelper: ValidTagHelper) {
- // 1. scoped declarations that are marked @Composable or emittable
- scope
- .collectAllFromMeAndParent { s -> s.getContributedDescriptors() }
- .asIterable()
- .forEach { collectDescriptor(it, tagHelper) }
-
- flushToResultSet()
-
- // 2. top level kotlin classes that are "emittable"
- //
- // throw away those that we know can't also be valid composables. keep the ones that might be.
- indiceshelper.getKotlinClasses({ true }).forEach { d ->
- if (d.modality == Modality.ABSTRACT) return@forEach
- if (d.kind == ClassKind.INTERFACE) return@forEach
- if (!d.isVisibleDescriptor()) return@forEach
- if (d.isEmittable()) {
- val element = d.constructLookupElement()
- if (element != null) collector.addElement(element)
- } else {
- collectComposable(d, d.defaultType, tagHelper)
- }
- }
- flushToResultSet()
-
- // 3. top level callables that are marked @Composable
- //
- // As a first order approximation, we will only capture non-nested composables here. keep the rest.
- indiceshelper.processTopLevelCallables({ true }) { d ->
- // if it is a constructor, we will capture it in the getKotlinClasses(...) call above
- if (d is ClassConstructorDescriptor) return@processTopLevelCallables
- if (!d.isVisibleDescriptor()) return@processTopLevelCallables
- if (d.isImmediatelyComposable()) {
- val element = d.constructLookupElement()
- if (element != null) collector.addElement(element)
- } else {
- val type = when (d) {
- is FunctionDescriptor -> d.returnType
- is VariableDescriptor -> d.type
- else -> null
- }
- collectComposable(d, type, tagHelper)
- }
- }
- flushToResultSet()
-
- // 4. top level java classes that are "emittable"
- AllClassesGetter.processJavaClasses(
- prefixMatcher,
- project,
- searchScope
- ) { psiClass: PsiClass ->
- if (psiClass is KtLightClass) {
- // Kotlin class should have already been added as kotlin element before
- return@processJavaClasses true
- }
- // TODO(lmr): deal with visibility here somehow?
- if (!psiClass.isEmittable()) {
- // filter out synthetic classes produced by Kotlin compiler
- return@processJavaClasses true
- }
- val element = psiClass.constructLookupElement()
- if (element != null) collector.addElement(element)
- return@processJavaClasses true
- }
-
- flushToResultSet()
- }
-
- private fun doPackageNameCompletion() {
- val receiverText = callTypeAndReceiver.receiver?.text
- val receiverFqName = receiverText?.let { FqName(it) }
- val prefixFqName = receiverFqName ?: FqName.ROOT
- val prefixString = receiverFqName?.asString() ?: ""
-
- val packageNames = PackageIndexUtil.getSubPackageFqNames(
- prefixFqName,
- searchScope,
- project,
- prefixMatcher.asNameFilter()
- ).filter { it.hasIdentifiersOnly() }.toMutableSet()
-
- psiFacade.findPackage(prefixString)?.getSubPackages(searchScope)?.forEach { psiPackage ->
- val name = psiPackage.name
-
- if (name?.isIdentifier() == true) {
- if (prefixString.isBlank()) {
- packageNames.add(FqName(name))
- } else {
- packageNames.add(FqName("$prefixString.$name"))
- }
- }
- }
-
- packageNames.forEach {
- val element = it.constructLookupElement()
- if (element != null) {
- collector.addElement(element)
- }
- }
-
- flushToResultSet()
- }
-
- override fun createSorter(): CompletionSorter {
- return super.createSorter()
- .weighBefore(
- KindWeigher.toString(),
- Weighers.IsImported
- )
- }
-
- private val tagInsertionHandler = InsertHandler<LookupElement> { context, item ->
- val descriptor = item.getUserData(DESCRIPTOR) ?: return@InsertHandler
- val allowsChildren = item.getUserData(ALLOWS_CHILDREN) ?: false
- val prefix = callTypeAndReceiver.receiver?.text?.let { "$it." } ?: ""
- val tagName = "$prefix${item.lookupString}"
- insertHandler(descriptor, context, allowsChildren, tagName)
- }
-
- private val psiClassTagInsertionHandler = InsertHandler<LookupElement> { context, item ->
- val psiClass = item.getUserData(PSI_CLASS) ?: return@InsertHandler
- val descriptor = psiClass.getJavaClassDescriptor(resolutionFacade) ?: return@InsertHandler
- val allowsChildren = item.getUserData(ALLOWS_CHILDREN) ?: false
- val prefix = callTypeAndReceiver.receiver?.text?.let { "$it." } ?: ""
- val tagName = "$prefix${item.lookupString}"
- insertHandler(descriptor, context, allowsChildren, tagName)
- }
-
- private val packageInsertionHandler = InsertHandler<LookupElement> { context, _ ->
- val document = context.document
- val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
- psiDocumentManager.commitAllDocuments()
- psiDocumentManager.doPostponedOperationsAndUnblockDocument(document)
-
- val token = context.file.findElementAt(context.startOffset) ?: return@InsertHandler
- val ktxElement = token.parentOfType<KtxElement>() ?: return@InsertHandler
- val tagName = ktxElement.simpleTagName ?: ktxElement.qualifiedTagName
- ?: return@InsertHandler
-
- val tailOffset = context.tailOffset
- val moveCaret = context.editor.caretModel.offset == tailOffset
- // if the inserted text is at the end of the tag name, we have more to type
- if (tagName.endOffset != context.editor.caretModel.offset) return@InsertHandler
-
- val textToInsert = "."
-
- document.insertString(tailOffset, textToInsert)
-
- if (moveCaret) {
- context.editor.caretModel.moveToOffset(tailOffset + 1)
- }
-
- AutoPopupController.getInstance(context.project)?.scheduleAutoPopup(context.editor)
- }
-
- private fun insertHandler(
- descriptor: DeclarationDescriptor,
- context: InsertionContext,
- allowsChildren: Boolean,
- tagName: String
- ) {
- val document = context.document
- val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
-
- psiDocumentManager.commitAllDocuments()
- if (DescriptorUtils.isTopLevelDeclaration(descriptor) &&
- !descriptor.isArtificialImportAliasedDescriptor) {
- ImportInsertHelper.getInstance(context.project)
- .importDescriptor(context.file as KtFile, descriptor)
- }
- psiDocumentManager.doPostponedOperationsAndUnblockDocument(document)
-
- val token = context.file.findElementAt(context.startOffset) ?: return
- val ktxElement = token.parentOfType<KtxElement>() ?: return
- val mightWrapBody = ktxElement.nextLeaf {
- it !is PsiWhiteSpace && it.textLength > 0
- }?.text != "}"
- val gt = ktxElement.node.findChildByType(KtTokens.GT)
- // if the tag has a GT token, then we don't need to close it
- if (gt != null) return
-
- val parameterNames: List<String> =
- if (allowsChildren) descriptor.childrenParameterNames() else emptyList()
-
- val tailOffset = context.tailOffset
- val moveCaret = context.editor.caretModel.offset == tailOffset
- val textToInsert = buildString {
- if (allowsChildren) {
- append(">")
- if (parameterNames.isNotEmpty()) {
- append(" ${parameterNames.joinToString()} ->")
- if (!mightWrapBody) append(" ")
- }
- if (!mightWrapBody) append("</$tagName>")
- } else append(" />")
- }
-
- document.insertString(tailOffset, textToInsert)
-
- if (moveCaret) {
- val addedSpace = if (!allowsChildren) 1 else 0
-
- context.editor.caretModel.moveToOffset(tailOffset + addedSpace)
-
- // TODO: Check if any attributes are valid, and if so, open autocomplete in `attributes` mode
- // since they just created a new tag, they might want to add some attributes.
- // AutoPopupController.getInstance(context.project)?.scheduleAutoPopup(context.editor)
- }
- }
-
- companion object {
- val ALLOWS_CHILDREN = Key<Boolean>("compose.allows_children")
- val DESCRIPTOR = Key<DeclarationDescriptor>("compose.descriptor")
- val PSI_CLASS = Key<PsiClass>("compose.psiClass")
- }
-}
-
-private fun <T, V> MutableMap<T, MutableList<V>>.multiPut(key: T, value: V) {
- val current = get(key)
- if (current != null) {
- current.add(value)
- } else {
- put(key, mutableListOf(value))
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/Weighers.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/Weighers.kt
deleted file mode 100644
index 6c82740..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/completion/Weighers.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package androidx.compose.plugins.idea.completion
-
-import com.intellij.codeInsight.lookup.LookupElement
-import com.intellij.codeInsight.lookup.LookupElementWeigher
-import com.intellij.codeInsight.lookup.WeighingContext
-import com.intellij.openapi.util.Key
-import androidx.compose.plugins.idea.AttributeInfo
-
-object Weighers {
- val IS_IMPORTED_KEY = Key<Boolean>("compose.is_imported")
- val ATTRIBUTE_INFO_KEY = Key<AttributeInfo>("compose.attribute_info")
-
- val IsImported = object : LookupElementWeigher("compose.importedWeigher") {
- override fun weigh(element: LookupElement, context: WeighingContext) =
- if (element.getUserData(IS_IMPORTED_KEY) == true) -1 else 0
- }
-
- abstract class AttributeInfoWeigher(key: String) : LookupElementWeigher(key) {
- override fun weigh(element: LookupElement, context: WeighingContext) =
- element.getUserData(ATTRIBUTE_INFO_KEY)?.let { weigh(it) } ?: 0
- abstract fun weigh(info: AttributeInfo): Int
- }
-
- val IsImmediateAttribute =
- object : AttributeInfoWeigher("compose.isImmediateAttributeWeigher") {
- override fun weigh(info: AttributeInfo): Int = if (info.isImmediate) -1 else 0
- }
- val IsRequiredAttribute =
- object : AttributeInfoWeigher("compose.isRequiredAttributeWeigher") {
- override fun weigh(info: AttributeInfo): Int = if (info.isRequired) -1 else 0
- }
-
- val IsChildAttribute = object : AttributeInfoWeigher("compose.isChildAttributeWeigher") {
- override fun weigh(info: AttributeInfo): Int = if (info.isChildren) 1 else 0
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/ComposeUnresolvedTagAnnotator.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/ComposeUnresolvedTagAnnotator.kt
deleted file mode 100644
index ca0ce86..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/ComposeUnresolvedTagAnnotator.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package androidx.compose.plugins.idea.editor
-
-import com.intellij.codeInspection.ProblemHighlightType
-import com.intellij.lang.annotation.AnnotationHolder
-import com.intellij.lang.annotation.Annotator
-import com.intellij.openapi.progress.ProcessCanceledException
-import com.intellij.psi.PsiElement
-import org.jetbrains.kotlin.diagnostics.Diagnostic
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
-import org.jetbrains.kotlin.diagnostics.Errors
-import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtVisitorVoid
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.kotlin.analysis.ComposeErrors
-import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingUtil
-
-/**
- * This class adds a red highlight to tag names in KTX elements that are unresolved. This could be done
- * by default by just using the `UNRESOLVED_REFERENCE` diagnostic, but we have decided not to use that
- * diagnostic in this case because the "Add Import" quick fix that it causes breaks in the case of
- * Open/Close KTX tags. Instead, we use our own `UNRESOLVED_TAG` diagnostic, which we register our own
- * quick fix for, and we also add the red highlight here.
- */
-class ComposeUnresolvedTagAnnotator : Annotator {
- override fun annotate(element: PsiElement, holder: AnnotationHolder) {
- val ktFile = element.containingFile as? KtFile ?: return
-
- if (!KotlinHighlightingUtil.shouldHighlight(ktFile)) return
-
- val analysisResult = ktFile.analyzeWithAllCompilerChecks()
- if (analysisResult.isError()) {
- throw ProcessCanceledException(analysisResult.error)
- }
-
- val bindingContext = analysisResult.bindingContext
- val diagnostics = bindingContext.diagnostics
-
- element.accept(object : KtVisitorVoid() {
- override fun visitKtxElement(element: KtxElement) {
- element.simpleTagName?.let {
- if (diagnostics.forElement(it).any {
- it.realDiagnosticFactory == ComposeErrors.UNRESOLVED_TAG
- }) {
- holder.createErrorAnnotation(it, null).apply {
- highlightType = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL
- }
- }
- }
- element.simpleClosingTagName?.let {
- if (diagnostics.forElement(it).any {
- it.realDiagnosticFactory == ComposeErrors.UNRESOLVED_TAG
- }) {
- holder.createErrorAnnotation(it, null).apply {
- highlightType = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL
- }
- }
- }
- }
- })
- }
-}
-
-private val Diagnostic.realDiagnosticFactory: DiagnosticFactory<*>
- get() =
- when (factory) {
- Errors.PLUGIN_ERROR -> Errors.PLUGIN_ERROR.cast(this).a.factory
- Errors.PLUGIN_WARNING -> Errors.PLUGIN_WARNING.cast(this).a.factory
- Errors.PLUGIN_INFO -> Errors.PLUGIN_INFO.cast(this).a.factory
- else -> factory
- }
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxAttributeAutoParensHandler.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxAttributeAutoParensHandler.kt
deleted file mode 100644
index aa8957e..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxAttributeAutoParensHandler.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-package androidx.compose.plugins.idea.editor
-
-import com.intellij.codeInsight.editorActions.TypedHandlerDelegate
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.EditorModificationUtil
-import com.intellij.openapi.editor.ex.EditorEx
-import com.intellij.openapi.fileTypes.FileType
-import com.intellij.openapi.project.Project
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.psi.PsiErrorElement
-import com.intellij.psi.PsiFile
-import com.intellij.psi.tree.TokenSet
-import org.jetbrains.kotlin.KtNodeTypes
-import org.jetbrains.kotlin.idea.completion.handlers.isCharAt
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.psi.KtxAttribute
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
-import org.jetbrains.kotlin.psi.psiUtil.startOffset
-import androidx.compose.plugins.idea.parentOfType
-
-class KtxAttributeAutoParensHandler : TypedHandlerDelegate() {
-
- private fun EditorEx.prevCharIs(char: Char): Boolean {
- return document.charsSequence.isCharAt(caretModel.offset - 2, char)
- }
-
- private fun wrapWithParensIfAtEndOfAttribute(
- file: PsiFile,
- editor: EditorEx,
- project: Project
- ): Boolean {
- if (editor.caretModel.caretCount > 1) return false
-
- PsiDocumentManager.getInstance(project).commitDocument(editor.document)
-
- val el = file.findElementAt(editor.caretModel.offset - 1)
- val errorElement = el?.parent as? PsiErrorElement ?: return false
- if (errorElement.parent !is KtxElement) return false
- val attr =
- errorElement.getPrevSiblingIgnoringWhitespaceAndComments(withItself = false) as?
- KtxAttribute ?: return false
- val valueExpr = attr.value ?: return false
-
- // This can happen if someone has started a parenthesized element with an open paren but hasn't typed the close paren yet. If
- // this is the case, insert only the close paren
- if (valueExpr.node.elementType ==
- KtNodeTypes.PARENTHESIZED && valueExpr.lastChild is PsiErrorElement) {
- editor.document.insertString(editor.caretModel.offset, ")")
- } else {
- editor.document.insertString(valueExpr.startOffset, "(")
- editor.document.insertString(editor.caretModel.offset, ")")
- }
- return true
- }
-
- private fun wrapValueExpressionIfNotTagCloseForChar(
- c: Char,
- file: PsiFile,
- editor: EditorEx
- ): Boolean {
-
- if (editor.caretModel.caretCount > 1) return false
-
- val el = file.findElementAt(editor.caretModel.offset - 1) ?: return false
- val ktxElement = el.parentOfType<KtxElement>() ?: return false
-
- val bracketElements = ktxElement.node.getChildren(BRACKET_SET)
- val gtCount = bracketElements.count { it.elementType == KtTokens.GT }
- val divCount = bracketElements.count { it.elementType == KtTokens.DIV }
-
- // there is no GT so the tag is unclosed, we definitely don't want to do anything here...
- if (gtCount == 0) return false
-
- val offset = editor.caretModel.offset
-
- // if we are typing after the first close bracket, we don't want to do anything
- if (bracketElements.first { it.elementType == KtTokens.GT }.startOffset < offset)
- return false
-
- when (c) {
- '>' -> {
- if (gtCount <= 1 && !(gtCount == 1 && divCount == 1)) return false
- }
- '/' -> {
- if (divCount == 0) return false
- }
- else -> return false
- }
-
- // if we made it here, we can attempt to wrap the value expression we are in...
-
- val attr = el.getPrevSiblingIgnoringWhitespaceAndComments(withItself = true) as?
- KtxAttribute ?: return false
- val valueExpr = attr.value ?: return false
-
- // This can happen if someone has started a parenthesized element with an open paren but hasn't typed the close paren yet. If
- // this is the case, insert only the close paren
- if (valueExpr.node.elementType == KtNodeTypes.PARENTHESIZED &&
- valueExpr.lastChild is PsiErrorElement) {
- editor.document.insertString(editor.caretModel.offset, "$c)")
- EditorModificationUtil.moveCaretRelatively(editor, 1)
- } else {
- editor.document.insertString(valueExpr.startOffset, "(")
- editor.document.insertString(editor.caretModel.offset, "$c)")
- EditorModificationUtil.moveCaretRelatively(editor, 1)
- }
-
- return true
- }
-
- override fun beforeCharTyped(
- c: Char,
- project: Project,
- editor: Editor,
- file: PsiFile,
- fileType: FileType
- ): Result {
- val editorEx = editor as EditorEx
- when (c) {
- '>' -> {
- val didWrap = wrapValueExpressionIfNotTagCloseForChar(c, file, editorEx)
- if (didWrap) return Result.STOP
- }
- '/' -> {
- val didWrap = wrapValueExpressionIfNotTagCloseForChar(c, file, editorEx)
- if (didWrap) return Result.STOP
- }
- }
- return Result.CONTINUE
- }
-
- override fun charTyped(
- c: Char,
- project: Project,
- editor: Editor,
- file: PsiFile
- ): Result {
- val editorEx = editor as EditorEx
- when (c) {
- // equality
- '=' -> {
- if (editorEx.prevCharIs('=') || editorEx.prevCharIs('!')) {
- val didWrap = wrapWithParensIfAtEndOfAttribute(file, editorEx, project)
- if (didWrap) return Result.STOP
- }
- }
- // single char binary operators, excluding '/' and '>'
- '+', '-', '%', '|', '&', '*', '<' -> {
- val didWrap = wrapWithParensIfAtEndOfAttribute(file, editorEx, project)
- if (didWrap) return Result.STOP
- }
- // nullary ?:
- ':' -> {
- if (editorEx.prevCharIs('?')) {
- val didWrap = wrapWithParensIfAtEndOfAttribute(file, editorEx, project)
- if (didWrap) return Result.STOP
- }
- }
- }
- return Result.CONTINUE
- }
-
- companion object {
- private val BRACKET_SET = TokenSet.create(KtTokens.DIV, KtTokens.GT)
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEditorOptions.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEditorOptions.kt
deleted file mode 100644
index 47e8703..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEditorOptions.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2018 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.plugins.idea.editor
-
-import com.intellij.openapi.components.PersistentStateComponent
-import com.intellij.openapi.components.ServiceManager
-import com.intellij.openapi.components.State
-import com.intellij.openapi.components.Storage
-import com.intellij.util.xmlb.XmlSerializerUtil
-
-@State(name = "KtxEditorOptions", storages = [Storage(file = "\$APP_CONFIG$/ktx.xml")])
-class KtxEditorOptions : PersistentStateComponent<KtxEditorOptions> {
- companion object {
- fun getInstance(): KtxEditorOptions {
- return ServiceManager.getService(KtxEditorOptions::class.java)
- }
- }
-
- var enableXmlToKtxConversion = true
- var enableAddComposableAnnotation = true
- var donTShowKtxConversionDialog = false
- var donTShowAddComposableAnnotationDialog = false
-
- override fun getState(): KtxEditorOptions? {
- return this
- }
-
- override fun loadState(state: KtxEditorOptions) {
- XmlSerializerUtil.copyBean(state, this)
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEditorOptionsConfigurable.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEditorOptionsConfigurable.kt
deleted file mode 100644
index 8f5e45a..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEditorOptionsConfigurable.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018 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.plugins.idea.editor
-
-import com.intellij.openapi.options.BeanConfigurable
-import com.intellij.openapi.options.UnnamedConfigurable
-
-class KtxEditorOptionsConfigurable : BeanConfigurable<KtxEditorOptions>(
- KtxEditorOptions.getInstance()
-), UnnamedConfigurable {
- init {
- val instance = KtxEditorOptions.getInstance()
- checkBox("Convert pasted XML code to Kotlin KTX",
- { instance.enableXmlToKtxConversion }, { instance.enableXmlToKtxConversion = it }
- )
- checkBox("Don't show XML to Kotlin KTX conversion dialog on paste",
- { instance.donTShowKtxConversionDialog },
- { instance.donTShowKtxConversionDialog = it })
- checkBox("Automatically add @Composable annotation and import androidx.compose.* when " +
- "converting XML to KTX code",
- {
- instance.enableAddComposableAnnotation &&
- instance.donTShowAddComposableAnnotationDialog
- },
- { shouldAutomaticallyAddAnnotation ->
- if (shouldAutomaticallyAddAnnotation) {
- instance.enableAddComposableAnnotation = true
- instance.donTShowAddComposableAnnotationDialog = true
- } else {
- // Unchecking the box means that on next paste, the dialog will be shown and the user will be able to cancel it and
- // check "Don't show this dialog next time".
- instance.enableAddComposableAnnotation = true
- instance.donTShowAddComposableAnnotationDialog = false
- }
- }
- )
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEnterHandler.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEnterHandler.kt
deleted file mode 100644
index ff28491..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxEnterHandler.kt
+++ /dev/null
@@ -1,289 +0,0 @@
-package androidx.compose.plugins.idea.editor
-
-import com.intellij.codeInsight.AutoPopupController
-import com.intellij.codeInsight.CodeInsightSettings
-import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate
-import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.EditorModificationUtil
-import com.intellij.openapi.editor.actionSystem.EditorActionHandler
-import com.intellij.openapi.util.Ref
-import com.intellij.openapi.util.TextRange
-import com.intellij.psi.PsiBlockStatement
-import com.intellij.psi.PsiComment
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.psi.PsiFile
-import com.intellij.psi.PsiWhiteSpace
-import com.intellij.psi.codeStyle.CodeStyleManager
-import com.intellij.util.IncorrectOperationException
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.KtxLambdaExpression
-import org.jetbrains.kotlin.psi.psiUtil.children
-import org.jetbrains.kotlin.psi.psiUtil.endOffset
-import org.jetbrains.kotlin.psi.psiUtil.leaves
-import org.jetbrains.kotlin.psi.psiUtil.startOffset
-import androidx.compose.plugins.idea.getNextLeafIgnoringWhitespace
-import androidx.compose.plugins.idea.getPrevLeafIgnoringWhitespace
-import androidx.compose.plugins.idea.parentOfType
-
-class KtxEnterHandler : EnterHandlerDelegateAdapter() {
- companion object {
- private val LOG = Logger.getInstance(KtxEnterHandler::class.java)
- }
-
- override fun preprocessEnter(
- file: PsiFile,
- editor: Editor,
- caretOffsetRef: Ref<Int>,
- caretAdvance: Ref<Int>,
- dataContext: DataContext,
- originalHandler: EditorActionHandler?
- ): EnterHandlerDelegate.Result? {
-
- val (
- smartIndent,
- insertClosingTag,
- indentClosingTag,
- element
- ) = getIndentMeta(file, editor, caretOffsetRef)
- ?: return EnterHandlerDelegate.Result.Continue
-
- if (insertClosingTag) {
- element?.let { ktxTag ->
- val openTag = ktxTag.simpleTagName ?: ktxTag.qualifiedTagName
- ?: error("malformed tag")
- val tagText = editor.document.charsSequence.subSequence(
- openTag.startOffset,
- openTag.endOffset
- )
- EditorModificationUtil.insertStringAtCaret(
- editor,
- "</$tagText>",
- true,
- false
- )
- }
- }
-
- if (indentClosingTag) {
- element?.let { ktxTag ->
- val closingTag =
- ktxTag.node.children().lastOrNull {
- child -> child.elementType == KtTokens.LT
- } ?: error("expected to find")
- editor.document.insertString(
- closingTag.startOffset,
- "\n"
- )
- PsiDocumentManager.getInstance(file.project).commitDocument(editor.document)
- try {
- CodeStyleManager.getInstance(file.project)!!.adjustLineIndent(
- file,
- closingTag.startOffset
- )
- } catch (e: IncorrectOperationException) {
- }
- }
- }
-
- if (smartIndent) {
- originalHandler?.execute(editor, editor.caretModel.currentCaret, dataContext)
- PsiDocumentManager.getInstance(file.project).commitDocument(editor.document)
- try {
- CodeStyleManager.getInstance(
- file.project
- )!!.adjustLineIndent(file, editor.caretModel.offset)
- } catch (e: IncorrectOperationException) {
- LOG.error(e)
- }
- }
-
- val autocomplete = shouldPopupAutocomplete(file, caretOffsetRef)
-
- if (autocomplete) {
- AutoPopupController.getInstance(editor.project)?.scheduleAutoPopup(editor)
- }
-
- return if (smartIndent) EnterHandlerDelegate.Result.DefaultForceIndent
- else EnterHandlerDelegate.Result.Continue
- }
-
- private data class Meta(
- var smartIndent: Boolean = false,
- var insertClosingTag: Boolean = false,
- var indentClosingTag: Boolean = false,
- var element: KtxElement? = null
- )
-
- /**
- * We want to do a few "smart" enter handlers for KTX-specific edits.
- *
- * 1. Last attribute:
- *
- * <Foo{{CURSOR}}></Foo>
- *
- * should transform into:
- *
- * <Foo
- * {{CURSOR}}
- * ></Foo>
- *
- * Note that we can also do this transformation when it is a self-closing tag
- *
- * 2. Between Open/Closing:
- *
- * <Foo>{{CURSOR}}</Foo>
- *
- * should transform into:
- *
- * <Foo>
- * {{CURSOR}}
- * </Foo>
- *
- * Note that in this transformation, we can also have a parameter list to the left side of the cursor.
- */
- private fun getIndentMeta(
- file: PsiFile,
- editor: Editor,
- caretOffsetRef: Ref<Int>
- ): Meta? {
- if (file !is KtFile) return null
-
- // honor normal kotlin settings for smart enter
- if (!CodeInsightSettings.getInstance()!!.SMART_INDENT_ON_ENTER) return null
-
- val document = editor.document
- val text = document.charsSequence
- val caretOffset = caretOffsetRef.get()!!.toInt()
-
- if (caretOffset !in 0..text.length) return null
-
- val elementAt = file.findElementAt(caretOffset) ?: return null
-
- val elementBefore = elementAt.getPrevLeafIgnoringWhitespace(includeSelf = false)
- ?: return null
- val elementAfter = elementAt.getNextLeafIgnoringWhitespace(includeSelf = true)
- ?: return null
- if (elementBefore.endOffset > caretOffset) return null
- if (caretOffset > elementAfter.startOffset) return null
-
- val returnsBetweenLeftAndCaret =
- document.getText(TextRange(elementBefore.endOffset, caretOffset)).contains('\n')
- val returnsBetweenRightAndCaret =
- document.getText(TextRange(caretOffset, elementAfter.startOffset)).contains('\n')
-
- val beforeParent = elementBefore.parent
- val afterParent = elementAfter.parent
-
- val isAfterGt = elementBefore.node?.elementType == KtTokens.GT
- val isAfterArrow = elementBefore.node?.elementType == KtTokens.ARROW
- val isBeforeGt = elementAfter.node?.elementType == KtTokens.GT
- val isBeforeDivAndGt =
- elementAfter.node?.elementType == KtTokens.DIV &&
- elementAfter.nextSibling?.node?.elementType == KtTokens.GT
- val isBeforeLtAndDiv =
- elementAfter.node?.elementType == KtTokens.LT &&
- elementAfter.nextSibling?.node?.elementType == KtTokens.DIV
- val afterIsSameKtxTag = afterParent is KtxElement &&
- beforeParent?.parentOfType<KtxElement>() == afterParent
-
- val ktxElement = if (beforeParent is KtxElement)
- beforeParent
- else if (isAfterArrow && beforeParent?.parent is KtxLambdaExpression)
- beforeParent.parent.parent as? KtxElement
- else null
-
- val isUnclosed = ktxElement?.let {
- it.node.children().none { child -> child.elementType == KtTokens.DIV }
- } ?: false
- val isSelfClosing = !isUnclosed && ktxElement?.let {
- it.simpleClosingTagName ?: it.qualifiedClosingTagName
- } == null
- val isBetweenOpenAndCloseOfKtxElement = ktxElement?.let {
- it.simpleClosingTagName ?: it.qualifiedClosingTagName
- }?.let {
- val closeTagStart = it.startOffset - 2
- caretOffset <= closeTagStart
- } ?: false
-
- val closingTagLt = ktxElement?.let {
- it.node.children().lastOrNull { child -> child.elementType == KtTokens.LT }
- }
- val newLineExistsBeforeClosingTag = closingTagLt?.let { lt ->
- lt
- .leaves(forward = false)
- .takeWhile { leaf ->
- when (leaf) {
- is PsiWhiteSpace -> true
- is PsiComment -> true
- is PsiBlockStatement -> true
- else -> false
- }
- }
- .any { it.textContains('\n') }
- } ?: false
-
- return Meta(
- smartIndent = when {
- returnsBetweenLeftAndCaret || (returnsBetweenRightAndCaret && afterIsSameKtxTag) ->
- false
- isBeforeGt && afterIsSameKtxTag -> true // // <Foo{{CURSOR}}></Foo>
- isBeforeDivAndGt && afterIsSameKtxTag -> true // <Foo{{CURSOR}}/>
- isAfterGt && isBeforeLtAndDiv && afterIsSameKtxTag ->
- true // <Foo>{{CURSOR}}</Foo>
- isAfterArrow && isBeforeLtAndDiv && afterIsSameKtxTag ->
- true // <Foo> x ->{{CURSOR}}</Foo>
-
- // <Foo>{{CURSOR}}<Bar /></Foo>
- (isAfterGt || isAfterArrow) && !isUnclosed && !afterIsSameKtxTag &&
- !isSelfClosing && isBetweenOpenAndCloseOfKtxElement &&
- !returnsBetweenRightAndCaret -> true
- // <Foo>{{CURSOR}}<Bar />
- (isAfterGt || isAfterArrow) && isUnclosed -> true
- else -> false
- },
- insertClosingTag = when {
- isSelfClosing -> false
- (isAfterGt || isAfterArrow) && isUnclosed -> true
- else -> false
- },
- indentClosingTag = when {
- isSelfClosing -> false
- !isUnclosed && !newLineExistsBeforeClosingTag &&
- !afterIsSameKtxTag && isBetweenOpenAndCloseOfKtxElement -> true
- else -> false
- },
- element = ktxElement
- )
- }
-
- private fun shouldPopupAutocomplete(
- file: PsiFile,
- caretOffsetRef: Ref<Int>
- ): Boolean {
- if (file !is KtFile) return false
- val caretOffset = caretOffsetRef.get()!!.toInt()
-
- val cursorEl = file.findElementAt(caretOffset) as? PsiWhiteSpace ?: return false
-
- val ktxElement = cursorEl.parent as? KtxElement ?: return false
-
- val firstDiv = ktxElement.node.findChildByType(KtTokens.DIV)
- val firstGt = ktxElement.node.findChildByType(KtTokens.GT)
-
- if (firstDiv == null && firstGt == null) return false
-
- val endOfOpenTag = Math.min(firstDiv?.startOffset ?: Int.MAX_VALUE, firstGt?.startOffset
- ?: Int.MAX_VALUE)
-
- if (caretOffset > endOfOpenTag) return false
-
- // TODO(lmr): if element to the right is not white space, false???
-
- return true
- }
-}
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxReferenceContributor.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxReferenceContributor.kt
deleted file mode 100644
index e843e79..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxReferenceContributor.kt
+++ /dev/null
@@ -1,322 +0,0 @@
-package androidx.compose.plugins.idea.editor
-
-import com.intellij.openapi.util.TextRange
-import com.intellij.patterns.PlatformPatterns
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiReference
-import com.intellij.psi.PsiReferenceProvider
-import com.intellij.psi.PsiReferenceRegistrar
-import com.intellij.util.ProcessingContext
-import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.idea.references.KtMultiReference
-import org.jetbrains.kotlin.idea.references.KtSimpleReference
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtSimpleNameExpression
-import org.jetbrains.kotlin.psi.KtxAttribute
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.kotlin.KtxNameConventions
-import androidx.compose.plugins.kotlin.ComposeUtils
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import androidx.compose.plugins.kotlin.EmitCallNode
-import androidx.compose.plugins.kotlin.EmitOrCallNode
-import androidx.compose.plugins.kotlin.ErrorNode
-import androidx.compose.plugins.kotlin.MemoizedCallNode
-import androidx.compose.plugins.kotlin.NonMemoizedCallNode
-import com.intellij.psi.PsiReferenceContributor
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
-import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
-import org.jetbrains.kotlin.util.OperatorNameConventions
-
-class KtxReferenceContributor : PsiReferenceContributor() {
- override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
- // We want to search for references on the closest ancestor of the element itself that we are putting a
- // reference on. This ends up being KtSimpleNameExpression for most things, which this provider deals with.
- // The reason we need to do it this way is because the reference handling is pretty broken in Kotlin, and the
- // "element" that the IDE ends up using to do the highlighting and surfacing of the references is always the
- // *last* element that the algorithm finds as it traverses up the spine of the tree. The way we are
- registrar.registerReferenceProvider(
- PlatformPatterns.psiElement(KtSimpleNameExpression::class.java),
- KtxReferenceProvider
- )
- // We are hanging references on the LT bracket of the KTX element itself. To do this, we need to target the
- // KTX element overall, though I think this would work even better if we could target the token directly, but I
- // can't figure out how to do that and it doesn't look like that's done anywhere else.
- registrar.registerReferenceProvider(
- PlatformPatterns.psiElement(KtxElement::class.java),
- KtxSyntaxReferenceProvider
- )
- }
-}
-
-object KtxSyntaxReferenceProvider : PsiReferenceProvider() {
- override fun getReferencesByElement(
- element: PsiElement,
- context: ProcessingContext
- ): Array<out PsiReference> {
- return when (element) {
- is KtxElement -> arrayOf(KtxSyntaxReference(element))
- else -> PsiReference.EMPTY_ARRAY
- }
- }
-}
-
-/**
- * Because we are targeting SimpleNameExpression instead of KtxElement (because things will conflict otherwise), we have to run a
- * bit of logic to ensure that the simple name expression we have is one that we want to target in the KTX tag.
- *
- * The reason we have to do this runaround logic is because the way references currently work is that it calls `getReferencesByElement()`
- * as it travels up the spine of the tree from the leaf element that the cursor is on top of. When it is done, the IDE will highlight
- * and surface the references on the element that is shown by the *last* reference in the list that it finds as it traverses. This means
- * that using the `KtxElement` as the root is too high because it will be the "last" element for everything that gets run below it,
- * including nested KTX tags where this ends up getting really bad. As a result, our strategy is to have `getReferencesByElement` run
- * on as small of a node as practical, even if it needs to look at it's parents and has the `ref.element` of the reference it returns
- * be on a bigger element.
- *
- * This combination of things seems to work, but overall the reference system in IntelliJ/Kotlin is *really* fragile, so be really
- * careful when modifying this code.
- */
-object KtxReferenceProvider : PsiReferenceProvider() {
- override fun getReferencesByElement(
- element: PsiElement,
- context: ProcessingContext
- ): Array<out PsiReference> {
- if (element !is KtSimpleNameExpression) return PsiReference.EMPTY_ARRAY
- val parent = element.parent
- when (parent) {
- is KtxElement -> {
- if (element == parent.simpleTagName) {
- // it is a simple open tag. include the syntax reference
- return arrayOf<PsiReference>(
- KtxSyntaxReferenceForImports(parent),
- KtxReference(parent, element)
- )
- } else if (element == parent.simpleClosingTagName) {
- // it is a simple closing tag
- return arrayOf<PsiReference>(
- KtxReference(
- parent,
- element
- )
- )
- }
- }
- is KtxAttribute -> {
- if (parent.key == element) {
- // attribute key
- return arrayOf<PsiReference>(
- AttributeReference(
- parent
- )
- )
- }
- }
- is KtDotQualifiedExpression -> {
- val superparent = parent.parent
- if (superparent is KtxElement) {
- val openFirstPart =
- firstNameForElement(superparent)
- if (openFirstPart == element) {
- // the first part of the open tag. we add a syntax reference here since it needs to have no receiver
- // in order to get used in the "unused imports" algorithm
- return arrayOf<PsiReference>(
- KtxSyntaxReferenceForImports(
- superparent
- )
- )
- } else {
- val openLastPart =
- lastNameForElement(superparent)
- val closingLastPart =
- superparent.qualifiedClosingTagName?.lastSimpleNameExpression()
- if (closingLastPart == element || openLastPart == element) {
- // if it's the last part of a dot qualified expression, on either the closing or open tag,
- // we go ahead and add our ktx reference
- return arrayOf<PsiReference>(
- KtxReference(
- superparent,
- element
- )
- )
- }
- }
- }
- }
- }
- return PsiReference.EMPTY_ARRAY
- }
-}
-
-open class KtxSyntaxReference(
- val ktxElement: KtxElement
-) : KtMultiReference<KtElement>(ktxElement) {
-
- override fun getRangeInElement() = TextRange(0, 1)
-
- override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
- val resolvedElementCall = context[ComposeWritableSlices.RESOLVED_KTX_CALL, ktxElement]
- ?: return emptyList()
-
- val results = mutableListOf<DeclarationDescriptor>()
-
- resolvedElementCall.getComposerCall?.resultingDescriptor?.let { results.add(it) }
-
- var node: EmitOrCallNode? = resolvedElementCall.emitOrCall
-
- while (node != null) {
- node = when (node) {
- is EmitCallNode -> {
- node.memoize.composerCall?.resultingDescriptor?.let { results.add(it) }
- null
- }
- is MemoizedCallNode -> {
- node.memoize.composerCall?.resultingDescriptor?.let { results.add(it) }
- node.call
- }
- is NonMemoizedCallNode -> node.nextCall
- is ErrorNode -> null
- }
- }
-
- return results
- }
-
- override val resolvesByNames: Collection<Name>
- get() = COMPOSER_NAMES
-}
-
-class KtxSyntaxReferenceForImports(ktxElement: KtxElement) : KtxSyntaxReference(ktxElement) {
- // NOTE(lmr): It's important that this return a KtNameReferenceElement or else this reference will not get used in the
- // Unused Import detection! Moreover, we want it to return a name expression that does *not* have a receiver to the left of
- // it, or else it will also get excluded. In this case we just return the left-most SimpleNameExpression of the KtxElement.
- override fun getElement() = firstNameForElement(ktxElement)
-
- override fun getRangeInElement() = TextRange.EMPTY_RANGE
-}
-
-/**
- * This is needed because sometimes attributes of KTX tags target a setter function with a different name than
- * the "attribute" name, which means that the extension setter won't show up as properly "used". This fixes that.
- *
- * In the long-term, I think we should not need this, since we should probably just target properties in kotlin and not setters
- */
-class AttributeReference(
- private var attribute: KtxAttribute
-) : KtSimpleReference<KtSimpleNameExpression>(attribute.key) {
-
- override fun getRangeInElement() = TextRange(0, attribute.key.textLength)
-
- override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
- return context[ComposeWritableSlices.ATTRIBUTE_KEY_REFERENCE_TARGET, attribute.key]
- ?: listOfNotNull(context[BindingContext.REFERENCE_TARGET, attribute.key])
- }
-
- override val resolvesByNames: Collection<Name>
- get() = attribute.key.getReferencedNameAsName().let {
- listOf(
- it,
- Name.identifier(ComposeUtils.setterMethodFromPropertyName(it.identifier))
- )
- }
-}
-
-class KtxReference(
- val ktxElement: KtxElement,
- val nameElement: KtSimpleNameExpression
-) : KtMultiReference<KtElement>(nameElement) {
-
- override fun getRangeInElement() = TextRange(0, nameElement.textLength)
-
- private fun primaryTargetOf(resolvedCall: ResolvedCall<*>?): List<DeclarationDescriptor> {
- return when (resolvedCall) {
- is VariableAsFunctionResolvedCall ->
- listOf(resolvedCall.variableCall.resultingDescriptor)
- null -> emptyList()
- else -> listOf(resolvedCall.resultingDescriptor)
- }
- }
-
- private fun primaryTargetsOf(node: EmitOrCallNode?): List<DeclarationDescriptor> {
- return when (node) {
- is EmitCallNode -> primaryTargetOf(node.memoize.ctorCall)
- is MemoizedCallNode ->
- primaryTargetOf(node.memoize.ctorCall) + primaryTargetsOf(node.call)
- is NonMemoizedCallNode ->
- primaryTargetOf(node.resolvedCall) + primaryTargetsOf(node.nextCall)
- is ErrorNode -> emptyList()
- null -> emptyList()
- }
- }
-
- private fun filterResultsIfInSameContainer(
- targets: List<DeclarationDescriptor>
- ): List<DeclarationDescriptor> {
- if (targets.size <= 1) return targets
- var node = targets.first()
- val results = mutableListOf(node)
- for (target in targets.drop(1)) {
- val prev = node
- node = target
-
- val container = target.containingDeclaration
- if (container == prev) continue
- if (prev is ClassConstructorDescriptor && prev.containingDeclaration == container)
- continue
-
- results.add(target)
- }
- return results
- }
-
- override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
- val resolvedElementCall = context[ComposeWritableSlices.RESOLVED_KTX_CALL, ktxElement]
- ?: return emptyList()
- val results = primaryTargetsOf(resolvedElementCall.emitOrCall)
-
- return filterResultsIfInSameContainer(results)
- }
-
- override val resolvesByNames: Collection<Name>
- get() = listOf(
- nameElement.getReferencedNameAsName(),
- OperatorNameConventions.INVOKE
- )
-}
-
-private val COMPOSER_NAMES = listOf(
- KtxNameConventions.COMPOSER,
- KtxNameConventions.EMIT,
- KtxNameConventions.CALL
-)
-
-private fun firstNameForElement(element: KtxElement): KtSimpleNameExpression {
- return element.simpleTagName
- ?: element.qualifiedTagName?.firstSimpleNameExpression()
- ?: error("Expected a simple name expression")
-}
-
-private fun KtExpression.firstSimpleNameExpression(): KtSimpleNameExpression? = when (this) {
- is KtSimpleNameExpression -> this
- is KtDotQualifiedExpression ->
- receiverExpression.firstSimpleNameExpression()
- ?: selectorExpression?.firstSimpleNameExpression()
- else -> null
-}
-
-private fun lastNameForElement(element: KtxElement): KtSimpleNameExpression {
- return element.simpleTagName
- ?: element.qualifiedTagName?.lastSimpleNameExpression()
- ?: error("Expected a simple name expression")
-}
-
-private fun KtExpression.lastSimpleNameExpression(): KtSimpleNameExpression? = when (this) {
- is KtSimpleNameExpression -> this
- is KtDotQualifiedExpression ->
- selectorExpression?.lastSimpleNameExpression()
- ?: receiverExpression.lastSimpleNameExpression()
- else -> null
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxTagNameSynchronizer.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxTagNameSynchronizer.kt
deleted file mode 100644
index 4da2cff..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxTagNameSynchronizer.kt
+++ /dev/null
@@ -1,334 +0,0 @@
-package androidx.compose.plugins.idea.editor
-
-import com.intellij.codeInsight.editorActions.XmlTagNameSynchronizer
-import com.intellij.codeInsight.lookup.LookupManager
-import com.intellij.codeInsight.lookup.impl.LookupImpl
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.command.CommandEvent
-import com.intellij.openapi.command.CommandListener
-import com.intellij.openapi.command.CommandProcessor
-import com.intellij.openapi.command.undo.UndoManager
-import com.intellij.openapi.diagnostic.Attachment
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.Caret
-import com.intellij.openapi.editor.Document
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.EditorFactory
-import com.intellij.openapi.editor.RangeMarker
-import com.intellij.openapi.editor.event.DocumentEvent
-import com.intellij.openapi.editor.event.DocumentListener
-import com.intellij.openapi.editor.event.EditorFactoryEvent
-import com.intellij.openapi.editor.event.EditorFactoryListener
-import com.intellij.openapi.editor.ex.DocumentEx
-import com.intellij.openapi.editor.impl.EditorImpl
-import com.intellij.openapi.fileEditor.FileDocumentManager
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.Couple
-import com.intellij.openapi.util.Key
-import com.intellij.openapi.util.TextRange
-import com.intellij.pom.core.impl.PomModelImpl
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiFile
-import com.intellij.psi.PsiManager
-import com.intellij.psi.impl.PsiDocumentManagerBase
-import com.intellij.util.SmartList
-import org.jetbrains.kotlin.idea.KotlinLanguage
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.psiUtil.endOffset
-import org.jetbrains.kotlin.psi.psiUtil.startOffset
-import androidx.compose.plugins.idea.parentOfType
-
-private val LOG = Logger.getInstance(XmlTagNameSynchronizer::class.java)
-private val SYNCHRONIZER_KEY = Key.create<TagNameSynchronizer>("ktx.tag_name_synchronizer")
-private val Document.synchronizers get() =
- EditorFactory.getInstance().getEditors(this).mapNotNull {
- it.getUserData(SYNCHRONIZER_KEY)
- }
-
-/**
- * Much of this file was modeled after the XML tag name synchronizer, which can be found here:
- *
- * https://github.com/JetBrains/intellij-community/blob/6eee222d1cda938673813a809ee5a8550baed005/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java
- */
-class KtxTagNameSynchronizer(
- editorFactory: EditorFactory,
- private val manager: FileDocumentManager
-) : CommandListener {
-
- init {
- editorFactory.addEditorFactoryListener(object : EditorFactoryListener {
- override fun editorReleased(e: EditorFactoryEvent) = uninstall(e.editor)
- override fun editorCreated(e: EditorFactoryEvent) = install(e.editor)
- }, ApplicationManager.getApplication())
-
- @Suppress("DEPRECATION")
- CommandProcessor.getInstance().addCommandListener(this)
- }
-
- private fun install(editor: Editor) {
- val project = editor.project ?: return
-
- val document = editor.document
- val file = manager.getFile(document) ?: return
- if (!file.isValid) return
- val psiFile = PsiManager.getInstance(project).findFile(file) ?: return
-
- if (psiFile.language == KotlinLanguage.INSTANCE) {
- TagNameSynchronizer(editor, project)
- }
- }
-
- private fun uninstall(@Suppress("UNUSED_PARAMETER") editor: Editor) {
- // Nothing to do here since we pass in itself as the disposable
- }
-
- override fun beforeCommandFinished(event: CommandEvent) {
- event.document?.synchronizers?.forEach { it.beforeCommandFinished() }
- }
-}
-
-private class TagNameSynchronizer(val editor: Editor, val project: Project) : DocumentListener {
-
- companion object {
- private fun Char.isValidTagNameChar() =
- isLetter() || isDigit() || this == '.' || this == '_'
- }
-
- private enum class State {
- INITIAL,
- TRACKING,
- APPLYING
- }
-
- private var manager = PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase
- private var state = State.INITIAL
- private val markers = SmartList<Couple<RangeMarker>>()
-
- init {
- editor.document.addDocumentListener(this, (editor as EditorImpl).disposable)
- editor.putUserData(SYNCHRONIZER_KEY, this)
- }
-
- override fun beforeDocumentChange(event: DocumentEvent) {
-
- val document = event.document
- if (
- state == State.APPLYING ||
- UndoManager.getInstance(editor.project!!).isUndoInProgress ||
- !PomModelImpl.isAllowPsiModification() ||
- (document as DocumentEx).isInBulkUpdate
- ) {
- return
- }
-
- val offset = event.offset
- val oldLength = event.oldLength
- val fragment = event.newFragment
- val newLength = event.newLength
-
- for (i in 0 until newLength) {
- if (!fragment[i].isValidTagNameChar()) {
- clearMarkers()
- return
- }
- }
-
- if (state == State.INITIAL) {
- val file = manager.getPsiFile(document)
- if (file == null || manager.synchronizer.isInSynchronization(document)) return
-
- val leaders = SmartList<RangeMarker>()
- for (caret in editor.caretModel.allCarets) {
- val leader = createTagNameMarker(caret)
- if (leader == null) {
- for (marker in leaders) {
- marker.dispose()
- }
- return
- }
- leader.isGreedyToLeft = true
- leader.isGreedyToRight = true
- leaders.add(leader)
- }
- if (leaders.isEmpty()) return
-
- if (manager.isUncommited(document)) {
- manager.commitDocument(document)
- }
-
- for (leader in leaders) {
- val support = findSupport(leader, file, document)
- if (support == null) {
- clearMarkers()
- return
- }
- support.isGreedyToLeft = true
- support.isGreedyToRight = true
- markers.add(Couple.of(leader, support))
- }
-
- if (!fitsInMarker(offset, oldLength)) {
- clearMarkers()
- return
- }
-
- state = State.TRACKING
- }
-
- if (markers.isEmpty()) return
-
- val fitsInMarker = fitsInMarker(offset, oldLength)
- if (!fitsInMarker || markers.size != editor.caretModel.caretCount) {
- clearMarkers()
- beforeDocumentChange(event)
- }
- }
-
- fun beforeCommandFinished() {
- if (markers.isEmpty()) return
-
- state = State.APPLYING
-
- val document = editor.document
- val apply = Runnable {
- for (couple in markers) {
- val leader = couple.first
- val support = couple.second
- if (document.textLength < leader.endOffset) {
- return@Runnable
- }
- val name = document.getText(TextRange(leader.startOffset, leader.endOffset))
- if (document.textLength >= support.endOffset && name != document.getText(
- TextRange(
- support.startOffset,
- support.endOffset
- )
- )
- ) {
- document.replaceString(support.startOffset, support.endOffset, name)
- }
- }
- }
- ApplicationManager.getApplication().runWriteAction {
- val lookup = LookupManager.getActiveLookup(editor) as? LookupImpl
- if (lookup != null) {
- lookup.performGuardedChange(apply)
- } else {
- apply.run()
- }
- }
-
- state = State.TRACKING
- }
-
- fun fitsInMarker(offset: Int, oldLength: Int): Boolean {
- var fitsInMarker = false
- for (leaderAndSupport in markers) {
- val leader = leaderAndSupport.first
- if (!leader.isValid) {
- fitsInMarker = false
- break
- }
- fitsInMarker = fitsInMarker or
- (offset >= leader.startOffset && offset + oldLength <= leader.endOffset)
- }
- return fitsInMarker
- }
-
- fun clearMarkers() {
- for (leaderAndSupport in markers) {
- leaderAndSupport.first.dispose()
- leaderAndSupport.second.dispose()
- }
- markers.clear()
- state = State.INITIAL
- }
-
- private fun createTagNameMarker(caret: Caret): RangeMarker? {
- val offset = caret.offset
- val document = editor.document
- val sequence = document.charsSequence
- var start = -1
- var end = -1
- for (i in offset - 1 downTo Math.max(0, offset - 50)) {
- try {
- val c = sequence[i]
- if (c == '<' || c == '/' && i > 0 && sequence[i - 1] == '<') {
- start = i + 1
- break
- }
- if (!c.isValidTagNameChar()) break
- } catch (e: IndexOutOfBoundsException) {
- LOG.error(
- "incorrect offset:$i, initial: $offset",
- Attachment("document.txt", sequence.toString())
- )
- return null
- }
- }
- if (start < 0) return null
- for (i in offset until Math.min(document.textLength, offset + 50)) {
- val c = sequence[i]
- if (!c.isValidTagNameChar()) {
- end = i
- break
- }
- }
- return if (end < 0 || start > end) null else document.createRangeMarker(start, end, true)
- }
-
- private fun findSupport(leader: RangeMarker, file: PsiFile, document: Document): RangeMarker? {
- val offset = leader.startOffset
- val element: PsiElement? = file.findElementAt(offset)
- val support: PsiElement? = findSupportElement(element)
-
- @Suppress("FoldInitializerAndIfToElvis")
- if (support == null) return findSupportForTagList(leader, element, document)
-
- val range = support.textRange
- return document.createRangeMarker(range.startOffset, range.endOffset, true)
- }
-
- private fun findSupportForTagList(
- leader: RangeMarker,
- element: PsiElement?,
- document: Document
- ): RangeMarker? {
- if (leader.startOffset != leader.endOffset || element == null) return null
-
- var support: PsiElement? = null
- if ("<>" == element.text) {
- val last = element.parent.lastChild
- if ("</>" == last.text) {
- support = last
- }
- }
- if ("</>" == element.text) {
- val first = element.parent.firstChild
- if ("<>" == first.text) {
- support = first
- }
- }
- if (support != null) {
- val range = support.textRange
- return document.createRangeMarker(range.endOffset - 1, range.endOffset - 1, true)
- }
- return null
- }
-
- private fun findSupportElement(el: PsiElement?): PsiElement? {
- if (el == null) return null
- val ktxEl = el.parentOfType<KtxElement>() ?: return null
- val start = ktxEl.simpleTagName ?: ktxEl.qualifiedTagName ?: return null
- val end = ktxEl.simpleClosingTagName ?: ktxEl.qualifiedClosingTagName
- return if (start.startOffset <= el.startOffset && start.endOffset >= el.endOffset) {
- // el is start tag, support is end tag
- end
- } else if (end != null && end.startOffset <= el.startOffset &&
- end.endOffset >= el.endOffset) {
- // el is the end tag, support is the start tag
- start
- } else null
- }
-}
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxTypedHandler.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxTypedHandler.kt
deleted file mode 100644
index d1ffb7b..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/editor/KtxTypedHandler.kt
+++ /dev/null
@@ -1,245 +0,0 @@
-package androidx.compose.plugins.idea.editor
-
-import com.intellij.codeInsight.editorActions.TypedHandlerDelegate
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.EditorModificationUtil
-import com.intellij.openapi.editor.ex.EditorEx
-import com.intellij.openapi.fileTypes.FileType
-import com.intellij.openapi.project.Project
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.psi.PsiErrorElement
-import com.intellij.psi.PsiFile
-import com.intellij.psi.codeStyle.CodeStyleManager
-import com.intellij.psi.tree.IElementType
-import com.intellij.util.IncorrectOperationException
-import org.jetbrains.kotlin.KtNodeTypes
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.psi.KtxAttribute
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.psiUtil.endOffset
-import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespace
-import org.jetbrains.kotlin.psi.psiUtil.startOffset
-import androidx.compose.plugins.idea.getNextLeafIgnoringWhitespace
-import androidx.compose.plugins.idea.parentOfType
-
-class KtxTypedHandler : TypedHandlerDelegate() {
-
- override fun beforeCharTyped(
- c: Char,
- project: Project,
- editor: Editor,
- file: PsiFile,
- fileType: FileType
- ): Result {
- when (c) {
- '>' -> {
- if (shouldOverwrite(KtTokens.GT, editor, file)) {
- EditorModificationUtil.moveCaretRelatively(editor, 1)
- return Result.STOP
- }
- }
- '/' -> {
- if (shouldOverwrite(KtTokens.DIV, editor, file)) {
- EditorModificationUtil.moveCaretRelatively(editor, 1)
- return Result.STOP
- }
- }
- '{' -> {
- // if '{' is typed at the start of an attribute value expression and right before a '>' or a '/', the '}' won't get
- // inserted by kotlin, so we do it for you...
- if (betweenEqOfAttributeAndCloseTag(editor, file)) {
- EditorModificationUtil.insertStringAtCaret(
- editor,
- "{}",
- true,
- false
- )
- EditorModificationUtil.moveCaretRelatively(editor, 1)
- return Result.STOP
- }
- }
- }
- return Result.CONTINUE
- }
-
- private fun betweenEqOfAttributeAndCloseTag(editor: Editor, file: PsiFile): Boolean {
- val el = file.findElementAt(editor.caretModel.offset) ?: return false
- if (el.node.elementType == KtTokens.DOUBLE_ARROW) {
- // this is a special case where <Foo x=> will end in a double arrow, but is really just a user in the middle of typing
- // an attribute value. If they type `{` after the `=` we want to do the right thing here
- if (editor.caretModel.offset != el.startOffset + 1) return false
- if (el.parent?.parent !is KtxElement) return false
- if (el.parent?.getPrevSiblingIgnoringWhitespace(withItself = false) !is KtxAttribute)
- return false
- return true
- }
- val next = el.nextSibling
- if (next == null) {
- return false
- }
- if (next.node.elementType != KtTokens.DIV && next.node.elementType != KtTokens.GT)
- return false
- if (el.getPrevSiblingIgnoringWhitespace(withItself = false) !is KtxAttribute) return false
-
- return true
- }
-
- private fun shouldOverwrite(token: IElementType, editor: Editor, file: PsiFile): Boolean {
- val el = file.findElementAt(editor.caretModel.offset) ?: return false
- if (el.node?.elementType != token) return false
- if (el.parent?.node?.elementType != KtNodeTypes.KTX_ELEMENT) return false
- return true
- }
-
- override fun charTyped(
- c: Char,
- project: Project,
- editor: Editor,
- file: PsiFile
- ): Result {
- @Suppress("NAME_SHADOWING") val editor = editor as EditorEx
- when (c) {
- '/' -> {
- // if `</` is typed, we can complete the closing tag...
- if (justTypedTheDivOfAClosingTag(editor)) {
- PsiDocumentManager.getInstance(project).commitDocument(editor.document)
- val div = file.findElementAt(editor.caretModel.offset - 1)
- ?: return Result.CONTINUE
- assert(div.node.elementType == KtTokens.DIV)
- val errorKtxElement = div.parent as? PsiErrorElement ?: return Result.CONTINUE
- // when the closing tag isn't fully formed, we actually parse it as an error tag in the body of the unclosed
- // parent tag. Thus, to find the tag we want to close, we find the first parent ktx element
- val el = errorKtxElement.parentOfType<KtxElement>()
- ?: return Result.CONTINUE
- val openTag = el.qualifiedTagName ?: el.simpleTagName
- ?: return Result.CONTINUE
- val tagText = editor.document.charsSequence.subSequence(
- openTag.startOffset,
- openTag.endOffset
- )
- EditorModificationUtil.insertStringAtCaret(
- editor,
- "$tagText>",
- false,
- true
- )
- PsiDocumentManager.getInstance(file.project).commitDocument(editor.document)
- try {
- CodeStyleManager.getInstance(file.project)!!.adjustLineIndent(
- file,
- openTag.endOffset + 1
- )
- } catch (e: IncorrectOperationException) {
- }
- return Result.STOP
- }
- // if `/` is typed before a tag is closed, we can add `>` to complete the self-closing tag
- if (maybeJustTypedTheDivOfASelfClosingTag(editor)) {
- // the above check can have false positives, so to be sure that we are in a KTX element, we commit the document,
- // and then perform an additional check to ensure that the parent of the div is a ktx element
- PsiDocumentManager.getInstance(project).commitDocument(editor.document)
- if (justTypedTheDivOfASelfClosingTagAfterCommit(editor, file)) {
- // now we can for sure check add the closing bracket
- EditorModificationUtil.insertStringAtCaret(
- editor,
- ">",
- true,
- false)
- return Result.STOP
- }
- }
- }
- '>' -> {
- // if `>` is typed on an open tag with no closing tag, we can insert the full close tag here...
- if (justTypedTheClosingBracketOfOpenTag(editor)) {
- PsiDocumentManager.getInstance(project).commitDocument(editor.document)
-
- val gt = file.findElementAt(editor.caretModel.offset - 1)
- ?: return Result.CONTINUE
- assert(gt.node.elementType == KtTokens.GT)
-
- val el = gt.parent as? KtxElement ?: return Result.CONTINUE
- val openTag = el.qualifiedTagName ?: el.simpleTagName ?: return Result.CONTINUE
- val closeTag = el.qualifiedClosingTagName ?: el.simpleClosingTagName
- if (closeTag != null) {
- // this element already has a closing tag, so we don't want to add another one... but we do want
- // to go ahead and ensure that the indent of the body of this tag is correct
- try {
- CodeStyleManager.getInstance(file.project)!!
- .adjustLineIndent(file, openTag.endOffset + 1)
- } catch (e: IncorrectOperationException) {
- }
- return Result.CONTINUE
- } else {
- val next = gt.getNextLeafIgnoringWhitespace()
- if (next == null || next.node.elementType == KtTokens.RBRACE) {
- // the next token is a close brace or empty, so we assume that the user's intent is not to wrap
- // content below with this tag. as a result, we automatically insert the closing tag
- val tagText = editor.document.charsSequence.subSequence(
- openTag.startOffset,
- openTag.endOffset
- )
- EditorModificationUtil.insertStringAtCaret(
- editor,
- "</$tagText>",
- true,
- false)
- return Result.STOP
- } else {
- // since there is content below the tag they are typing, we aren't going to auto-insert the close tag.
- return Result.CONTINUE
- }
- }
- }
- }
- }
- return Result.CONTINUE
- }
-
- private fun justTypedTheClosingBracketOfOpenTag(editor: EditorEx): Boolean {
- val iterator = editor.highlighter.createIterator(editor.caretModel.offset)
- with(iterator) {
- retreat()
- if (tokenType != KtTokens.GT) return false
-
- retreat()
- // if it's a DIV then the tag was self-closing and there's nothing we need to do
- if (tokenType == KtTokens.DIV) return false
- }
- return true
- }
-
- private fun justTypedTheDivOfAClosingTag(editor: EditorEx): Boolean {
- val iterator = editor.highlighter.createIterator(editor.caretModel.offset)
- with(iterator) {
- retreat()
- if (tokenType != KtTokens.DIV) return false
- retreat()
-
- if (tokenType != KtTokens.LT) return false // we aren't in a ktx closing tag
- }
- return true
- }
-
- private fun maybeJustTypedTheDivOfASelfClosingTag(editor: EditorEx): Boolean {
- val iterator = editor.highlighter.createIterator(editor.caretModel.offset)
- with(iterator) {
- retreat()
- if (tokenType != KtTokens.DIV) return false
- retreat()
-
- if (tokenType == KtTokens.LT) return false // we aren't in a ktx closing tag
- }
- return true
- }
-
- private fun justTypedTheDivOfASelfClosingTagAfterCommit(
- editor: EditorEx,
- file: PsiFile
- ): Boolean {
- val ktxEl = file.findElementAt(editor.caretModel.offset - 1)?.parent ?: return false
- if (ktxEl.node.elementType != KtNodeTypes.KTX_ELEMENT ||
- ktxEl.lastChild.node.elementType != KtTokens.DIV) return false
- return true
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AbstractKtxQuickFix.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AbstractKtxQuickFix.kt
deleted file mode 100644
index 47a2c0b..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AbstractKtxQuickFix.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package androidx.compose.plugins.idea.quickfix
-
-import com.intellij.codeInsight.intention.IntentionAction
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.project.Project
-import org.jetbrains.kotlin.diagnostics.Diagnostic
-import org.jetbrains.kotlin.idea.core.ShortenReferences
-import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
-import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtPsiFactory
-import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
-
-abstract class AbstractKtxQuickFix<T : KtElement>(element: T) : KotlinQuickFixAction<T>(element) {
- protected companion object {
- fun <T : KtElement> T.shortenReferences() = ShortenReferences.DEFAULT.process(this)
- }
-
- override fun getFamilyName() = "KTX"
-
- abstract fun invoke(ktPsiFactory: KtPsiFactory, element: T)
-
- final override fun invoke(project: Project, editor: Editor?, file: KtFile) {
- val clazz = element ?: return
- val ktPsiFactory = KtPsiFactory(project, markGenerated = true)
- invoke(ktPsiFactory, clazz)
- }
-
- abstract class AbstractFactory(
- private val f: Diagnostic.() -> IntentionAction?
- ) : KotlinSingleIntentionActionFactory() {
- companion object {
- inline fun <reified T : KtElement> Diagnostic.findElement() =
- psiElement.getNonStrictParentOfType<T>()
- }
-
- override fun createAction(diagnostic: Diagnostic) = f(diagnostic)
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AnnotateTargetWithComposableQuickFix.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AnnotateTargetWithComposableQuickFix.kt
deleted file mode 100644
index a1e390f..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AnnotateTargetWithComposableQuickFix.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2018 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.plugins.idea.quickfix
-
-import com.intellij.codeInsight.intention.IntentionAction
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.project.Project
-import org.jetbrains.kotlin.diagnostics.Diagnostic
-import org.jetbrains.kotlin.diagnostics.Errors
-import org.jetbrains.kotlin.idea.core.ShortenReferences
-import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
-import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
-import org.jetbrains.kotlin.idea.util.addAnnotation
-import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtLambdaExpression
-import org.jetbrains.kotlin.psi.KtNamedFunction
-import org.jetbrains.kotlin.psi.KtParameter
-import org.jetbrains.kotlin.psi.KtProperty
-import org.jetbrains.kotlin.psi.KtPsiFactory
-import androidx.compose.plugins.kotlin.ComposeFqNames
-import androidx.compose.plugins.kotlin.analysis.ComposeErrors
-
-class AnnotateTargetWithComposableQuickFix(
- private val expression: KtElement
-) : KotlinQuickFixAction<KtElement>(expression) {
- companion object MyFactory : KotlinSingleIntentionActionFactory() {
- override fun createAction(diagnostic: Diagnostic): IntentionAction? {
- val target = ComposeErrors.NON_COMPOSABLE_INVOCATION.cast(
- Errors.PLUGIN_WARNING.cast(diagnostic).a.diagnostic
- ).b
- val foundPsiElement = target.findPsi() as? KtElement ?: return null
-
- return AnnotateTargetWithComposableQuickFix(
- foundPsiElement
- )
- }
- }
-
- override fun getFamilyName() = "KTX"
-
- // TODO(lmr): we should pass in the name and use it here
- override fun getText() = "Annotate with ''@Composable''"
-
- override fun invoke(project: Project, editor: Editor?, file: KtFile) {
-// val ktPsiFactory = KtPsiFactory(project, markGenerated = true)
-
- when (expression) {
- is KtProperty -> {
- val typeRef = expression.typeReference
- val defaultValue = expression.initializer
- if (typeRef != null) {
- typeRef.addAnnotation(ComposeFqNames.Composable, "")
- } else if (defaultValue != null && defaultValue is KtLambdaExpression) {
- // do it to default value...
- defaultValue.addAnnotation(ComposeFqNames.Composable)
- } else {
- expression.addAnnotation(ComposeFqNames.Composable)
- }
- }
- is KtParameter -> {
- // if there's a type reference, add it to that
- // if not, try adding it to the expression...?
- val typeRef = expression.typeReference
- val defaultValue = expression.defaultValue
- if (typeRef != null) {
- typeRef.addAnnotation(ComposeFqNames.Composable, "")
- } else if (defaultValue != null && defaultValue is KtLambdaExpression) {
- // do it to default value...
- defaultValue.addAnnotation(ComposeFqNames.Composable)
- } else {
- expression.addAnnotation(ComposeFqNames.Composable)
- }
- }
- is KtNamedFunction -> expression.addAnnotation(ComposeFqNames.Composable)
- else -> error("Unknown element type: ${expression.node.elementType}")
- }
- }
-}
-
-private fun KtLambdaExpression.addAnnotation(
- annotationFqName: FqName,
- annotationInnerText: String? = null
-): Boolean {
- val annotationText = when (annotationInnerText) {
- null -> "@${annotationFqName.asString()}"
- else -> "@${annotationFqName.asString()}($annotationInnerText)"
- }
-
- val psiFactory = KtPsiFactory(this)
- val annotatedExpression = psiFactory.createExpression("$annotationText $text")
- val parent = parent as? KtElement
- replace(annotatedExpression)
- parent?.let { ShortenReferences.DEFAULT.process(it) }
-
- return true
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AnnotateWithComposableQuickFix.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AnnotateWithComposableQuickFix.kt
deleted file mode 100644
index 4ab38ba..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/AnnotateWithComposableQuickFix.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2018 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.plugins.idea.quickfix
-
-import org.jetbrains.kotlin.idea.core.ShortenReferences
-import org.jetbrains.kotlin.idea.util.addAnnotation
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtFunction
-import org.jetbrains.kotlin.psi.KtLambdaExpression
-import org.jetbrains.kotlin.psi.KtModifierListOwner
-import org.jetbrains.kotlin.psi.KtPsiFactory
-import androidx.compose.plugins.kotlin.ComposeFqNames
-
-class AnnotateWithComposableQuickFix(fn: KtElement) : AbstractKtxQuickFix<KtElement>(fn) {
- companion object MyFactory : AbstractFactory(
- {
- val element = psiElement
- if (element.node.elementType == KtTokens.IDENTIFIER) {
- // if it is an identifier, it is because it is a named function declaration that
- // needs to be annotated as @Composable. Here we find the function itself, and pass
- // it to the quickfix
- val targetFn: KtElement? = findElement<KtFunction>()
- targetFn?.let {
- AnnotateWithComposableQuickFix(
- it
- )
- }
- } else when (element) {
- // if it is a lambda expression, then we should annotate the expression itself
- is KtLambdaExpression -> AnnotateWithComposableQuickFix(
- element
- )
- else -> null
- }
- }
- )
-
- override fun getText() = "Annotate with ''@Composable''"
-
- override fun invoke(ktPsiFactory: KtPsiFactory, element: KtElement) {
- when (element) {
- // add it to the list of modifiers
- is KtModifierListOwner -> element.addAnnotation(ComposeFqNames.Composable)
- // annotate the expression itself
- is KtLambdaExpression -> element.addAnnotation(ComposeFqNames.Composable)
- else -> error("Expected a KtModifierListOwner or KtLambdaExpression. Found ${
- element.node.elementType
- }")
- }
- }
-}
-
-private fun KtLambdaExpression.addAnnotation(
- annotationFqName: FqName,
- annotationInnerText: String? = null
-): Boolean {
- val annotationText = when (annotationInnerText) {
- null -> "@${annotationFqName.asString()}"
- else -> "@${annotationFqName.asString()}($annotationInnerText)"
- }
-
- val psiFactory = KtPsiFactory(this)
- val annotatedExpression = psiFactory.createExpression("$annotationText $text")
- val parent = parent as? KtElement
- replace(annotatedExpression)
- parent?.let { ShortenReferences.DEFAULT.process(it) }
-
- return true
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeAddImportAction.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeAddImportAction.kt
deleted file mode 100644
index 30fe5d9..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeAddImportAction.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-package androidx.compose.plugins.idea.quickfix
-
-import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass
-import com.intellij.codeInsight.hint.HintManager
-import com.intellij.codeInsight.hint.QuestionAction
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.command.CommandProcessor
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.ui.popup.JBPopupFactory
-import com.intellij.openapi.ui.popup.PopupStep
-import com.intellij.openapi.ui.popup.util.BaseListPopupStep
-import com.intellij.psi.PsiDocumentManager
-import org.jetbrains.kotlin.idea.util.ImportInsertHelper
-import org.jetbrains.kotlin.psi.KtElement
-import androidx.compose.plugins.idea.ComposeBundle
-
-class ComposeAddImportAction(
- private val project: Project,
- private val editor: Editor,
- private val element: KtElement,
- private val variants: List<ImportVariant>
-) : QuestionAction {
- override fun execute(): Boolean {
- PsiDocumentManager.getInstance(project).commitAllDocuments()
- if (!element.isValid) return false
- if (variants.isEmpty()) return false
-
- if (variants.size == 1 || ApplicationManager.getApplication().isUnitTestMode) {
- addImport(variants.first())
- return true
- }
-
- JBPopupFactory.getInstance().createListPopup(
- getVariantSelectionPopup()
- ).showInBestPositionFor(editor)
- return true
- }
-
- fun showHint(): Boolean {
- if (variants.isEmpty()) return false
-
- val hintText = ShowAutoImportPass.getMessage(variants.size > 1, variants.first().hint)
- HintManager.getInstance()
- .showQuestionHint(
- editor,
- hintText,
- element.textOffset,
- element.textRange!!.endOffset,
- this
- )
-
- return true
- }
-
- private fun getVariantSelectionPopup(): BaseListPopupStep<ImportVariant> {
- return object : BaseListPopupStep<ImportVariant>(
- ComposeBundle.message("imports.chooser.title"),
- variants
- ) {
- override fun isAutoSelectionEnabled() = false
-
- override fun isSpeedSearchEnabled() = true
-
- override fun onChosen(
- selectedValue: ImportVariant?,
- finalChoice: Boolean
- ): PopupStep<String>? {
- if (selectedValue == null || project.isDisposed) return null
- addImport(selectedValue)
- return null
- }
-
- override fun hasSubstep(selectedValue: ImportVariant?) = false
- override fun getTextFor(value: ImportVariant) = value.hint
- }
- }
-
- private fun addImport(variant: ImportVariant) {
- PsiDocumentManager.getInstance(project).commitAllDocuments()
-
- CommandProcessor.getInstance().executeCommand(project, {
- ApplicationManager.getApplication().runWriteAction {
- ImportInsertHelper.getInstance(project)
- .importDescriptor(element.containingKtFile, variant.descriptor)
- }
- }, ComposeBundle.message("add.import"), null)
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeImportFix.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeImportFix.kt
deleted file mode 100644
index 8b0cb22..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeImportFix.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package androidx.compose.plugins.idea.quickfix
-
-import com.intellij.codeInsight.hint.HintManager
-import com.intellij.codeInsight.intention.HighPriorityAction
-import com.intellij.codeInspection.HintAction
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.command.CommandProcessor
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.project.Project
-import com.intellij.psi.util.PsiModificationTracker
-import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtFile
-import androidx.compose.plugins.idea.ComposeBundle
-
-abstract class ComposeImportFix(
- protected var expression: KtExpression
-) : KotlinQuickFixAction<KtExpression>(expression), HighPriorityAction, HintAction {
- private val project = expression.project
-
- private val modificationCountOnCreate =
- PsiModificationTracker.SERVICE.getInstance(project).modificationCount
-
- private lateinit var suggestions: Collection<ImportVariant>
-
- override fun invoke(project: Project, editor: Editor?, file: KtFile) {
- CommandProcessor.getInstance().runUndoTransparentAction {
- createAction(project, editor!!).execute()
- }
- }
-
- override fun getFamilyName(): String = ComposeBundle.message("import.fix")
-
- override fun getText(): String = ComposeBundle.message("import.fix")
-
- override fun startInWriteAction() = true
-
- override fun isAvailable(
- project: Project,
- editor: Editor?,
- file: KtFile
- ) = element != null && suggestions.isNotEmpty()
-
- fun isOutdated() =
- modificationCountOnCreate !=
- PsiModificationTracker.SERVICE.getInstance(project).modificationCount
-
- override fun showHint(editor: Editor): Boolean {
- if (!expression.isValid || isOutdated()) return false
-
- if (
- ApplicationManager.getApplication().isUnitTestMode &&
- HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)
- ) return false
-
- if (suggestions.isEmpty()) return false
-
- return createAction(project, editor).showHint()
- }
-
- fun collectSuggestions() {
- suggestions = computeSuggestions()
- }
-
- abstract fun computeSuggestions(): List<ImportVariant>
-
- private fun createAction(project: Project, editor: Editor): ComposeAddImportAction {
- return ComposeAddImportAction(
- project,
- editor,
- expression,
- suggestions.toList()
- )
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeQuickFixRegistry.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeQuickFixRegistry.kt
deleted file mode 100644
index 40e0e43..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ComposeQuickFixRegistry.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-package androidx.compose.plugins.idea.quickfix
-
-import org.jetbrains.kotlin.diagnostics.Errors
-import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor
-import org.jetbrains.kotlin.idea.quickfix.QuickFixes
-import androidx.compose.plugins.kotlin.analysis.ComposeErrors
-
-class ComposeQuickFixRegistry : QuickFixContributor {
- override fun registerQuickFixes(quickFixes: QuickFixes) {
-
- // Provide fixes to annotate things that need to be @Composable when they aren't
- quickFixes.register(
- ComposeErrors.KTX_IN_NON_COMPOSABLE,
- AnnotateWithComposableQuickFix
- )
- quickFixes.register(
- ComposeErrors.NON_COMPOSABLE_INVOCATION,
- AnnotateTargetWithComposableQuickFix
- )
-
- // Provide fixes for calling composable with a normal call
- quickFixes.register(
- ComposeErrors.SVC_INVOCATION,
- ConvertCallToKtxQuickFix
- )
-
- // "Add Import" quick fixes for unresolved attributes that have valid extension attributes
- quickFixes.register(
- ComposeErrors.MISMATCHED_ATTRIBUTE_TYPE,
- ImportAttributeFix
- )
- quickFixes.register(Errors.UNRESOLVED_REFERENCE,
- ImportAttributeFix
- )
- quickFixes.register(
- ComposeErrors.UNRESOLVED_ATTRIBUTE_KEY,
- ImportAttributeFix
- )
- quickFixes.register(
- ComposeErrors.UNRESOLVED_ATTRIBUTE_KEY_UNKNOWN_TYPE,
- ImportAttributeFix
- )
-
- // "Add Import" for unresolved tags on KTX elements. The normal "Unresolved Reference" quick fix
- // doesn't work in this case because if the KTX element has an open and closing tag, only one of
- // them gets changes, and then the tag becomes invalid, and then only the one tag changes. We
- // wrote our own custom add import quick fix that handles it correctly, but it must use the
- // UNRESOLVED_TAG diagnostic in order to not compete with the built-in one. If JetBrains adds
- // a better prioritization system for quick fixes, we could use `UNRESOLVED_TAG` only if we
- // wanted.
- quickFixes.register(Errors.UNRESOLVED_REFERENCE,
- ImportComponentFix
- )
- quickFixes.register(
- ComposeErrors.UNRESOLVED_TAG,
- ImportComponentFix
- )
-
- // when a composer isn't in scope, this proovides a quickfix to import one
- quickFixes.register(
- ComposeErrors.NO_COMPOSER_FOUND,
- ImportComposerFix
- )
- }
-}
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ConvertCallToKtxQuickFix.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ConvertCallToKtxQuickFix.kt
deleted file mode 100644
index 21702b9..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ConvertCallToKtxQuickFix.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2018 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.plugins.idea.quickfix
-
-import com.intellij.codeInsight.intention.IntentionAction
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.project.Project
-import com.intellij.psi.PsiElement
-import org.jetbrains.kotlin.diagnostics.Diagnostic
-import org.jetbrains.kotlin.diagnostics.Errors
-import org.jetbrains.kotlin.idea.caches.resolve.analyze
-import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
-import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
-import org.jetbrains.kotlin.psi.KtCallExpression
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtPsiFactory
-import org.jetbrains.kotlin.psi.KtxElement
-import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
-import androidx.compose.plugins.kotlin.analysis.ComposeErrors
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
-
-class ConvertCallToKtxQuickFix(
- private val expression: KtCallExpression
-) : KotlinQuickFixAction<KtElement>(expression) {
- companion object MyFactory : KotlinSingleIntentionActionFactory() {
- override fun createAction(diagnostic: Diagnostic): IntentionAction? {
- val target = ComposeErrors.SVC_INVOCATION.cast(
- Errors.PLUGIN_ERROR.cast(diagnostic).a.diagnostic
- ).psiElement
- val foundPsiElement = target.containingCallExpression() ?: return null
-
- return ConvertCallToKtxQuickFix(foundPsiElement)
- }
- }
-
- override fun getFamilyName() = "KTX"
-
- override fun getText() = "Convert to a tag"
-
- override fun invoke(project: Project, editor: Editor?, file: KtFile) {
- val bindingContext = expression.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
- val call = bindingContext.get(BindingContext.CALL, expression.calleeExpression)
- val resolvedCall = bindingContext.get(BindingContext.RESOLVED_CALL, call) ?: return
-
- val factory = KtPsiFactory(expression)
-
- // Create a KTX_ELEMENT node with dummy values for the arguments
- val ktxElement = factory.createBlock(
- "<Test ${
- resolvedCall.valueArguments.map { "${it.key.name}=1" }.joinToString(" ")
- } />"
- ).statements.first() as? KtxElement ?: return
-
- // Replace the dummy arguments with the existing expressions
- var index = 0
- resolvedCall.valueArguments.forEach { _, resolvedValueArgument ->
- val ktxAttribute = ktxElement.attributes[index++]
- val originalReference = resolvedValueArgument.arguments.first() as? PsiElement
- val originalValue = originalReference?.getChildOfType<KtExpression>() as? PsiElement
- originalValue?.let { ktxAttribute.value?.replace(it) }
- }
-
- expression.replace(ktxElement)
- }
-}
-
-fun KtElement.containingCallExpression(): KtCallExpression? =
- this as? KtCallExpression ?: (this.parent as? KtElement)?.containingCallExpression()
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportAttributeFix.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportAttributeFix.kt
deleted file mode 100644
index b87567b..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportAttributeFix.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-package androidx.compose.plugins.idea.quickfix
-
-import com.intellij.codeInsight.ImportFilter
-import com.intellij.codeInsight.intention.IntentionAction
-import org.jetbrains.kotlin.descriptors.ClassifierDescriptorWithTypeParameters
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.diagnostics.Diagnostic
-import org.jetbrains.kotlin.idea.caches.resolve.analyze
-import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
-import org.jetbrains.kotlin.idea.caches.resolve.util.getResolveScope
-import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
-import org.jetbrains.kotlin.idea.core.isVisible
-import org.jetbrains.kotlin.idea.imports.importableFqName
-import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
-import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
-import org.jetbrains.kotlin.idea.util.getFileResolutionScope
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtSimpleNameExpression
-import org.jetbrains.kotlin.psi.KtxAttribute
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.kotlin.ComposeUtils
-import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
-import org.jetbrains.kotlin.resolve.scopes.LexicalScope
-import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
-import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
-import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
-import org.jetbrains.kotlin.types.typeUtil.isUnit
-
-class ImportAttributeFix(expression: KtSimpleNameExpression) : ComposeImportFix(expression) {
- private val ktxAttribute = expression.parent as KtxAttribute
-
- private fun getReceiverTypes(
- bindingContext: BindingContext,
- element: KtxElement
- ): List<KotlinType>? {
- val ktxCall = bindingContext[ComposeWritableSlices.RESOLVED_KTX_CALL, element]
- ?: return null
- return ktxCall.emitOrCall.resolvedCalls().mapNotNull { it.resultingDescriptor.returnType }
- }
-
- override fun computeSuggestions(): List<ImportVariant> {
- val ktxElement = ktxAttribute.parent as? KtxElement ?: return emptyList()
- if (!ktxAttribute.isValid) return emptyList()
- if (ktxAttribute.containingFile !is KtFile) return emptyList()
-
- val file = ktxAttribute.containingKtFile
-
- val name = ktxAttribute.name ?: return emptyList()
-
- val bindingContext = ktxAttribute.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
-
- val receiverTypes = getReceiverTypes(bindingContext, ktxElement) ?: return emptyList()
- val expectedTypeInfo = bindingContext[
- BindingContext.EXPRESSION_TYPE_INFO,
- ktxAttribute.value ?: ktxAttribute.key
- ]
-
- val searchScope = getResolveScope(file)
-
- val resolutionFacade = file.getResolutionFacade()
-
- val topLevelScope = resolutionFacade.getFileResolutionScope(file)
-
- val callTypeAndReceiver = CallTypeAndReceiver.DOT(ktxElement)
-
- fun isVisible(descriptor: DeclarationDescriptor): Boolean {
- if (descriptor is DeclarationDescriptorWithVisibility) {
- return descriptor.isVisible(
- ktxAttribute,
- callTypeAndReceiver.receiver as? KtExpression,
- bindingContext,
- resolutionFacade
- )
- }
-
- return true
- }
-
- fun isValid(descriptor: DeclarationDescriptor): Boolean {
- return when (descriptor) {
- is FunctionDescriptor -> when {
- descriptor.valueParameters.size != 1 -> false
- descriptor.returnType?.isUnit() != true -> false
- ComposeUtils.propertyNameFromSetterMethod(descriptor.name.asString()) ==
- name -> true
- else -> false
- }
- else -> false
- }
- }
-
- val type = expectedTypeInfo?.type
-
- fun isCorrectType(descriptor: DeclarationDescriptor): Boolean {
- if (type == null) return true
- return when (descriptor) {
- is FunctionDescriptor -> type.isSubtypeOf(descriptor.valueParameters.first().type)
- else -> false
- }
- }
-
- fun shouldShow(descriptor: DeclarationDescriptor): Boolean {
- if (!ImportFilter.shouldImport(file, descriptor.fqNameSafe.asString())) return false
-
- if (isAlreadyImported(descriptor, topLevelScope, descriptor.fqNameSafe)) return false
-
- return true
- }
-
- val indicesHelper = KotlinIndicesHelper(
- resolutionFacade,
- searchScope,
- ::isVisible,
- file = file
- )
-
- val candidates = indicesHelper.getCallableTopLevelExtensions(
- callTypeAndReceiver = callTypeAndReceiver,
- receiverTypes = receiverTypes,
- nameFilter = { true },
- declarationFilter = { true }
- )
- .asSequence()
- .filter { isValid(it) }
- .filter { isVisible(it) }
- .filter { shouldShow(it) }
- .toList()
-
- val filteredByTypeCandidates = candidates.filter { isCorrectType(it) }
-
- val finalCandidates =
- if (filteredByTypeCandidates.isNotEmpty()) filteredByTypeCandidates else candidates
-
- return finalCandidates.map {
- ImportVariant.forAttribute(
- it
- )
- }
- }
-
- private fun isAlreadyImported(
- target: DeclarationDescriptor,
- topLevelScope: LexicalScope,
- targetFqName: FqName
- ): Boolean {
- val name = target.name
- return when (target) {
- is ClassifierDescriptorWithTypeParameters -> {
- val classifier = topLevelScope.findClassifier(name, NoLookupLocation.FROM_IDE)
- classifier?.importableFqName == targetFqName
- }
-
- is FunctionDescriptor ->
- topLevelScope.findFunction(name, NoLookupLocation.FROM_IDE) {
- it.importableFqName == targetFqName
- } != null
-
- is PropertyDescriptor ->
- topLevelScope.findVariable(name, NoLookupLocation.FROM_IDE) {
- it.importableFqName == targetFqName
- } != null
-
- else -> false
- }
- }
-
- companion object MyFactory : KotlinSingleIntentionActionFactory() {
- override fun createAction(diagnostic: Diagnostic): IntentionAction? {
- val element = diagnostic.psiElement as? KtSimpleNameExpression ?: return null
- val attribute = element.parent as? KtxAttribute ?: return null
- if (element != attribute.key) return null
- return ImportAttributeFix(element).apply { collectSuggestions() }
- }
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportComponentFix.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportComponentFix.kt
deleted file mode 100644
index 5ff7276..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportComponentFix.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-package androidx.compose.plugins.idea.quickfix
-
-import com.intellij.codeInsight.ImportFilter
-import com.intellij.codeInsight.intention.IntentionAction
-import org.jetbrains.kotlin.descriptors.ClassifierDescriptorWithTypeParameters
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.diagnostics.Diagnostic
-import org.jetbrains.kotlin.idea.caches.resolve.analyze
-import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
-import org.jetbrains.kotlin.idea.caches.resolve.util.getResolveScope
-import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
-import org.jetbrains.kotlin.idea.core.isVisible
-import org.jetbrains.kotlin.idea.imports.importableFqName
-import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
-import org.jetbrains.kotlin.idea.util.getFileResolutionScope
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtSimpleNameExpression
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.idea.parentOfType
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
-import org.jetbrains.kotlin.resolve.scopes.LexicalScope
-import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
-import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
-import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
-
-class ImportComponentFix(
- element: KtSimpleNameExpression,
- private val ktxElement: KtxElement
-) : ComposeImportFix(element) {
-
- private val name = element.getReferencedName()
-
- override fun computeSuggestions(): List<ImportVariant> {
- if (!ktxElement.isValid) return emptyList()
-
- if (ktxElement.containingFile !is KtFile) return emptyList()
-
- val file = ktxElement.containingKtFile
-
- val bindingContext = ktxElement.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
-
- val searchScope = getResolveScope(file)
-
- val resolutionFacade = file.getResolutionFacade()
-
- val topLevelScope = resolutionFacade.getFileResolutionScope(file)
-
- fun isVisible(descriptor: DeclarationDescriptor): Boolean {
- if (descriptor is DeclarationDescriptorWithVisibility) {
- return descriptor.isVisible(
- ktxElement,
- null,
- bindingContext,
- resolutionFacade
- )
- }
-
- return true
- }
-
- fun shouldShow(descriptor: DeclarationDescriptor): Boolean {
- if (!ImportFilter.shouldImport(file, descriptor.fqNameSafe.asString())) return false
-
- if (isAlreadyImported(descriptor, topLevelScope, descriptor.fqNameSafe)) return false
-
- return true
- }
-
- val candidates = mutableListOf<ImportVariant>()
-
- val indicesHelper = KotlinIndicesHelper(
- resolutionFacade,
- searchScope,
- ::isVisible,
- file = file
- )
-
- indicesHelper.processTopLevelCallables({ it == name }) loop@{ descriptor ->
- if (!isVisible(descriptor)) return@loop
- if (!shouldShow(descriptor)) return@loop
- // NOTE(lmr): we could restrict some options here, but it might be best to include everything and then let
- // the next level of error resolution tell the user how to fix. For instance, if we checked for @Composable, we might
- // miss out on a freshly created component function that the user wants to use, but hasn't annotated properly
- // yet. In that case we might still want to include it in this list, since duplicate names will be rare.
- candidates.add(
- ImportVariant.forComponent(
- descriptor
- )
- )
- }
-
- indicesHelper.getJvmClassesByName(name).forEach { descriptor ->
- if (!isVisible(descriptor)) return@forEach
- if (!shouldShow(descriptor)) return@forEach
- candidates.add(
- ImportVariant.forComponent(
- descriptor
- )
- )
- }
-
- return candidates
- }
-
- private fun isAlreadyImported(
- target: DeclarationDescriptor,
- topLevelScope: LexicalScope,
- targetFqName: FqName
- ): Boolean {
- val name = target.name
- return when (target) {
- is ClassifierDescriptorWithTypeParameters -> {
- val classifier = topLevelScope.findClassifier(name, NoLookupLocation.FROM_IDE)
- classifier?.importableFqName == targetFqName
- }
-
- is FunctionDescriptor ->
- topLevelScope.findFunction(name, NoLookupLocation.FROM_IDE) {
- it.importableFqName == targetFqName
- } != null
-
- is PropertyDescriptor ->
- topLevelScope.findVariable(name, NoLookupLocation.FROM_IDE) {
- it.importableFqName == targetFqName
- } != null
-
- else -> false
- }
- }
-
- companion object MyFactory : KotlinSingleIntentionActionFactory() {
- override fun createAction(diagnostic: Diagnostic): IntentionAction? {
- val element = diagnostic.psiElement as? KtSimpleNameExpression ?: return null
- val ktxElement = element.parentOfType<KtxElement>() ?: return null
- val tagOpen = ktxElement.simpleTagName /* ?: ktxElement.qualifiedTagName */
- ?: return null
- if (tagOpen.textRange.contains(element.textRange)) {
- return ImportComponentFix(
- element,
- ktxElement
- ).apply { collectSuggestions() }
- }
- val tagClose =
- ktxElement.simpleClosingTagName /* ?: ktxElement.qualifiedClosingTagName */
- ?: return null
- if (tagClose.textRange.contains(element.textRange)) {
- return ImportComponentFix(
- element,
- ktxElement
- ).apply { collectSuggestions() }
- }
- return null
- }
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportComposerFix.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportComposerFix.kt
deleted file mode 100644
index 875c1c8..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportComposerFix.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-package androidx.compose.plugins.idea.quickfix
-
-import com.intellij.codeInsight.ImportFilter
-import com.intellij.codeInsight.intention.IntentionAction
-import org.jetbrains.kotlin.descriptors.ClassifierDescriptorWithTypeParameters
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.diagnostics.Diagnostic
-import org.jetbrains.kotlin.idea.caches.resolve.analyze
-import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
-import org.jetbrains.kotlin.idea.caches.resolve.util.getResolveScope
-import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
-import org.jetbrains.kotlin.idea.core.isVisible
-import org.jetbrains.kotlin.idea.imports.importableFqName
-import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
-import org.jetbrains.kotlin.idea.util.getFileResolutionScope
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtxElement
-import androidx.compose.plugins.kotlin.KtxNameConventions
-import androidx.compose.plugins.idea.parentOfType
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
-import org.jetbrains.kotlin.resolve.scopes.LexicalScope
-import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
-import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
-import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
-
-class ImportComposerFix(
- element: KtExpression,
- private val ktxElement: KtxElement
-) : ComposeImportFix(element) {
-
- override fun computeSuggestions(): List<ImportVariant> {
- if (!ktxElement.isValid) return emptyList()
-
- if (ktxElement.containingFile !is KtFile) return emptyList()
-
- val file = ktxElement.containingKtFile
-
- val bindingContext = ktxElement.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
-
- val searchScope = getResolveScope(file)
-
- val resolutionFacade = file.getResolutionFacade()
-
- val topLevelScope = resolutionFacade.getFileResolutionScope(file)
-
- fun isVisible(descriptor: DeclarationDescriptor): Boolean {
- if (descriptor is DeclarationDescriptorWithVisibility) {
- return descriptor.isVisible(
- ktxElement,
- null,
- bindingContext,
- resolutionFacade
- )
- }
-
- return true
- }
-
- fun shouldShow(descriptor: DeclarationDescriptor): Boolean {
- if (!ImportFilter.shouldImport(file, descriptor.fqNameSafe.asString())) return false
-
- if (isAlreadyImported(descriptor, topLevelScope, descriptor.fqNameSafe)) return false
-
- return true
- }
-
- val candidates = mutableListOf<ImportVariant>()
-
- val indicesHelper = KotlinIndicesHelper(
- resolutionFacade,
- searchScope,
- ::isVisible,
- file = file
- )
-
- val composerName = KtxNameConventions.COMPOSER.identifier
-
- indicesHelper.processTopLevelCallables({ it == composerName }) loop@{ descriptor ->
- if (descriptor.name != KtxNameConventions.COMPOSER) return@loop
- if (!isVisible(descriptor)) return@loop
- if (!shouldShow(descriptor)) return@loop
- candidates.add(
- ImportVariant.forComponent(
- descriptor
- )
- )
- }
-
- return candidates
- }
-
- private fun isAlreadyImported(
- target: DeclarationDescriptor,
- topLevelScope: LexicalScope,
- targetFqName: FqName
- ): Boolean {
- val name = target.name
- return when (target) {
- is ClassifierDescriptorWithTypeParameters -> {
- val classifier = topLevelScope.findClassifier(name, NoLookupLocation.FROM_IDE)
- classifier?.importableFqName == targetFqName
- }
-
- is FunctionDescriptor ->
- topLevelScope.findFunction(name, NoLookupLocation.FROM_IDE) {
- it.importableFqName == targetFqName
- } != null
-
- is PropertyDescriptor ->
- topLevelScope.findVariable(name, NoLookupLocation.FROM_IDE) {
- it.importableFqName == targetFqName
- } != null
-
- else -> false
- }
- }
-
- companion object MyFactory : KotlinSingleIntentionActionFactory() {
- override fun createAction(diagnostic: Diagnostic): IntentionAction? {
- val element = diagnostic.psiElement as? KtExpression ?: return null
- val ktxElement = element.parentOfType<KtxElement>() ?: return null
- return ImportComposerFix(element, ktxElement).apply { collectSuggestions() }
- }
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportVariant.kt b/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportVariant.kt
deleted file mode 100644
index 49dad57..0000000
--- a/compose/compose-ide-plugin/src/main/java/androidx/compose/plugins/idea/quickfix/ImportVariant.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package androidx.compose.plugins.idea.quickfix
-
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.name.FqName
-import androidx.compose.plugins.kotlin.ComposeUtils
-import org.jetbrains.kotlin.renderer.DescriptorRenderer
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-
-data class ImportVariant(
- val descriptor: DeclarationDescriptor,
- val hint: String,
- val fqName: FqName
-) {
- companion object {
- private val RENDERER = DescriptorRenderer.SHORT_NAMES_IN_TYPES.withOptions {
- parameterNamesInFunctionalTypes = false
- }
-
- fun forAttribute(descriptor: DeclarationDescriptor): ImportVariant {
- val fqName = descriptor.fqNameSafe
- val type = when (descriptor) {
- is FunctionDescriptor -> descriptor.valueParameters.first().type
- else -> null
- }
- val attrName = when (descriptor) {
- is FunctionDescriptor -> ComposeUtils.propertyNameFromSetterMethod(
- descriptor.name.asString()
- )
- else -> descriptor.name.asString()
- }
- val hint = "$attrName=(${type?.let { RENDERER.renderType(it) }}) (from ${
- descriptor.containingDeclaration?.fqNameSafe
- })"
-
- return ImportVariant(descriptor, hint, fqName)
- }
-
- fun forComponent(descriptor: DeclarationDescriptor): ImportVariant {
- return ImportVariant(
- descriptor,
- descriptor.fqNameSafe.asString(),
- descriptor.fqNameSafe
- )
- }
- }
-}
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/resources/META-INF/plugin.xml b/compose/compose-ide-plugin/src/main/resources/META-INF/plugin.xml
index 3e5bfc9..aa18ef3 100644
--- a/compose/compose-ide-plugin/src/main/resources/META-INF/plugin.xml
+++ b/compose/compose-ide-plugin/src/main/resources/META-INF/plugin.xml
@@ -26,31 +26,11 @@
<idea-version since-build="191.5109.14" until-build="192.*"/>
<depends>org.jetbrains.kotlin</depends>
- <extensions defaultExtensionNs="com.intellij">
- <completion.contributor language="kotlin" implementationClass="androidx.compose.plugins.idea.completion.ComposeCompletionContributor" />
- <typedHandler implementation="androidx.compose.plugins.idea.editor.KtxTypedHandler" />
- <typedHandler implementation="androidx.compose.plugins.idea.editor.KtxAttributeAutoParensHandler" />
- <enterHandlerDelegate implementation="androidx.compose.plugins.idea.editor.KtxEnterHandler" />
- <annotator language="kotlin" implementationClass="androidx.compose.plugins.idea.editor.ComposeUnresolvedTagAnnotator" />
- <psi.referenceContributor language="kotlin" implementation="androidx.compose.plugins.idea.editor.KtxReferenceContributor"/>
- </extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
- <ktxParsingExtension implementation="androidx.compose.plugins.kotlin.ComposeKtxParsingExtension" />
- <storageComponentContainerContributor implementation="androidx.compose.plugins.kotlin.ComponentsClosedDeclarationChecker" />
<storageComponentContainerContributor implementation="androidx.compose.plugins.kotlin.ComposableAnnotationChecker" />
<storageComponentContainerContributor implementation="androidx.compose.plugins.kotlin.UnionAnnotationCheckerProvider" />
- <ktxTypeResolutionExtension implementation="androidx.compose.plugins.kotlin.ComposeKtxTypeResolutionExtension"/>
<callResolutionInterceptorExtension implementation="androidx.compose.plugins.kotlin.ComposeCallResolutionInterceptorExtension"/>
- <ktxControlFlowExtension implementation="androidx.compose.plugins.kotlin.ComposeKtxControlFlowExtension"/>
<typeResolutionInterceptorExtension implementation="androidx.compose.plugins.kotlin.ComposeTypeResolutionInterceptorExtension"/>
- <completionExtension implementation="androidx.compose.plugins.idea.completion.ComposeCompletionExtension"/>
<diagnosticSuppressor implementation="androidx.compose.plugins.kotlin.ComposeDiagnosticSuppressor"/>
- <quickFixContributor implementation="androidx.compose.plugins.idea.quickfix.ComposeQuickFixRegistry" />
</extensions>
-
- <application-components>
- <component>
- <implementation-class>androidx.compose.plugins.idea.editor.KtxTagNameSynchronizer</implementation-class>
- </component>
- </application-components>
</idea-plugin>
\ No newline at end of file
diff --git a/compose/compose-ide-plugin/src/main/resources/androidx/compose/plugins/idea/ComposeBundle.properties b/compose/compose-ide-plugin/src/main/resources/androidx/compose/plugins/idea/ComposeBundle.properties
deleted file mode 100644
index 75d537a..0000000
--- a/compose/compose-ide-plugin/src/main/resources/androidx/compose/plugins/idea/ComposeBundle.properties
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# Copyright 2019 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.
-#
-
-#quick fix messages
-import.fix=Import
-add.import=Add Import
-imports.chooser.title=Imports
\ No newline at end of file
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt
index 876da5f..3bed268 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt
@@ -16,7 +16,6 @@
package androidx.compose.benchmark.dbmonster
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.Model
import androidx.compose.composer
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt
index c77cfd8..43f6cd1 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt
@@ -16,7 +16,6 @@
package androidx.compose.benchmark.deeptree
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.dp
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt
index 9d420ae..1169bad 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt
@@ -16,7 +16,6 @@
package androidx.compose.benchmark.siblings
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.Key
import androidx.compose.composer
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Children.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Children.kt
deleted file mode 100644
index c4584d3..0000000
--- a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Children.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2019 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
-
-/**
- * [Children] can be applied to one parameter of a composable function, to indicate that this
- * parameter represents the composable's children. The general expectation is that the parameter
- * this annotates is used to describe the composable's sub-nodes within the composable's view
- * hierarchy. The type of the parameter that [Children] annotates is expected to be a function of
- * some type, and it is assumed to be [Composable] unless otherwise specified with the [composable]
- * parameter. One does not need to use both [Composable] and [Children] in conjunction with one
- * another.
- *
- * NOTE: It is expected that this annotation will eventually go away and function parameters at the
- * end of a parameter list will be treated as Children parameters, just like postfix lambda
- * arguments in normal Kotlin.
- *
- * @param composable If false, the type of the argument will be compiled as a function that is _not_
- * [Composable]. Defaults to true.
- *
- * @see Composable
- */
-@MustBeDocumented
-@Target(
- // Parameters of [Composable] functions
- AnnotationTarget.VALUE_PARAMETER,
- // Properties of [Component] classes
- AnnotationTarget.PROPERTY,
- // Setter functions of [Component] classes
- AnnotationTarget.FUNCTION
-)
-annotation class Children(val composable: Boolean = true)
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Component.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Component.kt
index 0327da2..c073d0e 100644
--- a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Component.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Component.kt
@@ -51,7 +51,6 @@
*
* @see Composable
*/
-@Stateful
@Suppress("PLUGIN_ERROR")
abstract class Component {
@HiddenAttribute
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
index 82f9c4d..9203840 100644
--- a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
@@ -1836,3 +1836,9 @@
else break
}
}
+
+object NullCompitionScope {
+ val composer = Unit
+}
+
+inline fun <T> escapeCompose(block: NullCompitionScope.() -> T) = NullCompitionScope.block()
diff --git a/ui/ui-android-view/src/main/java/androidx/ui/androidview/WebComponentActivity.kt b/ui/ui-android-view/src/main/java/androidx/ui/androidview/WebComponentActivity.kt
index 94de6b7..a137403 100644
--- a/ui/ui-android-view/src/main/java/androidx/ui/androidview/WebComponentActivity.kt
+++ b/ui/ui-android-view/src/main/java/androidx/ui/androidview/WebComponentActivity.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package androidx.ui.androidview
import android.app.Activity
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt
index 6043816..9894036 100644
--- a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt
@@ -23,6 +23,7 @@
import androidx.compose.unaryPlus
import androidx.ui.animation.animatedFloat
import androidx.ui.core.Alignment
+import androidx.ui.core.ambientDensity
import androidx.ui.core.dp
import androidx.ui.core.withDensity
import androidx.ui.foundation.ColoredRect
@@ -42,12 +43,12 @@
// Composable that users can drag over 300 dp.
val max = 300.dp
val min = 0.dp
- val (minPx, maxPx) = +withDensity {
+ val (minPx, maxPx) = withDensity(+ambientDensity()) {
min.toPx().value to max.toPx().value
}
Draggable(DragDirection.Horizontal, minPx, maxPx) { dragValue ->
// dragValue is the current value in progress of dragging
- val draggedDp = +withDensity {
+ val draggedDp = withDensity(+ambientDensity()) {
dragValue.toDp()
}
val squareSize = 50.dp
@@ -70,7 +71,7 @@
// and the value will gravitate to 0, 150 or 300 dp
val max = 300.dp
val min = 0.dp
- val (minPx, maxPx) = +withDensity {
+ val (minPx, maxPx) = withDensity(+ambientDensity()) {
min.toPx().value to max.toPx().value
}
// define anchors and related animation controller
@@ -86,7 +87,7 @@
) { dragValue ->
// dragValue is the current value in progress
// of dragging or animation
- val draggedDp = +withDensity {
+ val draggedDp = withDensity(+ambientDensity()) {
dragValue.toDp()
}
val squareSize = 50.dp
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Popup.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Popup.kt
index 4448586..f41f664 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Popup.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Popup.kt
@@ -32,6 +32,7 @@
import androidx.compose.ambient
import androidx.compose.composer
import androidx.compose.disposeComposition
+import androidx.compose.escapeCompose
import androidx.compose.memo
import androidx.compose.onCommit
import androidx.compose.onDispose
@@ -136,14 +137,14 @@
val providedTestTag = +ambient(TestTagAmbient)
val popupLayout = +memo(popupProperties) {
- PopupLayout(
+ escapeCompose { PopupLayout(
context = context,
composeView = composeView,
popupProperties = popupProperties,
popupPositionProperties = popupPositionProperties,
calculatePopupPosition = calculatePopupPosition,
testTag = providedTestTag
- )
+ ) }
}
popupLayout.calculatePopupPosition = calculatePopupPosition
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
index 9850ba5..c93722d 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
@@ -20,7 +20,6 @@
import androidx.animation.AnimationEndReason
import androidx.animation.ExponentialDecay
import androidx.animation.ValueHolder
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.Model
@@ -148,7 +147,7 @@
scrollerPosition.value = position
},
isScrollable: Boolean = true,
- @Children child: @Composable() () -> Unit
+ child: @Composable() () -> Unit
) {
Scroller(scrollerPosition, onScrollPositionChanged, true, isScrollable, child)
}
@@ -181,7 +180,7 @@
scrollerPosition.value = position
},
isScrollable: Boolean = true,
- @Children child: @Composable() () -> Unit
+ child: @Composable() () -> Unit
) {
Scroller(scrollerPosition, onScrollPositionChanged, false, isScrollable, child)
}
@@ -192,7 +191,7 @@
onScrollPositionChanged: (position: Px, maxPosition: Px) -> Unit,
isVertical: Boolean,
isScrollable: Boolean,
- @Children child: @Composable() () -> Unit
+ child: @Composable() () -> Unit
) {
val maxPosition = +state { Px.Infinity }
val direction = if (isVertical) DragDirection.Vertical else DragDirection.Horizontal
diff --git a/ui/ui-framework/api/0.1.0-dev02.txt b/ui/ui-framework/api/0.1.0-dev02.txt
index ac9847d..0f68ae1 100644
--- a/ui/ui-framework/api/0.1.0-dev02.txt
+++ b/ui/ui-framework/api/0.1.0-dev02.txt
@@ -143,7 +143,6 @@
method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityScope,? extends R> block);
}
}
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index ac9847d..0f68ae1 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -143,7 +143,6 @@
method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityScope,? extends R> block);
}
}
diff --git a/ui/ui-framework/api/public_plus_experimental_0.1.0-dev02.txt b/ui/ui-framework/api/public_plus_experimental_0.1.0-dev02.txt
index ac9847d..0f68ae1 100644
--- a/ui/ui-framework/api/public_plus_experimental_0.1.0-dev02.txt
+++ b/ui/ui-framework/api/public_plus_experimental_0.1.0-dev02.txt
@@ -143,7 +143,6 @@
method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityScope,? extends R> block);
}
}
diff --git a/ui/ui-framework/api/public_plus_experimental_current.txt b/ui/ui-framework/api/public_plus_experimental_current.txt
index ac9847d..0f68ae1 100644
--- a/ui/ui-framework/api/public_plus_experimental_current.txt
+++ b/ui/ui-framework/api/public_plus_experimental_current.txt
@@ -143,7 +143,6 @@
method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityScope,? extends R> block);
}
}
diff --git a/ui/ui-framework/api/restricted_0.1.0-dev02.txt b/ui/ui-framework/api/restricted_0.1.0-dev02.txt
index ac9847d..0f68ae1 100644
--- a/ui/ui-framework/api/restricted_0.1.0-dev02.txt
+++ b/ui/ui-framework/api/restricted_0.1.0-dev02.txt
@@ -143,7 +143,6 @@
method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityScope,? extends R> block);
}
}
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index ac9847d..0f68ae1 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -143,7 +143,6 @@
method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityScope,? extends R> block);
}
}
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
index 9ca9993..974f4f0 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
@@ -30,7 +30,6 @@
import androidx.ui.material.Button
import androidx.ui.material.MaterialTheme
import androidx.ui.material.themeTextStyle
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.state
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
index 031632f..d9f4116 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
@@ -16,7 +16,6 @@
package androidx.ui.framework.demos
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
index 565ab99..d0cf6ad 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
@@ -16,7 +16,6 @@
package androidx.ui.framework.demos
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
index b1aa6b6..bc078c9 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
@@ -18,7 +18,6 @@
import android.app.Activity
import android.os.Bundle
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.unaryPlus
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt
index c141109..b1c76ac 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt
@@ -19,7 +19,6 @@
import android.app.Activity
import android.graphics.Rect
import android.os.Bundle
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.ambient
import androidx.compose.composer
@@ -102,7 +101,7 @@
fun Autofill(
autofillTypes: List<AutofillType>,
onFill: ((String) -> Unit),
- @Children children: @Composable() (AutofillNode) -> Unit
+ children: @Composable() (AutofillNode) -> Unit
) {
val autofillNode = AutofillNode( autofillTypes = autofillTypes)
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedPressDemo.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedPressDemo.kt
index c5e852a..e114e50 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedPressDemo.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedPressDemo.kt
@@ -18,7 +18,6 @@
import android.app.Activity
import android.os.Bundle
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.state
import androidx.compose.unaryPlus
@@ -66,7 +65,7 @@
paddingTop: Dp? = null,
paddingRight: Dp? = null,
paddingBottom: Dp? = null,
- @Children children: (@Composable() () -> Unit)
+ children: (@Composable() () -> Unit)
) {
val defaultColor = DefaultBackgroundColor
val pressedColor = PressedColor
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
index c63b0d6..c830a8a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
@@ -16,7 +16,6 @@
package androidx.ui.core
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.engine.geometry.Shape
@@ -28,7 +27,7 @@
*/
@Composable
inline fun Clip(shape: Shape, crossinline children: @Composable() () -> Unit) {
- <RepaintBoundaryNode name=null shape=shape clipToShape=true>
+ RepaintBoundaryNode(name=null, shape=shape, clipToShape=true) {
children()
- </RepaintBoundaryNode>
+ }
}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Draw.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Draw.kt
index daf526c..e9c3d4b 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Draw.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Draw.kt
@@ -39,11 +39,11 @@
) {
// Hide the internals of DrawNode
if (+ambient(InspectionMode)) {
- <RepaintBoundaryNode name=null>
- <DrawNode >
- </RepaintBoundaryNode>
+ RepaintBoundaryNode(name=null) {
+ DrawNode(>
+ }
} else {
- <DrawNode >
+ DrawNode(>
}
}
@@ -63,14 +63,14 @@
) {
// Hide the internals of DrawNode
if (+ambient(InspectionMode)) {
- <RepaintBoundaryNode name=null>
- <DrawNode >
+ RepaintBoundaryNode(name=null) {
+ DrawNode( {
children()
- </DrawNode>
- </RepaintBoundaryNode>
+ }
+ }
} else {
- <DrawNode >
+ DrawNode( {
children()
- </DrawNode>
+ }
}
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/DrawShadow.kt b/ui/ui-framework/src/main/java/androidx/ui/core/DrawShadow.kt
index 05f436c..39e7a05 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/DrawShadow.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/DrawShadow.kt
@@ -34,5 +34,5 @@
@Composable
@Suppress("NOTHING_TO_INLINE")
inline fun DrawShadow(shape: Shape, elevation: Dp) {
- <RepaintBoundaryNode name=null shape=shape elevation=elevation/>
+ RepaintBoundaryNode(name=null, shape=shape, elevation=elevation)
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
index 73d8b53..bc8a00e 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
@@ -134,9 +134,9 @@
measureBlocks: LayoutNode.MeasureBlocks,
modifier: Modifier
) {
- <LayoutNode modifier=modifier measureBlocks=measureBlocks>
+ LayoutNode(modifier=modifier, measureBlocks=measureBlocks) {
children()
- </LayoutNode>
+ }
}
/**
@@ -415,9 +415,9 @@
} else {
@Composable {
childrenArray.forEach { childrenComposable ->
- <DataNode key=ChildGroupKey value=childrenComposable>
+ DataNode(key=ChildGroupKey, value=childrenComposable) {
childrenComposable()
- </DataNode>
+ }
}
}
}
@@ -518,11 +518,11 @@
) = error("Intrinsic measurements not supported for WithConstraints")
}
- <LayoutNode
- ref=layoutNodeRef
- modifier=Modifier.None
+ LayoutNode(
+ ref=layoutNodeRef,
+ modifier=Modifier.None,
measureBlocks=measureBlocks
- />
+ )
}
/**
@@ -545,7 +545,7 @@
inline fun OnPositioned(
noinline onPositioned: (coordinates: LayoutCoordinates) -> Unit
) {
- <DataNode key=OnPositionedKey value=onPositioned/>
+ DataNode(key=OnPositionedKey, value=onPositioned)
}
/**
@@ -567,7 +567,7 @@
noinline onPositioned: (coordinates: LayoutCoordinates) -> Unit,
crossinline children: @Composable() () -> Unit
) {
- <DataNode key=OnChildPositionedKey value=onPositioned>
+ DataNode(key=OnChildPositionedKey, value=onPositioned) {
children()
- </DataNode>
+ }
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
index e8debcd..3f81bee 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
@@ -17,7 +17,6 @@
package androidx.ui.core
import androidx.annotation.FloatRange
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
@@ -35,7 +34,7 @@
@FloatRange(from = 0.0, to = 1.0) opacity: Float,
crossinline children: @Composable() () -> Unit
) {
- <RepaintBoundaryNode name=null opacity=opacity>
+ RepaintBoundaryNode(name=null, opacity=opacity) {
children()
- </RepaintBoundaryNode>
+ }
}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt b/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
index b13ecb9e..42eb651 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
@@ -15,7 +15,6 @@
*/
package androidx.ui.core
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
@@ -54,7 +53,7 @@
*/
@Composable
inline fun ParentData(data: Any, crossinline children: @Composable() () -> Unit) {
- <DataNode key=ParentDataKey value=data>
+ DataNode(key=ParentDataKey, value=data) {
children()
- </DataNode>
+ }
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
index bbd3fc5..845a9ff 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
@@ -13,10 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package androidx.ui.core
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
@@ -27,7 +25,7 @@
crossinline children: @Composable() () -> Unit
) {
// Hide the internals of PointerInputNode
- <PointerInputNode pointerInputHandler>
+ PointerInputNode(pointerInputHandler=pointerInputHandler) {
children()
- </PointerInputNode>
+ }
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt b/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
index cbf1760..5d4b9b2 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
@@ -16,7 +16,6 @@
package androidx.ui.core
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
@@ -32,7 +31,7 @@
*/
@Composable
fun RepaintBoundary(name: String? = null, children: @Composable() () -> Unit) {
- <RepaintBoundaryNode name=name>
- <children/>
- </RepaintBoundaryNode>
+ RepaintBoundaryNode(name=name) {
+ children()
+ }
}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
index a75a022..ca9b79f 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
@@ -17,7 +17,6 @@
package androidx.ui.core
import androidx.compose.Ambient
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
index bae2121..f733208 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
@@ -18,7 +18,6 @@
import androidx.compose.Applier
import androidx.compose.ApplyAdapter
-import androidx.compose.Children
import androidx.compose.Component
import androidx.compose.Composable
import androidx.compose.Composer
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
index 025c8ae..ae051a7 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
@@ -54,7 +54,7 @@
fun ComposeView(children: @Composable() () -> Unit) {
val rootRef = +memo { Ref<AndroidComposeView>() }
- <AndroidComposeView ref=rootRef>
+ AndroidComposeView(ref=rootRef) {
var reference: CompositionReference? = null
var cc: CompositionContext? = null
@@ -89,12 +89,13 @@
// If this value is inlined where it is used, an error that includes 'Precise Reference:
// kotlinx.coroutines.Dispatchers' not instance of 'Precise Reference: androidx.compose.Ambient'.
val coroutineContext = Dispatchers.Main
- cc = Compose.composeInto(container = rootLayoutNode, context = context, parent = reference) {
- WrapWithAmbients(rootRef.value!!, context, coroutineContext) {
- children()
+ cc =
+ Compose.composeInto(container = rootLayoutNode, context = context, parent = reference) {
+ WrapWithAmbients(rootRef.value!!, context, coroutineContext) {
+ children()
+ }
}
- }
- </AndroidComposeView>
+ }
}
/**
@@ -259,29 +260,6 @@
effectOf<Density> { +ambient(DensityAmbient) }
/**
- * An effect to be able to convert dimensions between each other.
- * A [Density] object will be taken from an ambient.
- *
- * Usage examples:
- *
- * +withDensity() {
- * val pxHeight = DpHeight.toPx()
- * }
- *
- * or
- *
- * val pxHeight = +withDensity(density) { DpHeight.toPx() }
- *
- */
-@CheckResult(suggest = "+")
-// can't make this inline as tests are failing with "DensityKt.$jacocoInit()' is inaccessible"
-/*inline*/ fun <R> withDensity(/*crossinline*/ block: DensityScope.() -> R) =
- effectOf<R> {
- @Suppress("USELESS_CAST")
- withDensity(+ambientDensity(), block as DensityScope.() -> R)
- }
-
-/**
* A component to be able to convert dimensions between each other.
* A [Density] object will be take from an ambient.
*
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
index e91a1e34..459b88a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
@@ -17,7 +17,6 @@
package androidx.ui.core.gesture
import androidx.ui.core.PxPosition
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
index 636c602..bd7297a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
@@ -18,6 +18,7 @@
import androidx.compose.Composable
import androidx.compose.memo
+import androidx.compose.composer
import androidx.compose.unaryPlus
import androidx.ui.core.Direction
import androidx.ui.core.PxPosition
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetector.kt
index f490466..4b1307a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetector.kt
@@ -29,6 +29,7 @@
import androidx.ui.core.PointerInputWrapper
import androidx.compose.composer
import androidx.ui.core.IntPx
+import androidx.ui.core.ambientDensity
// TODO(shepshapard): Convert to functional component with effects once effects are ready.
/**
@@ -48,7 +49,7 @@
canDrag: ((Direction) -> Boolean)? = null,
children: @Composable() () -> Unit
) {
- val touchSlop = +withDensity { TouchSlop.toIntPx() }
+ val touchSlop = withDensity(+ambientDensity()) { TouchSlop.toIntPx() }
val recognizer = +memo { TouchSlopExceededGestureRecognizer(touchSlop) }
recognizer.canDrag = canDrag
diff --git a/ui/ui-framework/src/main/java/androidx/ui/graphics/vector/VectorCompose.kt b/ui/ui-framework/src/main/java/androidx/ui/graphics/vector/VectorCompose.kt
index 9fb9fdf..9fd6448 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/graphics/vector/VectorCompose.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/graphics/vector/VectorCompose.kt
@@ -16,7 +16,6 @@
package androidx.ui.graphics.vector
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.compositionReference
@@ -26,6 +25,7 @@
import androidx.ui.core.Dp
import androidx.ui.core.Draw
import androidx.ui.core.Px
+import androidx.ui.core.ambientDensity
import androidx.ui.core.withDensity
import androidx.ui.graphics.Brush
import androidx.ui.graphics.StrokeCap
@@ -52,10 +52,10 @@
viewportWidth: Float = unset,
viewportHeight: Float = unset,
name: String = "",
- @Children children: @Composable() VectorScope.(viewportWidth: Float, viewportHeight: Float) -> Unit
+ children: @Composable() VectorScope.(viewportWidth: Float, viewportHeight: Float) -> Unit
) {
- val widthPx = +withDensity { defaultWidth.toPx() }
- val heightPx = +withDensity { defaultHeight.toPx() }
+ val widthPx = withDensity(+ambientDensity()) { defaultWidth.toPx() }
+ val heightPx = withDensity(+ambientDensity()) { defaultHeight.toPx() }
val vpWidth = if (viewportWidth == unset) widthPx.value else viewportWidth
val vpHeight = if (viewportHeight == unset) heightPx.value else viewportHeight
@@ -78,7 +78,7 @@
viewportWidth: Float = defaultWidth.value,
viewportHeight: Float = defaultHeight.value,
name: String = "",
- @Children children: @Composable() VectorScope.(viewportWidth: Float, viewportHeight: Float) -> Unit
+ children: @Composable() VectorScope.(viewportWidth: Float, viewportHeight: Float) -> Unit
) {
val vector =
+memo(name, viewportWidth, viewportHeight) {
@@ -117,19 +117,19 @@
clipPathData: Array<PathNode> = EmptyPath,
children: @Composable() VectorScope.() -> Unit
) {
- <GroupComponent
- name = name
- rotation = rotation
- pivotX = pivotX
- pivotY = pivotY
- scaleX = scaleX
- scaleY = scaleY
- translationX = translationX
- translationY = translationY
+ GroupComponent(
+ name = name,
+ rotation = rotation,
+ pivotX = pivotX,
+ pivotY = pivotY,
+ scaleX = scaleX,
+ scaleY = scaleY,
+ translationX = translationX,
+ translationY = translationY,
clipPathData = clipPathData
- >
+ ) {
children()
- </GroupComponent>
+ }
}
@Composable
@@ -145,16 +145,16 @@
strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
strokeLineMiter: Float = DefaultStrokeLineMiter
) {
- <PathComponent
- name
- pathData
- fill
- fillAlpha
- stroke
- strokeAlpha
- strokeLineWidth
- strokeLineJoin
- strokeLineCap
- strokeLineMiter
- />
+ PathComponent(
+ name=name,
+ pathData=pathData,
+ fill=fill,
+ fillAlpha=fillAlpha,
+ stroke=stroke,
+ strokeAlpha=strokeAlpha,
+ strokeLineWidth=strokeLineWidth,
+ strokeLineJoin=strokeLineJoin,
+ strokeLineCap=strokeLineCap,
+ strokeLineMiter=strokeLineMiter
+ )
}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/semantics/Semantics.kt b/ui/ui-framework/src/main/java/androidx/ui/semantics/Semantics.kt
index e3ba12c..4b1161d 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/semantics/Semantics.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/semantics/Semantics.kt
@@ -15,7 +15,6 @@
*/
package androidx.ui.semantics
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.ambient
import androidx.compose.composer
@@ -64,11 +63,13 @@
it.testTag = it.getOrNull(SemanticsProperties.TestTag) ?: providedTestTag
}
- <SemanticsComponentNode
- container
- explicitChildNodes
- semanticsConfiguration> TestTag(tag = DefaultTestTag) {
- children()
+ SemanticsComponentNode(
+ container=container,
+ explicitChildNodes=explicitChildNodes,
+ semanticsConfiguration=semanticsConfiguration
+ ) {
+ TestTag(tag = DefaultTestTag) {
+ children()
+ }
}
- </SemanticsComponentNode>
}
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AspectRatioModifierTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AspectRatioModifierTest.kt
index d7a3575..cacd9d9 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AspectRatioModifierTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AspectRatioModifierTest.kt
@@ -16,7 +16,7 @@
package androidx.ui.layout.test
-import androidx.compose.Children
+import androidx.compose.Composable
import androidx.compose.composer
import androidx.test.filters.SmallTest
import androidx.ui.core.Constraints
@@ -43,7 +43,7 @@
class AspectRatioModifierTest : LayoutTest() {
@Test
fun testAspectRatioModifier_intrinsicDimensions() = withDensity(density) {
- testIntrinsics(@Children {
+ testIntrinsics(@Composable {
Container(modifier = AspectRatio(2f), width = 30.dp, height = 40.dp) { }
}) { minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight ->
assertEquals(40.ipx, minIntrinsicWidth(20.ipx))
@@ -99,7 +99,7 @@
val size = Ref<PxSize>()
val position = Ref<PxPosition>()
show {
- Layout(@Children {
+ Layout(@Composable {
Container(AspectRatio(aspectRatio)) {
SaveLayoutInfo(size, position, positionedLatch)
}
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AspectRatioTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AspectRatioTest.kt
index c06806f2..544e3dd 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AspectRatioTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AspectRatioTest.kt
@@ -19,7 +19,7 @@
package androidx.ui.layout.test
-import androidx.compose.Children
+import androidx.compose.Composable
import androidx.test.filters.SmallTest
import androidx.ui.core.Layout
import androidx.ui.core.OnChildPositioned
@@ -137,7 +137,7 @@
@Test
fun testAspectRatio_intrinsicDimensions() = withDensity(density) {
- testIntrinsics(@Children {
+ testIntrinsics(@Composable {
AspectRatio(2f) {
Container(width = 30.dp, height = 40.dp) { }
}
@@ -159,7 +159,7 @@
val size = Ref<PxSize>()
show {
Align(alignment = Alignment.TopLeft) {
- val children = @Children {
+ val children = @Composable {
AspectRatio(aspectRatio) {
SaveLayoutInfo(size, Ref<PxPosition>(), positionedLatch)
}
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ExpandedModifierTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ExpandedModifierTest.kt
index 1c3e365..a91eef7 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ExpandedModifierTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ExpandedModifierTest.kt
@@ -17,7 +17,6 @@
package androidx.ui.layout.test
import android.content.res.Resources
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.test.filters.SmallTest
@@ -89,7 +88,7 @@
val size = Ref<PxSize>()
val position = Ref<PxPosition>()
show {
- Layout(@Children {
+ Layout(@Composable {
Align(alignment = Alignment.TopLeft) {
Container(modifier = modifier) {
Container(width = Width, height = Height) { }
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
index 1c76244..b9ce811 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
@@ -67,7 +67,7 @@
}
fun inflexible(children: @Composable() () -> Unit) {
- childrenList += {
+ childrenList += @Composable {
ParentData(data = FlexInfo(flex = 0f, fit = FlexFit.Loose), children = children)
}
}
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
index 3784df0..070c338 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
@@ -28,6 +28,7 @@
import androidx.ui.animation.Transition
import androidx.ui.core.Alignment
import androidx.ui.core.Text
+import androidx.ui.core.ambientDensity
import androidx.ui.core.dp
import androidx.ui.core.toPx
import androidx.ui.core.withDensity
@@ -343,8 +344,10 @@
// Padding to set the 'offset'
Container(expanded = true, alignment = Alignment.BottomLeft) {
Transition(transitionDefinition, selectedIndex) { state ->
- val offset = +withDensity { state[indicatorStart].toDp() }
- val width = +withDensity { (state[indicatorEnd] - state[indicatorStart]).toDp() }
+ val offset = withDensity(+ambientDensity()) { state[indicatorStart].toDp() }
+ val width = withDensity(+ambientDensity()) {
+ (state[indicatorEnd] - state[indicatorStart]).toDp()
+ }
Padding(left = offset) {
Container(width = width) {
// Pass the current color to the indicator
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt b/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
index 6a76a4a..9bb106b 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
@@ -31,6 +31,7 @@
import androidx.ui.core.PxPosition
import androidx.ui.core.PxSize
import androidx.ui.core.Text
+import androidx.ui.core.ambientDensity
import androidx.ui.core.dp
import androidx.ui.core.sp
import androidx.ui.core.withDensity
@@ -172,7 +173,7 @@
Align(Alignment.BottomLeft) {
AlignmentLineOffset(
alignmentLine = LastBaseline,
- after = +withDensity { AppBarTitleBaselineOffset.toDp() }
+ after = withDensity(+ambientDensity()) { AppBarTitleBaselineOffset.toDp() }
) {
// TODO: AlignmentLineOffset requires a child, so in case title() is
// empty we just add an empty wrap here - should be fixed when we move to
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
index 68b1041..f415c8a 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
@@ -32,6 +32,7 @@
import androidx.ui.core.Layout
import androidx.ui.core.Px
import androidx.ui.core.RepaintBoundary
+import androidx.ui.core.ambientDensity
import androidx.ui.core.dp
import androidx.ui.core.hasBoundedHeight
import androidx.ui.core.hasBoundedWidth
@@ -129,7 +130,7 @@
if (!pxConstraints.hasBoundedWidth) {
throw IllegalStateException("Drawer shouldn't have infinite width")
}
- val constraints = +withDensity {
+ val constraints = withDensity(+ambientDensity()) {
DpConstraints(pxConstraints)
}
val minValue = -pxConstraints.maxWidth.value.toFloat()
@@ -199,7 +200,7 @@
if (!pxConstraints.hasBoundedHeight) {
throw IllegalStateException("Drawer shouldn't have infinite height")
}
- val constraints = +withDensity {
+ val constraints = withDensity(+ambientDensity()) {
DpConstraints(pxConstraints)
}
val minValue = 0f
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
index ee3d757..2c5e31f 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -25,6 +25,7 @@
import androidx.compose.memo
import androidx.compose.unaryPlus
import androidx.ui.core.CurrentTextStyleProvider
+import androidx.ui.core.ambientDensity
import androidx.ui.core.dp
import androidx.ui.core.sp
import androidx.ui.core.withDensity
@@ -308,7 +309,7 @@
*/
@Composable
fun MaterialShapeTheme(children: @Composable() () -> Unit) {
- val value = +withDensity {
+ val value = withDensity(+ambientDensity()) {
Shapes(
button = RoundedCornerShape(4.dp),
card = RectangleShape
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt b/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
index 075938d..03dd8e2 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
@@ -24,7 +24,6 @@
import androidx.animation.transitionDefinition
import androidx.annotation.CheckResult
import androidx.annotation.FloatRange
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.effectOf
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
index 8abd451..77ec45b 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
@@ -24,6 +24,7 @@
import androidx.ui.core.DensityScope
import androidx.ui.core.Draw
import androidx.ui.core.PxSize
+import androidx.ui.core.ambientDensity
import androidx.ui.core.dp
import androidx.ui.core.px
import androidx.ui.core.withDensity
@@ -73,7 +74,7 @@
@Composable
private fun SwitchImpl(checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, color: Color) {
val minBound = 0f
- val maxBound = +withDensity { ThumbPathLength.toPx().value }
+ val maxBound = withDensity(+ambientDensity()) { ThumbPathLength.toPx().value }
StateDraggable(
state = checked,
?: {},
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt b/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
index 8771dda..c997c3f 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
@@ -33,6 +33,7 @@
import androidx.ui.core.Placeable
import androidx.ui.core.Px
import androidx.ui.core.Text
+import androidx.ui.core.ambientDensity
import androidx.ui.core.coerceIn
import androidx.ui.core.dp
import androidx.ui.core.toPx
@@ -191,7 +192,7 @@
divider: @Composable() () -> Unit,
indicatorContainer: @Composable() (tabPositions: List<TabPosition>) -> Unit
) {
- val edgeOffset = +withDensity { ScrollableTabRowEdgeOffset.toIntPx() }
+ val edgeOffset = withDensity(+ambientDensity()) { ScrollableTabRowEdgeOffset.toIntPx() }
// TODO: unfortunate 1f lag as we need to first calculate tab positions before drawing the
// indicator container
@@ -361,11 +362,13 @@
) {
// TODO: should we animate the width of the indicator as it moves between tabs of different
// sizes inside a scrollable tab row?
- val currentTabWidth = +withDensity { tabPositions[selectedIndex].width.toDp() }
+ val currentTabWidth = withDensity(+ambientDensity()) {
+ tabPositions[selectedIndex].width.toDp()
+ }
Container(expanded = true, alignment = Alignment.BottomLeft) {
IndicatorTransition(tabPositions, selectedIndex) { indicatorOffset ->
- Padding(left = +withDensity { indicatorOffset.toDp() }) {
+ Padding(left = withDensity(+ambientDensity()) { indicatorOffset.toDp() }) {
Container(width = currentTabWidth) {
indicator()
}
@@ -618,7 +621,7 @@
// TabRow specifications
private val IndicatorHeight = 2.dp
-private const val DividerOpacity = 0.12f
+private val DividerOpacity = 0.12f
// How far from the start and end of a scrollable TabRow should the first Tab be displayed
private val ScrollableTabRowEdgeOffset = 52.dp
private val ScrollableTabRowMinimumTabWidth = 90.dp
@@ -626,13 +629,13 @@
// Tab specifications
private val SmallTabHeight = 48.dp
private val LargeTabHeight = 72.dp
-private const val InactiveTabOpacity = 0.74f
-private const val MaxTitleLineCount = 2
+private val InactiveTabOpacity = 0.74f
+private val MaxTitleLineCount = 2
// Tab transition specifications
-private const val TabFadeInAnimationDuration = 150
-private const val TabFadeInAnimationDelay = 100
-private const val TabFadeOutAnimationDuration = 100
+private val TabFadeInAnimationDuration = 150
+private val TabFadeInAnimationDelay = 100
+private val TabFadeOutAnimationDuration = 100
// The horizontal padding on the left and right of text
private val HorizontalTextPadding = 16.dp
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ToolingTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ToolingTest.kt
index e2d67c5..6470b72 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ToolingTest.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ToolingTest.kt
@@ -17,7 +17,6 @@
package androidx.ui.tooling
import android.os.Handler
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.test.rule.ActivityTestRule
@@ -50,7 +49,7 @@
OnPositioned( positionedLatch.countDown() })
}
- internal fun show(@Children composable: @Composable() () -> Unit) {
+ internal fun show(composable: @Composable() () -> Unit) {
positionedLatch = CountDownLatch(1)
activityTestRule.onUiThread {
activity.setContent {
diff --git a/ui/ui-tooling/src/main/java/androidx/ui/tooling/Inspectable.kt b/ui/ui-tooling/src/main/java/androidx/ui/tooling/Inspectable.kt
index 6ee1947..9cbca8a 100644
--- a/ui/ui-tooling/src/main/java/androidx/ui/tooling/Inspectable.kt
+++ b/ui/ui-tooling/src/main/java/androidx/ui/tooling/Inspectable.kt
@@ -16,7 +16,6 @@
package androidx.ui.tooling
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.SlotTable
import androidx.compose.ambient
@@ -30,7 +29,7 @@
* is in inspection mode.
*/
@Composable
-fun Inspectable(@Children children: @Composable() () -> Unit) {
+fun Inspectable(children: @Composable() () -> Unit) {
composer.composer.collectKeySourceInformation()
tables.add(composer.composer.slotTable)
InspectionMode.Provider(true) {
@@ -45,7 +44,7 @@
* in the composition when the composition is in inspection mode.
*/
@Composable
-fun InInspectionModeOnly(@Children children: @Composable() () -> Unit) {
+fun InInspectionModeOnly(children: @Composable() () -> Unit) {
if (+ambient(InspectionMode)) {
children()
}