FIR: preliminary implementation of diagnostics highlighter (~30% supported)
This commit is contained in:
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.references.*
|
||||
import org.jetbrains.kotlin.fir.render
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.collectors.FirDiagnosticsCollector
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDesignatedBodyResolveTransformer
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.runResolve
|
||||
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
|
||||
@@ -101,17 +102,33 @@ fun KtClassOrObject.getOrBuildFir(
|
||||
return firClass
|
||||
}
|
||||
|
||||
private fun KtFile.getOrBuildRawFirFile(state: FirResolveState): Pair<IdeFirProvider, FirFile> {
|
||||
val session = state.getSession(this)
|
||||
val firProvider = FirProvider.getInstance(session) as IdeFirProvider
|
||||
return firProvider to firProvider.getOrBuildFile(this)
|
||||
}
|
||||
|
||||
fun KtFile.getOrBuildFir(
|
||||
state: FirResolveState,
|
||||
phase: FirResolvePhase = FirResolvePhase.DECLARATIONS
|
||||
): FirFile {
|
||||
val session = state.getSession(this)
|
||||
val firProvider = FirProvider.getInstance(session) as IdeFirProvider
|
||||
val firFile = firProvider.getOrBuildFile(this)
|
||||
val (firProvider, firFile) = getOrBuildRawFirFile(state)
|
||||
firFile.runResolve(firFile, firProvider, phase, state)
|
||||
return firFile
|
||||
}
|
||||
|
||||
fun KtFile.getOrBuildFirWithDiagnostics(state: FirResolveState): FirFile {
|
||||
// TODO: consider adding some locks
|
||||
val (_, firFile) = getOrBuildRawFirFile(state)
|
||||
firFile.runResolve(toPhase = FirResolvePhase.BODY_RESOLVE, fromPhase = firFile.resolvePhase)
|
||||
|
||||
if (state.hasDiagnosticsForFile(this)) return firFile
|
||||
|
||||
val coneDiagnostics = FirDiagnosticsCollector.create().collectDiagnostics(firFile)
|
||||
state.setDiagnosticsForFile(this, firFile, coneDiagnostics)
|
||||
return firFile
|
||||
}
|
||||
|
||||
private fun FirDeclaration.runResolve(
|
||||
file: FirFile,
|
||||
firProvider: IdeFirProvider,
|
||||
@@ -120,33 +137,34 @@ private fun FirDeclaration.runResolve(
|
||||
) {
|
||||
val nonLazyPhase = minOf(toPhase, FirResolvePhase.DECLARATIONS)
|
||||
file.runResolve(toPhase = nonLazyPhase, fromPhase = this.resolvePhase)
|
||||
if (toPhase > nonLazyPhase) {
|
||||
val designation = mutableListOf<FirElement>()
|
||||
designation += file
|
||||
if (this !is FirFile) {
|
||||
val id = when (this) {
|
||||
is FirCallableDeclaration<*> -> {
|
||||
this.symbol.callableId.classId
|
||||
}
|
||||
is FirRegularClass -> {
|
||||
this.symbol.classId
|
||||
}
|
||||
else -> error("Unsupported: ${render()}")
|
||||
if (toPhase <= nonLazyPhase) return
|
||||
val designation = mutableListOf<FirDeclaration>(file)
|
||||
if (this !is FirFile) {
|
||||
val id = when (this) {
|
||||
is FirCallableDeclaration<*> -> {
|
||||
this.symbol.callableId.classId
|
||||
}
|
||||
val outerClasses = generateSequence(id) { classId ->
|
||||
classId.outerClassId
|
||||
}.mapTo(mutableListOf()) { firProvider.getFirClassifierByFqName(it)!! }
|
||||
designation += outerClasses.asReversed()
|
||||
if (this is FirCallableDeclaration<*>) {
|
||||
designation += this
|
||||
is FirRegularClass -> {
|
||||
this.symbol.classId
|
||||
}
|
||||
else -> error("Unsupported: ${render()}")
|
||||
}
|
||||
val outerClasses = generateSequence(id) { classId ->
|
||||
classId.outerClassId
|
||||
}.mapTo(mutableListOf()) { firProvider.getFirClassifierByFqName(it)!! }
|
||||
designation += outerClasses.asReversed()
|
||||
if (this is FirCallableDeclaration<*>) {
|
||||
designation += this
|
||||
}
|
||||
val transformer = FirDesignatedBodyResolveTransformer(
|
||||
designation.iterator(), state.getSession(psi as KtElement),
|
||||
implicitTypeOnly = toPhase == FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE
|
||||
)
|
||||
file.transform<FirFile, ResolutionMode>(transformer, ResolutionMode.ContextDependent)
|
||||
}
|
||||
if (designation.all { it.resolvePhase >= toPhase }) {
|
||||
return
|
||||
}
|
||||
val transformer = FirDesignatedBodyResolveTransformer(
|
||||
designation.iterator(), state.getSession(psi as KtElement),
|
||||
implicitTypeOnly = toPhase == FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE
|
||||
)
|
||||
file.transform<FirFile, ResolutionMode>(transformer, ResolutionMode.ContextDependent)
|
||||
}
|
||||
|
||||
fun KtElement.getOrBuildFir(
|
||||
|
||||
@@ -6,18 +6,19 @@
|
||||
package org.jetbrains.kotlin.idea.fir
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.FirSessionProvider
|
||||
import org.jetbrains.kotlin.fir.dependenciesWithoutSelf
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.fir.*
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.java.FirLibrarySession
|
||||
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnostic
|
||||
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.isLibraryClasses
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.IDEPackagePartProvider
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
|
||||
private fun createLibrarySession(moduleInfo: IdeaModuleInfo, project: Project, provider: FirProjectSessionProvider): FirLibrarySession {
|
||||
@@ -58,17 +59,51 @@ interface FirResolveState {
|
||||
|
||||
operator fun get(psi: KtElement): FirElement?
|
||||
|
||||
fun getDiagnostics(psi: KtElement): List<Diagnostic>
|
||||
|
||||
fun hasDiagnosticsForFile(file: KtFile): Boolean
|
||||
|
||||
fun record(psi: KtElement, fir: FirElement)
|
||||
|
||||
fun record(psi: KtElement, diagnostic: Diagnostic)
|
||||
|
||||
fun setDiagnosticsForFile(file: KtFile, fir: FirFile, diagnostics: Iterable<ConeDiagnostic>)
|
||||
}
|
||||
|
||||
class FirResolveStateImpl(override val sessionProvider: FirSessionProvider) : FirResolveState {
|
||||
private val cache = mutableMapOf<KtElement, FirElement>()
|
||||
|
||||
private val diagnosticCache = mutableMapOf<KtElement, MutableList<Diagnostic>>()
|
||||
|
||||
private val diagnosedFiles = mutableSetOf<KtFile>()
|
||||
|
||||
override fun get(psi: KtElement): FirElement? = cache[psi]
|
||||
|
||||
override fun getDiagnostics(psi: KtElement): List<Diagnostic> {
|
||||
return diagnosticCache[psi] ?: emptyList()
|
||||
}
|
||||
|
||||
override fun hasDiagnosticsForFile(file: KtFile): Boolean {
|
||||
return file in diagnosedFiles
|
||||
}
|
||||
|
||||
override fun record(psi: KtElement, fir: FirElement) {
|
||||
cache[psi] = fir
|
||||
}
|
||||
|
||||
override fun record(psi: KtElement, diagnostic: Diagnostic) {
|
||||
// TODO: consider implementing custom FirIdeDiagnosticReported/Collector
|
||||
val list = diagnosticCache.getOrPut(psi) { mutableListOf() }
|
||||
list += diagnostic
|
||||
}
|
||||
|
||||
override fun setDiagnosticsForFile(file: KtFile, fir: FirFile, diagnostics: Iterable<ConeDiagnostic>) {
|
||||
for (diagnostic in diagnostics) {
|
||||
(diagnostic.source.psi as? KtElement)?.let { record(it, diagnostic.diagnostic) }
|
||||
}
|
||||
|
||||
diagnosedFiles += file
|
||||
}
|
||||
}
|
||||
|
||||
fun KtElement.firResolveState(): FirResolveState =
|
||||
|
||||
@@ -43,13 +43,13 @@ import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.fir.FirResolution
|
||||
import org.jetbrains.kotlin.idea.fir.firResolveState
|
||||
import org.jetbrains.kotlin.idea.fir.getOrBuildFirWithDiagnostics
|
||||
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.QuickFixes
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
@@ -63,7 +63,19 @@ open class KotlinPsiChecker : Annotator, HighlightRangeExtension {
|
||||
|
||||
if (!KotlinHighlightingUtil.shouldHighlight(file)) return
|
||||
|
||||
val analysisResult = file.analyzeWithAllCompilerChecks()
|
||||
if (FirResolution.enabled) {
|
||||
annotateElementUsingFrontendIR(element, file, holder)
|
||||
} else {
|
||||
annotateElement(element, file, holder)
|
||||
}
|
||||
}
|
||||
|
||||
private fun annotateElement(
|
||||
element: PsiElement,
|
||||
containingFile: KtFile,
|
||||
holder: AnnotationHolder
|
||||
) {
|
||||
val analysisResult = containingFile.analyzeWithAllCompilerChecks()
|
||||
if (analysisResult.isError()) {
|
||||
throw ProcessCanceledException(analysisResult.error)
|
||||
}
|
||||
@@ -75,6 +87,25 @@ open class KotlinPsiChecker : Annotator, HighlightRangeExtension {
|
||||
annotateElement(element, holder, bindingContext.diagnostics)
|
||||
}
|
||||
|
||||
private fun annotateElementUsingFrontendIR(
|
||||
element: PsiElement,
|
||||
containingFile: KtFile,
|
||||
holder: AnnotationHolder
|
||||
) {
|
||||
if (element !is KtElement) return
|
||||
val state = containingFile.firResolveState()
|
||||
containingFile.getOrBuildFirWithDiagnostics(state)
|
||||
|
||||
val diagnostics = state.getDiagnostics(element)
|
||||
if (diagnostics.isEmpty()) return
|
||||
|
||||
if (KotlinHighlightingUtil.shouldHighlightErrors(element)) {
|
||||
ElementAnnotator(element, holder) { param ->
|
||||
shouldSuppressUnusedParameter(param)
|
||||
}.registerDiagnosticsAnnotations(diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isForceHighlightParents(file: PsiFile): Boolean {
|
||||
return file is KtFile
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user