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