| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.jetbrains.python.psi.types; |
| |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.jetbrains.python.psi.Callable; |
| import com.jetbrains.python.psi.PyTypedElement; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author yole |
| */ |
| public class TypeEvalContext { |
| public static class Key { |
| private static final Key INSTANCE = new Key(); |
| |
| private Key() {} |
| } |
| |
| private final boolean myAllowDataFlow; |
| private final boolean myAllowStubToAST; |
| private List<String> myTrace; |
| private String myTraceIndent = ""; |
| @Nullable private final PsiFile myOrigin; |
| |
| private final Map<PyTypedElement, PyType> myEvaluated = new HashMap<PyTypedElement, PyType>(); |
| private final Map<Callable, PyType> myEvaluatedReturn = new HashMap<Callable, PyType>(); |
| private final ThreadLocal<Set<PyTypedElement>> myEvaluating = new ThreadLocal<Set<PyTypedElement>>() { |
| @Override |
| protected Set<PyTypedElement> initialValue() { |
| return new HashSet<PyTypedElement>(); |
| } |
| }; |
| private final ThreadLocal<Set<Callable>> myEvaluatingReturn = new ThreadLocal<Set<Callable>>() { |
| @Override |
| protected Set<Callable> initialValue() { |
| return new HashSet<Callable>(); |
| } |
| }; |
| |
| private TypeEvalContext(boolean allowDataFlow, boolean allowStubToAST, @Nullable PsiFile origin) { |
| myAllowDataFlow = allowDataFlow; |
| myAllowStubToAST = allowStubToAST; |
| myOrigin = origin; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("TypeEvalContext(%b, %b, %s)", myAllowDataFlow, myAllowStubToAST, myOrigin); |
| } |
| |
| public boolean allowDataFlow(PsiElement element) { |
| return myAllowDataFlow || element.getContainingFile() == myOrigin; |
| } |
| |
| public boolean allowReturnTypes(PsiElement element) { |
| return myAllowDataFlow || element.getContainingFile() == myOrigin; |
| } |
| |
| public boolean allowLocalUsages(@NotNull PsiElement element) { |
| return myAllowStubToAST && myAllowDataFlow && element.getContainingFile() == myOrigin; |
| } |
| |
| /** |
| * Create the most detailed type evaluation context for user-initiated actions. |
| * |
| * Should be used for code completion, go to definition, find usages, refactorings, documentation. |
| */ |
| public static TypeEvalContext userInitiated(@Nullable PsiFile origin) { |
| return new TypeEvalContext(true, true, origin); |
| } |
| |
| /** |
| * Create a type evaluation context for performing analysis operations on the specified file which is currently open in the editor, |
| * without accessing stubs. For such a file, additional slow operations are allowed. |
| * |
| * Inspections should not create a new type evaluation context. They should re-use the context of the inspection session. |
| */ |
| public static TypeEvalContext codeAnalysis(@Nullable PsiFile origin) { |
| return new TypeEvalContext(false, false, origin); |
| } |
| |
| /** |
| * Create the most shallow type evaluation context for code insight purposes when other more detailed contexts are not available. |
| * |
| * It's use should be minimized. |
| */ |
| public static TypeEvalContext codeInsightFallback() { |
| return new TypeEvalContext(false, false, null); |
| } |
| |
| /** |
| * Create a type evaluation context for deeper and slower code insight. |
| * |
| * Should be used only when normal code insight context is not enough for getting good results. |
| */ |
| public static TypeEvalContext deepCodeInsight() { |
| return new TypeEvalContext(false, true, null); |
| } |
| |
| public TypeEvalContext withTracing() { |
| if (myTrace == null) { |
| myTrace = new ArrayList<String>(); |
| } |
| return this; |
| } |
| |
| public void trace(String message, Object... args) { |
| if (myTrace != null) { |
| myTrace.add(myTraceIndent + String.format(message, args)); |
| } |
| } |
| |
| public void traceIndent() { |
| if (myTrace != null) { |
| myTraceIndent += " "; |
| } |
| } |
| |
| public void traceUnindent() { |
| if (myTrace != null && myTraceIndent.length() >= 2) { |
| myTraceIndent = myTraceIndent.substring(0, myTraceIndent.length()-2); |
| } |
| } |
| |
| public String printTrace() { |
| return StringUtil.join(myTrace, "\n"); |
| } |
| |
| public boolean tracing() { |
| return myTrace != null; |
| } |
| |
| @Nullable |
| public PyType getType(@NotNull final PyTypedElement element) { |
| final Set<PyTypedElement> evaluating = myEvaluating.get(); |
| if (evaluating.contains(element)) { |
| return null; |
| } |
| evaluating.add(element); |
| try { |
| synchronized (myEvaluated) { |
| if (myEvaluated.containsKey(element)) { |
| final PyType type = myEvaluated.get(element); |
| assertValid(type, element); |
| return type; |
| } |
| } |
| final PyType type = element.getType(this, Key.INSTANCE); |
| assertValid(type, element); |
| synchronized (myEvaluated) { |
| myEvaluated.put(element, type); |
| } |
| return type; |
| } |
| finally { |
| evaluating.remove(element); |
| } |
| } |
| |
| @Nullable |
| public PyType getReturnType(@NotNull final Callable callable) { |
| final Set<Callable> evaluating = myEvaluatingReturn.get(); |
| if (evaluating.contains(callable)) { |
| return null; |
| } |
| evaluating.add(callable); |
| try { |
| synchronized (myEvaluatedReturn) { |
| if (myEvaluatedReturn.containsKey(callable)) { |
| final PyType type = myEvaluatedReturn.get(callable); |
| assertValid(type, callable); |
| return type; |
| } |
| } |
| final PyType type = callable.getReturnType(this, Key.INSTANCE); |
| assertValid(type, callable); |
| synchronized (myEvaluatedReturn) { |
| myEvaluatedReturn.put(callable, type); |
| } |
| return type; |
| } |
| finally { |
| evaluating.remove(callable); |
| } |
| } |
| |
| private static void assertValid(@Nullable PyType result, @NotNull PyTypedElement element) { |
| if (result != null) { |
| result.assertValid(element.toString()); |
| } |
| } |
| |
| public boolean maySwitchToAST(@NotNull PsiElement element) { |
| return myAllowStubToAST || myOrigin == element.getContainingFile(); |
| } |
| |
| @Nullable |
| public PsiFile getOrigin() { |
| return myOrigin; |
| } |
| } |