Skip to content

Commit 8cad781

Browse files
jaebaekSpace Cloud
authored and
Space Cloud
committed
K2 AA CodeGen: Set annotation for declarations out of CodeGen targets
The Fir2Ir has two steps in terms of the annotation setup: Step #1. Creating `IrMutableAnnotationContainer` Step #2. Filling `IrMutableAnnotationContainer` with annotations When the `IrMutableAnnotationContainer` is out of the source module, `Fir2IrCallableDeclarationsGenerator` sets annotations for a `IrMutableAnnotationContainer` when creating its IR object. For example, when `foo()` defined in `B.kt` is called in file `A.kt`, and `A.kt` is a part of the source module while `B.kt` is a part of library, `Fir2IrCallableDeclarationsGenerator` sets annotations of `IrFunction` for `foo()`. On the other hand, if `foo()` is a part of the source module, `Fir2IrCallableDeclarationsGenerator` does not set annotations for it when creating `IrMutableAnnotationContainer` (Step #1). Later, when it fills contents of `IrMutableAnnotationContainer`, it conducts Step #2. However, if `foo()` is a part of the source module, but it is not in a compile target file, filling contents of `foo()` does not happen (because it is not a compile target). As a result, set up annotations for `foo()` is not done by Fir2Ir. This missing annotation setup causes a serious bug for Android Compose app. This commit updates code to decide whether we have to set up the annotations of `IrMutableAnnotationContainer` in Step #1 or not. We have to check not only if it is a part of the source code but also if it is a part of compile targets or not. ^KT-66532 Fixed
1 parent 395167f commit 8cad781

File tree

15 files changed

+227
-8
lines changed

15 files changed

+227
-8
lines changed

analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/compilerFacility/FirIdeNormalAnalysisSourceModuleFirPluginPrototypeMultiModuleCompilerFacilityTestGenerated.java

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.analysis.test.framework.base.AbstractAnalysisApiBase
1717
import org.jetbrains.kotlin.analysis.test.framework.services.expressionMarkerProvider
1818
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
1919
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
20+
import org.jetbrains.kotlin.backend.jvm.ir.parentClassId
2021
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
2122
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
2223
import org.jetbrains.kotlin.codegen.BytecodeListingTextCollectingVisitor
@@ -27,18 +28,35 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
2728
import org.jetbrains.kotlin.config.JVMConfigurationKeys
2829
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
2930
import org.jetbrains.kotlin.fir.plugin.services.PluginRuntimeAnnotationsProvider
31+
import org.jetbrains.kotlin.ir.IrElement
32+
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
33+
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
3034
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
35+
import org.jetbrains.kotlin.ir.expressions.IrCall
36+
import org.jetbrains.kotlin.ir.expressions.IrFieldAccessExpression
37+
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
3138
import org.jetbrains.kotlin.ir.util.DumpIrTreeOptions
3239
import org.jetbrains.kotlin.ir.util.dump
33-
import org.jetbrains.kotlin.psi.*
40+
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
41+
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
42+
import org.jetbrains.kotlin.name.ClassId
43+
import org.jetbrains.kotlin.name.FqName
44+
import org.jetbrains.kotlin.name.FqNameUnsafe
45+
import org.jetbrains.kotlin.psi.KtCodeFragment
46+
import org.jetbrains.kotlin.psi.KtElement
47+
import org.jetbrains.kotlin.psi.KtFile
48+
import org.jetbrains.kotlin.psi.KtPsiFactory
3449
import org.jetbrains.kotlin.test.Constructor
3550
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
3651
import org.jetbrains.kotlin.test.directives.ConfigurationDirectives
3752
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
3853
import org.jetbrains.kotlin.test.directives.model.DirectiveApplicability
3954
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
4055
import org.jetbrains.kotlin.test.model.TestModule
41-
import org.jetbrains.kotlin.test.services.*
56+
import org.jetbrains.kotlin.test.services.EnvironmentConfigurator
57+
import org.jetbrains.kotlin.test.services.RuntimeClasspathProvider
58+
import org.jetbrains.kotlin.test.services.TestServices
59+
import org.jetbrains.kotlin.test.services.assertions
4260
import org.jetbrains.org.objectweb.asm.ClassReader
4361
import org.jetbrains.org.objectweb.asm.Opcodes
4462
import org.jetbrains.org.objectweb.asm.Type
@@ -75,7 +93,8 @@ abstract class AbstractCompilerFacilityTest : AbstractAnalysisApiBasedTest() {
7593
override fun doTestByMainFile(mainFile: KtFile, mainModule: TestModule, testServices: TestServices) {
7694
val testFile = mainModule.files.single { it.name == mainFile.name }
7795

78-
val irCollector = CollectingIrGenerationExtension()
96+
val annotationToCheckCalls = mainModule.directives[Directives.CHECK_CALLS_WITH_ANNOTATION].singleOrNull()
97+
val irCollector = CollectingIrGenerationExtension(annotationToCheckCalls)
7998

8099
val project = mainFile.project
81100
project.extensionArea.getExtensionPoint(IrGenerationExtension.extensionPointName)
@@ -109,6 +128,12 @@ abstract class AbstractCompilerFacilityTest : AbstractAnalysisApiBasedTest() {
109128
if (result is KtCompilationResult.Success) {
110129
testServices.assertions.assertEqualsToTestDataFileSibling(irCollector.result, extension = ".ir.txt")
111130
}
131+
132+
if (annotationToCheckCalls != null) {
133+
testServices.assertions.assertEqualsToTestDataFileSibling(
134+
irCollector.functionsWithAnnotationToCheckCalls.joinToString("\n"), extension = ".check_calls.txt"
135+
)
136+
}
112137
}
113138
}
114139

@@ -187,6 +212,10 @@ abstract class AbstractCompilerFacilityTest : AbstractAnalysisApiBasedTest() {
187212
val ATTACH_DUPLICATE_STDLIB by directive(
188213
"Attach the 'stdlib-jvm-minimal-for-test' library to simulate duplicate stdlib dependency"
189214
)
215+
216+
val CHECK_CALLS_WITH_ANNOTATION by stringDirective(
217+
"Check whether all functions of calls and getters of properties with a given annotation are listed in *.check_calls.txt or not"
218+
)
190219
}
191220
}
192221

@@ -220,10 +249,12 @@ internal fun createCodeFragment(ktFile: KtFile, module: TestModule, testServices
220249
}
221250
}
222251

223-
private class CollectingIrGenerationExtension : IrGenerationExtension {
252+
private class CollectingIrGenerationExtension(private val annotationToCheckCalls: String?) : IrGenerationExtension {
224253
lateinit var result: String
225254
private set
226255

256+
val functionsWithAnnotationToCheckCalls: MutableSet<String> = mutableSetOf()
257+
227258
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
228259
assertFalse { ::result.isInitialized }
229260

@@ -235,5 +266,49 @@ private class CollectingIrGenerationExtension : IrGenerationExtension {
235266
)
236267

237268
result = moduleFragment.dump(dumpOptions)
269+
270+
annotationToCheckCalls?.let { annotationFqName ->
271+
moduleFragment.accept(
272+
CheckCallsWithAnnotationVisitor(annotationFqName) { functionsWithAnnotationToCheckCalls.add(it.name.asString()) }, null
273+
)
274+
}
275+
}
276+
277+
/**
278+
* This class recursively visits all calls of functions and getters, and if the function or the getter used for a call has
279+
* an annotation whose FqName is [annotationFqName], it runs [handleFunctionWithAnnotation] for the function or the getter.
280+
*/
281+
private class CheckCallsWithAnnotationVisitor(
282+
private val annotationFqName: String,
283+
private val handleFunctionWithAnnotation: (declaration: IrDeclarationWithName) -> Unit,
284+
) : IrElementVisitorVoid {
285+
val annotationClassId by lazy {
286+
val annotationFqNameUnsafe = FqNameUnsafe(annotationFqName)
287+
ClassId(FqName(annotationFqNameUnsafe.parent()), FqName(annotationFqNameUnsafe.shortName().asString()), false)
288+
}
289+
290+
override fun visitElement(element: IrElement) {
291+
element.acceptChildrenVoid(this)
292+
}
293+
294+
override fun visitCall(expression: IrCall) {
295+
@OptIn(UnsafeDuringIrConstructionAPI::class)
296+
val function = expression.symbol.owner
297+
if (function.containsAnnotationToCheckCalls()) {
298+
handleFunctionWithAnnotation(function)
299+
}
300+
}
301+
302+
override fun visitFieldAccess(expression: IrFieldAccessExpression) {
303+
@OptIn(UnsafeDuringIrConstructionAPI::class)
304+
val field = expression.symbol.owner
305+
if (field.containsAnnotationToCheckCalls()) {
306+
handleFunctionWithAnnotation(field)
307+
}
308+
}
309+
310+
private fun IrAnnotationContainer.containsAnnotationToCheckCalls() =
311+
@OptIn(UnsafeDuringIrConstructionAPI::class)
312+
annotations.any { it.symbol.owner.parentClassId == annotationClassId }
238313
}
239314
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BookmarkButton
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
MODULE_FRAGMENT
2+
FILE fqName:<root> fileName:main.kt
3+
FUN name:PostCardSimple visibility:public modality:FINAL <> (navigateToArticle:kotlin.Function1<kotlin.String, kotlin.Unit>, isFavorite:kotlin.Boolean, onToggleFavorite:kotlin.Function0<kotlin.Unit>) returnType:kotlin.Unit
4+
annotations:
5+
MyComposable
6+
VALUE_PARAMETER name:navigateToArticle index:0 type:kotlin.Function1<kotlin.String, kotlin.Unit>
7+
VALUE_PARAMETER name:isFavorite index:1 type:kotlin.Boolean
8+
VALUE_PARAMETER name:onToggleFavorite index:2 type:kotlin.Function0<kotlin.Unit>
9+
BLOCK_BODY
10+
CALL 'public final fun BookmarkButton (isBookmarked: kotlin.Boolean, onClick: kotlin.Function0<kotlin.Unit>): kotlin.Unit declared in p3.JetnewsIconsKt' type=kotlin.Unit origin=null
11+
isBookmarked: GET_VAR 'isFavorite: kotlin.Boolean declared in <root>.PostCardSimple' type=kotlin.Boolean origin=null
12+
onClick: GET_VAR 'onToggleFavorite: kotlin.Function0<kotlin.Unit> declared in <root>.PostCardSimple' type=kotlin.Function0<kotlin.Unit> origin=null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// WITH_FIR_TEST_COMPILER_PLUGIN
2+
// DUMP_IR
3+
// CHECK_CALLS_WITH_ANNOTATION: org.jetbrains.kotlin.fir.plugin.MyComposable
4+
5+
// MODULE: main
6+
// FILE: main.kt
7+
import org.jetbrains.kotlin.fir.plugin.MyComposable
8+
import p3.BookmarkButton
9+
10+
@MyComposable
11+
fun PostCardSimple(
12+
navigateToArticle: (String) -> Unit,
13+
isFavorite: Boolean,
14+
onToggleFavorite: () -> Unit
15+
) {
16+
BookmarkButton(
17+
isBookmarked = isFavorite,
18+
onClick = onToggleFavorite,
19+
)
20+
}
21+
// FILE: utils/JetnewsIcons.kt
22+
package p3
23+
24+
import org.jetbrains.kotlin.fir.plugin.MyComposable
25+
26+
@MyComposable
27+
fun BookmarkButton(
28+
isBookmarked: Boolean,
29+
onClick: () -> Unit,
30+
) {
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public final class MainKt {
2+
// source: 'main.kt'
3+
public final static method PostCardSimple(p0: kotlin.jvm.functions.Function1, p1: boolean, p2: kotlin.jvm.functions.Function0): void
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<get-foo>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
MODULE_FRAGMENT
2+
FILE fqName:<root> fileName:main.kt
3+
FUN name:Greeting visibility:public modality:FINAL <> () returnType:kotlin.String
4+
annotations:
5+
MyComposable
6+
BLOCK_BODY
7+
RETURN type=kotlin.Nothing from='public final fun Greeting (): kotlin.String declared in <root>'
8+
STRING_CONCATENATION type=kotlin.String
9+
CONST String type=kotlin.String value="Hi "
10+
CALL 'public final fun <get-foo> (): kotlin.Int declared in p3.FooKt' type=kotlin.Int origin=GET_PROPERTY
11+
CONST String type=kotlin.String value="!"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// WITH_FIR_TEST_COMPILER_PLUGIN
2+
// DUMP_IR
3+
// CHECK_CALLS_WITH_ANNOTATION: org.jetbrains.kotlin.fir.plugin.MyComposable
4+
5+
// MODULE: main
6+
// FILE: main.kt
7+
import org.jetbrains.kotlin.fir.plugin.MyComposable
8+
import p3.foo
9+
10+
@MyComposable
11+
fun Greeting(): String {
12+
return "Hi $foo!"
13+
}
14+
15+
// FILE: p3/foo.kt
16+
package p3
17+
18+
import org.jetbrains.kotlin.fir.plugin.MyComposable
19+
20+
private var foo_ = 0
21+
22+
fun setFoo(newFoo: Int) {
23+
foo_ = newFoo
24+
}
25+
26+
val foo: Int
27+
@MyComposable get() = foo_ + 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public final class MainKt {
2+
// source: 'main.kt'
3+
public final static method Greeting(): java.lang.String
4+
}

compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrComponents.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.backend
77

88
import org.jetbrains.kotlin.fir.FirSession
99
import org.jetbrains.kotlin.fir.backend.generators.*
10+
import org.jetbrains.kotlin.fir.declarations.FirFile
1011
import org.jetbrains.kotlin.fir.resolve.ScopeSession
1112
import org.jetbrains.kotlin.fir.signaturer.FirBasedSignatureComposer
1213
import org.jetbrains.kotlin.ir.IrLock
@@ -62,6 +63,24 @@ interface Fir2IrComponents {
6263

6364
val annotationsFromPluginRegistrar: Fir2IrIrGeneratedDeclarationsRegistrar
6465

66+
/**
67+
* A set of FIR files serving as input for the fir2ir ([Fir2IrConverter.createIrModuleFragment] function) for conversion to IR.
68+
*
69+
* We set annotations for IR objects, such as IrFunction, in two scenarios:
70+
* 1. For FIR declared in library or precompiled: when creating IR object from FIR
71+
* 2. For FIR declared in a source module: when filling contents of IR object in Fir2IrVisitor
72+
*
73+
* Since Fir2IrVisitor will recursively visit all FIR objects and generate IR objects for them, we handle the first scenario
74+
* above as a corner case.
75+
*
76+
* However, when we use CodeGen analysis API, even FIRs declared in the source module can be out of the compile target files,
77+
* because we can run the CodeGen only for a few files of the source module. We use [filesBeingCompiled] for that case
78+
* to determine whether a given FIR is declared in a source file to be compiled or not for the CodeGen API. If it is not
79+
* declared in a file to be compiled (i.e., target of CodeGen), we have to set annotations for IR when creating its IR like
80+
* the first scenario above. We set [filesBeingCompiled] as `null` if we do not use the CodeGen analysis API.
81+
*/
82+
val filesBeingCompiled: Set<FirFile>?
83+
6584
interface Manglers {
6685
val irMangler: KotlinMangler.IrMangler
6786
val firMangler: FirMangler

compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrComponentsStorage.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.backend
77

88
import org.jetbrains.kotlin.fir.FirSession
99
import org.jetbrains.kotlin.fir.backend.generators.*
10+
import org.jetbrains.kotlin.fir.declarations.FirFile
1011
import org.jetbrains.kotlin.fir.descriptors.FirModuleDescriptor
1112
import org.jetbrains.kotlin.fir.resolve.ScopeSession
1213
import org.jetbrains.kotlin.fir.signaturer.FirBasedSignatureComposer
@@ -25,6 +26,7 @@ class Fir2IrComponentsStorage(
2526
override val extensions: Fir2IrExtensions,
2627
override val configuration: Fir2IrConfiguration,
2728
override val visibilityConverter: Fir2IrVisibilityConverter,
29+
override val filesBeingCompiled: Set<FirFile>?,
2830
irFakeOverrideBuilderProvider: (IrBuiltIns) -> IrFakeOverrideBuilder,
2931
moduleDescriptor: FirModuleDescriptor,
3032
commonMemberStorage: Fir2IrCommonMemberStorage,

compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrConverter.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import org.jetbrains.kotlin.ir.util.*
6060
import org.jetbrains.kotlin.name.Name
6161
import org.jetbrains.kotlin.platform.jvm.isJvm
6262
import org.jetbrains.kotlin.psi.KtFile
63+
import org.jetbrains.kotlin.utils.addToStdlib.runIf
6364

6465
class Fir2IrConverter(
6566
private val moduleDescriptor: FirModuleDescriptor,
@@ -727,7 +728,13 @@ class Fir2IrConverter(
727728
session.lazyDeclarationResolver.disableLazyResolveContractChecks()
728729
val moduleDescriptor = FirModuleDescriptor.createSourceModuleDescriptor(session, kotlinBuiltIns)
729730
val components = Fir2IrComponentsStorage(
730-
session, scopeSession, irFactory, fir2IrExtensions, fir2IrConfiguration, visibilityConverter,
731+
session,
732+
scopeSession,
733+
irFactory,
734+
fir2IrExtensions,
735+
fir2IrConfiguration,
736+
visibilityConverter,
737+
runIf(fir2IrConfiguration.allowNonCachedDeclarations) { firFiles.toSet() },
731738
{ irBuiltins ->
732739
IrFakeOverrideBuilder(
733740
typeContextProvider(irBuiltins),
@@ -738,7 +745,11 @@ class Fir2IrConverter(
738745
fir2IrExtensions.externalOverridabilityConditions
739746
)
740747
},
741-
moduleDescriptor, commonMemberStorage, irMangler, specialSymbolProvider, initializedIrBuiltIns
748+
moduleDescriptor,
749+
commonMemberStorage,
750+
irMangler,
751+
specialSymbolProvider,
752+
initializedIrBuiltIns
742753
)
743754

744755
fir2IrExtensions.registerDeclarations(commonMemberStorage.symbolTable)

compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/Fir2IrCallableDeclarationsGenerator.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package org.jetbrains.kotlin.fir.backend.generators
77

8-
import com.intellij.openapi.progress.ProcessCanceledException
98
import org.jetbrains.kotlin.descriptors.*
109
import org.jetbrains.kotlin.fir.*
1110
import org.jetbrains.kotlin.fir.backend.*
@@ -26,6 +25,8 @@ import org.jetbrains.kotlin.fir.lazy.Fir2IrLazyProperty
2625
import org.jetbrains.kotlin.fir.references.toResolvedBaseSymbol
2726
import org.jetbrains.kotlin.fir.resolve.calls.FirSimpleSyntheticPropertySymbol
2827
import org.jetbrains.kotlin.fir.resolve.isKFunctionInvoke
28+
import org.jetbrains.kotlin.fir.resolve.providers.firProvider
29+
import org.jetbrains.kotlin.fir.resolve.providers.getContainingFile
2930
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
3031
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
3132
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
@@ -965,10 +966,18 @@ class Fir2IrCallableDeclarationsGenerator(val components: Fir2IrComponents) : Fi
965966
) {
966967
if ((firAnnotationContainer as? FirDeclaration)?.let { it.isFromLibrary || it.isPrecompiled } == true
967968
|| origin == IrDeclarationOrigin.FAKE_OVERRIDE
969+
// When `firAnnotationContainer` is not in a compile target file, we will not fill contents for
970+
// this annotation container later. Therefore, we have to set its annotations here.
971+
|| firAnnotationContainer.isDeclaredInFilesBeingCompiled()
968972
) {
969973
annotationGenerator.generate(this, firAnnotationContainer)
970974
}
971975
}
976+
977+
private fun FirAnnotationContainer.isDeclaredInFilesBeingCompiled(): Boolean {
978+
if (filesBeingCompiled == null || this !is FirDeclaration) return false
979+
return moduleData.session.firProvider.getContainingFile(symbol) !in filesBeingCompiled
980+
}
972981
}
973982

974983
internal fun IrDeclaration.setParent(irParent: IrDeclarationParent?) {

plugins/fir-plugin-prototype/plugin-annotations/src/commonMain/kotlin/org/jetbrains/kotlin/fir/plugin/annotations.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ annotation class SupertypeWithTypeArgument(val kClass: KClass<*>)
3838

3939
annotation class MetaSupertype
4040

41-
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
41+
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.PROPERTY_GETTER)
4242
annotation class MyComposable
4343

4444
annotation class AllPropertiesConstructor

0 commit comments

Comments
 (0)