Add missing @NotNull annotations on arguments of PyFile#find* methods
[idea/community.git] / python / src / com / jetbrains / python / psi / impl / PyFileImpl.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.psi.impl;
17
18 import com.google.common.collect.Lists;
19 import com.google.common.collect.Maps;
20 import com.intellij.extapi.psi.PsiFileBase;
21 import com.intellij.icons.AllIcons;
22 import com.intellij.lang.Language;
23 import com.intellij.navigation.ItemPresentation;
24 import com.intellij.openapi.fileTypes.FileType;
25 import com.intellij.openapi.roots.ProjectFileIndex;
26 import com.intellij.openapi.util.Computable;
27 import com.intellij.openapi.util.Key;
28 import com.intellij.openapi.util.RecursionManager;
29 import com.intellij.openapi.util.io.FileUtil;
30 import com.intellij.openapi.vfs.VfsUtilCore;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.psi.*;
33 import com.intellij.psi.scope.PsiScopeProcessor;
34 import com.intellij.psi.stubs.StubElement;
35 import com.intellij.psi.util.PsiModificationTracker;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import com.intellij.psi.util.QualifiedName;
38 import com.intellij.reference.SoftReference;
39 import com.intellij.util.IncorrectOperationException;
40 import com.intellij.util.Processor;
41 import com.intellij.util.containers.ContainerUtil;
42 import com.intellij.util.indexing.IndexingDataKeys;
43 import com.jetbrains.python.PyElementTypes;
44 import com.jetbrains.python.PyNames;
45 import com.jetbrains.python.PythonFileType;
46 import com.jetbrains.python.PythonLanguage;
47 import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
48 import com.jetbrains.python.documentation.docstrings.DocStringUtil;
49 import com.jetbrains.python.inspections.PythonVisitorFilter;
50 import com.jetbrains.python.psi.*;
51 import com.jetbrains.python.psi.impl.references.PyReferenceImpl;
52 import com.jetbrains.python.psi.resolve.*;
53 import com.jetbrains.python.psi.stubs.PyFileStub;
54 import com.jetbrains.python.psi.types.PyModuleType;
55 import com.jetbrains.python.psi.types.PyType;
56 import com.jetbrains.python.psi.types.TypeEvalContext;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59
60 import javax.swing.*;
61 import java.io.File;
62 import java.util.*;
63
64 public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
65   protected PyType myType;
66
67   //private volatile Boolean myAbsoluteImportEnabled;
68   private final Map<FutureFeature, Boolean> myFutureFeatures;
69   private List<String> myDunderAll;
70   private boolean myDunderAllCalculated;
71   private volatile SoftReference<ExportedNameCache> myExportedNameCache = new SoftReference<>(null);
72   private final PsiModificationTracker myModificationTracker;
73
74   private class ExportedNameCache {
75     private final List<String> myNameDefinerNegativeCache = new ArrayList<>();
76     private long myNameDefinerOOCBModCount = -1;
77     private final long myModificationStamp;
78     private final Map<String, List<PsiNamedElement>> myNamedElements = Maps.newHashMap();
79     private final List<PyImportedNameDefiner> myImportedNameDefiners = Lists.newArrayList();
80
81     private ExportedNameCache(long modificationStamp) {
82       myModificationStamp = modificationStamp;
83
84       processDeclarations(PyPsiUtils.collectAllStubChildren(PyFileImpl.this, getStub()), element -> {
85         if (element instanceof PsiNamedElement && !(element instanceof PyKeywordArgument)) {
86           final PsiNamedElement namedElement = (PsiNamedElement)element;
87           final String name = namedElement.getName();
88           if (!myNamedElements.containsKey(name)) {
89             myNamedElements.put(name, Lists.<PsiNamedElement>newArrayList());
90           }
91           final List<PsiNamedElement> elements = myNamedElements.get(name);
92           elements.add(namedElement);
93         }
94         if (element instanceof PyImportedNameDefiner) {
95           myImportedNameDefiners.add((PyImportedNameDefiner)element);
96         }
97         if (element instanceof PyFromImportStatement) {
98           final PyFromImportStatement fromImportStatement = (PyFromImportStatement)element;
99           final PyStarImportElement starImportElement = fromImportStatement.getStarImportElement();
100           if (starImportElement != null) {
101             myImportedNameDefiners.add(starImportElement);
102           }
103           else {
104             Collections.addAll(myImportedNameDefiners, fromImportStatement.getImportElements());
105           }
106         }
107         else if (element instanceof PyImportStatement) {
108           final PyImportStatement importStatement = (PyImportStatement)element;
109           Collections.addAll(myImportedNameDefiners, importStatement.getImportElements());
110         }
111         return true;
112       });
113       for (List<PsiNamedElement> elements : myNamedElements.values()) {
114         Collections.reverse(elements);
115       }
116       Collections.reverse(myImportedNameDefiners);
117     }
118
119     private boolean processDeclarations(@NotNull List<PsiElement> elements, @NotNull Processor<PsiElement> processor) {
120       for (PsiElement child : elements) {
121         if (!processor.process(child)) {
122           return false;
123         }
124         if (child instanceof PyExceptPart) {
125           final PyExceptPart part = (PyExceptPart)child;
126           if (!processDeclarations(PyPsiUtils.collectAllStubChildren(part, part.getStub()), processor)) {
127             return false;
128           }
129         }
130       }
131       return true;
132     }
133
134     @NotNull
135     private List<RatedResolveResult> multiResolve(@NotNull String name) {
136       synchronized (myNameDefinerNegativeCache) {
137         final long modCount = myModificationTracker.getOutOfCodeBlockModificationCount();
138         if (modCount != myNameDefinerOOCBModCount) {
139           myNameDefinerNegativeCache.clear();
140           myNameDefinerOOCBModCount = modCount;
141         }
142         else {
143           if (myNameDefinerNegativeCache.contains(name)) {
144             return Collections.emptyList();
145           }
146         }
147       }
148
149       final PyResolveProcessor processor = new PyResolveProcessor(name);
150       boolean stopped = false;
151       if (myNamedElements.containsKey(name)) {
152         for (PsiNamedElement element : myNamedElements.get(name)) {
153           if (!processor.execute(element, ResolveState.initial())) {
154             stopped = true;
155             break;
156           }
157         }
158       }
159       if (!stopped) {
160         for (PyImportedNameDefiner definer : myImportedNameDefiners) {
161           if (!processor.execute(definer, ResolveState.initial())) {
162             break;
163           }
164         }
165       }
166       final Map<PsiElement, PyImportedNameDefiner> results = processor.getResults();
167       if (!results.isEmpty()) {
168         final ResolveResultList resultList = new ResolveResultList();
169         final TypeEvalContext typeEvalContext = TypeEvalContext.codeInsightFallback(getProject());
170         for (Map.Entry<PsiElement, PyImportedNameDefiner> entry : results.entrySet()) {
171           final PsiElement element = entry.getKey();
172           final PyImportedNameDefiner definer = entry.getValue();
173           if (element != null) {
174             final int elementRate = PyReferenceImpl.getRate(element, typeEvalContext);
175             if (definer != null) {
176               resultList.add(new ImportedResolveResult(element, elementRate, definer));
177             }
178             else {
179               resultList.poke(element, elementRate);
180             }
181           }
182         }
183         return resultList;
184       }
185
186       synchronized (myNameDefinerNegativeCache) {
187         myNameDefinerNegativeCache.add(name);
188       }
189       return Collections.emptyList();
190     }
191
192     public long getModificationStamp() {
193       return myModificationStamp;
194     }
195   }
196
197   public PyFileImpl(FileViewProvider viewProvider) {
198     this(viewProvider, PythonLanguage.getInstance());
199   }
200
201   public PyFileImpl(FileViewProvider viewProvider, Language language) {
202     super(viewProvider, language);
203     myFutureFeatures = new HashMap<>();
204     myModificationTracker = PsiModificationTracker.SERVICE.getInstance(getProject());
205   }
206
207   @Override
208   @NotNull
209   public FileType getFileType() {
210     return PythonFileType.INSTANCE;
211   }
212
213   public String toString() {
214     return "PyFile:" + getName();
215   }
216
217   @Override
218   public PyFunction findTopLevelFunction(@NotNull String name) {
219     return findByName(name, getTopLevelFunctions());
220   }
221
222   @Override
223   public PyClass findTopLevelClass(@NotNull String name) {
224     return findByName(name, getTopLevelClasses());
225   }
226
227   @Override
228   public PyTargetExpression findTopLevelAttribute(@NotNull String name) {
229     return findByName(name, getTopLevelAttributes());
230   }
231
232   @Nullable
233   private static <T extends PsiNamedElement> T findByName(@NotNull String name, @NotNull List<T> namedElements) {
234     for (T namedElement : namedElements) {
235       if (name.equals(namedElement.getName())) {
236         return namedElement;
237       }
238     }
239     return null;
240   }
241
242   @Override
243   public LanguageLevel getLanguageLevel() {
244     if (myOriginalFile != null) {
245       return ((PyFileImpl)myOriginalFile).getLanguageLevel();
246     }
247     VirtualFile virtualFile = getVirtualFile();
248
249     if (virtualFile == null) {
250       virtualFile = getUserData(IndexingDataKeys.VIRTUAL_FILE);
251     }
252     if (virtualFile == null) {
253       virtualFile = getViewProvider().getVirtualFile();
254     }
255     return PyUtil.getLanguageLevelForVirtualFile(getProject(), virtualFile);
256   }
257
258   @Override
259   public Icon getIcon(int flags) {
260     return PythonFileType.INSTANCE.getIcon();
261   }
262
263   @Override
264   public void accept(@NotNull PsiElementVisitor visitor) {
265     if (isAcceptedFor(visitor.getClass())) {
266       if (visitor instanceof PyElementVisitor) {
267         ((PyElementVisitor)visitor).visitPyFile(this);
268       }
269       else {
270         super.accept(visitor);
271       }
272     }
273   }
274
275   public boolean isAcceptedFor(@NotNull Class visitorClass) {
276     for (Language lang : getViewProvider().getLanguages()) {
277       final List<PythonVisitorFilter> filters = PythonVisitorFilter.INSTANCE.allForLanguage(lang);
278       for (PythonVisitorFilter filter : filters) {
279         if (!filter.isSupported(visitorClass, this)) {
280           return false;
281         }
282       }
283     }
284     return true;
285   }
286
287   private final Key<Set<PyFile>> PROCESSED_FILES = Key.create("PyFileImpl.processDeclarations.processedFiles");
288
289   @Override
290   public boolean processDeclarations(@NotNull final PsiScopeProcessor processor,
291                                      @NotNull ResolveState resolveState,
292                                      PsiElement lastParent,
293                                      @NotNull PsiElement place) {
294     final List<String> dunderAll = getDunderAll();
295     final List<String> remainingDunderAll = dunderAll == null ? null : new ArrayList<>(dunderAll);
296     PsiScopeProcessor wrapper = new PsiScopeProcessor() {
297       @Override
298       public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
299         if (!processor.execute(element, state)) return false;
300         if (remainingDunderAll != null && element instanceof PyElement) {
301           remainingDunderAll.remove(((PyElement)element).getName());
302         }
303         return true;
304       }
305
306       @Override
307       public <T> T getHint(@NotNull Key<T> hintKey) {
308         return processor.getHint(hintKey);
309       }
310
311       @Override
312       public void handleEvent(@NotNull Event event, @Nullable Object associated) {
313         processor.handleEvent(event, associated);
314       }
315     };
316
317     Set<PyFile> pyFiles = resolveState.get(PROCESSED_FILES);
318     if (pyFiles == null) {
319       pyFiles = new HashSet<>();
320       resolveState = resolveState.put(PROCESSED_FILES, pyFiles);
321     }
322     if (pyFiles.contains(this)) return true;
323     pyFiles.add(this);
324     for (PyClass c : getTopLevelClasses()) {
325       if (c == lastParent) continue;
326       if (!wrapper.execute(c, resolveState)) return false;
327     }
328     for (PyFunction f : getTopLevelFunctions()) {
329       if (f == lastParent) continue;
330       if (!wrapper.execute(f, resolveState)) return false;
331     }
332     for (PyTargetExpression e : getTopLevelAttributes()) {
333       if (e == lastParent) continue;
334       if (!wrapper.execute(e, resolveState)) return false;
335     }
336
337     for (PyImportElement e : getImportTargets()) {
338       if (e == lastParent) continue;
339       if (!wrapper.execute(e, resolveState)) return false;
340     }
341
342     for (PyFromImportStatement e : getFromImports()) {
343       if (e == lastParent) continue;
344       if (!e.processDeclarations(wrapper, resolveState, null, this)) return false;
345     }
346
347     if (remainingDunderAll != null) {
348       for (String s : remainingDunderAll) {
349         if (!PyNames.isIdentifier(s)) {
350           continue;
351         }
352         if (!processor.execute(new LightNamedElement(myManager, PythonLanguage.getInstance(), s), resolveState)) return false;
353       }
354     }
355     return true;
356   }
357
358   @Override
359   public List<PyStatement> getStatements() {
360     List<PyStatement> stmts = new ArrayList<>();
361     for (PsiElement child : getChildren()) {
362       if (child instanceof PyStatement) {
363         PyStatement statement = (PyStatement)child;
364         stmts.add(statement);
365       }
366     }
367     return stmts;
368   }
369
370   @Override
371   public List<PyClass> getTopLevelClasses() {
372     return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.CLASS_DECLARATION, PyClass.class);
373   }
374
375   @NotNull
376   @Override
377   public List<PyFunction> getTopLevelFunctions() {
378     return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.FUNCTION_DECLARATION, PyFunction.class);
379   }
380
381   @Override
382   public List<PyTargetExpression> getTopLevelAttributes() {
383     return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.class);
384   }
385
386   @Override
387   @Nullable
388   public PsiElement findExportedName(final String name) {
389     final List<RatedResolveResult> results = multiResolveName(name);
390     final List<PsiElement> elements = Lists.newArrayList();
391     for (RatedResolveResult result : results) {
392       final PsiElement element = result.getElement();
393       final ImportedResolveResult importedResult = PyUtil.as(result, ImportedResolveResult.class);
394       if (importedResult != null) {
395         final PyImportedNameDefiner definer = importedResult.getDefiner();
396         if (definer != null) {
397           elements.add(definer);
398         }
399       }
400       else if (element != null && element.getContainingFile() == this) {
401         elements.add(element);
402       }
403     }
404     final PsiElement element = elements.isEmpty() ? null : elements.get(elements.size() - 1);
405     if (element != null && !element.isValid()) {
406       throw new PsiInvalidElementAccessException(element);
407     }
408     return element;
409   }
410
411   @NotNull
412   @Override
413   public List<RatedResolveResult> multiResolveName(@NotNull final String name) {
414     final List<RatedResolveResult> results = RecursionManager.doPreventingRecursion(this, false,
415                                                                                     () -> getExportedNameCache().multiResolve(name));
416     if (results != null && !results.isEmpty()) {
417       return results;
418     }
419     final List<String> allNames = getDunderAll();
420     if (allNames != null && allNames.contains(name)) {
421       final PsiElement allElement = findExportedName(PyNames.ALL);
422       final ResolveResultList allFallbackResults = new ResolveResultList();
423       allFallbackResults.poke(allElement, RatedResolveResult.RATE_LOW);
424       return allFallbackResults;
425     }
426     return Collections.emptyList();
427   }
428
429   private ExportedNameCache getExportedNameCache() {
430     ExportedNameCache cache;
431     cache = myExportedNameCache != null ? myExportedNameCache.get() : null;
432     final long modificationStamp = getModificationStamp();
433     if (myExportedNameCache != null && cache != null && modificationStamp != cache.getModificationStamp()) {
434       myExportedNameCache.clear();
435       cache = null;
436     }
437     if (cache == null) {
438       cache = new ExportedNameCache(modificationStamp);
439       myExportedNameCache = new SoftReference<>(cache);
440     }
441     return cache;
442   }
443
444   @Nullable
445   public PsiElement getElementNamed(final String name) {
446     final List<RatedResolveResult> results = multiResolveName(name);
447     final List<PsiElement> elements = PyUtil.filterTopPriorityResults(results.toArray(new ResolveResult[results.size()]));
448     final PsiElement element = elements.isEmpty() ? null : elements.get(elements.size() - 1);
449     if (element != null) {
450       if (!element.isValid()) {
451         throw new PsiInvalidElementAccessException(element);
452       }
453       return element;
454     }
455     return null;
456   }
457
458   @NotNull
459   public Iterable<PyElement> iterateNames() {
460     final List<PyElement> result = new ArrayList<>();
461     VariantsProcessor processor = new VariantsProcessor(this) {
462       @Override
463       protected void addElement(String name, PsiElement element) {
464         element = PyUtil.turnDirIntoInit(element);
465         if (element instanceof PyElement) {
466           result.add((PyElement)element);
467         }
468       }
469     };
470     processor.setAllowedNames(getDunderAll());
471     processDeclarations(processor, ResolveState.initial(), null, this);
472     return result;
473   }
474
475   @Override
476   @NotNull
477   public List<PyImportElement> getImportTargets() {
478     List<PyImportElement> ret = new ArrayList<>();
479     List<PyImportStatement> imports =
480       PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.IMPORT_STATEMENT, PyImportStatement.class);
481     for (PyImportStatement one : imports) {
482       ContainerUtil.addAll(ret, one.getImportElements());
483     }
484     return ret;
485   }
486
487   @Override
488   @NotNull
489   public List<PyFromImportStatement> getFromImports() {
490     return PyPsiUtils.collectStubChildren(this, getStub(), PyElementTypes.FROM_IMPORT_STATEMENT, PyFromImportStatement.class);
491   }
492
493   @Override
494   public List<String> getDunderAll() {
495     final StubElement stubElement = getStub();
496     if (stubElement instanceof PyFileStub) {
497       return ((PyFileStub)stubElement).getDunderAll();
498     }
499     if (!myDunderAllCalculated) {
500       final List<String> dunderAll = calculateDunderAll();
501       myDunderAll = dunderAll == null ? null : Collections.unmodifiableList(dunderAll);
502       myDunderAllCalculated = true;
503     }
504     return myDunderAll;
505   }
506
507   @Nullable
508   public List<String> calculateDunderAll() {
509     final DunderAllBuilder builder = new DunderAllBuilder();
510     accept(builder);
511     return builder.result();
512   }
513
514   private static class DunderAllBuilder extends PyRecursiveElementVisitor {
515     private List<String> myResult = null;
516     private boolean myDynamic = false;
517     private boolean myFoundDunderAll = false;
518
519     // hashlib builds __all__ by concatenating multiple lists of strings, and we want to understand this
520     private final Map<String, List<String>> myDunderLike = new HashMap<>();
521
522     @Override
523     public void visitPyFile(PyFile node) {
524       if (node.getText().contains(PyNames.ALL)) {
525         super.visitPyFile(node);
526       }
527     }
528
529     @Override
530     public void visitPyTargetExpression(PyTargetExpression node) {
531       if (PyNames.ALL.equals(node.getName())) {
532         myFoundDunderAll = true;
533         PyExpression value = node.findAssignedValue();
534         if (value instanceof PyBinaryExpression) {
535           PyBinaryExpression binaryExpression = (PyBinaryExpression)value;
536           if (binaryExpression.isOperator("+")) {
537             List<String> lhs = getStringListFromValue(binaryExpression.getLeftExpression());
538             List<String> rhs = getStringListFromValue(binaryExpression.getRightExpression());
539             if (lhs != null && rhs != null) {
540               myResult = new ArrayList<>(lhs);
541               myResult.addAll(rhs);
542             }
543           }
544         }
545         else {
546           myResult = PyUtil.getStringListFromTargetExpression(node);
547         }
548       }
549       if (!myFoundDunderAll) {
550         List<String> names = PyUtil.getStringListFromTargetExpression(node);
551         if (names != null) {
552           myDunderLike.put(node.getName(), names);
553         }
554       }
555     }
556
557     @Nullable
558     private List<String> getStringListFromValue(PyExpression expression) {
559       if (expression instanceof PyReferenceExpression && !((PyReferenceExpression)expression).isQualified()) {
560         return myDunderLike.get(((PyReferenceExpression)expression).getReferencedName());
561       }
562       return PyUtil.strListValue(expression);
563     }
564
565     @Override
566     public void visitPyAugAssignmentStatement(PyAugAssignmentStatement node) {
567       if (PyNames.ALL.equals(node.getTarget().getName())) {
568         myDynamic = true;
569       }
570     }
571
572     @Override
573     public void visitPyCallExpression(PyCallExpression node) {
574       final PyExpression callee = node.getCallee();
575       if (callee instanceof PyQualifiedExpression) {
576         final PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier();
577         if (qualifier != null && PyNames.ALL.equals(qualifier.getText())) {
578           // TODO handle append and extend with constant arguments here
579           myDynamic = true;
580         }
581       }
582     }
583
584     @Nullable
585     List<String> result() {
586       return myDynamic ? null : myResult;
587     }
588   }
589
590   @Nullable
591   public static List<String> getStringListFromTargetExpression(final String name, List<PyTargetExpression> attrs) {
592     for (PyTargetExpression attr : attrs) {
593       if (name.equals(attr.getName())) {
594         return PyUtil.getStringListFromTargetExpression(attr);
595       }
596     }
597     return null;
598   }
599
600   @Override
601   public boolean hasImportFromFuture(FutureFeature feature) {
602     final StubElement stub = getStub();
603     if (stub instanceof PyFileStub) {
604       return ((PyFileStub)stub).getFutureFeatures().get(feature.ordinal());
605     }
606     Boolean enabled = myFutureFeatures.get(feature);
607     if (enabled == null) {
608       enabled = calculateImportFromFuture(feature);
609       myFutureFeatures.put(feature, enabled);
610       // NOTE: ^^^ not synchronized. if two threads will try to modify this, both can only be expected to set the same value.
611     }
612     return enabled;
613   }
614
615   @Override
616   public String getDeprecationMessage() {
617     final StubElement stub = getStub();
618     if (stub instanceof PyFileStub) {
619       return ((PyFileStub)stub).getDeprecationMessage();
620     }
621     return extractDeprecationMessage();
622   }
623
624   @Override
625   public List<PyImportStatementBase> getImportBlock() {
626     final List<PyImportStatementBase> result = new ArrayList<>();
627     final PsiElement firstChild = getFirstChild();
628     final PyImportStatementBase firstImport;
629     if (firstChild instanceof PyImportStatementBase) {
630       firstImport = (PyImportStatementBase)firstChild;
631     }
632     else {
633       firstImport = PsiTreeUtil.getNextSiblingOfType(firstChild, PyImportStatementBase.class);
634     }
635     if (firstImport != null) {
636       result.add(firstImport);
637       PsiElement nextImport = PyPsiUtils.getNextNonCommentSibling(firstImport, true);
638       while (nextImport instanceof PyImportStatementBase) {
639         result.add((PyImportStatementBase)nextImport);
640         nextImport = PyPsiUtils.getNextNonCommentSibling(nextImport, true);
641       }
642     }
643     return result;
644   }
645
646   public String extractDeprecationMessage() {
647     if (canHaveDeprecationMessage(getText())) {
648       return PyFunctionImpl.extractDeprecationMessage(getStatements());
649     }
650     else {
651       return null;
652     }
653   }
654
655   private static boolean canHaveDeprecationMessage(String text) {
656     return text.contains(PyNames.DEPRECATION_WARNING) || text.contains(PyNames.PENDING_DEPRECATION_WARNING);
657   }
658
659   public boolean calculateImportFromFuture(FutureFeature feature) {
660     if (getText().contains(feature.toString())) {
661       final List<PyFromImportStatement> fromImports = getFromImports();
662       for (PyFromImportStatement fromImport : fromImports) {
663         if (fromImport.isFromFuture()) {
664           final PyImportElement[] pyImportElements = fromImport.getImportElements();
665           for (PyImportElement element : pyImportElements) {
666             final QualifiedName qName = element.getImportedQName();
667             if (qName != null && qName.matches(feature.toString())) {
668               return true;
669             }
670           }
671         }
672       }
673     }
674     return false;
675   }
676
677
678   @Override
679   public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
680     if (myType == null) myType = new PyModuleType(this);
681     return myType;
682   }
683
684   @Nullable
685   @Override
686   public String getDocStringValue() {
687     return DocStringUtil.getDocStringValue(this);
688   }
689
690   @Nullable
691   @Override
692   public StructuredDocString getStructuredDocString() {
693     return DocStringUtil.getStructuredDocString(this);
694   }
695
696   @Nullable
697   @Override
698   public PyStringLiteralExpression getDocStringExpression() {
699     return DocStringUtil.findDocStringExpression(this);
700   }
701
702   @Override
703   public void subtreeChanged() {
704     super.subtreeChanged();
705     ControlFlowCache.clear(this);
706     myDunderAllCalculated = false;
707     myFutureFeatures.clear(); // probably no need to synchronize
708     myExportedNameCache.clear();
709   }
710
711   @Override
712   public void delete() throws IncorrectOperationException {
713     String path = getVirtualFile().getPath();
714     super.delete();
715     PyUtil.deletePycFiles(path);
716   }
717
718   @Override
719   public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
720     String path = getVirtualFile().getPath();
721     final PsiElement newElement = super.setName(name);
722     PyUtil.deletePycFiles(path);
723     return newElement;
724   }
725
726   private static class ArrayListThreadLocal extends ThreadLocal<List<String>> {
727     @Override
728     protected List<String> initialValue() {
729       return new ArrayList<>();
730     }
731   }
732
733   @Override
734   public ItemPresentation getPresentation() {
735     return new ItemPresentation() {
736       @Override
737       public String getPresentableText() {
738         return getModuleName(PyFileImpl.this);
739       }
740
741       @Override
742       public String getLocationString() {
743         final String name = getLocationName();
744         return name != null ? "(" + name + ")" : null;
745       }
746
747       @Override
748       public Icon getIcon(final boolean open) {
749         if (PyUtil.isPackage(PyFileImpl.this)) {
750           return AllIcons.Modules.SourceFolder;
751         }
752         return PyFileImpl.this.getIcon(0);
753       }
754
755       @NotNull
756       private String getModuleName(@NotNull PyFile file) {
757         if (PyUtil.isPackage(file)) {
758           final PsiDirectory dir = file.getContainingDirectory();
759           if (dir != null) {
760             return dir.getName();
761           }
762         }
763         return FileUtil.getNameWithoutExtension(file.getName());
764       }
765
766       @Nullable
767       private String getLocationName() {
768         final QualifiedName name = QualifiedNameFinder.findShortestImportableQName(PyFileImpl.this);
769         if (name != null) {
770           final QualifiedName prefix = name.removeTail(1);
771           if (prefix.getComponentCount() > 0) {
772             return prefix.toString();
773           }
774         }
775         final String relativePath = getRelativeContainerPath();
776         if (relativePath != null) {
777           return relativePath;
778         }
779         final PsiDirectory psiDirectory = getParent();
780         if (psiDirectory != null) {
781           return psiDirectory.getVirtualFile().getPresentableUrl();
782         }
783         return null;
784       }
785
786       @Nullable
787       private String getRelativeContainerPath() {
788         final PsiDirectory psiDirectory = getParent();
789         if (psiDirectory != null) {
790           final VirtualFile virtualFile = getVirtualFile();
791           if (virtualFile != null) {
792             final VirtualFile root = ProjectFileIndex.SERVICE.getInstance(getProject()).getContentRootForFile(virtualFile);
793             if (root != null) {
794               final VirtualFile parent = virtualFile.getParent();
795               final VirtualFile rootParent = root.getParent();
796               if (rootParent != null && parent != null) {
797                 return VfsUtilCore.getRelativePath(parent, rootParent, File.separatorChar);
798               }
799             }
800           }
801         }
802         return null;
803       }
804     };
805   }
806 }