blob: f3c688bc88ae31386e5455bdef96062c22671b1c [file] [log] [blame]
/*
* 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;
}
}