[FIR] Create CFG for files to track top-level property initialization
In order to properly analyze top-level property initialization, a control-flow graph must be created for FirFiles. This change adds the foundation for the file CFG and updates body resolve to create the CFG. Checking the CFG for proper initialization is separated into a following change to ease code review. KT-56683
This commit is contained in:
+1
-1
@@ -42,7 +42,7 @@ sealed class LLFirResolveTarget(
|
||||
abstract fun forEachTarget(action: (FirElementWithResolveState) -> Unit)
|
||||
|
||||
override fun toString(): String = buildString {
|
||||
append(this::class.simpleName)
|
||||
append(this@LLFirResolveTarget::class.simpleName)
|
||||
append("(")
|
||||
buildList {
|
||||
add(firFile.name)
|
||||
|
||||
+1
-1
@@ -161,5 +161,5 @@ private fun KtNamedFunction.isReanalyzableContainer(): Boolean = hasBlockBody()
|
||||
|
||||
private fun KtPropertyAccessor.isReanalyzableContainer(): Boolean = isSetter || hasBlockBody() || property.typeReference != null
|
||||
|
||||
private fun KtProperty.isReanalyzableContainer(): Boolean = typeReference != null && (isTopLevel || !hasDelegateExpressionOrInitializer())
|
||||
private fun KtProperty.isReanalyzableContainer(): Boolean = typeReference != null && !hasDelegateExpressionOrInitializer()
|
||||
|
||||
|
||||
+2
@@ -115,6 +115,8 @@ internal class FileStructure private constructor(
|
||||
}
|
||||
|
||||
fun getAllDiagnosticsForFile(diagnosticCheckerFilter: DiagnosticCheckerFilter): Collection<KtPsiDiagnostic> {
|
||||
// TODO, KT-60799: Add a new FileStructure for file diagnostics
|
||||
firFile.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
|
||||
val structureElements = getAllStructureElements()
|
||||
return buildList {
|
||||
collectDiagnosticsFromStructureElements(structureElements, diagnosticCheckerFilter)
|
||||
|
||||
+44
-2
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirResolveCont
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.contracts.FirContractsDslNames
|
||||
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isUsedInControlFlowGraphBuilderForClass
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isUsedInControlFlowGraphBuilderForFile
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
@@ -114,6 +115,19 @@ private class LLFirBodyTargetResolver(
|
||||
|
||||
return true
|
||||
}
|
||||
is FirFile -> {
|
||||
if (target.resolvePhase >= resolverPhase) return true
|
||||
|
||||
resolveFileAnnotationContainerIfNeeded(target)
|
||||
|
||||
// resolve file CFG graph here, to do this we need to have property blocks resoled
|
||||
resolveMembersForControlFlowGraph(target)
|
||||
performCustomResolveUnderLock(target) {
|
||||
calculateControlFlowGraph(target)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
is FirCodeFragment -> {
|
||||
resolveCodeFragmentContext(target)
|
||||
performCustomResolveUnderLock(target) {
|
||||
@@ -156,6 +170,35 @@ private class LLFirBodyTargetResolver(
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateControlFlowGraph(target: FirFile) {
|
||||
checkWithAttachment(
|
||||
target.controlFlowGraphReference == null,
|
||||
{ "'controlFlowGraphReference' should be 'null' if the file phase < $resolverPhase)" },
|
||||
) {
|
||||
withFirEntry("firFile", target)
|
||||
}
|
||||
|
||||
val dataFlowAnalyzer = transformer.declarationsTransformer.dataFlowAnalyzer
|
||||
dataFlowAnalyzer.enterFile(target, buildGraph = true)
|
||||
val controlFlowGraph = dataFlowAnalyzer.exitFile()
|
||||
?: errorWithAttachment("CFG should not be 'null' as 'buildGraph' is specified") {
|
||||
withFirEntry("firFile", target)
|
||||
}
|
||||
|
||||
target.replaceControlFlowGraphReference(FirControlFlowGraphReferenceImpl(controlFlowGraph))
|
||||
}
|
||||
|
||||
private fun resolveMembersForControlFlowGraph(target: FirFile) {
|
||||
withFile(target) {
|
||||
for (member in target.declarations) {
|
||||
if (member is FirControlFlowGraphOwner && member.isUsedInControlFlowGraphBuilderForFile) {
|
||||
member.lazyResolveToPhase(resolverPhase.previous)
|
||||
performResolve(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveCodeFragmentContext(firCodeFragment: FirCodeFragment) {
|
||||
val ktCodeFragment = firCodeFragment.psi as? KtCodeFragment
|
||||
?: errorWithAttachment("Code fragment source not found") {
|
||||
@@ -191,7 +234,7 @@ private class LLFirBodyTargetResolver(
|
||||
|
||||
override fun doLazyResolveUnderLock(target: FirElementWithResolveState) {
|
||||
when (target) {
|
||||
is FirRegularClass, is FirCodeFragment -> error("Should have been resolved in ${::doResolveWithoutLock.name}")
|
||||
is FirFile, is FirRegularClass, is FirCodeFragment -> error("Should have been resolved in ${::doResolveWithoutLock.name}")
|
||||
is FirConstructor -> resolve(target, BodyStateKeepers.CONSTRUCTOR)
|
||||
is FirFunction -> resolve(target, BodyStateKeepers.FUNCTION)
|
||||
is FirProperty -> resolve(target, BodyStateKeepers.PROPERTY)
|
||||
@@ -202,7 +245,6 @@ private class LLFirBodyTargetResolver(
|
||||
is FirDanglingModifierList,
|
||||
is FirFileAnnotationsContainer,
|
||||
is FirTypeAlias,
|
||||
is FirFile,
|
||||
-> {
|
||||
// No bodies here
|
||||
}
|
||||
|
||||
+3
@@ -190,6 +190,9 @@ var KtFile.originalKtFile by UserDataProperty(ORIGINAL_KT_FILE_KEY)
|
||||
|
||||
|
||||
private fun KtClassLikeDeclaration.findFir(provider: FirProvider): FirClassLikeDeclaration? {
|
||||
// TODO, KTIJ-26848: this is a workaround for IDE index inconsistency for unnamed classes
|
||||
if (name == null) return null
|
||||
|
||||
return if (provider is LLFirProvider) {
|
||||
provider.getFirClassifierByDeclaration(this)
|
||||
} else {
|
||||
|
||||
@@ -6,4 +6,4 @@ var x: Int/* ReanalyzablePropertyStructureElement */
|
||||
|
||||
val y = 42/* NonReanalyzableNonClassDeclarationStructureElement */
|
||||
|
||||
var z: Int = 15/* ReanalyzablePropertyStructureElement */
|
||||
var z: Int = 15/* NonReanalyzableNonClassDeclarationStructureElement */
|
||||
|
||||
@@ -4,7 +4,7 @@ fun () {/* ReanalyzableFunctionStructureElement */
|
||||
|
||||
}
|
||||
|
||||
val : Int = 4/* ReanalyzablePropertyStructureElement */
|
||||
val : Int = 4/* NonReanalyzableNonClassDeclarationStructureElement */
|
||||
|
||||
var : Int/* ReanalyzablePropertyStructureElement */
|
||||
get() = 4
|
||||
|
||||
+1
-7
@@ -1,7 +1 @@
|
||||
BEFORE MODIFICATION:
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val i: R|kotlin/Int| = Int(42)
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
|
||||
AFTER MODIFICATION:
|
||||
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val i: R|kotlin/Int| = LAZY_EXPRESSION
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
|
||||
Vendored
+1
-18
@@ -1,18 +1 @@
|
||||
BEFORE MODIFICATION:
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val x: R|kotlin/Int|by <Unresolved name: lazy>#(<L> = [ResolvedTo(BODY_RESOLVE)] lazy@fun <anonymous>(): R|kotlin/Int| <inline=Unknown> {
|
||||
local final [ResolvedTo(BODY_RESOLVE)] fun doSmth([ResolvedTo(BODY_RESOLVE)] i: R|kotlin/String|): R|kotlin/Int| {
|
||||
^doSmth Int(4)
|
||||
}
|
||||
|
||||
^ R|<local>/doSmth|(String(str))
|
||||
}
|
||||
)
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
|
||||
^ D|/x|.<Unresolved name: getValue>#(Null(null), ::R|/x|)
|
||||
}
|
||||
|
||||
AFTER MODIFICATION:
|
||||
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val x: R|kotlin/Int|by LAZY_EXPRESSION
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
|
||||
^ D|/x|.<Unresolved name: getValue>#(Null(null), ::R|/x|)
|
||||
}
|
||||
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
|
||||
+1
-18
@@ -1,18 +1 @@
|
||||
BEFORE MODIFICATION:
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val <no name provided>: R|kotlin/Int|by <Unresolved name: lazy>#(<L> = [ResolvedTo(BODY_RESOLVE)] lazy@fun <anonymous>(): R|kotlin/Int| <inline=Unknown> {
|
||||
local final [ResolvedTo(BODY_RESOLVE)] fun doSmth([ResolvedTo(BODY_RESOLVE)] i: R|kotlin/String|): R|kotlin/Int| {
|
||||
^doSmth Int(4)
|
||||
}
|
||||
|
||||
^ R|<local>/doSmth|(String(str))
|
||||
}
|
||||
)
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
|
||||
^ D|/<no name provided>|.<Unresolved name: getValue>#(Null(null), ::R|/<no name provided>|)
|
||||
}
|
||||
|
||||
AFTER MODIFICATION:
|
||||
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val <no name provided>: R|kotlin/Int|by LAZY_EXPRESSION
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
|
||||
^ D|/<no name provided>|.<Unresolved name: getValue>#(Null(null), ::R|/<no name provided>|)
|
||||
}
|
||||
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
|
||||
Vendored
+1
-11
@@ -1,11 +1 @@
|
||||
BEFORE MODIFICATION:
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val x: R|kotlin/Int|by <Unresolved name: ErrorDelegate>#()
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
|
||||
^ D|/x|.<Unresolved name: getValue>#(Null(null), ::R|/x|)
|
||||
}
|
||||
|
||||
AFTER MODIFICATION:
|
||||
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val x: R|kotlin/Int|by LAZY_EXPRESSION
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
|
||||
^ D|/x|.<Unresolved name: getValue>#(Null(null), ::R|/x|)
|
||||
}
|
||||
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
|
||||
Vendored
+1
-7
@@ -1,7 +1 @@
|
||||
BEFORE MODIFICATION:
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val x: R|kotlin/Int| = R|/doSmth|(String(str))
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
|
||||
AFTER MODIFICATION:
|
||||
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val x: R|kotlin/Int| = LAZY_EXPRESSION
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
|
||||
+1
-7
@@ -1,7 +1 @@
|
||||
BEFORE MODIFICATION:
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val <no name provided>: R|kotlin/Int| = R|/doSmth|(String(str))
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
|
||||
AFTER MODIFICATION:
|
||||
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val <no name provided>: R|kotlin/Int| = LAZY_EXPRESSION
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
|
||||
@@ -480,12 +480,12 @@ FILE: [ResolvedTo(BODY_RESOLVE)] fileElements.kt
|
||||
public [ResolvedTo(STATUS)] [ContainingClassKey=Anno] get(): R|kotlin/Int|
|
||||
|
||||
}
|
||||
public? final? [ResolvedTo(RAW_FIR)] val a: <implicit> = LAZY_EXPRESSION
|
||||
public? [ResolvedTo(RAW_FIR)] get(): <implicit>
|
||||
public? final? [ResolvedTo(RAW_FIR)] val b: <implicit> = LAZY_EXPRESSION
|
||||
public? [ResolvedTo(RAW_FIR)] get(): <implicit>
|
||||
public? final? [ResolvedTo(RAW_FIR)] val c: <implicit> = LAZY_EXPRESSION
|
||||
public? [ResolvedTo(RAW_FIR)] get(): <implicit>
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val a: R|kotlin/Int| = Int(1)
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val b: R|kotlin/Int| = Int(2).R|kotlin/Int.plus|(R|one/a|)
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
public final [ResolvedTo(BODY_RESOLVE)] val c: R|kotlin/Int| = R|one/b|.R|kotlin/Int.plus|(R|one/a|)
|
||||
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
|
||||
public? final? [ResolvedTo(RAW_FIR)] fun test1(): R|kotlin/Unit| { LAZY_BLOCK }
|
||||
@Deprecated[Unresolved](String()) @Anno[Unresolved](IntegerLiteral(2)) public? final? [ResolvedTo(RAW_FIR)] fun test2(): R|kotlin/Unit| { LAZY_BLOCK }
|
||||
public? final? [ResolvedTo(RAW_FIR)] class A : R|kotlin/Any| {
|
||||
|
||||
Reference in New Issue
Block a user