Cached version of DiagnosticsWithSuppression.

This commit is contained in:
Andrey Breslav
2013-09-18 18:09:59 +04:00
parent 2dfaa81995
commit 9d7b3a7a96
@@ -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;
}
}
}