Cached version of DiagnosticsWithSuppression.
This commit is contained in:
+185
-37
@@ -1,11 +1,15 @@
|
||||
package org.jetbrains.jet.lang.resolve;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ConcurrentWeakValueHashMap;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.FilteringIterator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
|
||||
@@ -15,18 +19,41 @@ import org.jetbrains.jet.lang.diagnostics.Severity;
|
||||
import org.jetbrains.jet.lang.psi.JetAnnotationEntry;
|
||||
import org.jetbrains.jet.lang.psi.JetDeclaration;
|
||||
import org.jetbrains.jet.lang.psi.JetModifierList;
|
||||
import org.jetbrains.jet.lang.resolve.constants.*;
|
||||
import org.jetbrains.jet.lang.resolve.constants.ArrayValue;
|
||||
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
|
||||
import org.jetbrains.jet.lang.resolve.constants.StringValue;
|
||||
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class DiagnosticsWithSuppression implements Diagnostics {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(DiagnosticsWithSuppression.class);
|
||||
|
||||
private final BindingContext context;
|
||||
private final Collection<Diagnostic> diagnostics;
|
||||
|
||||
// The cache is weak: we're OK with losing it
|
||||
private final Map<JetDeclaration, Suppressor> suppressors = new ConcurrentWeakValueHashMap<JetDeclaration, Suppressor>();
|
||||
|
||||
// Caching frequently used values:
|
||||
private final ClassDescriptor suppressClass;
|
||||
private final ValueParameterDescriptor suppressParameter;
|
||||
private final Condition<Diagnostic> filter = new Condition<Diagnostic>() {
|
||||
@Override
|
||||
public boolean value(Diagnostic diagnostic) {
|
||||
return !isSuppressed(diagnostic);
|
||||
}
|
||||
};
|
||||
|
||||
public DiagnosticsWithSuppression(@NotNull BindingContext context, @NotNull Collection<Diagnostic> diagnostics) {
|
||||
this.context = context;
|
||||
this.diagnostics = diagnostics;
|
||||
|
||||
this.suppressClass = KotlinBuiltIns.getInstance().getSuppressAnnotationClass();
|
||||
ConstructorDescriptor primaryConstructor = suppressClass.getUnsubstitutedPrimaryConstructor();
|
||||
assert primaryConstructor != null : "No primary constructor in " + suppressClass;
|
||||
this.suppressParameter = primaryConstructor.getValueParameters().get(0);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -38,18 +65,13 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<Diagnostic> iterator() {
|
||||
return all().iterator();
|
||||
return new FilteringIterator<Diagnostic, Diagnostic>(diagnostics.iterator(), filter);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<Diagnostic> all() {
|
||||
return ContainerUtil.filter(diagnostics, new Condition<Diagnostic>() {
|
||||
@Override
|
||||
public boolean value(Diagnostic diagnostic) {
|
||||
return !isSuppressed(diagnostic);
|
||||
}
|
||||
});
|
||||
return ContainerUtil.filter(diagnostics, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,53 +81,106 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
||||
|
||||
private boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
||||
PsiElement element = diagnostic.getPsiElement();
|
||||
if (element instanceof JetDeclaration && isSuppressedByDeclaration(diagnostic, (JetDeclaration) element)) return true;
|
||||
|
||||
while (true) {
|
||||
JetDeclaration declaration = PsiTreeUtil.getParentOfType(element, JetDeclaration.class, true);
|
||||
if (declaration == null) return false;
|
||||
JetDeclaration declaration = PsiTreeUtil.getParentOfType(element, JetDeclaration.class, false);
|
||||
if (declaration == null) return false;
|
||||
|
||||
if (isSuppressedByDeclaration(diagnostic, declaration)) return true;
|
||||
|
||||
if (element == declaration) {
|
||||
element = declaration.getParent();
|
||||
}
|
||||
else {
|
||||
element = declaration;
|
||||
}
|
||||
}
|
||||
return isSuppressedByDeclaration(diagnostic, declaration, 0);
|
||||
}
|
||||
|
||||
private boolean isSuppressedByDeclaration(@NotNull Diagnostic diagnostic, @NotNull JetDeclaration declaration) {
|
||||
JetModifierList modifierList = declaration.getModifierList();
|
||||
if (modifierList == null) return false;
|
||||
/*
|
||||
The cache is optimized for the case where no warnings are suppressed at a declaration (most frequent one)
|
||||
|
||||
ClassDescriptor suppress = KotlinBuiltIns.getInstance().getSuppressAnnotationClass();
|
||||
ConstructorDescriptor primaryConstructor = suppress.getUnsubstitutedPrimaryConstructor();
|
||||
assert primaryConstructor != null : "No primary constructor in " + suppress;
|
||||
ValueParameterDescriptor parameter = primaryConstructor.getValueParameters().get(0);
|
||||
trait Root {
|
||||
suppress("X")
|
||||
trait A {
|
||||
trait B {
|
||||
suppress("Y")
|
||||
trait C {
|
||||
fun foo() = warning
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Nothing is suppressed at foo, so we look above. While looking above we went up to the root (once) and propagated
|
||||
all the suppressors down, so now we have:
|
||||
|
||||
foo - suppress(Y) from C
|
||||
C - suppress(Y) from C
|
||||
B - suppress(X) from A
|
||||
A - suppress(X) from A
|
||||
Root - suppress() from Root
|
||||
|
||||
Next time we look up anything under foo, we try the Y-suppressor and then immediately the X-suppressor, then to the empty
|
||||
suppressor at the root. All the intermediate empty nodes are skipped, because every suppressor remembers its definition point.
|
||||
|
||||
This way we need no more lookups than the number of suppress() annotations from here to the root.
|
||||
*/
|
||||
private boolean isSuppressedByDeclaration(@NotNull Diagnostic diagnostic, @NotNull JetDeclaration declaration, int debugDepth) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Declaration: ", declaration.getName());
|
||||
LOG.debug("Depth: ", debugDepth);
|
||||
LOG.debug("Cache size: ", suppressors.size(), "\n");
|
||||
}
|
||||
|
||||
Suppressor suppressor = getOrCreateSuppressor(declaration);
|
||||
if (suppressor.isSuppressed(diagnostic)) return true;
|
||||
|
||||
JetDeclaration declarationAbove = PsiTreeUtil.getParentOfType(suppressor.getDeclaration(), JetDeclaration.class, true);
|
||||
if (declarationAbove == null) return false;
|
||||
|
||||
boolean suppressed = isSuppressedByDeclaration(diagnostic, declarationAbove, debugDepth + 1);
|
||||
Suppressor suppressorAbove = suppressors.get(declarationAbove);
|
||||
if (suppressorAbove != null && suppressorAbove.dominates(suppressor)) {
|
||||
suppressors.put(declaration, suppressorAbove);
|
||||
}
|
||||
|
||||
return suppressed;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Suppressor getOrCreateSuppressor(@NotNull JetDeclaration declaration) {
|
||||
Suppressor suppressor = suppressors.get(declaration);
|
||||
if (suppressor == null) {
|
||||
Set<String> strings = getSuppressingStrings(declaration.getModifierList());
|
||||
if (strings.isEmpty()) {
|
||||
suppressor = new EmptySuppressor(declaration);
|
||||
}
|
||||
else if (strings.size() == 1) {
|
||||
suppressor = new SingularSuppressor(declaration, strings.iterator().next());
|
||||
}
|
||||
else {
|
||||
suppressor = new MultiSuppressor(declaration, strings);
|
||||
}
|
||||
suppressors.put(declaration, suppressor);
|
||||
}
|
||||
return suppressor;
|
||||
}
|
||||
|
||||
private Set<String> getSuppressingStrings(@Nullable JetModifierList modifierList) {
|
||||
if (modifierList == null) return ImmutableSet.of();
|
||||
|
||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
||||
for (JetAnnotationEntry annotationEntry : modifierList.getAnnotationEntries()) {
|
||||
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
|
||||
if (annotationDescriptor == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!suppress.equals(annotationDescriptor.getType().getConstructor().getDeclarationDescriptor())) continue;
|
||||
if (!suppressClass.equals(annotationDescriptor.getType().getConstructor().getDeclarationDescriptor())) continue;
|
||||
|
||||
Map<ValueParameterDescriptor, CompileTimeConstant<?>> arguments = annotationDescriptor.getAllValueArguments();
|
||||
CompileTimeConstant<?> value = arguments.get(parameter);
|
||||
CompileTimeConstant<?> value = arguments.get(suppressParameter);
|
||||
if (value instanceof ArrayValue) {
|
||||
ArrayValue arrayValue = (ArrayValue) value;
|
||||
List<CompileTimeConstant<?>> values = arrayValue.getValue();
|
||||
|
||||
if (isSuppressedByStrings(diagnostic, strings(values))) {
|
||||
return true;
|
||||
}
|
||||
addStrings(builder, values);
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static boolean isSuppressedByStrings(@NotNull Diagnostic diagnostic, @NotNull Set<String> strings) {
|
||||
@@ -115,14 +190,87 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
||||
}
|
||||
|
||||
// We only add strings and skip other values to facilitate recovery in presence of erroneous code
|
||||
private static Set<String> strings(List<CompileTimeConstant<?>> values) {
|
||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
||||
private static void addStrings(ImmutableSet.Builder<String> builder, List<CompileTimeConstant<?>> values) {
|
||||
for (CompileTimeConstant<?> value : values) {
|
||||
if (value instanceof StringValue) {
|
||||
StringValue stringValue = (StringValue) value;
|
||||
builder.add(stringValue.getValue().toLowerCase());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static abstract class Suppressor {
|
||||
private final JetDeclaration declaration;
|
||||
|
||||
protected Suppressor(@NotNull JetDeclaration declaration) {
|
||||
this.declaration = declaration;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JetDeclaration getDeclaration() {
|
||||
return declaration;
|
||||
}
|
||||
|
||||
public abstract boolean isSuppressed(@NotNull Diagnostic diagnostic);
|
||||
|
||||
// true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x)
|
||||
public abstract boolean dominates(@NotNull Suppressor other);
|
||||
}
|
||||
|
||||
private static class EmptySuppressor extends Suppressor {
|
||||
|
||||
private EmptySuppressor(@NotNull JetDeclaration declaration) {
|
||||
super(declaration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dominates(@NotNull Suppressor other) {
|
||||
return other instanceof EmptySuppressor;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SingularSuppressor extends Suppressor {
|
||||
private final String string;
|
||||
|
||||
private SingularSuppressor(@NotNull JetDeclaration declaration, @NotNull String string) {
|
||||
super(declaration);
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
||||
return isSuppressedByStrings(diagnostic, ImmutableSet.of(string));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dominates(@NotNull Suppressor other) {
|
||||
return other instanceof EmptySuppressor
|
||||
|| (other instanceof SingularSuppressor && ((SingularSuppressor) other).string.equals(string));
|
||||
}
|
||||
}
|
||||
|
||||
private static class MultiSuppressor extends Suppressor {
|
||||
private final Set<String> strings;
|
||||
|
||||
private MultiSuppressor(@NotNull JetDeclaration declaration, @NotNull Set<String> strings) {
|
||||
super(declaration);
|
||||
this.strings = strings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
||||
return isSuppressedByStrings(diagnostic, strings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dominates(@NotNull Suppressor other) {
|
||||
// it's too costly to check set inclusion
|
||||
return other instanceof EmptySuppressor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user