Lazy diagnostics API in frontend
Relates to #KT-37702
This commit is contained in:
committed by
Space
parent
8f675fe757
commit
558338f997
+6
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+67
-33
@@ -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) {
|
||||
|
||||
+7
-3
@@ -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
|
||||
|
||||
+6
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user