K2: Attach function parameter annotation to function expression When a function parameter has an annotation, the exist FIR2IR does not attach the annotation to the function expression (lambda argument), which results in a missing annotation. It is critical to the Compose, because the compose compiler / compose runtime rely on the Composable annotation. This commit attaches the annotation of a function parameter to the function expression used for the argument (like lambda) to fix this issue. ^KT-64994 Fixed
diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/compilerFacility/FirIdeNormalAnalysisSourceModuleMultiModuleCompilerFacilityTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/compilerFacility/FirIdeNormalAnalysisSourceModuleMultiModuleCompilerFacilityTestGenerated.java index 75c5a0fc..3b6e1ca 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/compilerFacility/FirIdeNormalAnalysisSourceModuleMultiModuleCompilerFacilityTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/compilerFacility/FirIdeNormalAnalysisSourceModuleMultiModuleCompilerFacilityTestGenerated.java
@@ -64,6 +64,18 @@ runTest("analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/internalUsage.kt"); } + @Test + @TestMetadata("lambdaParameterWithAnnotation.kt") + public void testLambdaParameterWithAnnotation() throws Exception { + runTest("analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.kt"); + } + + @Test + @TestMetadata("lambdaParameterWithAnnotation2.kt") + public void testLambdaParameterWithAnnotation2() throws Exception { + runTest("analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.kt"); + } + @Nested @TestMetadata("analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/codeFragments") @TestDataPath("$PROJECT_ROOT")
diff --git a/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.ir.txt b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.ir.txt new file mode 100644 index 0000000..8dff5bc --- /dev/null +++ b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.ir.txt
@@ -0,0 +1,26 @@ +MODULE_FRAGMENT + FILE fqName:<root> fileName:main.kt + FUN name:Greeting visibility:public modality:FINAL <> (name:kotlin.String) returnType:kotlin.Unit + annotations: + Composable + VALUE_PARAMETER name:name index:0 type:kotlin.String + BLOCK_BODY + CALL 'public final fun show (str: kotlin.String): kotlin.Unit declared in <root>' type=kotlin.Unit origin=null + str: STRING_CONCATENATION type=kotlin.String + CONST String type=kotlin.String value="hi " + GET_VAR 'name: kotlin.String declared in <root>.Greeting' type=kotlin.String origin=null + CONST String type=kotlin.String value="!" + FUN name:show visibility:public modality:FINAL <> (str:kotlin.String) returnType:kotlin.Unit + VALUE_PARAMETER name:str index:0 type:kotlin.String + BLOCK_BODY + FUN name:test visibility:public modality:FINAL <> () returnType:kotlin.Int + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun test (): kotlin.Int declared in <root>' + CALL 'public final fun setContent (content: @[Composable] kotlin.Function0<kotlin.Unit>): kotlin.Int declared in p3.FooKt' type=kotlin.Int origin=null + content: FUN_EXPR type=kotlin.Function0<kotlin.Unit> origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.Unit + annotations: + Composable + BLOCK_BODY + CALL 'public final fun Greeting (name: kotlin.String): kotlin.Unit declared in <root>' type=kotlin.Unit origin=null + name: CONST String type=kotlin.String value="test"
diff --git a/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.kt b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.kt new file mode 100644 index 0000000..5a93532 --- /dev/null +++ b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.kt
@@ -0,0 +1,40 @@ +// MODULE: compose +// FILE: p2/bar.kt +package p2; + +@MustBeDocumented +@Retention(AnnotationRetention.BINARY) +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.TYPE, + AnnotationTarget.TYPE_PARAMETER, + AnnotationTarget.PROPERTY_GETTER +) +annotation class Composable +// MODULE: lib(compose) +// FILE: p3/foo.kt +package p3; + +import p2.Composable + +fun setContent(content: @Composable () -> Unit): Int { + content() + return 3 +} +// MODULE: main(lib, compose) +// FILE: main.kt +import p2.Composable +import p3.setContent + +fun test(): Int { + return setContent { + Greeting("test") + } +} + +@Composable +fun Greeting(name: String) { + show("hi $name!") +} + +fun show(str: String) {} \ No newline at end of file
diff --git a/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.txt b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.txt new file mode 100644 index 0000000..1046687 --- /dev/null +++ b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation.txt
@@ -0,0 +1,18 @@ +final class MainKt$test$1 { + // source: 'main.kt' + enclosing method MainKt.test()I + public final static field INSTANCE: MainKt$test$1 + inner (anonymous) class MainKt$test$1 + static method <clinit>(): void + method <init>(): void + public synthetic bridge method invoke(): java.lang.Object + public final method invoke(): void +} + +public final class MainKt { + // source: 'main.kt' + inner (anonymous) class MainKt$test$1 + public final static method Greeting(p0: java.lang.String): void + public final static method show(p0: java.lang.String): void + public final static method test(): int +}
diff --git a/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.ir.txt b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.ir.txt new file mode 100644 index 0000000..f3b8345 --- /dev/null +++ b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.ir.txt
@@ -0,0 +1,14 @@ +MODULE_FRAGMENT + FILE fqName:<root> fileName:main.kt + FUN name:Bar visibility:public modality:FINAL <> () returnType:kotlin.Unit + annotations: + Composable + BLOCK_BODY + CALL 'public final fun Foo (text: @[Composable] kotlin.Function0<kotlin.Unit>): kotlin.Unit declared in p3.FooKt' type=kotlin.Unit origin=null + text: FUN_EXPR type=kotlin.Function0<kotlin.Unit> origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.Unit + annotations: + Composable + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.Unit declared in <root>.Bar' + GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public superTypes:[kotlin.Any]' type=kotlin.Unit
diff --git a/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.kt b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.kt new file mode 100644 index 0000000..4c8fe0a --- /dev/null +++ b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.kt
@@ -0,0 +1,34 @@ +// MODULE: compose +// FILE: p2/bar.kt +package p2; + +@MustBeDocumented +@Retention(AnnotationRetention.BINARY) +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.TYPE, + AnnotationTarget.TYPE_PARAMETER, + AnnotationTarget.PROPERTY_GETTER +) +annotation class Composable +// MODULE: lib(compose) +// FILE: p3/foo.kt +package p3; + +import p2.Composable + +@Composable +public fun Foo( + text: @Composable () -> Unit, +) {} +// MODULE: main(lib, compose) +// FILE: main.kt +import p2.Composable +import p3.Foo + +@Composable +public fun Bar() { + Foo( + text = {}, // @Composable invocations can only happen from the context of a @Composable function + ) +} \ No newline at end of file
diff --git a/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.txt b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.txt new file mode 100644 index 0000000..b4464de --- /dev/null +++ b/analysis/analysis-api/testData/components/compilerFacility/compilationMultiModule/lambdaParameterWithAnnotation2.txt
@@ -0,0 +1,16 @@ +final class MainKt$Bar$1 { + // source: 'main.kt' + enclosing method MainKt.Bar()V + public final static field INSTANCE: MainKt$Bar$1 + inner (anonymous) class MainKt$Bar$1 + static method <clinit>(): void + method <init>(): void + public synthetic bridge method invoke(): java.lang.Object + public final method invoke(): void +} + +public final class MainKt { + // source: 'main.kt' + inner (anonymous) class MainKt$Bar$1 + public final static method Bar(): void +}
diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/CallAndReferenceGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/CallAndReferenceGenerator.kt index d9ffc51..94b682e 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/CallAndReferenceGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/CallAndReferenceGenerator.kt
@@ -44,10 +44,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.* -import org.jetbrains.kotlin.ir.util.dump -import org.jetbrains.kotlin.ir.util.isPrimitiveArray -import org.jetbrains.kotlin.ir.util.isUnsignedArray -import org.jetbrains.kotlin.ir.util.render +import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.calls.NewCommonSuperTypeCalculator.commonSuperType import org.jetbrains.kotlin.types.Variance @@ -1065,6 +1062,7 @@ // See KT-62598 and its fix for details. val expectedType = unsubstitutedParameterType.takeIf { visitor.annotationMode && unsubstitutedParameterType?.isArrayType == true } var irArgument = visitor.convertToIrExpression(argument, expectedType = expectedType) + irArgument.attachAnnotationsFromReturnTypeIfNeeded(parameter?.returnTypeRef) if (unsubstitutedParameterType != null) { with(visitor.implicitCastInserter) { val argumentType = argument.resolvedType.fullyExpandedType(session) @@ -1111,6 +1109,11 @@ return this } + private fun IrExpression.attachAnnotationsFromReturnTypeIfNeeded(firTypeRef: FirTypeRef?) { + if (firTypeRef == null || this !is IrFunctionExpression) return + function.copyAnnotationsFrom(firTypeRef.toIrType()) + } + private fun IrExpression.applyImplicitIntegerCoercionIfNeeded( argument: FirExpression, parameter: FirValueParameter?,