[Frontend] Make DiagnosticSuppressor a project-level extension

Originally it was an application-level component, which caused non-trivial
  logic and cognitive load to carefully handle those extensions to avoid
  memory leaks.

6740a596 introduced a way to easily register `DiagnosticSuppressor` to
  project, and this commit continues this work, making it a proper
  project-level extension

A lot of changes caused by the fact, that this extension is needed to be
  obtained from `BindingContext` (see `BindingContextSuppressCache` and
  its usages), so almost all changes are introducing `Project` to
  `BindingContext`

^KT-66449 Fixed
This commit is contained in:
Dmitriy Novozhilov
2024-03-08 08:52:28 +02:00
committed by Space Team
parent a552238874
commit d352cc9d96
45 changed files with 165 additions and 127 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.cfg.pseudocode;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cfg.ControlFlowProcessor;
@@ -95,6 +96,12 @@ public class PseudocodeUtil {
public boolean wantsDiagnostics() {
return false;
}
@Nullable
@Override
public Project getProject() {
return bindingContext.getProject();
}
};
return new ControlFlowProcessor(mockTrace, languageVersionSettings).generatePseudocode(declaration);
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.resolve;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import kotlin.Pair;
@@ -89,8 +90,17 @@ public interface BindingContext {
public void addOwnDataTo(@NotNull BindingTrace trace, boolean commitDiagnostics) {
// Do nothing
}
@Nullable
@Override
public Project getProject() {
return null;
}
};
@Nullable
Project getProject();
WritableSlice<KtAnnotationEntry, AnnotationDescriptor> ANNOTATION = Slices.createSimpleSlice();
WritableSlice<KtExpression, CompileTimeConstant<?>> COMPILE_TIME_VALUE = new BasicWritableSlice<>(COMPILE_TIME_VALUE_REWRITE_POLICY);
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.resolve;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.diagnostics.DiagnosticSink;
@@ -30,6 +31,9 @@ public interface BindingTrace extends DiagnosticSink {
@NotNull
BindingContext getBindingContext();
@Nullable
Project getProject();
<K, V> void record(WritableSlice<K, V> slice, K key, V value);
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve;
import com.google.common.collect.ImmutableMap;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
@@ -42,6 +43,7 @@ public class BindingTraceContext implements BindingTrace {
private final MutableSlicedMap map;
private final MutableDiagnosticsWithSuppression mutableDiagnostics;
private final Project project;
private final boolean isValidationEnabled;
@@ -85,36 +87,43 @@ public class BindingTraceContext implements BindingTrace {
public void clear() {
map.clear();
}
@Nullable
@Override
public Project getProject() {
return project;
}
};
public BindingTraceContext() {
this(false);
public BindingTraceContext(Project project) {
this(false, project);
}
public BindingTraceContext(boolean allowSliceRewrite) {
this(BindingTraceFilter.Companion.getACCEPT_ALL(), allowSliceRewrite);
public BindingTraceContext(boolean allowSliceRewrite, Project project) {
this(BindingTraceFilter.Companion.getACCEPT_ALL(), allowSliceRewrite, project);
}
public BindingTraceContext(BindingTraceFilter filter, boolean allowSliceRewrite) {
this(filter, allowSliceRewrite, VALIDATION);
public BindingTraceContext(BindingTraceFilter filter, boolean allowSliceRewrite, Project project) {
this(filter, allowSliceRewrite, VALIDATION, project);
}
public BindingTraceContext(BindingTraceFilter filter, boolean allowSliceRewrite, boolean isValidationEnabled) {
this(TRACK_REWRITES && !allowSliceRewrite ? new TrackingSlicedMap(TRACK_WITH_STACK_TRACES) : new SlicedMapImpl(allowSliceRewrite), filter, isValidationEnabled);
public BindingTraceContext(BindingTraceFilter filter, boolean allowSliceRewrite, boolean isValidationEnabled, Project project) {
this(TRACK_REWRITES && !allowSliceRewrite ? new TrackingSlicedMap(TRACK_WITH_STACK_TRACES) : new SlicedMapImpl(allowSliceRewrite), filter, isValidationEnabled, project);
}
private BindingTraceContext(@NotNull MutableSlicedMap map, BindingTraceFilter filter, boolean isValidationEnabled) {
private BindingTraceContext(@NotNull MutableSlicedMap map, BindingTraceFilter filter, boolean isValidationEnabled, Project project) {
this.map = map;
this.mutableDiagnostics =
filter.getIgnoreDiagnostics()
? null
: new MutableDiagnosticsWithSuppression(new BindingContextSuppressCache(bindingContext), Diagnostics.Companion.getEMPTY());
this.isValidationEnabled = isValidationEnabled;
this.project = project;
}
@TestOnly
public static BindingTraceContext createTraceableBindingTrace() {
return new BindingTraceContext(new TrackingSlicedMap(TRACK_WITH_STACK_TRACES), BindingTraceFilter.Companion.getACCEPT_ALL(), VALIDATION);
public static BindingTraceContext createTraceableBindingTrace(Project project) {
return new BindingTraceContext(new TrackingSlicedMap(TRACK_WITH_STACK_TRACES), BindingTraceFilter.Companion.getACCEPT_ALL(), VALIDATION, project);
}
@Override
@@ -125,6 +134,11 @@ public class BindingTraceContext implements BindingTrace {
mutableDiagnostics.report(diagnostic);
}
@Override
public Project getProject() {
return project;
}
public void clearDiagnostics() {
if (mutableDiagnostics != null) {
mutableDiagnostics.clear();
@@ -27,6 +27,6 @@ interface CodeAnalyzerInitializer {
}
}
class DummyCodeAnalyzerInitializer : CodeAnalyzerInitializer {
override fun createTrace(): BindingTrace = BindingTraceContext(true)
class DummyCodeAnalyzerInitializer(val project: Project) : CodeAnalyzerInitializer {
override fun createTrace(): BindingTrace = BindingTraceContext(/* allowSliceRewrite = */ true, project)
}
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import com.google.common.collect.ImmutableMap
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.diagnostics.Diagnostic
import com.intellij.psi.PsiElement
import com.intellij.openapi.util.ModificationTracker
@@ -66,6 +67,10 @@ class CompositeBindingContext private constructor(
// Do nothing
}
override fun getProject(): Project? {
return delegates.firstOrNull()?.project
}
private class CompositeDiagnostics(
private val delegates: List<Diagnostics>
) : Diagnostics {
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.resolve
import com.google.common.collect.ImmutableMap
import com.intellij.openapi.project.Project
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
@@ -66,6 +67,10 @@ open class DelegatingBindingTrace(
override fun <K, V> getSliceContents(slice: ReadOnlySlice<K, V>): ImmutableMap<K, V> {
return ImmutableMap.copyOf(parentContext.getSliceContents(slice) + map.getSliceContents(slice))
}
override fun getProject(): Project? {
return this@DelegatingBindingTrace.getProject()
}
}
private val bindingContext = MyBindingContext()
@@ -176,4 +181,8 @@ open class DelegatingBindingTrace(
override fun wantsDiagnostics(): Boolean = mutableDiagnostics != null
override fun toString(): String = name
override fun getProject(): Project? {
return parentContext.project
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.resolve;
import com.intellij.openapi.project.Project;
import com.intellij.util.SmartFMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -102,4 +103,10 @@ public class ObservableBindingTrace implements BindingTrace {
public String toString() {
return "ObservableTrace over " + originalTrace.toString();
}
@Nullable
@Override
public Project getProject() {
return originalTrace.getProject();
}
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve.diagnostics
import com.google.common.collect.ImmutableSet
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
@@ -30,7 +31,6 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.util.ExtensionProvider
interface DiagnosticSuppressor {
fun isSuppressed(diagnostic: Diagnostic): Boolean
@@ -48,9 +48,9 @@ interface DiagnosticSuppressor {
}
}
abstract class KotlinSuppressCache : AbstractKotlinSuppressCache<PsiElement>() {
abstract class KotlinSuppressCache(project: Project?) : AbstractKotlinSuppressCache<PsiElement>() {
private val diagnosticSuppressors = ExtensionProvider.create(DiagnosticSuppressor.extensionPointName)
private val diagnosticSuppressors: List<DiagnosticSuppressor> = project?.let { DiagnosticSuppressor.getInstances(it) } ?: emptyList()
val filter: (Diagnostic) -> Boolean = { diagnostic: Diagnostic ->
!isSuppressed(DiagnosticSuppressRequest(diagnostic))
@@ -99,7 +99,7 @@ abstract class KotlinSuppressCache : AbstractKotlinSuppressCache<PsiElement>() {
}
if (request is DiagnosticSuppressRequest) {
for (suppressor in diagnosticSuppressors.get()) {
for (suppressor in diagnosticSuppressors) {
if (isSuppressedByExtension(suppressor, request.diagnostic)) return true
}
}
@@ -122,7 +122,7 @@ abstract class KotlinSuppressCache : AbstractKotlinSuppressCache<PsiElement>() {
}
}
class BindingContextSuppressCache(val context: BindingContext) : KotlinSuppressCache() {
class BindingContextSuppressCache(val context: BindingContext) : KotlinSuppressCache(context.project) {
override fun getSuppressionAnnotations(annotated: PsiElement): List<AnnotationDescriptor> {
val descriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, annotated)
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
import org.jetbrains.kotlin.resolve.BindingContext
class OnDemandSuppressCache(private val context: BindingContext) : KotlinSuppressCache() {
class OnDemandSuppressCache(private val context: BindingContext) : KotlinSuppressCache(context.project) {
private val processedRoots = mutableSetOf<KtFile>()
private val storage = mutableMapOf<PsiElement, List<AnnotationDescriptor>>()
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.storage
import com.intellij.openapi.project.Project
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.resolve.BindingContext
@@ -53,6 +54,10 @@ class LockBasedLazyResolveStorageManager(private val storageManager: StorageMana
@TestOnly
override fun <K, V> getSliceContents(slice: ReadOnlySlice<K, V>) = storageManager.compute { context.getSliceContents<K, V>(slice) }
override fun getProject(): Project? {
return context.project
}
}
private class LockProtectedTrace(private val storageManager: StorageManager, private val trace: BindingTrace) : BindingTrace {
@@ -87,5 +92,9 @@ class LockBasedLazyResolveStorageManager(private val storageManager: StorageMana
override fun toString(): String {
return "Lock-protected trace of LockBasedLazyResolveStorageManager $storageManager"
}
override fun getProject(): Project? {
return trace.project
}
}
}
@@ -1,55 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.util
import com.intellij.openapi.application.Application
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.extensions.ExtensionPointName
import java.lang.ref.WeakReference
open class MappedExtensionProvider<T : Any, out R>
protected constructor(
private val epName: ExtensionPointName<T>,
private val map: (List<T>) -> R
) {
private var cached = WeakReference<Pair<Application, R>>(null)
fun get(): R {
val cached = cached.get() ?: return update()
val (app, extensions) = cached
return if (app == ApplicationManager.getApplication()) {
extensions
} else {
update()
}
}
private fun update(): R {
val newVal = ApplicationManager.getApplication().let { app ->
Pair(app, map(app.extensionArea.getExtensionPoint(epName).extensionList))
}
cached = WeakReference(newVal)
return newVal.second
}
}
class ExtensionProvider<T : Any>(epName: ExtensionPointName<T>) : MappedExtensionProvider<T, List<T>>(epName, { it }) {
companion object {
@JvmStatic
fun <T : Any> create(epName: ExtensionPointName<T>): ExtensionProvider<T> = ExtensionProvider(epName)
}
}