FIR IDE: separate diagnostics collection and checker context collection

This commit is contained in:
Ilya Kirillov
2021-03-24 17:58:05 +01:00
parent 29a0c448a6
commit e79c133331
12 changed files with 405 additions and 349 deletions
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerConte
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
import org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor
import org.jetbrains.kotlin.fir.analysis.collectors.components.*
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
@@ -34,23 +35,6 @@ internal abstract class AbstractFirIdeDiagnosticsCollector(
) : AbstractDiagnosticCollector(
session
) {
private val beforeElementDiagnosticCollectionHandler: BeforeElementDiagnosticCollectionHandler? =
session.beforeElementDiagnosticCollectionHandler
@Suppress("LeakingThis")
override val visitor = run {
val returnTypeCalculator = createReturnTypeCalculatorForIDE(
session,
ScopeSession(),
ImplicitBodyResolveComputationSession(),
::FirIdeDesignatedBodyResolveTransformerForReturnTypeCalculator
)
CollectingVisitor(
PersistentCheckerContext(this, returnTypeCalculator),
components
)
}
init {
val declarationCheckers = CheckersFactory.createDeclarationCheckers(useExtendedCheckers)
val expressionCheckers = CheckersFactory.createExpressionCheckers(useExtendedCheckers)
@@ -80,19 +64,6 @@ internal abstract class AbstractFirIdeDiagnosticsCollector(
reporter = Reporter()
}
inner class CollectingVisitor(
context: PersistentCheckerContext,
components: List<AbstractDiagnosticCollectorComponent>
) : DiagnosticCollectingVisitor(context, components) {
override fun beforeRunningSingleComponentOnElement(element: FirElement) {
checkCanceled()
}
override fun beforeRunningAllComponentsOnElement(element: FirElement) {
beforeElementDiagnosticCollectionHandler?.beforeCollectingForElement(element)
}
}
override fun getCollectedDiagnostics(): List<FirDiagnostic<*>> {
// Not necessary in IDE
@@ -5,8 +5,22 @@
package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.SessionHolderImpl
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir.PersistenceContextCollector
internal abstract class FileStructureElementDiagnosticRetriever {
abstract fun retrieve(firFile: FirFile, collector: FileStructureElementDiagnosticsCollector): FileStructureElementDiagnosticList
}
internal class SingleNonLocalDeclarationDiagnosticRetriever(
private val declaration: FirDeclaration
) : FileStructureElementDiagnosticRetriever() {
override fun retrieve(firFile: FirFile, collector: FileStructureElementDiagnosticsCollector): FileStructureElementDiagnosticList {
val sessionHolder = SessionHolderImpl(firFile.session, ScopeSession())
val context = PersistenceContextCollector.collectContext(sessionHolder, firFile, declaration)
return collector.collectForStructureElement(declaration, context)
}
}
@@ -7,10 +7,12 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext
import org.jetbrains.kotlin.fir.analysis.collectors.DiagnosticCollectorDeclarationAction
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir.PersistentCheckerContextFactory
import org.jetbrains.kotlin.idea.fir.low.level.api.util.addValueFor
internal class FileStructureElementDiagnosticsCollector private constructor(private val useExtendedCheckers: Boolean) {
@@ -20,22 +22,27 @@ internal class FileStructureElementDiagnosticsCollector private constructor(priv
}
fun collectForStructureElement(
firFile: FirFile,
firDeclaration: FirDeclaration,
initialContext: PersistentCheckerContext?,
onDeclarationExit: (FirDeclaration) -> Unit = {},
onDeclarationEnter: (FirDeclaration) -> DiagnosticCollectorDeclarationAction,
onDeclarationEnter: (FirDeclaration) -> DiagnosticCollectorDeclarationAction = {
DiagnosticCollectorDeclarationAction.CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
},
): FileStructureElementDiagnosticList =
FirIdeStructureElementDiagnosticsCollector(
firFile.session,
firDeclaration.session,
initialContext,
useExtendedCheckers,
onDeclarationEnter,
onDeclarationExit
).let { collector ->
collector.collectDiagnostics(firFile)
collector.collectDiagnostics(firDeclaration)
FileStructureElementDiagnosticList(collector.result)
}
private class FirIdeStructureElementDiagnosticsCollector(
session: FirSession,
initialContext: PersistentCheckerContext?,
useExtendedCheckers: Boolean,
private val onDeclarationEnter: (FirDeclaration) -> DiagnosticCollectorDeclarationAction,
private val onDeclarationExit: (FirDeclaration) -> Unit
@@ -49,13 +56,18 @@ internal class FileStructureElementDiagnosticsCollector private constructor(priv
result.addValueFor(diagnostic.psiElement, diagnostic)
}
override fun getDeclarationActionOnDeclarationEnter(
declaration: FirDeclaration,
): DiagnosticCollectorDeclarationAction =
onDeclarationEnter.invoke(declaration)
override val visitor = object : FirIdeDiagnosticVisitor(
initialContext ?: PersistentCheckerContextFactory.createEmptyPersistenceCheckerContext(this),
components
) {
override fun getDeclarationActionOnDeclarationEnter(
declaration: FirDeclaration,
): DiagnosticCollectorDeclarationAction =
onDeclarationEnter.invoke(declaration)
override fun onDeclarationExit(declaration: FirDeclaration) {
onDeclarationExit.invoke(declaration)
override fun onDeclarationExit(declaration: FirDeclaration) {
onDeclarationExit.invoke(declaration)
}
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext
import org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor
import org.jetbrains.kotlin.fir.analysis.collectors.components.AbstractDiagnosticCollectorComponent
import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir.PersistentCheckerContextFactory
import org.jetbrains.kotlin.idea.fir.low.level.api.util.checkCanceled
open class FirIdeDiagnosticVisitor(
context: PersistentCheckerContext,
components: List<AbstractDiagnosticCollectorComponent>
) : CheckerRunningDiagnosticCollectorVisitor(context, components) {
override fun beforeRunningSingleComponentOnElement(element: FirElement) {
checkCanceled()
}
override fun beforeRunningAllComponentsOnElement(element: FirElement) {
session.beforeElementDiagnosticCollectionHandler?.beforeCollectingForElement(element)
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.renderWithType
import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.idea.fir.low.level.api.util.collectDesignation
private class ContextCollectingDiagnosticCollectorVisitor private constructor(
sessionHolder: SessionHolder,
private val designation: Iterator<FirDeclaration>,
) : AbstractDiagnosticCollectorVisitor(
PersistentCheckerContextFactory.createEmptyPersistenceCheckerContext(sessionHolder),
components = emptyList()
) {
private var savedContext: PersistentCheckerContext? = null
override fun goToNestedDeclarations(element: FirElement) {
if (designation.hasNext()) {
designation.next().accept(this, null)
} else {
savedContext = context
}
}
override fun runComponents(element: FirElement) {}
companion object {
fun collect(sessionHolder: SessionHolder, firFile: FirFile, designation: List<FirDeclaration>): PersistentCheckerContext {
val visitor = ContextCollectingDiagnosticCollectorVisitor(sessionHolder, designation.iterator())
firFile.accept(visitor, null)
return visitor.savedContext
?: error("Context was not saved")
}
}
}
internal object PersistenceContextCollector {
fun collectContext(
sessionHolder: SessionHolder,
firFile: FirFile,
declaration: FirDeclaration,
): PersistentCheckerContext {
val isLocal = when (declaration) {
is FirClassLikeDeclaration<*> -> declaration.symbol.classId.isLocal
is FirCallableDeclaration<*> -> declaration.symbol.callableId.isLocal
else -> error("Unsupported declaration ${declaration.renderWithType()}")
}
require(!isLocal) {
"Cannot collect context for local declaration ${declaration.renderWithType()}"
}
val designation = declaration.collectDesignation()
return ContextCollectingDiagnosticCollectorVisitor.collect(sessionHolder, firFile, designation)
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir
import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.ImplicitBodyResolveComputationSession
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.createReturnTypeCalculatorForIDE
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirIdeDesignatedBodyResolveTransformerForReturnTypeCalculator
internal object PersistentCheckerContextFactory {
fun createEmptyPersistenceCheckerContext(sessionHolder: SessionHolder): PersistentCheckerContext {
val returnTypeCalculator = createReturnTypeCalculatorForIDE(
sessionHolder.session,
ScopeSession(),
ImplicitBodyResolveComputationSession(),
::FirIdeDesignatedBodyResolveTransformerForReturnTypeCalculator
)
return PersistentCheckerContext(sessionHolder, returnTypeCalculator)
}
}
@@ -44,7 +44,7 @@ internal class FileStructure(
val structureElement = structureElements.compute(declaration) { _, structureElement ->
when {
structureElement == null -> createStructureElement(declaration)
structureElement is ReanalyzableStructureElement<KtDeclaration> && !structureElement.isUpToDate() -> {
structureElement is ReanalyzableStructureElement<KtDeclaration, *> && !structureElement.isUpToDate() -> {
structureElement.reanalyze(
newKtDeclaration = declaration as KtDeclaration,
cache = moduleFileCache,
@@ -90,7 +90,7 @@ internal class FileStructure(
override fun visitDeclaration(dcl: KtDeclaration) {
val structureElement = getStructureElementFor(dcl)
structureElements += structureElement
if (structureElement !is ReanalyzableStructureElement<*>) {
if (structureElement !is ReanalyzableStructureElement<*, *>) {
dcl.acceptChildren(this)
}
}
@@ -12,10 +12,12 @@ import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.*
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.FileStructureElementDiagnosticList
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.FileStructureElementDiagnosticRetriever
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.FileStructureElementDiagnostics
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.FileStructureElementDiagnosticsCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.SingleNonLocalDeclarationDiagnosticRetriever
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataContextCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
@@ -31,9 +33,11 @@ internal sealed class FileStructureElement(val firFile: FirFile) {
abstract val diagnostics: FileStructureElementDiagnostics
}
internal sealed class ReanalyzableStructureElement<KT : KtDeclaration>(firFile: FirFile) : FileStructureElement(firFile) {
internal sealed class ReanalyzableStructureElement<KT : KtDeclaration, S: AbstractFirBasedSymbol<*>>(
firFile: FirFile,
protected val firSymbol: S,
) : FileStructureElement(firFile) {
abstract override val psi: KtDeclaration
abstract val firSymbol: AbstractFirBasedSymbol<*>
abstract val timestamp: Long
/**
@@ -46,36 +50,11 @@ internal sealed class ReanalyzableStructureElement<KT : KtDeclaration>(firFile:
firLazyDeclarationResolver: FirLazyDeclarationResolver,
firIdeProvider: FirIdeProvider,
towerDataContextCollector: FirTowerDataContextCollector,
): ReanalyzableStructureElement<KT>
): ReanalyzableStructureElement<KT, S>
fun isUpToDate(): Boolean = psi.getModificationStamp() == timestamp
override val diagnostics = FileStructureElementDiagnostics(firFile, FileStructureElementSingleDeclarationDiagnosticRetriever())
inner class FileStructureElementSingleDeclarationDiagnosticRetriever : FileStructureElementDiagnosticRetriever() {
override fun retrieve(firFile: FirFile, collector: FileStructureElementDiagnosticsCollector): FileStructureElementDiagnosticList {
var inCurrentDeclaration = false
val declaration = firSymbol.fir as FirDeclaration
return collector.collectForStructureElement(
firFile,
onDeclarationEnter = { firDeclaration ->
when {
firDeclaration == declaration -> {
inCurrentDeclaration = true
DiagnosticCollectorDeclarationAction.CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
}
inCurrentDeclaration -> DiagnosticCollectorDeclarationAction.CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
else -> DiagnosticCollectorDeclarationAction.DO_NOT_CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
}
},
onDeclarationExit = { firDeclaration ->
if (declaration == firDeclaration) {
inCurrentDeclaration = false
}
}
)
}
}
override val diagnostics = FileStructureElementDiagnostics(firFile, SingleNonLocalDeclarationDiagnosticRetriever(firSymbol.fir as FirDeclaration))
companion object {
val recorder = FirElementsRecorder()
@@ -85,9 +64,9 @@ internal sealed class ReanalyzableStructureElement<KT : KtDeclaration>(firFile:
internal class ReanalyzableFunctionStructureElement(
firFile: FirFile,
override val psi: KtNamedFunction,
override val firSymbol: FirFunctionSymbol<*>,
firSymbol: FirFunctionSymbol<*>,
override val timestamp: Long
) : ReanalyzableStructureElement<KtNamedFunction>(firFile) {
) : ReanalyzableStructureElement<KtNamedFunction, FirFunctionSymbol<*>>(firFile, firSymbol) {
override val mappings: Map<KtElement, FirElement> =
FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder)
@@ -125,9 +104,9 @@ internal class ReanalyzableFunctionStructureElement(
internal class ReanalyzablePropertyStructureElement(
firFile: FirFile,
override val psi: KtProperty,
override val firSymbol: FirPropertySymbol,
firSymbol: FirPropertySymbol,
override val timestamp: Long
) : ReanalyzableStructureElement<KtProperty>(firFile) {
) : ReanalyzableStructureElement<KtProperty, FirPropertySymbol>(firFile, firSymbol) {
override val mappings: Map<KtElement, FirElement> =
FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder)
@@ -164,52 +143,13 @@ internal class ReanalyzablePropertyStructureElement(
internal class NonReanalyzableDeclarationStructureElement(
firFile: FirFile,
private val fir: FirDeclaration,
fir: FirDeclaration,
override val psi: KtDeclaration,
) : FileStructureElement(firFile) {
override val mappings: Map<KtElement, FirElement> =
FirElementsRecorder.recordElementsFrom(fir, recorder)
override val diagnostics = FileStructureElementDiagnostics(firFile, DiagnosticRetriever())
private inner class DiagnosticRetriever : FileStructureElementDiagnosticRetriever() {
override fun retrieve(firFile: FirFile, collector: FileStructureElementDiagnosticsCollector): FileStructureElementDiagnosticList {
var inCurrentDeclaration = false
return collector.collectForStructureElement(
firFile,
onDeclarationEnter = { firDeclaration ->
when {
firDeclaration.isGeneratedDeclaration -> when {
// a generated primary constructor need to be checked. For example it may have a delegated super constructor
// call or some annotations
firDeclaration is FirConstructor && firDeclaration.isPrimary ->
DiagnosticCollectorDeclarationAction.CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
// Some generated declaration contains structures that we need to check. For example the FIR representation of an
// enum entry initializer, when present, is a generated anonymous object of kind `ENUM_ENTRY`.
else -> DiagnosticCollectorDeclarationAction.DO_NOT_CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
}
firDeclaration is FirFile -> DiagnosticCollectorDeclarationAction.DO_NOT_CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
firDeclaration == fir -> {
inCurrentDeclaration = true
DiagnosticCollectorDeclarationAction.CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
}
FileElementFactory.isReanalyzableContainer(firDeclaration.ktDeclaration) -> {
DiagnosticCollectorDeclarationAction.SKIP
}
inCurrentDeclaration -> {
DiagnosticCollectorDeclarationAction.CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
}
else -> DiagnosticCollectorDeclarationAction.DO_NOT_CHECK_IN_CURRENT_DECLARATION_AND_LOOKUP_FOR_NESTED
}
},
onDeclarationExit = { firDeclaration ->
if (firDeclaration == fir) {
inCurrentDeclaration = false
}
},
)
}
}
override val diagnostics = FileStructureElementDiagnostics(firFile, SingleNonLocalDeclarationDiagnosticRetriever(fir))
companion object {
@@ -243,7 +183,7 @@ internal class RootStructureElement(
private object DiagnosticRetriever : FileStructureElementDiagnosticRetriever() {
override fun retrieve(firFile: FirFile, collector: FileStructureElementDiagnosticsCollector): FileStructureElementDiagnosticList {
return collector.collectForStructureElement(firFile) { firDeclaration ->
return collector.collectForStructureElement(firFile, initialContext = null) { firDeclaration ->
if (firDeclaration is FirFile) DiagnosticCollectorDeclarationAction.CHECK_IN_CURRENT_DECLARATION_AND_DO_NOT_LOOKUP_FOR_NESTED
else DiagnosticCollectorDeclarationAction.SKIP
}