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.openapi.util.ModificationTracker;
023 import com.intellij.psi.PsiElement;
024 import com.intellij.psi.PsiFile;
025 import com.intellij.util.containers.ConcurrentWeakValueHashMap;
026 import com.intellij.util.containers.ContainerUtil;
027 import com.intellij.util.containers.FilteringIterator;
028 import org.jetbrains.annotations.NotNull;
029 import org.jetbrains.annotations.TestOnly;
030 import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
031 import org.jetbrains.jet.lang.diagnostics.Diagnostic;
032 import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory;
033 import org.jetbrains.jet.lang.diagnostics.Errors;
034 import org.jetbrains.jet.lang.diagnostics.Severity;
035 import org.jetbrains.jet.lang.psi.JetAnnotated;
036 import org.jetbrains.jet.lang.psi.JetAnnotationEntry;
037 import org.jetbrains.jet.lang.psi.JetStubbedPsiUtil;
038 import org.jetbrains.jet.lang.psi.JetFile;
039 import org.jetbrains.jet.lang.psi.codeFragmentUtil.CodeFragmentUtilPackage;
040 import org.jetbrains.jet.lang.resolve.constants.ArrayValue;
041 import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
042 import org.jetbrains.jet.lang.resolve.constants.StringValue;
043 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
044
045 import java.util.Collection;
046 import java.util.Iterator;
047 import java.util.Map;
048 import java.util.Set;
049
050 public class DiagnosticsWithSuppression implements Diagnostics {
051
052 private static final Logger LOG = Logger.getInstance(DiagnosticsWithSuppression.class);
053
054 private final BindingContext context;
055 private final Collection<Diagnostic> diagnostics;
056
057 // The cache is weak: we're OK with losing it
058 private final Map<JetAnnotated, Suppressor> suppressors = new ConcurrentWeakValueHashMap<JetAnnotated, Suppressor>();
059
060 private final Condition<Diagnostic> filter = new Condition<Diagnostic>() {
061 @Override
062 public boolean value(Diagnostic diagnostic) {
063 return !isSuppressed(diagnostic);
064 }
065 };
066 private final DiagnosticsElementsCache elementsCache = new DiagnosticsElementsCache(this);
067
068 public DiagnosticsWithSuppression(@NotNull BindingContext context, @NotNull Collection<Diagnostic> diagnostics) {
069 this.context = context;
070 this.diagnostics = diagnostics;
071 }
072
073 @NotNull
074 @Override
075 public Diagnostics noSuppression() {
076 return new SimpleDiagnostics(diagnostics);
077 }
078
079 @NotNull
080 @Override
081 public Iterator<Diagnostic> iterator() {
082 return new FilteringIterator<Diagnostic, Diagnostic>(diagnostics.iterator(), filter);
083 }
084
085 @NotNull
086 @Override
087 public Collection<Diagnostic> all() {
088 return ContainerUtil.filter(diagnostics, filter);
089 }
090
091 @NotNull
092 @Override
093 public Collection<Diagnostic> forElement(@NotNull PsiElement psiElement) {
094 return elementsCache.getDiagnostics(psiElement);
095 }
096
097 @Override
098 public boolean isEmpty() {
099 return all().isEmpty();
100 }
101
102 private boolean isSuppressed(@NotNull Diagnostic diagnostic) {
103 PsiElement element = diagnostic.getPsiElement();
104
105 if (isSuppressedForDebugger(diagnostic, element)) return true;
106
107 JetAnnotated annotated = JetStubbedPsiUtil.getPsiOrStubParent(element, JetAnnotated.class, false);
108 if (annotated == null) return false;
109
110 return isSuppressedByAnnotated(diagnostic, annotated, 0);
111 }
112
113 private static boolean isSuppressedForDebugger(@NotNull Diagnostic diagnostic, @NotNull PsiElement element) {
114 PsiFile containingFile = element.getContainingFile();
115 if (containingFile instanceof JetFile && CodeFragmentUtilPackage.getSkipVisibilityCheck((JetFile) containingFile)) {
116 DiagnosticFactory<?> diagnosticFactory = diagnostic.getFactory();
117 return diagnosticFactory == Errors.INVISIBLE_MEMBER ||
118 diagnosticFactory == Errors.INVISIBLE_REFERENCE ||
119 diagnosticFactory == Errors.INVISIBLE_SETTER;
120 }
121 return false;
122 }
123
124 /*
125 The cache is optimized for the case where no warnings are suppressed (most frequent one)
126
127 trait Root {
128 suppress("X")
129 trait A {
130 trait B {
131 suppress("Y")
132 trait C {
133 fun foo() = warning
134 }
135 }
136 }
137 }
138
139 Nothing is suppressed at foo, so we look above. While looking above we went up to the root (once) and propagated
140 all the suppressors down, so now we have:
141
142 foo - suppress(Y) from C
143 C - suppress(Y) from C
144 B - suppress(X) from A
145 A - suppress(X) from A
146 Root - suppress() from Root
147
148 Next time we look up anything under foo, we try the Y-suppressor and then immediately the X-suppressor, then to the empty
149 suppressor at the root. All the intermediate empty nodes are skipped, because every suppressor remembers its definition point.
150
151 This way we need no more lookups than the number of suppress() annotations from here to the root.
152 */
153 private boolean isSuppressedByAnnotated(@NotNull Diagnostic diagnostic, @NotNull JetAnnotated annotated, int debugDepth) {
154 if (LOG.isDebugEnabled()) {
155 LOG.debug("Annotated: ", annotated.getName());
156 LOG.debug("Depth: ", debugDepth);
157 LOG.debug("Cache size: ", suppressors.size(), "\n");
158 }
159
160 Suppressor suppressor = getOrCreateSuppressor(annotated);
161 if (suppressor.isSuppressed(diagnostic)) return true;
162
163 JetAnnotated annotatedAbove = JetStubbedPsiUtil.getPsiOrStubParent(suppressor.getAnnotatedElement(), JetAnnotated.class, true);
164 if (annotatedAbove == null) return false;
165
166 boolean suppressed = isSuppressedByAnnotated(diagnostic, annotatedAbove, debugDepth + 1);
167 Suppressor suppressorAbove = suppressors.get(annotatedAbove);
168 if (suppressorAbove != null && suppressorAbove.dominates(suppressor)) {
169 suppressors.put(annotated, suppressorAbove);
170 }
171
172 return suppressed;
173 }
174
175 @NotNull
176 private Suppressor getOrCreateSuppressor(@NotNull JetAnnotated annotated) {
177 Suppressor suppressor = suppressors.get(annotated);
178 if (suppressor == null) {
179 Set<String> strings = getSuppressingStrings(annotated);
180 if (strings.isEmpty()) {
181 suppressor = new EmptySuppressor(annotated);
182 }
183 else if (strings.size() == 1) {
184 suppressor = new SingularSuppressor(annotated, strings.iterator().next());
185 }
186 else {
187 suppressor = new MultiSuppressor(annotated, strings);
188 }
189 suppressors.put(annotated, suppressor);
190 }
191 return suppressor;
192 }
193
194 private Set<String> getSuppressingStrings(@NotNull JetAnnotated annotated) {
195 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
196 for (JetAnnotationEntry annotationEntry : annotated.getAnnotationEntries()) {
197 AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
198 if (annotationDescriptor == null) continue;
199
200 if (!KotlinBuiltIns.getInstance().isSuppressAnnotation(annotationDescriptor)) continue;
201
202 // We only add strings and skip other values to facilitate recovery in presence of erroneous code
203 for (CompileTimeConstant<?> arrayValue : annotationDescriptor.getAllValueArguments().values()) {
204 if ((arrayValue instanceof ArrayValue)) {
205 for (CompileTimeConstant<?> value : ((ArrayValue) arrayValue).getValue()) {
206 if (value instanceof StringValue) {
207 builder.add(String.valueOf(((StringValue) value).getValue()).toLowerCase());
208 }
209 }
210 }
211 }
212 }
213 return builder.build();
214 }
215
216 public static boolean isSuppressedByStrings(@NotNull Diagnostic diagnostic, @NotNull Set<String> strings) {
217 if (strings.contains("warnings") && diagnostic.getSeverity() == Severity.WARNING) return true;
218
219 return strings.contains(diagnostic.getFactory().getName().toLowerCase());
220 }
221
222 @NotNull
223 @Override
224 public ModificationTracker getModificationTracker() {
225 throw new IllegalStateException("Trying to obtain modification tracker for readonly DiagnosticsWithSuppression.");
226 }
227
228 private static abstract class Suppressor {
229 private final JetAnnotated annotated;
230
231 protected Suppressor(@NotNull JetAnnotated annotated) {
232 this.annotated = annotated;
233 }
234
235 @NotNull
236 public JetAnnotated getAnnotatedElement() {
237 return annotated;
238 }
239
240 public abstract boolean isSuppressed(@NotNull Diagnostic diagnostic);
241
242 // true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x)
243 public abstract boolean dominates(@NotNull Suppressor other);
244 }
245
246 private static class EmptySuppressor extends Suppressor {
247
248 private EmptySuppressor(@NotNull JetAnnotated annotated) {
249 super(annotated);
250 }
251
252 @Override
253 public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
254 return false;
255 }
256
257 @Override
258 public boolean dominates(@NotNull Suppressor other) {
259 return other instanceof EmptySuppressor;
260 }
261 }
262
263 private static class SingularSuppressor extends Suppressor {
264 private final String string;
265
266 private SingularSuppressor(@NotNull JetAnnotated annotated, @NotNull String string) {
267 super(annotated);
268 this.string = string;
269 }
270
271 @Override
272 public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
273 return isSuppressedByStrings(diagnostic, ImmutableSet.of(string));
274 }
275
276 @Override
277 public boolean dominates(@NotNull Suppressor other) {
278 return other instanceof EmptySuppressor
279 || (other instanceof SingularSuppressor && ((SingularSuppressor) other).string.equals(string));
280 }
281 }
282
283 private static class MultiSuppressor extends Suppressor {
284 private final Set<String> strings;
285
286 private MultiSuppressor(@NotNull JetAnnotated annotated, @NotNull Set<String> strings) {
287 super(annotated);
288 this.strings = strings;
289 }
290
291 @Override
292 public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
293 return isSuppressedByStrings(diagnostic, strings);
294 }
295
296 @Override
297 public boolean dominates(@NotNull Suppressor other) {
298 // it's too costly to check set inclusion
299 return other instanceof EmptySuppressor;
300 }
301 }
302
303 @TestOnly
304 @NotNull
305 public Collection<Diagnostic> getDiagnostics() {
306 return diagnostics;
307 }
308 }