blob: eb8fa3438236148a57ae5069bb6724f9bfada466 [file] [log] [blame]
package androidx.compose.compiler.plugins.kotlin.analysis
import androidx.compose.compiler.plugins.kotlin.AbstractComposeDiagnosticsTest
import org.junit.Assume.assumeTrue
import org.junit.Test
class ComposableCheckerTests(useFir: Boolean) : AbstractComposeDiagnosticsTest(useFir) {
@Test
fun testCfromNC() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() {}
fun <!COMPOSABLE_EXPECTED!>NC<!>() { <!COMPOSABLE_INVOCATION!>C<!>() }
"""
)
@Test
fun testNCfromC() = check(
"""
import androidx.compose.runtime.*
fun NC() {}
@Composable fun C() { NC() }
"""
)
@Test
fun testCfromC() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() {}
@Composable fun C2() { C() }
"""
)
@Test
fun testCinCLambdaArg() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() { }
@Composable fun C2(lambda: @Composable () -> Unit) { lambda() }
@Composable fun C3() {
C2 {
C()
}
}
"""
)
@Test
fun testCinInlinedNCLambdaArg() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() { }
inline fun InlineNC(lambda: () -> Unit) { lambda() }
@Composable fun C3() {
InlineNC {
C()
}
}
"""
)
@Test
fun testCinNoinlineNCLambdaArg() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() { }
<!NOTHING_TO_INLINE!>inline<!> fun NoinlineNC(noinline lambda: () -> Unit) { lambda() }
@Composable fun C3() {
NoinlineNC {
<!COMPOSABLE_INVOCATION!>C<!>()
}
}
"""
)
@Test
fun testCinCrossinlineNCLambdaArg() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() { }
inline fun CrossinlineNC(crossinline lambda: () -> Unit) { lambda() }
@Composable fun C3() {
CrossinlineNC {
<!COMPOSABLE_INVOCATION!>C<!>()
}
}
"""
)
@Test
fun testCinNestedInlinedNCLambdaArg() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() { }
inline fun InlineNC(lambda: () -> Unit) { lambda() }
@Composable fun C3() {
InlineNC {
InlineNC {
C()
}
}
}
"""
)
@Test
fun testCinLambdaArgOfNC() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() { }
fun NC(lambda: () -> Unit) { lambda() }
@Composable fun C3() {
NC {
<!COMPOSABLE_INVOCATION!>C<!>()
}
}
"""
)
@Test
fun testCinLambdaArgOfC() = check(
"""
import androidx.compose.runtime.*
@Composable fun C() { }
@Composable fun C2(lambda: () -> Unit) { lambda() }
@Composable fun C3() {
C2 {
<!COMPOSABLE_INVOCATION!>C<!>()
}
}
"""
)
@Test
fun testCinCPropGetter() = check(
"""
import androidx.compose.runtime.*
@Composable fun C(): Int { return 123 }
val cProp: Int @Composable get() = C()
"""
)
@Test
fun testCinNCPropGetter() = check(
"""
import androidx.compose.runtime.*
@Composable fun C(): Int { return 123 }
val <!COMPOSABLE_EXPECTED!>ncProp<!>: Int get() = <!COMPOSABLE_INVOCATION!>C<!>()
"""
)
@Test
fun testCinTopLevelInitializer() = check(
"""
import androidx.compose.runtime.*
@Composable fun C(): Int { return 123 }
val ncProp: Int = <!COMPOSABLE_INVOCATION!>C<!>()
"""
)
@Test
fun testCTypeAlias() = check(
"""
import androidx.compose.runtime.*
typealias Content = @Composable () -> Unit
@Composable fun C() {}
@Composable fun C2(content: Content) { content() }
@Composable fun C3() {
val inner: Content = { C() }
C2 { C() }
C2 { inner() }
}
"""
)
@Test
fun testCfromComposableFunInterface() = check(
"""
import androidx.compose.runtime.Composable
fun interface A { @Composable fun f() }
@Composable fun B() { A { B() } }
"""
)
@Test
fun testGenericComposableInference1() {
assumeTrue(useFir)
check("""
import androidx.compose.runtime.Composable
fun <T> identity(value: T): T = value
// We should infer `ComposableFunction0<Unit>` for `T`
val cl = identity(@Composable {})
val l: () -> Unit = <!INITIALIZER_TYPE_MISMATCH!>cl<!>
""")
}
@Test
fun testGenericComposableInference2() {
assumeTrue(useFir)
check("""
import androidx.compose.runtime.Composable
@Composable fun A() {}
fun <T> identity(value: T): T = value
// Explicitly instantiate `T` with `ComposableFunction0<Unit>`
val cl = identity<@Composable () -> Unit> { A() }
val l: () -> Unit = <!INITIALIZER_TYPE_MISMATCH!>cl<!>
""")
}
@Test
fun testGenericComposableInference3() {
assumeTrue(useFir)
check("""
import androidx.compose.runtime.Composable
@Composable fun A() {}
fun <T> identity(value: T): T = value
// We should infer `T` as `ComposableFunction0<Unit>` from the context and then
// infer that the argument to `identity` is a composable lambda.
val cl: @Composable () -> Unit = identity { A() }
""")
}
@Test
fun testGenericComposableInference4() {
assumeTrue(useFir)
check("""
import androidx.compose.runtime.Composable
fun <T> identity(value: T): T = value
// We should infer `T` as `Function0<Unit>` from the context and
// reject the lambda which is explicitly typed as `ComposableFunction...`.
val cl: () -> Unit = identity(@Composable <!ARGUMENT_TYPE_MISMATCH!>{}<!>)
""")
}
@Test
fun testGenericComposableInference5() {
assumeTrue(useFir)
check("""
import androidx.compose.runtime.Composable
fun <T> identity(value: T): T = value
// We should infer `Function0<Unit>` for `T`
val lambda = identity<() -> Unit>(@Composable <!ARGUMENT_TYPE_MISMATCH!>{}<!>)
""")
}
@Test
fun testCfromAnnotatedComposableFunInterface() = check(
"""
import androidx.compose.runtime.Composable
fun interface A { @Composable fun f() }
@Composable fun B() {
val f = @Composable { B() }
A(f)
}
"""
)
@Test
fun testCfromComposableFunInterfaceArgument() = check(
"""
import androidx.compose.runtime.Composable
fun interface A { @Composable fun f() }
@Composable fun B(a: (A) -> Unit) { a { B(a) } }
"""
)
@Test
fun testCfromComposableTypeAliasFunInterface() = check(
"""
import androidx.compose.runtime.Composable
fun interface A { @Composable fun f() }
typealias B = A
@Composable fun C() { A { C() } }
"""
)
@Test
fun testCfromNonComposableFunInterface() = check(
"""
import androidx.compose.runtime.Composable
fun interface A { fun f() }
@Composable fun B() {
A {
<!COMPOSABLE_INVOCATION!>B<!>()
}
}
"""
)
@Test
fun testCfromNonComposableFunInterfaceArgument() = check(
"""
import androidx.compose.runtime.Composable
fun interface A { fun f() }
@Composable fun B(a: (A) -> Unit) {
a {
<!COMPOSABLE_INVOCATION!>B<!>(a)
}
}
"""
)
@Test
fun testPreventedCaptureOnInlineLambda() = check(
"""
import androidx.compose.runtime.*
@Composable inline fun A(
lambda: @DisallowComposableCalls () -> Unit
) { if (Math.random() > 0.5) lambda() }
@Composable fun B() {}
@Composable fun C() {
A { <!CAPTURED_COMPOSABLE_INVOCATION!>B<!>() }
}
"""
)
@Test
fun testComposableReporting001() {
checkFail(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
fun myStatelessFunctionalComponent() {
Leaf()
}
"""
)
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
@Composable
fun myStatelessFunctionalComponent() {
Leaf()
}
@Composable
fun foo() {
myStatelessFunctionalComponent()
}
"""
)
}
@Test
fun testComposableReporting002() {
checkFail(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
val myLambda1 = { Leaf() }
val myLambda2: () -> Unit = { Leaf() }
"""
)
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
val myLambda1 = @Composable { Leaf() }
val myLambda2: @Composable ()->Unit = { Leaf() }
"""
)
}
@Test
fun testComposableReporting006() {
checkFail(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
fun foo() {
val bar = {
Leaf()
}
bar()
System.out.println(bar)
}
"""
)
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
@Composable
fun foo() {
val bar = @Composable {
Leaf()
}
bar()
System.out.println(bar)
}
"""
)
}
@Test
fun testComposableReporting007() {
checkFail(
"""
import androidx.compose.runtime.*;
fun foo(content: @Composable ()->Unit) {
<!SVC_INVOCATION!>content<!>()
}
"""
)
}
@Test
fun testComposableReporting008() {
check(
"""
import androidx.compose.runtime.*;
@Composable fun Leaf() {}
fun <!COMPOSABLE_EXPECTED!>foo<!>() {
val bar: @Composable ()->Unit = @Composable {
Leaf()
}
<!COMPOSABLE_INVOCATION!>bar<!>()
System.out.println(bar)
}
"""
)
}
@Test
fun testComposableReporting009() {
check(
"""
import androidx.compose.runtime.*;
@Composable fun Leaf() {}
@Composable
fun myStatelessFunctionalComponent() {
Leaf()
}
fun <!COMPOSABLE_EXPECTED!>noise<!>() {
<!COMPOSABLE_INVOCATION!>myStatelessFunctionalComponent<!>()
}
"""
)
}
@Test
fun testComposableReporting017() {
checkFail(
"""
import androidx.compose.runtime.*;
@Composable fun Leaf() {}
@Composable
fun Foo(content: ()->Unit) {
content()
}
@Composable
fun test() {
Foo { Leaf() }
}
"""
)
check(
"""
import androidx.compose.runtime.*;
@Composable fun Leaf() {}
@Composable
fun Foo(content: ()->Unit) {
content()
}
@Composable
fun test() {
Foo { <!COMPOSABLE_INVOCATION!>Leaf<!>() }
}
"""
)
}
@Test
fun testComposableReporting018() {
checkFail(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
fun foo() {
val myVariable: ()->Unit = @Composable { Leaf() }
System.out.println(myVariable)
}
"""
)
val error = if (useFir) "INITIALIZER_TYPE_MISMATCH" else "TYPE_MISMATCH"
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
fun foo() {
val myVariable: ()->Unit = <!$error!>@Composable { Leaf() }<!>
System.out.println(myVariable)
}
"""
)
}
@Test
fun testComposableReporting021() {
// In K1, we erroneously allowed `@Composable` annotations on non-composable inline
// lambdas. See b/281975618.
assumeTrue(useFir)
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
@Composable
fun foo() {
val myList = listOf(1,2,3,4,5)
myList.forEach @Composable <!ARGUMENT_TYPE_MISMATCH!>{ value: Int ->
Leaf()
System.out.println(value)
}<!>
}
"""
)
}
@Test
fun testComposableReporting022() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
fun <!COMPOSABLE_EXPECTED!>foo<!>() {
val myList = listOf(1,2,3,4,5)
myList.forEach { value: Int ->
<!COMPOSABLE_INVOCATION!>Leaf<!>()
println(value)
}
}
"""
)
}
@Test
fun testComposableReporting023() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
@Composable
fun foo() {
val myList = listOf(1,2,3,4,5)
myList.forEach { value: Int ->
Leaf()
println(value)
}
}
"""
)
}
@Test
fun testComposableReporting024() {
check(
"""
import androidx.compose.runtime.*
var x: (@Composable () -> Unit)? = null
class Foo
fun Foo.setContent(content: @Composable () -> Unit) {
x = content
}
@Composable
fun Leaf() {}
fun Example(foo: Foo) {
foo.setContent { Leaf() }
}
"""
)
}
@Test
fun testComposableReporting024x() {
check(
"""
import androidx.compose.runtime.*
var x: (@Composable () -> Unit)? = null
fun <!COMPOSABLE_EXPECTED!>Example<!>(content: @Composable () -> Unit) {
x = content
<!COMPOSABLE_INVOCATION!>content<!>()
}
"""
)
}
@Test
fun testComposableReporting025() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
@Composable
fun foo() {
listOf(1,2,3,4,5).forEach { Leaf() }
}
"""
)
}
@Test
fun testComposableReporting026() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
@Composable
fun Group(content: @Composable () -> Unit) { content() }
@Composable
fun foo() {
Group {
Leaf()
}
}
"""
)
}
@Test
fun testComposableReporting027() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun Leaf() {}
@Composable
fun Group(content: @Composable () -> Unit) { content() }
@Composable
fun foo() {
Group {
listOf(1,2,3).forEach {
Leaf()
}
}
}
"""
)
}
@Test
fun testComposableReporting028() {
checkFail(
"""
import androidx.compose.runtime.*;
fun foo(v: @Composable ()->Unit) {
val myVariable: ()->Unit = v
myVariable()
}
"""
)
val error = if (useFir) "INITIALIZER_TYPE_MISMATCH" else "TYPE_MISMATCH"
check(
"""
import androidx.compose.runtime.*;
fun foo(v: @Composable ()->Unit) {
val myVariable: ()->Unit = <!$error!>v<!>
myVariable()
}
"""
)
}
@Test
fun testComposableReporting030() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun foo() {
val myVariable: @Composable ()->Unit = {};
myVariable()
}
"""
)
}
@Test
fun testComposableReporting032() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun MyComposable(content: @Composable ()->Unit) { content() }
@Composable
fun Leaf() {}
@Composable
fun foo() {
MyComposable { Leaf() }
}
"""
)
}
@Test
fun testComposableReporting033() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun MyComposable(content: @Composable ()->Unit) { content() }
@Composable
fun Leaf() {}
@Composable
fun foo() {
MyComposable(content={ Leaf() })
}
"""
)
}
@Test
fun testComposableReporting034() {
checkFail(
"""
import androidx.compose.runtime.*;
fun identity(f: ()->Unit): ()->Unit { return f; }
@Composable
fun test(f: @Composable ()->Unit) {
val f2: @Composable ()->Unit = identity(f);
f2()
}
"""
)
val argumentTypeMismatch = if (useFir) "ARGUMENT_TYPE_MISMATCH" else "TYPE_MISMATCH"
val initializerTypeMismatch = if (useFir) "INITIALIZER_TYPE_MISMATCH" else "TYPE_MISMATCH"
check(
"""
import androidx.compose.runtime.*;
fun identity(f: ()->Unit): ()->Unit { return f; }
@Composable
fun test(f: @Composable ()->Unit) {
val f2: @Composable ()->Unit = <!$initializerTypeMismatch!>identity (<!$argumentTypeMismatch!>f<!>)<!>;
f2()
}
"""
)
}
@Test
fun testComposableReporting035() {
check(
"""
import androidx.compose.runtime.*
@Composable
fun Foo(x: String) {
@Composable operator fun String.invoke() {}
x()
}
"""
)
}
@Test
fun testComposableReporting039() {
check(
"""
import androidx.compose.runtime.*
fun composeInto(l: @Composable ()->Unit) { System.out.println(l) }
fun Foo() {
composeInto {
Baz()
}
}
fun Bar() {
Foo()
}
@Composable fun Baz() {}
"""
)
}
@Test
fun testComposableReporting041() {
check(
"""
import androidx.compose.runtime.*
typealias COMPOSABLE_UNIT_LAMBDA = @Composable () -> Unit
@Composable
fun ComposeWrapperComposable(content: COMPOSABLE_UNIT_LAMBDA) {
MyComposeWrapper {
content()
}
}
@Composable fun MyComposeWrapper(content: COMPOSABLE_UNIT_LAMBDA) {
print(content.hashCode())
}
"""
)
}
@Test
fun testComposableReporting043() {
check(
"""
import androidx.compose.runtime.*
@Composable
fun FancyButton() {}
fun <!COMPOSABLE_EXPECTED!>Noise<!>() {
<!COMPOSABLE_INVOCATION!>FancyButton<!>()
}
"""
)
}
@Test
fun testComposableReporting044() {
check(
"""
import androidx.compose.runtime.*
typealias UNIT_LAMBDA = () -> Unit
@Composable
fun FancyButton() {}
@Composable
fun Noise() {
FancyButton()
}
"""
)
}
@Test
fun testComposableReporting045() {
check(
"""
import androidx.compose.runtime.*;
@Composable
fun foo() {
val bar = @Composable {}
bar()
System.out.println(bar)
}
"""
)
}
@Test
fun testComposableReporting048() {
// Type inference for non-null @Composable lambdas
checkFail(
"""
import androidx.compose.runtime.*
val lambda: @Composable (() -> Unit)? = null
@Composable
fun Foo() {
// Should fail as null cannot be coerced to non-null
Bar(lambda)
Bar(null)
Bar {}
}
@Composable
fun Bar(child: @Composable () -> Unit) {
child()
}
"""
)
// Type inference for nullable @Composable lambdas, with no default value
check(
"""
import androidx.compose.runtime.*
val lambda: @Composable (() -> Unit)? = null
@Composable
fun Foo() {
Bar(lambda)
Bar(null)
Bar {}
}
@Composable
fun Bar(child: @Composable (() -> Unit)?) {
child?.invoke()
}
"""
)
// Type inference for nullable @Composable lambdas, with a nullable default value
check(
"""
import androidx.compose.runtime.*
val lambda: @Composable (() -> Unit)? = null
@Composable
fun Foo() {
Bar()
Bar(lambda)
Bar(null)
Bar {}
}
@Composable
fun Bar(child: @Composable (() -> Unit)? = null) {
child?.invoke()
}
"""
)
// Type inference for nullable @Composable lambdas, with a non-null default value
check(
"""
import androidx.compose.runtime.*
val lambda: @Composable (() -> Unit)? = null
@Composable
fun Foo() {
Bar()
Bar(lambda)
Bar(null)
Bar {}
}
@Composable
fun Bar(child: @Composable (() -> Unit)? = {}) {
child?.invoke()
}
"""
)
}
@Test
fun testComposableReporting049() {
check(
"""
import androidx.compose.runtime.*
fun foo(<!WRONG_ANNOTATION_TARGET!>@Composable<!> bar: ()->Unit) {
println(bar)
}
"""
)
}
@Test
fun testComposableReporting050() {
check(
"""
import androidx.compose.runtime.*;
val foo: Int @Composable get() = 123
fun <!COMPOSABLE_EXPECTED!>App<!>() {
<!COMPOSABLE_INVOCATION!>foo<!>
}
"""
)
check(
"""
import androidx.compose.runtime.*;
val foo: Int @Composable get() = 123
@Composable
fun App() {
println(foo)
}
"""
)
}
@Test
fun testComposableReporting051() {
checkFail(
"""
import androidx.compose.runtime.*;
class A {
@Composable val bar get() = 123
}
@Composable val A.bam get() = 123
fun App() {
val a = A()
a.bar
}
"""
)
checkFail(
"""
import androidx.compose.runtime.*;
class A {
@Composable val bar get() = 123
}
@Composable val A.bam get() = 123
fun App() {
val a = A()
a.bam
}
"""
)
check(
"""
import androidx.compose.runtime.*;
class A {
val bar @Composable get() = 123
}
val A.bam @Composable get() = 123
@Composable
fun App() {
val a = A()
a.bar
a.bam
with(a) {
bar
bam
}
}
"""
)
}
@Test
fun testComposableReporting052() {
check(
"""
import androidx.compose.runtime.*;
@Composable fun Foo() {}
val <!COMPOSABLE_EXPECTED!>bam<!>: Int get() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
return 123
}
"""
)
check(
"""
import androidx.compose.runtime.*;
@Composable fun Foo() {}
val bam: Int @Composable get() {
Foo()
return 123
}
"""
)
}
@Test
fun testComposableReporting053() {
check(
"""
import androidx.compose.runtime.*;
@Composable fun foo(): Int = 123
fun <!COMPOSABLE_EXPECTED!>App<!>() {
val x = <!COMPOSABLE_INVOCATION!>foo<!>()
print(x)
}
"""
)
}
@Test
fun testComposableReporting054() {
check(
"""
import androidx.compose.runtime.*;
@Composable fun Foo() {}
val <!COMPOSABLE_EXPECTED!>y<!>: Any get() =
<!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(1) }
fun App() {
val x = object {
val <!COMPOSABLE_EXPECTED!>a<!> get() =
<!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(2) }
val c @Composable get() = remember { mutableStateOf(4) }
@Composable fun bar() { Foo() }
fun <!COMPOSABLE_EXPECTED!>foo<!>() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
}
}
class Bar {
val <!COMPOSABLE_EXPECTED!>b<!> get() =
<!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(6) }
val c @Composable get() = remember { mutableStateOf(7) }
}
fun <!COMPOSABLE_EXPECTED!>Bam<!>() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
}
@Composable fun Boo() {
Foo()
}
print(x)
}
"""
)
}
@Test
fun testComposableReporting055() {
check(
"""
import androidx.compose.runtime.*;
@Composable fun Foo() {}
@Composable fun App() {
val x = object {
val <!COMPOSABLE_EXPECTED!>a<!> get() = <!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(2) }
val c @Composable get() = remember { mutableStateOf(4) }
fun <!COMPOSABLE_EXPECTED!>foo<!>() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
}
@Composable fun bar() { Foo() }
}
class Bar {
val <!COMPOSABLE_EXPECTED!>b<!> get() = <!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(6) }
val c @Composable get() = remember { mutableStateOf(7) }
}
fun <!COMPOSABLE_EXPECTED!>Bam<!>() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
}
@Composable fun Boo() {
Foo()
}
print(x)
}
"""
)
}
@Test
fun testComposableReporting057() {
check(
"""
import androidx.compose.runtime.*;
@Composable fun App() {
val x = object {
val b = remember { mutableStateOf(3) }
}
class Bar {
val a = <!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(5) }
}
print(x)
}
"""
)
}
@Test
fun testDisallowComposableCallPropagation() = check(
"""
import androidx.compose.runtime.*
class Foo
@Composable inline fun a(block1: @DisallowComposableCalls () -> Foo): Foo {
return block1()
}
@Composable inline fun b(<!MISSING_DISALLOW_COMPOSABLE_CALLS_ANNOTATION!>block2: () -> Foo<!>): Foo {
return a { block2() }
}
@Composable inline fun c(block2: @DisallowComposableCalls () -> Foo): Foo {
return a { block2() }
}
"""
)
@Test
fun testDisallowComposableCallPropagationWithInvoke() {
// The frontend distinguishes between implicit and explicit invokes, which is why this test
// fails in K1.
assumeTrue(useFir)
check(
"""
import androidx.compose.runtime.*
class Foo
@Composable inline fun a(block1: @DisallowComposableCalls () -> Foo): Foo {
return block1()
}
@Composable inline fun b(<!MISSING_DISALLOW_COMPOSABLE_CALLS_ANNOTATION!>block2: () -> Foo<!>): Foo {
return a { block2.invoke() }
}
@Composable inline fun c(block2: @DisallowComposableCalls () -> Foo): Foo {
return a { block2.invoke() }
}
"""
)
}
@Test
fun testComposableLambdaToAll() = check(
"""
import androidx.compose.runtime.*
fun foo() {
val lambda = @Composable { }
println(lambda) // println accepts Any, verify no type mismatach.
}
"""
)
@Test
fun testReadOnlyComposablePropagation() = check(
"""
import androidx.compose.runtime.*
@Composable @ReadOnlyComposable
fun readOnly(): Int = 10
val readonlyVal: Int
@Composable @ReadOnlyComposable get() = 10
@Composable
fun normal(): Int = 10
val normalVal: Int
@Composable get() = 10
@Composable
fun test1() {
print(readOnly())
print(readonlyVal)
}
@Composable @ReadOnlyComposable
fun test2() {
print(readOnly())
print(readonlyVal)
}
@Composable
fun test3() {
print(readOnly())
print(normal())
print(readonlyVal)
print(normalVal)
}
@Composable @ReadOnlyComposable
fun test4() {
print(readOnly())
print(<!NONREADONLY_CALL_IN_READONLY_COMPOSABLE!>normal<!>())
print(readonlyVal)
print(<!NONREADONLY_CALL_IN_READONLY_COMPOSABLE!>normalVal<!>)
}
val test5: Int
@Composable
get() {
print(readOnly())
print(readonlyVal)
return 10
}
val test6: Int
@Composable @ReadOnlyComposable
get() {
print(readOnly())
print(readonlyVal)
return 10
}
val test7: Int
@Composable
get() {
print(readOnly())
print(normal())
print(readonlyVal)
print(normalVal)
return 10
}
val test8: Int
@Composable @ReadOnlyComposable
get() {
print(readOnly())
print(<!NONREADONLY_CALL_IN_READONLY_COMPOSABLE!>normal<!>())
print(readonlyVal)
print(<!NONREADONLY_CALL_IN_READONLY_COMPOSABLE!>normalVal<!>)
return 10
}
"""
)
@Test
fun testNothingAsAValidComposableFunctionBody() = check("""
import androidx.compose.runtime.*
val test1: @Composable () -> Unit = TODO()
@Composable
fun Test2(): Unit = TODO()
@Composable
fun Wrapper(content: @Composable () -> Unit) = content()
@Composable
fun Test3() {
Wrapper {
TODO()
}
}
""")
@Test
fun testComposableValueOperator() {
check(
"""
import androidx.compose.runtime.Composable
import kotlin.reflect.KProperty
class Foo
class FooDelegate {
@Composable
operator fun getValue(thisObj: Any?, property: KProperty<*>) {}
@Composable
operator fun <!COMPOSE_INVALID_DELEGATE!>setValue<!>(thisObj: Any?, property: KProperty<*>, value: Any) {}
}
@Composable operator fun Foo.getValue(thisObj: Any?, property: KProperty<*>) {}
@Composable operator fun Foo.<!COMPOSE_INVALID_DELEGATE!>setValue<!>(thisObj: Any?, property: KProperty<*>, value: Any) {}
fun <!COMPOSABLE_EXPECTED!>nonComposable<!>() {
val fooValue = Foo()
val foo by fooValue
val fooDelegate by FooDelegate()
var mutableFoo by <!COMPOSE_INVALID_DELEGATE!>fooValue<!>
val bar = Bar()
println(<!COMPOSABLE_INVOCATION!>foo<!>)
println(<!COMPOSABLE_INVOCATION!>fooDelegate<!>)
println(bar.<!COMPOSABLE_INVOCATION!>foo<!>)
<!COMPOSABLE_INVOCATION!>mutableFoo<!> = Unit
}
@Composable
fun TestComposable() {
val fooValue = Foo()
val foo by fooValue
val fooDelegate by FooDelegate()
val bar = Bar()
println(foo)
println(fooDelegate)
println(bar.foo)
}
class Bar {
val <!COMPOSABLE_EXPECTED!>foo<!> by Foo()
@get:Composable
val foo2 by Foo()
}
"""
)
}
@Test
fun testComposableFunInterfaceInNonComposableFunction() {
check(
"""
import androidx.compose.runtime.Composable
fun interface FunInterfaceWithComposable {
@Composable fun content()
}
fun Test1() {
val funInterfaceWithComposable = FunInterfaceWithComposable {
TODO()
}
println(funInterfaceWithComposable) // use it to avoid UNUSED warning
}
fun <!COMPOSABLE_EXPECTED!>Test2<!>() {
val funInterfaceWithComposable = FunInterfaceWithComposable {
TODO()
}
funInterfaceWithComposable.<!COMPOSABLE_INVOCATION!>content<!>()
}
"""
)
}
@Test
fun testComposableInsideInlineWithOverload() {
check(
"""
import androidx.compose.runtime.Composable
@Composable
fun ListTest(list: List<Any>) {
list.flatMap { Test(it) }
}
@Composable
fun Test(any: Any): List<Any> = TODO(any.toString())
"""
)
}
@Test
fun testComposableInsideInlineInsideLocal() {
check(
"""
import androidx.compose.runtime.Composable
@Composable
fun ListTest(list: List<Any>) {
fun <!COMPOSABLE_EXPECTED!>test<!>() {
list.flatMap { <!COMPOSABLE_INVOCATION!>Test<!>(it) }
}
test()
}
@Composable
fun Test(any: Any): List<Any> = TODO(any.toString())
"""
)
}
@Test
fun classDelegateToComposablePropertyInComposable() {
check(
"""
import androidx.compose.runtime.Composable
interface A
interface B {
val property: A @Composable get() = TODO()
}
@Composable fun Test(b: B) {
val a = object : A by b.property {}
println(a)
}
"""
)
}
@Test
fun classDelegateToComposablePropertyInNonComposable() {
check(
"""
import androidx.compose.runtime.Composable
interface A
interface B {
val property: A @Composable get() = TODO()
}
fun <!COMPOSABLE_EXPECTED!>Test<!>(b: B) {
val a = object : A by b.<!COMPOSABLE_INVOCATION!>property<!> {}
println(a)
}
"""
)
}
@Test
fun testErrorInAnonymousFunctionPropertyInitializer() {
assumeTrue(!useFir)
check(
"""
import androidx.compose.runtime.Composable
@Composable fun ComposableFunction() {}
fun getMyClass(): Any {
class MyClass {
val property = <!COMPOSABLE_EXPECTED!>fun() {
<!COMPOSABLE_INVOCATION!>ComposableFunction<!>() // invocation
}<!>
}
return MyClass()
}
"""
)
}
@Test
fun testErrorInAnonymousFunctionPropertyInitializerForK2() {
assumeTrue(useFir)
check(
"""
import androidx.compose.runtime.Composable
@Composable fun ComposableFunction() {}
fun getMyClass(): Any {
class MyClass {
val property = <!COMPOSABLE_EXPECTED!>fun()<!> {
<!COMPOSABLE_INVOCATION!>ComposableFunction<!>() // invocation
}
}
return MyClass()
}
"""
)
}
@Test
fun testComposableCallInsideNonComposableNonInlinedLambda() {
check(
"""
import androidx.compose.runtime.Composable
@Composable fun ComposableFunction() {}
fun functionThatTakesALambda(content: () -> Unit) { content() }
fun NonComposableFunction() {
functionThatTakesALambda {
<!COMPOSABLE_INVOCATION!>ComposableFunction<!>() // invocation
}
}
"""
)
}
}