Cached version of DiagnosticsWithSuppression.
This commit is contained in:
+185
-37
@@ -1,11 +1,15 @@
|
|||||||
package org.jetbrains.jet.lang.resolve;
|
package org.jetbrains.jet.lang.resolve;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.util.Condition;
|
import com.intellij.openapi.util.Condition;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
|
import com.intellij.util.containers.ConcurrentWeakValueHashMap;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
|
import com.intellij.util.containers.FilteringIterator;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
|
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
|
||||||
import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
|
import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
|
||||||
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
|
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.JetAnnotationEntry;
|
||||||
import org.jetbrains.jet.lang.psi.JetDeclaration;
|
import org.jetbrains.jet.lang.psi.JetDeclaration;
|
||||||
import org.jetbrains.jet.lang.psi.JetModifierList;
|
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 org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class DiagnosticsWithSuppression implements Diagnostics {
|
public class DiagnosticsWithSuppression implements Diagnostics {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getInstance(DiagnosticsWithSuppression.class);
|
||||||
|
|
||||||
private final BindingContext context;
|
private final BindingContext context;
|
||||||
private final Collection<Diagnostic> diagnostics;
|
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) {
|
public DiagnosticsWithSuppression(@NotNull BindingContext context, @NotNull Collection<Diagnostic> diagnostics) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.diagnostics = diagnostics;
|
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
|
@NotNull
|
||||||
@@ -38,18 +65,13 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Diagnostic> iterator() {
|
public Iterator<Diagnostic> iterator() {
|
||||||
return all().iterator();
|
return new FilteringIterator<Diagnostic, Diagnostic>(diagnostics.iterator(), filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Collection<Diagnostic> all() {
|
public Collection<Diagnostic> all() {
|
||||||
return ContainerUtil.filter(diagnostics, new Condition<Diagnostic>() {
|
return ContainerUtil.filter(diagnostics, filter);
|
||||||
@Override
|
|
||||||
public boolean value(Diagnostic diagnostic) {
|
|
||||||
return !isSuppressed(diagnostic);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,53 +81,106 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
|||||||
|
|
||||||
private boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
private boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
||||||
PsiElement element = diagnostic.getPsiElement();
|
PsiElement element = diagnostic.getPsiElement();
|
||||||
if (element instanceof JetDeclaration && isSuppressedByDeclaration(diagnostic, (JetDeclaration) element)) return true;
|
|
||||||
|
|
||||||
while (true) {
|
JetDeclaration declaration = PsiTreeUtil.getParentOfType(element, JetDeclaration.class, false);
|
||||||
JetDeclaration declaration = PsiTreeUtil.getParentOfType(element, JetDeclaration.class, true);
|
if (declaration == null) return false;
|
||||||
if (declaration == null) return false;
|
|
||||||
|
|
||||||
if (isSuppressedByDeclaration(diagnostic, declaration)) return true;
|
return isSuppressedByDeclaration(diagnostic, declaration, 0);
|
||||||
|
|
||||||
if (element == declaration) {
|
|
||||||
element = declaration.getParent();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
element = declaration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSuppressedByDeclaration(@NotNull Diagnostic diagnostic, @NotNull JetDeclaration declaration) {
|
/*
|
||||||
JetModifierList modifierList = declaration.getModifierList();
|
The cache is optimized for the case where no warnings are suppressed at a declaration (most frequent one)
|
||||||
if (modifierList == null) return false;
|
|
||||||
|
|
||||||
ClassDescriptor suppress = KotlinBuiltIns.getInstance().getSuppressAnnotationClass();
|
trait Root {
|
||||||
ConstructorDescriptor primaryConstructor = suppress.getUnsubstitutedPrimaryConstructor();
|
suppress("X")
|
||||||
assert primaryConstructor != null : "No primary constructor in " + suppress;
|
trait A {
|
||||||
ValueParameterDescriptor parameter = primaryConstructor.getValueParameters().get(0);
|
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()) {
|
for (JetAnnotationEntry annotationEntry : modifierList.getAnnotationEntries()) {
|
||||||
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
|
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
|
||||||
if (annotationDescriptor == null) {
|
if (annotationDescriptor == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!suppress.equals(annotationDescriptor.getType().getConstructor().getDeclarationDescriptor())) continue;
|
if (!suppressClass.equals(annotationDescriptor.getType().getConstructor().getDeclarationDescriptor())) continue;
|
||||||
|
|
||||||
Map<ValueParameterDescriptor, CompileTimeConstant<?>> arguments = annotationDescriptor.getAllValueArguments();
|
Map<ValueParameterDescriptor, CompileTimeConstant<?>> arguments = annotationDescriptor.getAllValueArguments();
|
||||||
CompileTimeConstant<?> value = arguments.get(parameter);
|
CompileTimeConstant<?> value = arguments.get(suppressParameter);
|
||||||
if (value instanceof ArrayValue) {
|
if (value instanceof ArrayValue) {
|
||||||
ArrayValue arrayValue = (ArrayValue) value;
|
ArrayValue arrayValue = (ArrayValue) value;
|
||||||
List<CompileTimeConstant<?>> values = arrayValue.getValue();
|
List<CompileTimeConstant<?>> values = arrayValue.getValue();
|
||||||
|
|
||||||
if (isSuppressedByStrings(diagnostic, strings(values))) {
|
addStrings(builder, values);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return false;
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSuppressedByStrings(@NotNull Diagnostic diagnostic, @NotNull Set<String> strings) {
|
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
|
// We only add strings and skip other values to facilitate recovery in presence of erroneous code
|
||||||
private static Set<String> strings(List<CompileTimeConstant<?>> values) {
|
private static void addStrings(ImmutableSet.Builder<String> builder, List<CompileTimeConstant<?>> values) {
|
||||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
|
||||||
for (CompileTimeConstant<?> value : values) {
|
for (CompileTimeConstant<?> value : values) {
|
||||||
if (value instanceof StringValue) {
|
if (value instanceof StringValue) {
|
||||||
StringValue stringValue = (StringValue) value;
|
StringValue stringValue = (StringValue) value;
|
||||||
builder.add(stringValue.getValue().toLowerCase());
|
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