001 /*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.jet.lang.resolve;
018
019 import com.google.common.collect.ImmutableSet;
020 import com.intellij.openapi.diagnostic.Logger;
021 import com.intellij.openapi.util.Condition;
022 import com.intellij.psi.PsiElement;
023 import com.intellij.psi.util.PsiTreeUtil;
024 import com.intellij.util.containers.ConcurrentWeakValueHashMap;
025 import com.intellij.util.containers.ContainerUtil;
026 import com.intellij.util.containers.FilteringIterator;
027 import org.jetbrains.annotations.NotNull;
028 import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
029 import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
030 import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
031 import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
032 import org.jetbrains.jet.lang.diagnostics.Diagnostic;
033 import org.jetbrains.jet.lang.diagnostics.Severity;
034 import org.jetbrains.jet.lang.psi.JetAnnotated;
035 import org.jetbrains.jet.lang.psi.JetAnnotationEntry;
036 import org.jetbrains.jet.lang.resolve.constants.ArrayValue;
037 import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
038 import org.jetbrains.jet.lang.resolve.constants.StringValue;
039 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
040
041 import java.util.*;
042
043 public class DiagnosticsWithSuppression implements Diagnostics {
044
045 private static final Logger LOG = Logger.getInstance(DiagnosticsWithSuppression.class);
046
047 private final BindingContext context;
048 private final Collection<Diagnostic> diagnostics;
049
050 // The cache is weak: we're OK with losing it
051 private final Map<JetAnnotated, Suppressor> suppressors = new ConcurrentWeakValueHashMap<JetAnnotated, Suppressor>();
052
053 // Caching frequently used values:
054 private final ClassDescriptor suppressClass;
055 private final ValueParameterDescriptor suppressParameter;
056 private final Condition<Diagnostic> filter = new Condition<Diagnostic>() {
057 @Override
058 public boolean value(Diagnostic diagnostic) {
059 return !isSuppressed(diagnostic);
060 }
061 };
062 private final DiagnosticsElementsCache elementsCache = new DiagnosticsElementsCache(this);
063
064 public DiagnosticsWithSuppression(@NotNull BindingContext context, @NotNull Collection<Diagnostic> diagnostics) {
065 this.context = context;
066 this.diagnostics = diagnostics;
067
068 this.suppressClass = KotlinBuiltIns.getInstance().getSuppressAnnotationClass();
069 ConstructorDescriptor primaryConstructor = suppressClass.getUnsubstitutedPrimaryConstructor();
070 assert primaryConstructor != null : "No primary constructor in " + suppressClass;
071 this.suppressParameter = primaryConstructor.getValueParameters().get(0);
072 }
073
074 @NotNull
075 @Override
076 public Diagnostics noSuppression() {
077 return new SimpleDiagnostics(diagnostics);
078 }
079
080 @NotNull
081 @Override
082 public Iterator<Diagnostic> iterator() {
083 return new FilteringIterator<Diagnostic, Diagnostic>(diagnostics.iterator(), filter);
084 }
085
086 @NotNull
087 @Override
088 public Collection<Diagnostic> all() {
089 return ContainerUtil.filter(diagnostics, filter);
090 }
091
092 @NotNull
093 @Override
094 public Collection<Diagnostic> forElement(@NotNull PsiElement psiElement) {
095 return elementsCache.getDiagnostics(psiElement);
096 }
097
098 @Override
099 public boolean isEmpty() {
100 return all().isEmpty();
101 }
102
103 private boolean isSuppressed(@NotNull Diagnostic diagnostic) {
104 PsiElement element = diagnostic.getPsiElement();
105
106 JetAnnotated annotated = PsiTreeUtil.getParentOfType(element, JetAnnotated.class, false);
107 if (annotated == null) return false;
108
109 return isSuppressedByAnnotated(diagnostic, annotated, 0);
110 }
111
112 /*
113 The cache is optimized for the case where no warnings are suppressed (most frequent one)
114
115 trait Root {
116 suppress("X")
117 trait A {
118 trait B {
119 suppress("Y")
120 trait C {
121 fun foo() = warning
122 }
123 }
124 }
125 }
126
127 Nothing is suppressed at foo, so we look above. While looking above we went up to the root (once) and propagated
128 all the suppressors down, so now we have:
129
130 foo - suppress(Y) from C
131 C - suppress(Y) from C
132 B - suppress(X) from A
133 A - suppress(X) from A
134 Root - suppress() from Root
135
136 Next time we look up anything under foo, we try the Y-suppressor and then immediately the X-suppressor, then to the empty
137 suppressor at the root. All the intermediate empty nodes are skipped, because every suppressor remembers its definition point.
138
139 This way we need no more lookups than the number of suppress() annotations from here to the root.
140 */
141 private boolean isSuppressedByAnnotated(@NotNull Diagnostic diagnostic, @NotNull JetAnnotated annotated, int debugDepth) {
142 if (LOG.isDebugEnabled()) {
143 LOG.debug("Annotated: ", annotated.getName());
144 LOG.debug("Depth: ", debugDepth);
145 LOG.debug("Cache size: ", suppressors.size(), "\n");
146 }
147
148 Suppressor suppressor = getOrCreateSuppressor(annotated);
149 if (suppressor.isSuppressed(diagnostic)) return true;
150
151 JetAnnotated annotatedAbove = PsiTreeUtil.getParentOfType(suppressor.getAnnotatedElement(), JetAnnotated.class, true);
152 if (annotatedAbove == null) return false;
153
154 boolean suppressed = isSuppressedByAnnotated(diagnostic, annotatedAbove, debugDepth + 1);
155 Suppressor suppressorAbove = suppressors.get(annotatedAbove);
156 if (suppressorAbove != null && suppressorAbove.dominates(suppressor)) {
157 suppressors.put(annotated, suppressorAbove);
158 }
159
160 return suppressed;
161 }
162
163 @NotNull
164 private Suppressor getOrCreateSuppressor(@NotNull JetAnnotated annotated) {
165 Suppressor suppressor = suppressors.get(annotated);
166 if (suppressor == null) {
167 Set<String> strings = getSuppressingStrings(annotated);
168 if (strings.isEmpty()) {
169 suppressor = new EmptySuppressor(annotated);
170 }
171 else if (strings.size() == 1) {
172 suppressor = new SingularSuppressor(annotated, strings.iterator().next());
173 }
174 else {
175 suppressor = new MultiSuppressor(annotated, strings);
176 }
177 suppressors.put(annotated, suppressor);
178 }
179 return suppressor;
180 }
181
182 private Set<String> getSuppressingStrings(@NotNull JetAnnotated annotated) {
183 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
184 for (JetAnnotationEntry annotationEntry : annotated.getAnnotationEntries()) {
185 AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
186 if (annotationDescriptor == null) {
187 continue;
188 }
189
190 if (!suppressClass.equals(annotationDescriptor.getType().getConstructor().getDeclarationDescriptor())) continue;
191
192 Map<ValueParameterDescriptor, CompileTimeConstant<?>> arguments = annotationDescriptor.getAllValueArguments();
193 CompileTimeConstant<?> value = arguments.get(suppressParameter);
194 if (value instanceof ArrayValue) {
195 ArrayValue arrayValue = (ArrayValue) value;
196 List<CompileTimeConstant<?>> values = arrayValue.getValue();
197
198 addStrings(builder, values);
199 }
200
201 }
202 return builder.build();
203 }
204
205 public static boolean isSuppressedByStrings(@NotNull Diagnostic diagnostic, @NotNull Set<String> strings) {
206 if (strings.contains("warnings") && diagnostic.getSeverity() == Severity.WARNING) return true;
207
208 return strings.contains(diagnostic.getFactory().getName().toLowerCase());
209 }
210
211 // We only add strings and skip other values to facilitate recovery in presence of erroneous code
212 private static void addStrings(ImmutableSet.Builder<String> builder, List<CompileTimeConstant<?>> values) {
213 for (CompileTimeConstant<?> value : values) {
214 if (value instanceof StringValue) {
215 StringValue stringValue = (StringValue) value;
216 builder.add(stringValue.getValue().toLowerCase());
217 }
218 }
219 }
220
221 private static abstract class Suppressor {
222 private final JetAnnotated annotated;
223
224 protected Suppressor(@NotNull JetAnnotated annotated) {
225 this.annotated = annotated;
226 }
227
228 @NotNull
229 public JetAnnotated getAnnotatedElement() {
230 return annotated;
231 }
232
233 public abstract boolean isSuppressed(@NotNull Diagnostic diagnostic);
234
235 // true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x)
236 public abstract boolean dominates(@NotNull Suppressor other);
237 }
238
239 private static class EmptySuppressor extends Suppressor {
240
241 private EmptySuppressor(@NotNull JetAnnotated annotated) {
242 super(annotated);
243 }
244
245 @Override
246 public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
247 return false;
248 }
249
250 @Override
251 public boolean dominates(@NotNull Suppressor other) {
252 return other instanceof EmptySuppressor;
253 }
254 }
255
256 private static class SingularSuppressor extends Suppressor {
257 private final String string;
258
259 private SingularSuppressor(@NotNull JetAnnotated annotated, @NotNull String string) {
260 super(annotated);
261 this.string = string;
262 }
263
264 @Override
265 public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
266 return isSuppressedByStrings(diagnostic, ImmutableSet.of(string));
267 }
268
269 @Override
270 public boolean dominates(@NotNull Suppressor other) {
271 return other instanceof EmptySuppressor
272 || (other instanceof SingularSuppressor && ((SingularSuppressor) other).string.equals(string));
273 }
274 }
275
276 private static class MultiSuppressor extends Suppressor {
277 private final Set<String> strings;
278
279 private MultiSuppressor(@NotNull JetAnnotated annotated, @NotNull Set<String> strings) {
280 super(annotated);
281 this.strings = strings;
282 }
283
284 @Override
285 public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
286 return isSuppressedByStrings(diagnostic, strings);
287 }
288
289 @Override
290 public boolean dominates(@NotNull Suppressor other) {
291 // it's too costly to check set inclusion
292 return other instanceof EmptySuppressor;
293 }
294 }
295 }