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
@@ -52,9 +52,9 @@ open class AnalysisResult protected constructor(
fun isError(): Boolean = this is InternalError || this is CompilationError fun isError(): Boolean = this is InternalError || this is CompilationError
fun throwIfError() { fun throwIfError() {
when { when (this) {
this is InternalError -> throw IllegalStateException("failed to analyze: " + error, error) is InternalError -> throw IllegalStateException("failed to analyze: $error", error)
this is CompilationError -> throw CompilationErrorException() is CompilationError -> throw CompilationErrorException()
} }
} }
@@ -53,6 +53,15 @@ public interface DiagnosticSink {
} }
}; };
interface DiagnosticsCallback {
void callback(Diagnostic diagnostic);
}
void report(@NotNull Diagnostic diagnostic); void report(@NotNull Diagnostic diagnostic);
default void setCallback(@NotNull DiagnosticsCallback callback) { }
default void resetCallback() { }
boolean wantsDiagnostics(); boolean wantsDiagnostics();
} }
@@ -48,16 +48,16 @@ sealed class RenderingContext {
} }
@JvmStatic @JvmStatic
fun fromDiagnostic(d: Diagnostic): RenderingContext { fun parameters(d: Diagnostic): List<Any> = when (d) {
val parameters = when (d) { is SimpleDiagnostic<*> -> listOf()
is SimpleDiagnostic<*> -> listOf() is DiagnosticWithParameters1<*, *> -> listOf(d.a)
is DiagnosticWithParameters1<*, *> -> listOf(d.a) is DiagnosticWithParameters2<*, *, *> -> listOf(d.a, d.b)
is DiagnosticWithParameters2<*, *, *> -> listOf(d.a, d.b) is DiagnosticWithParameters3<*, *, *, *> -> listOf(d.a, d.b, d.c)
is DiagnosticWithParameters3<*, *, *, *> -> listOf(d.a, d.b, d.c) is ParametrizedDiagnostic<*> -> error("Unexpected diagnostic: ${d::class.java}")
is ParametrizedDiagnostic<*> -> error("Unexpected diagnostic: ${d::class.java}") else -> listOf()
else -> listOf()
}
return Impl(parameters)
} }
@JvmStatic
fun fromDiagnostic(d: Diagnostic): RenderingContext = Impl(parameters(d))
} }
} }
@@ -37,6 +37,8 @@ abstract class AbstractFilteringTrace(
} }
override fun report(diagnostic: Diagnostic) { override fun report(diagnostic: Diagnostic) {
diagnosticsCallback?.callback(diagnostic)
parentTrace.report(diagnostic) parentTrace.report(diagnostic)
} }
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.resolve
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.diagnostics.BindingContextSuppressCache import org.jetbrains.kotlin.resolve.diagnostics.BindingContextSuppressCache
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
@@ -156,6 +157,18 @@ open class DelegatingBindingTrace(
mutableDiagnostics.report(diagnostic) mutableDiagnostics.report(diagnostic)
} }
protected var diagnosticsCallback: DiagnosticSink.DiagnosticsCallback? = null
override fun setCallback(callback: DiagnosticSink.DiagnosticsCallback) {
diagnosticsCallback = callback
mutableDiagnostics?.setCallback(callback)
}
override fun resetCallback() {
diagnosticsCallback = null
mutableDiagnostics?.resetCallback()
}
override fun wantsDiagnostics(): Boolean = mutableDiagnostics != null override fun wantsDiagnostics(): Boolean = mutableDiagnostics != null
override fun toString(): String = name override fun toString(): String = name
@@ -16,9 +16,10 @@
package org.jetbrains.kotlin.resolve.diagnostics package org.jetbrains.kotlin.resolve.diagnostics
import com.intellij.psi.PsiElement
import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticSink.DiagnosticsCallback
import org.jetbrains.kotlin.diagnostics.GenericDiagnostics import org.jetbrains.kotlin.diagnostics.GenericDiagnostics
interface Diagnostics : GenericDiagnostics<Diagnostic> { interface Diagnostics : GenericDiagnostics<Diagnostic> {
@@ -37,6 +38,9 @@ interface Diagnostics : GenericDiagnostics<Diagnostic> {
fun noSuppression(): Diagnostics fun noSuppression(): Diagnostics
fun setCallback(callback: DiagnosticsCallback) {}
fun resetCallback() {}
companion object { companion object {
val EMPTY: Diagnostics = object : Diagnostics { val EMPTY: Diagnostics = object : Diagnostics {
override fun noSuppression(): Diagnostics = this override fun noSuppression(): Diagnostics = this
@@ -23,6 +23,7 @@ import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly; import org.jetbrains.annotations.TestOnly;
import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.DiagnosticSink;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
@@ -73,6 +74,16 @@ public class DiagnosticsWithSuppression implements Diagnostics {
throw new IllegalStateException("Trying to obtain modification tracker for readonly DiagnosticsWithSuppression."); throw new IllegalStateException("Trying to obtain modification tracker for readonly DiagnosticsWithSuppression.");
} }
@Override
public void setCallback(@NotNull DiagnosticSink.DiagnosticsCallback callback) {
}
@Override
public void resetCallback() {
}
@TestOnly @TestOnly
@NotNull @NotNull
public Collection<Diagnostic> getDiagnostics() { public Collection<Diagnostic> getDiagnostics() {
@@ -24,10 +24,7 @@ import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.psi.KtAnnotated import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtStubbedPsiUtil
import org.jetbrains.kotlin.psi.doNotAnalyze
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.constants.StringValue
@@ -111,7 +108,12 @@ abstract class KotlinSuppressCache {
This way we need no more lookups than the number of suppress() annotations from here to the root. This way we need no more lookups than the number of suppress() annotations from here to the root.
*/ */
private fun isSuppressedByAnnotated(suppressionKey: String, severity: Severity, annotated: KtAnnotated, debugDepth: Int): Boolean { private fun isSuppressedByAnnotated(
suppressionKey: String,
severity: Severity,
annotated: KtAnnotated,
debugDepth: Int
): Boolean {
val suppressor = getOrCreateSuppressor(annotated) val suppressor = getOrCreateSuppressor(annotated)
if (suppressor.isSuppressed(suppressionKey, severity)) return true if (suppressor.isSuppressed(suppressionKey, severity)) return true
@@ -140,6 +142,7 @@ abstract class KotlinSuppressCache {
private fun getSuppressingStrings(annotated: KtAnnotated): Set<String> { private fun getSuppressingStrings(annotated: KtAnnotated): Set<String> {
val builder = ImmutableSet.builder<String>() val builder = ImmutableSet.builder<String>()
for (annotationDescriptor in getSuppressionAnnotations(annotated)) { for (annotationDescriptor in getSuppressionAnnotations(annotated)) {
processAnnotation(builder, annotationDescriptor) processAnnotation(builder, annotationDescriptor)
} }
@@ -22,12 +22,16 @@ import com.intellij.psi.util.CachedValueProvider
import com.intellij.util.CachedValueImpl import com.intellij.util.CachedValueImpl
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtStubbedPsiUtil
class MutableDiagnosticsWithSuppression( class MutableDiagnosticsWithSuppression(
private val suppressCache: KotlinSuppressCache, private val suppressCache: KotlinSuppressCache,
private val delegateDiagnostics: Diagnostics, private val delegateDiagnostics: Diagnostics,
) : Diagnostics { ) : Diagnostics {
private val diagnosticList = ArrayList<Diagnostic>() private val diagnosticList = ArrayList<Diagnostic>()
private var diagnosticsCallback: DiagnosticSink.DiagnosticsCallback? = null
//NOTE: CachedValuesManager is not used because it requires Project passed to this object //NOTE: CachedValuesManager is not used because it requires Project passed to this object
private val cache = CachedValueImpl { private val cache = CachedValueImpl {
@@ -43,16 +47,38 @@ class MutableDiagnosticsWithSuppression(
override fun forElement(psiElement: PsiElement) = readonlyView().forElement(psiElement) override fun forElement(psiElement: PsiElement) = readonlyView().forElement(psiElement)
override fun noSuppression() = readonlyView().noSuppression() override fun noSuppression() = readonlyView().noSuppression()
override fun setCallback(callback: DiagnosticSink.DiagnosticsCallback) {
assert(diagnosticsCallback == null) { "diagnostic callback has been already registered" }
diagnosticsCallback = callback
delegateDiagnostics.setCallback(callback)
}
override fun resetCallback() {
diagnosticsCallback = null
delegateDiagnostics.resetCallback()
}
//essential that this list is readonly //essential that this list is readonly
fun getOwnDiagnostics(): List<Diagnostic> { fun getOwnDiagnostics(): List<Diagnostic> {
return diagnosticList return diagnosticList
} }
fun report(diagnostic: Diagnostic) { fun report(diagnostic: Diagnostic) {
onFlyDiagnosticsCallback(diagnostic)?.callback(diagnostic)
diagnosticList.add(diagnostic) diagnosticList.add(diagnostic)
modificationTracker.incModificationCount() modificationTracker.incModificationCount()
} }
private fun onFlyDiagnosticsCallback(diagnostic: Diagnostic): DiagnosticSink.DiagnosticsCallback? =
diagnosticsCallback.takeIf {
diagnosticsCallback != null &&
// Due to a potential recursion in filter.invoke (via LazyAnnotations) do not try to report
// diagnostic on fly if it happened in annotations
KtStubbedPsiUtil.getPsiOrStubParent(diagnostic.psiElement, KtAnnotationEntry::class.java, false) == null &&
suppressCache.filter.invoke(diagnostic)
}
fun clear() { fun clear() {
diagnosticList.clear() diagnosticList.clear()
modificationTracker.incModificationCount() modificationTracker.incModificationCount()
@@ -22,7 +22,6 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
import org.jetbrains.kotlin.util.slicedMap.WritableSlice import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import org.jetbrains.kotlin.resolve.TraceEntryFilter
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.idea.FrontendInternals import org.jetbrains.kotlin.idea.FrontendInternals
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -167,8 +168,11 @@ inline fun <reified T> T.analyzeWithContent(): BindingContext where T : KtDeclar
* @ref [org.jetbrains.kotlin.idea.caches.resolve.PerFileAnalysisCache] * @ref [org.jetbrains.kotlin.idea.caches.resolve.PerFileAnalysisCache]
*/ */
fun KtFile.analyzeWithAllCompilerChecks(vararg extraFiles: KtFile): AnalysisResult = fun KtFile.analyzeWithAllCompilerChecks(vararg extraFiles: KtFile): AnalysisResult =
this.analyzeWithAllCompilerChecks(null, *extraFiles)
fun KtFile.analyzeWithAllCompilerChecks(callback: ((Diagnostic) -> Unit)?, vararg extraFiles: KtFile): AnalysisResult =
KotlinCacheService.getInstance(project).getResolutionFacade(listOf(this) + extraFiles.toList()) KotlinCacheService.getInstance(project).getResolutionFacade(listOf(this) + extraFiles.toList())
.analyzeWithAllCompilerChecks(listOf(this)) .analyzeWithAllCompilerChecks(listOf(this), callback)
/** /**
* This function is expected to produce the same result as compiler for the given element and its children (including diagnostics, * This function is expected to produce the same result as compiler for the given element and its children (including diagnostics,
@@ -12,6 +12,8 @@ import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.ResolverForProject import org.jetbrains.kotlin.analyzer.ResolverForProject
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.idea.FrontendInternals import org.jetbrains.kotlin.idea.FrontendInternals
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
@@ -24,7 +26,7 @@ interface ResolutionFacade {
fun analyze(element: KtElement, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): BindingContext fun analyze(element: KtElement, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): BindingContext
fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext
fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>): AnalysisResult fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>, callback: DiagnosticSink.DiagnosticsCallback? = null): AnalysisResult
fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): DeclarationDescriptor fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): DeclarationDescriptor
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.container.tryGetService
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.FrontendInternals 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.IdeaModuleInfo
import org.jetbrains.kotlin.idea.project.ResolveElementCache import org.jetbrains.kotlin.idea.project.ResolveElementCache
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade 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() ResolveInDispatchThreadManager.assertNoResolveInDispatchThread()
return runWithCancellationCheck { 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.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.frontend.di.createContainerForLazyBodyResolve import org.jetbrains.kotlin.frontend.di.createContainerForLazyBodyResolve
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
@@ -89,32 +90,50 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
return null return null
} }
internal fun getAnalysisResults(element: KtElement): AnalysisResult { internal fun getAnalysisResults(element: KtElement, callback: DiagnosticSink.DiagnosticsCallback? = null): AnalysisResult {
check(element) check(element)
val analyzableParent = KotlinResolveDataProvider.findAnalyzableParent(element) ?: return AnalysisResult.EMPTY 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 { return guardLock.guarded {
// step 1: perform incremental analysis IF it is applicable // 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 // cache does not contain AnalysisResult per each kt/psi element
// instead it looks up analysis for its parents - see lookUp(analyzableElement) // instead it looks up analysis for its parents - see lookUp(analyzableElement)
// step 2: return result if it is cached // step 2: return result if it is cached
lookUp(analyzableParent)?.let { 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 // 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 cache[analyzableParent] = result
return@guarded result return@guarded result
} }
} }
private fun getIncrementalAnalysisResult(): AnalysisResult? { private fun getIncrementalAnalysisResult(callback: DiagnosticSink.DiagnosticsCallback?): AnalysisResult? {
updateFileResultFromCache() updateFileResultFromCache()
val inBlockModifications = file.inBlockModifications 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) analysisResult = wrapResult(result, newResult, trace)
} }
file.clearInBlockModifications() 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() ProgressIndicatorProvider.checkCanceled()
val project = analyzableElement.project val project = analyzableElement.project
@@ -242,7 +267,8 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
codeFragmentAnalyzer, codeFragmentAnalyzer,
bodyResolveCache, bodyResolveCache,
analyzableElement, analyzableElement,
bindingTrace bindingTrace,
callback
) )
} catch (e: ProcessCanceledException) { } catch (e: ProcessCanceledException) {
throw e throw e
@@ -412,12 +438,14 @@ private object KotlinResolveDataProvider {
codeFragmentAnalyzer: CodeFragmentAnalyzer, codeFragmentAnalyzer: CodeFragmentAnalyzer,
bodyResolveCache: BodyResolveCache, bodyResolveCache: BodyResolveCache,
analyzableElement: KtElement, analyzableElement: KtElement,
bindingTrace: BindingTrace? bindingTrace: BindingTrace?,
callback: DiagnosticSink.DiagnosticsCallback?
): AnalysisResult { ): AnalysisResult {
try { try {
if (analyzableElement is KtCodeFragment) { if (analyzableElement is KtCodeFragment) {
val bodyResolveMode = BodyResolveMode.PARTIAL_FOR_COMPLETION 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) return AnalysisResult.success(bindingContext, moduleDescriptor)
} }
@@ -427,34 +455,40 @@ private object KotlinResolveDataProvider {
allowSliceRewrite = true allowSliceRewrite = true
) )
val moduleInfo = analyzableElement.containingFile.getModuleInfo() val moduleInfo = analyzableElement.containingKtFile.getModuleInfo()
val targetPlatform = moduleInfo.platform val targetPlatform = moduleInfo.platform
/* callback?.let { trace.setCallback(it) }
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)
Also note that for function bodies, we'll create DelegatingBindingTrace in ResolveElementCache anyways try {
(see 'functionAdditionalResolve'). However, this trace is still needed, because we have other /*
codepaths for other KtDeclarationWithBodies (like property accessors/secondary constructors/class initializers) 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).
val lazyTopDownAnalyzer = createContainerForLazyBodyResolve( Resolving bodies in top-level trace may lead to memory leaks and incorrect resolution, because top-level
//TODO: should get ModuleContext trace isn't invalidated on in-block modifications (while body resolution surely does)
globalContext.withProject(project).withModule(moduleDescriptor),
resolveSession,
trace,
targetPlatform,
bodyResolveCache,
targetPlatform.findAnalyzerServices(project),
analyzableElement.languageVersionSettings,
IdeaModuleStructureOracle(),
IdeMainFunctionDetectorFactory()
).get<LazyTopDownAnalyzer>()
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) return AnalysisResult.success(trace.bindingContext, moduleDescriptor)
} catch (e: ProcessCanceledException) { } catch (e: ProcessCanceledException) {
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.analyzer.*
import org.jetbrains.kotlin.context.GlobalContextImpl import org.jetbrains.kotlin.context.GlobalContextImpl
import org.jetbrains.kotlin.context.withProject import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor 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.*
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
@@ -143,10 +144,13 @@ internal class ProjectResolutionFacade(
internal fun findModuleDescriptor(ideaModuleInfo: IdeaModuleInfo): ModuleDescriptor { internal fun findModuleDescriptor(ideaModuleInfo: IdeaModuleInfo): ModuleDescriptor {
return cachedResolverForProject.descriptorForModule(ideaModuleInfo) return cachedResolverForProject.descriptorForModule(ideaModuleInfo)
} }
internal fun getResolverForProject(): ResolverForProject<IdeaModuleInfo> = cachedResolverForProject 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" } assert(elements.isNotEmpty()) { "elements collection should not be empty" }
val cache = analysisResultsSimpleLock.guarded { val cache = analysisResultsSimpleLock.guarded {
@@ -157,7 +161,7 @@ internal class ProjectResolutionFacade(
val containingKtFile = it.containingKtFile val containingKtFile = it.containingKtFile
val perFileCache = cache[containingKtFile] val perFileCache = cache[containingKtFile]
try { try {
perFileCache.getAnalysisResults(it) perFileCache.getAnalysisResults(it, callback)
} catch (e: Throwable) { } catch (e: Throwable) {
if (e is ControlFlowException) { if (e is ControlFlowException) {
throw e throw e
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.analyzer.ResolverForProject
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.FrontendInternals 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.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade 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) }) { return wrapExceptions({ ResolvingWhat(elements) }) {
delegate.analyzeWithAllCompilerChecks(elements) delegate.analyzeWithAllCompilerChecks(elements, callback)
} }
} }
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.getService import org.jetbrains.kotlin.container.getService
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.idea.FrontendInternals import org.jetbrains.kotlin.idea.FrontendInternals
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
@@ -72,7 +73,10 @@ class KotlinResolutionFacadeForRepl(
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
override fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>): AnalysisResult { override fun analyzeWithAllCompilerChecks(
elements: Collection<KtElement>,
callback: DiagnosticSink.DiagnosticsCallback?
): AnalysisResult {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }