diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalConstraints.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalConstraints.java index ebeaf366a6ad..f3a94b336de2 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalConstraints.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalConstraints.java @@ -30,14 +30,17 @@ import org.jetbrains.annotations.Nullable; class TypeEvalConstraints { final boolean myAllowDataFlow; final boolean myAllowStubToAST; + final boolean myAllowCallContext; @Nullable final PsiFile myOrigin; /** * @see com.jetbrains.python.psi.types.TypeEvalContext */ - TypeEvalConstraints(final boolean allowDataFlow, final boolean allowStubToAST, @Nullable final PsiFile origin) { + TypeEvalConstraints(final boolean allowDataFlow, final boolean allowStubToAST, final boolean allowCallContext, + @Nullable final PsiFile origin) { myAllowDataFlow = allowDataFlow; myAllowStubToAST = allowStubToAST; + myAllowCallContext = allowCallContext; myOrigin = origin; } @@ -50,6 +53,7 @@ class TypeEvalConstraints { if (myAllowDataFlow != that.myAllowDataFlow) return false; if (myAllowStubToAST != that.myAllowStubToAST) return false; + if (myAllowCallContext != that.myAllowCallContext) return false; if (myOrigin != null ? !myOrigin.equals(that.myOrigin) : that.myOrigin != null) return false; return true; @@ -60,11 +64,12 @@ class TypeEvalConstraints { int result = (myAllowDataFlow ? 1 : 0); result = 31 * result + (myAllowStubToAST ? 1 : 0); result = 31 * result + (myOrigin != null ? myOrigin.hashCode() : 0); + result = 31 * result + (myAllowCallContext ? 1 : 0); return result; } @Override public String toString() { - return String.format("TypeEvalConstraints(%b, %b, %s)", myAllowDataFlow, myAllowStubToAST, myOrigin); + return String.format("TypeEvalConstraints(%b, %b, %b, %s)", myAllowDataFlow, myAllowStubToAST, myAllowCallContext, myOrigin); } } diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java index 002a566eeb39..54af2a23e3c4 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java @@ -65,8 +65,8 @@ public class TypeEvalContext { } }; - private TypeEvalContext(boolean allowDataFlow, boolean allowStubToAST, @Nullable PsiFile origin) { - myConstraints = new TypeEvalConstraints(allowDataFlow, allowStubToAST, origin); + private TypeEvalContext(boolean allowDataFlow, boolean allowStubToAST, boolean allowCallContext, @Nullable PsiFile origin) { + myConstraints = new TypeEvalConstraints(allowDataFlow, allowStubToAST, allowCallContext, origin); } @Override @@ -83,17 +83,29 @@ public class TypeEvalContext { return myConstraints.myAllowDataFlow || element.getContainingFile() == myConstraints.myOrigin; } - public boolean allowLocalUsages(@NotNull PsiElement element) { - return myConstraints.myAllowStubToAST && myConstraints.myAllowDataFlow && element.getContainingFile() == myConstraints.myOrigin; + public boolean allowCallContext(@NotNull PsiElement element) { + return myConstraints.myAllowCallContext && element.getContainingFile() == myConstraints.myOrigin; + } + + /** + * Create a context for code completion. + *

+ * It is as detailed as {@link TypeEvalContext#userInitiated(Project, PsiFile)}, but allows inferring types based on the context in which + * the analyzed code was called or may be called. Since this is basically guesswork, the results should be used only for code completion. + */ + public static TypeEvalContext codeCompletion(@NotNull final Project project, @Nullable final PsiFile origin) { + return CACHE.getContext(project, new TypeEvalContext(true, true, true, origin)); } /** * Create the most detailed type evaluation context for user-initiated actions. *

- * Should be used for code completion, go to definition, find usages, refactorings, documentation. + * Should be used go to definition, find usages, refactorings, documentation. + *

+ * For code completion see {@link TypeEvalContext#codeCompletion(Project, PsiFile)}. */ public static TypeEvalContext userInitiated(@NotNull final Project project, @Nullable final PsiFile origin) { - return CACHE.getContext(project, new TypeEvalContext(true, true, origin)); + return CACHE.getContext(project, new TypeEvalContext(true, true, false, origin)); } /** @@ -103,20 +115,18 @@ public class TypeEvalContext { * Inspections should not create a new type evaluation context. They should re-use the context of the inspection session. */ public static TypeEvalContext codeAnalysis(@NotNull final Project project, @Nullable final PsiFile origin) { - return CACHE.getContext(project, new TypeEvalContext(false, false, origin)); + return CACHE.getContext(project, new TypeEvalContext(false, 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. - *

- *

* * @param project pass project here to enable cache. Pass null if you do not have any project. * Always do your best to pass project here: it increases performance! */ public static TypeEvalContext codeInsightFallback(@Nullable final Project project) { - final TypeEvalContext anchor = new TypeEvalContext(false, false, null); + final TypeEvalContext anchor = new TypeEvalContext(false, false, false, null); if (project != null) { return CACHE.getContext(project, anchor); } @@ -129,7 +139,7 @@ public class TypeEvalContext { * Should be used only when normal code insight context is not enough for getting good results. */ public static TypeEvalContext deepCodeInsight(@NotNull final Project project) { - return CACHE.getContext(project, new TypeEvalContext(false, true, null)); + return CACHE.getContext(project, new TypeEvalContext(false, true, false, null)); } public TypeEvalContext withTracing() { diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyDictKeyNamesCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyDictKeyNamesCompletionContributor.java index e5b2dcc2dc65..efdb192ae6f4 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyDictKeyNamesCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyDictKeyNamesCompletionContributor.java @@ -129,7 +129,7 @@ public class PyDictKeyNamesCompletionContributor extends CompletionContributor { if (callee == null) return; final String name = callee.getText(); if ("dict".equals(name)) { - final TypeEvalContext context = TypeEvalContext.userInitiated(callee.getProject(), callee.getContainingFile()); + final TypeEvalContext context = TypeEvalContext.codeCompletion(callee.getProject(), callee.getContainingFile()); final PyType type = context.getType(dictConstructor); if (type != null && type.isBuiltin()) { final PyArgumentList list = dictConstructor.getArgumentList(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index 222a98df8a5f..921c43e2b866 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -244,8 +244,8 @@ public class PyNamedParameterImpl extends PyBaseElementImpl types = new ArrayList(); processLocalCalls(func, new Processor() { @Override diff --git a/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java b/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java index e6edd70a516e..8736e54766ac 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java +++ b/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java @@ -66,7 +66,7 @@ public class KeywordArgumentCompletionUtil { visited.add(def); boolean needSelf = def.getContainingClass() != null && def.getModifier() != PyFunction.Modifier.STATICMETHOD; final KwArgParameterCollector collector = new KwArgParameterCollector(needSelf, ret); - final TypeEvalContext context = TypeEvalContext.userInitiated(def.getProject(), def.getContainingFile()); + final TypeEvalContext context = TypeEvalContext.codeCompletion(def.getProject(), def.getContainingFile()); final List parameters = PyUtil.getParameters(def, context); for (PyParameter parameter : parameters) { parameter.accept(collector); diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java index be7921bd6b22..13100e9c0600 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java @@ -97,7 +97,8 @@ public class PyImportReference extends PyReferenceImpl { } PyExpression qualifier = myElement.getQualifier(); - final TypeEvalContext context = TypeEvalContext.userInitiated(myElement.getProject(), CompletionUtil.getOriginalOrSelf(myElement).getContainingFile()); + final TypeEvalContext context = TypeEvalContext.codeCompletion(myElement.getProject(), + CompletionUtil.getOriginalOrSelf(myElement).getContainingFile()); if (qualifier != null) { // qualifier's type must be module, it should know how to complete PyType type = context.getType(qualifier); diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index 65b15fad3458..070ee1165c36 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -264,7 +264,7 @@ public class PyQualifiedReference extends PyReferenceImpl { } final PyQualifiedExpression element = CompletionUtil.getOriginalOrSelf(myElement); - PyType qualifierType = TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).getType(qualifier); + PyType qualifierType = TypeEvalContext.codeCompletion(element.getProject(), element.getContainingFile()).getType(qualifier); ProcessingContext ctx = new ProcessingContext(); final Set namesAlready = new HashSet(); ctx.put(PyType.CTX_NAMES, namesAlready); diff --git a/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java b/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java index ddc8f939970b..7f8799fde597 100644 --- a/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java +++ b/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java @@ -68,7 +68,7 @@ public class CompletionVariantsProcessor extends VariantsProcessor { !isSingleArgDecoratorCall(myContext, (PyFunction)object)) { final Project project = ((PyFunction)object).getProject(); item = item.withInsertHandler(PyFunctionInsertHandler.INSTANCE); - final TypeEvalContext context = TypeEvalContext.userInitiated(project, myContext != null ? myContext.getContainingFile() : null); + final TypeEvalContext context = TypeEvalContext.codeCompletion(project, myContext != null ? myContext.getContainingFile() : null); final List parameters = PyUtil.getParameters((PyFunction)object, context); final String params = StringUtil.join(parameters, new Function() { @Override diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 25de66311ee1..a7b0d548ea23 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -418,7 +418,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { CompletionUtil.getOriginalOrSelf(location) .getContainingFile() : null; - final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myClass.getProject(), origin); + final TypeEvalContext typeEvalContext = TypeEvalContext.codeCompletion(myClass.getProject(), origin); addInheritedMembers(prefix, location, namesAlready, context, ret, typeEvalContext); // from providers diff --git a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java index c393dfb219b1..fde4e18fabe4 100644 --- a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java @@ -88,7 +88,7 @@ public class PyFunctionType implements PyCallableType { @Override public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) { - final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(location.getProject(), location.getContainingFile()); + final TypeEvalContext typeEvalContext = TypeEvalContext.codeCompletion(location.getProject(), location.getContainingFile()); final PyClassType delegate; if (location instanceof PyReferenceExpression) { delegate = selectFakeType(((PyReferenceExpression)location).getQualifier(), typeEvalContext); diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 8a3b9ae9c934..6d7737bc31d4 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -589,13 +589,14 @@ public class PyTypeTest extends PyTestCase { } public void testParameterFromUsages() { - doTest("int | str | unknown", - "def foo(bar):\n" + - " expr = bar\n" + - "def use_foo(x):\n" + - " foo(x)\n" + - " foo(3)\n" + - " foo('bar')\n"); + final String text = "def foo(bar):\n" + + " expr = bar\n" + + "def use_foo(x):\n" + + " foo(x)\n" + + " foo(3)\n" + + " foo('bar')\n"; + final PyExpression expr = parseExpr(text); + doTest("int | str | unknown", expr, TypeEvalContext.codeCompletion(expr.getProject(), expr.getContainingFile())); } public void testUpperBoundGeneric() { @@ -995,6 +996,12 @@ public class PyTypeTest extends PyTestCase { return myFixture.findElementByText("expr", PyExpression.class); } + private static void doTest(final String expectedType, final PyExpression expr, final TypeEvalContext context) { + PyType actual = context.getType(expr); + final String actualType = PythonDocumentationProvider.getTypeName(actual, context); + assertEquals(expectedType, actualType); + } + private void doTest(final String expectedType, final String text) { PyExpression expr = parseExpr(text); TypeEvalContext context = getTypeEvalContext(expr);