Lazy diagnostics API in frontend

Relates to #KT-37702
This commit is contained in:
Vladimir Dolzhenko
2020-12-10 21:34:07 +01:00
committed by Space
parent 8f675fe757
commit 558338f997
17 changed files with 186 additions and 63 deletions
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.container.tryGetService
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.FrontendInternals
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.project.ResolveElementCache
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
@@ -75,11 +76,14 @@ internal class ModuleResolutionFacadeImpl(
}
}
override fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>): AnalysisResult {
override fun analyzeWithAllCompilerChecks(
elements: Collection<KtElement>,
callback: DiagnosticSink.DiagnosticsCallback?
): AnalysisResult {
ResolveInDispatchThreadManager.assertNoResolveInDispatchThread()
return runWithCancellationCheck {
projectFacade.getAnalysisResultsForElements(elements)
projectFacade.getAnalysisResultsForElements(elements, callback)
}
}
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.context.withModule
import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.frontend.di.createContainerForLazyBodyResolve
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
@@ -89,32 +90,50 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
return null
}
internal fun getAnalysisResults(element: KtElement): AnalysisResult {
internal fun getAnalysisResults(element: KtElement, callback: DiagnosticSink.DiagnosticsCallback? = null): AnalysisResult {
check(element)
val analyzableParent = KotlinResolveDataProvider.findAnalyzableParent(element) ?: return AnalysisResult.EMPTY
fun handleResult(result: AnalysisResult, callback: DiagnosticSink.DiagnosticsCallback?): AnalysisResult {
callback?.let { result.bindingContext.diagnostics.forEach(it::callback) }
return result
}
return guardLock.guarded {
// step 1: perform incremental analysis IF it is applicable
getIncrementalAnalysisResult()?.let { return@guarded it }
getIncrementalAnalysisResult(callback)?.let {
return@guarded handleResult(it, callback)
}
// cache does not contain AnalysisResult per each kt/psi element
// instead it looks up analysis for its parents - see lookUp(analyzableElement)
// step 2: return result if it is cached
lookUp(analyzableParent)?.let {
return@guarded it
return@guarded handleResult(it, callback)
}
val localDiagnostics = mutableSetOf<Diagnostic>()
val localCallback = if (callback != null) { d: Diagnostic ->
localDiagnostics.add(d)
callback.callback(d)
} else null
// step 3: perform analyze of analyzableParent as nothing has been cached yet
val result = analyze(analyzableParent)
val result = analyze(analyzableParent, null, localCallback)
// some of diagnostics could be not handled with a callback - send out the rest
callback?.let { c ->
result.bindingContext.diagnostics.filterNot { it in localDiagnostics }.forEach(c::callback)
}
cache[analyzableParent] = result
return@guarded result
}
}
private fun getIncrementalAnalysisResult(): AnalysisResult? {
private fun getIncrementalAnalysisResult(callback: DiagnosticSink.DiagnosticsCallback?): AnalysisResult? {
updateFileResultFromCache()
val inBlockModifications = file.inBlockModifications
@@ -151,7 +170,9 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
)
}
val newResult = analyze(inBlockModification, trace)
callback?.let { trace.parentDiagnosticsApartElement.forEach(it::callback) }
val newResult = analyze(inBlockModification, trace, callback)
analysisResult = wrapResult(result, newResult, trace)
}
file.clearInBlockModifications()
@@ -224,7 +245,11 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
}
}
private fun analyze(analyzableElement: KtElement, bindingTrace: BindingTrace? = null): AnalysisResult {
private fun analyze(
analyzableElement: KtElement,
bindingTrace: BindingTrace?,
callback: DiagnosticSink.DiagnosticsCallback?
): AnalysisResult {
ProgressIndicatorProvider.checkCanceled()
val project = analyzableElement.project
@@ -242,7 +267,8 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
codeFragmentAnalyzer,
bodyResolveCache,
analyzableElement,
bindingTrace
bindingTrace,
callback
)
} catch (e: ProcessCanceledException) {
throw e
@@ -412,12 +438,14 @@ private object KotlinResolveDataProvider {
codeFragmentAnalyzer: CodeFragmentAnalyzer,
bodyResolveCache: BodyResolveCache,
analyzableElement: KtElement,
bindingTrace: BindingTrace?
bindingTrace: BindingTrace?,
callback: DiagnosticSink.DiagnosticsCallback?
): AnalysisResult {
try {
if (analyzableElement is KtCodeFragment) {
val bodyResolveMode = BodyResolveMode.PARTIAL_FOR_COMPLETION
val bindingContext = codeFragmentAnalyzer.analyzeCodeFragment(analyzableElement, bodyResolveMode).bindingContext
val trace: BindingTrace = codeFragmentAnalyzer.analyzeCodeFragment(analyzableElement, bodyResolveMode)
val bindingContext = trace.bindingContext
return AnalysisResult.success(bindingContext, moduleDescriptor)
}
@@ -427,34 +455,40 @@ private object KotlinResolveDataProvider {
allowSliceRewrite = true
)
val moduleInfo = analyzableElement.containingFile.getModuleInfo()
val moduleInfo = analyzableElement.containingKtFile.getModuleInfo()
val targetPlatform = moduleInfo.platform
/*
Note that currently we *have* to re-create LazyTopDownAnalyzer with custom trace in order to disallow resolution of
bodies in top-level trace (trace from DI-container).
Resolving bodies in top-level trace may lead to memory leaks and incorrect resolution, because top-level
trace isn't invalidated on in-block modifications (while body resolution surely does)
callback?.let { trace.setCallback(it) }
Also note that for function bodies, we'll create DelegatingBindingTrace in ResolveElementCache anyways
(see 'functionAdditionalResolve'). However, this trace is still needed, because we have other
codepaths for other KtDeclarationWithBodies (like property accessors/secondary constructors/class initializers)
*/
val lazyTopDownAnalyzer = createContainerForLazyBodyResolve(
//TODO: should get ModuleContext
globalContext.withProject(project).withModule(moduleDescriptor),
resolveSession,
trace,
targetPlatform,
bodyResolveCache,
targetPlatform.findAnalyzerServices(project),
analyzableElement.languageVersionSettings,
IdeaModuleStructureOracle(),
IdeMainFunctionDetectorFactory()
).get<LazyTopDownAnalyzer>()
try {
/*
Note that currently we *have* to re-create LazyTopDownAnalyzer with custom trace in order to disallow resolution of
bodies in top-level trace (trace from DI-container).
Resolving bodies in top-level trace may lead to memory leaks and incorrect resolution, because top-level
trace isn't invalidated on in-block modifications (while body resolution surely does)
lazyTopDownAnalyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, listOf(analyzableElement))
Also note that for function bodies, we'll create DelegatingBindingTrace in ResolveElementCache anyways
(see 'functionAdditionalResolve'). However, this trace is still needed, because we have other
codepaths for other KtDeclarationWithBodies (like property accessors/secondary constructors/class initializers)
*/
val lazyTopDownAnalyzer = createContainerForLazyBodyResolve(
//TODO: should get ModuleContext
globalContext.withProject(project).withModule(moduleDescriptor),
resolveSession,
trace,
targetPlatform,
bodyResolveCache,
targetPlatform.findAnalyzerServices(project),
analyzableElement.languageVersionSettings,
IdeaModuleStructureOracle(),
IdeMainFunctionDetectorFactory()
).get<LazyTopDownAnalyzer>()
lazyTopDownAnalyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, listOf(analyzableElement))
} finally {
trace.resetCallback()
}
return AnalysisResult.success(trace.bindingContext, moduleDescriptor)
} catch (e: ProcessCanceledException) {
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.analyzer.*
import org.jetbrains.kotlin.context.GlobalContextImpl
import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.idea.caches.project.*
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
@@ -143,10 +144,13 @@ internal class ProjectResolutionFacade(
internal fun findModuleDescriptor(ideaModuleInfo: IdeaModuleInfo): ModuleDescriptor {
return cachedResolverForProject.descriptorForModule(ideaModuleInfo)
}
internal fun getResolverForProject(): ResolverForProject<IdeaModuleInfo> = cachedResolverForProject
internal fun getAnalysisResultsForElements(elements: Collection<KtElement>): AnalysisResult {
internal fun getAnalysisResultsForElements(
elements: Collection<KtElement>,
callback: DiagnosticSink.DiagnosticsCallback? = null
): AnalysisResult {
assert(elements.isNotEmpty()) { "elements collection should not be empty" }
val cache = analysisResultsSimpleLock.guarded {
@@ -157,7 +161,7 @@ internal class ProjectResolutionFacade(
val containingKtFile = it.containingKtFile
val perFileCache = cache[containingKtFile]
try {
perFileCache.getAnalysisResults(it)
perFileCache.getAnalysisResults(it, callback)
} catch (e: Throwable) {
if (e is ControlFlowException) {
throw e
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.analyzer.ResolverForProject
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.FrontendInternals
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
@@ -52,9 +53,12 @@ private class ResolutionFacadeWithDebugInfo(
}
}
override fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>): AnalysisResult {
override fun analyzeWithAllCompilerChecks(
elements: Collection<KtElement>,
callback: DiagnosticSink.DiagnosticsCallback?
): AnalysisResult {
return wrapExceptions({ ResolvingWhat(elements) }) {
delegate.analyzeWithAllCompilerChecks(elements)
delegate.analyzeWithAllCompilerChecks(elements, callback)
}
}