From d3525c55f9d9e89e2f623a602ccae201ac09438d Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 13 May 2013 22:08:47 +0400 Subject: [PATCH] Infer parameter types from file-local function usages during code completion --- .../python/psi/types/TypeEvalContext.java | 4 ++ .../python/psi/impl/PyNamedParameterImpl.java | 41 ++++++++++++++++- .../completion/parameterFromUsages.after.py | 6 +++ .../completion/parameterFromUsages.py | 6 +++ .../com/jetbrains/python/PyTypeTest.java | 45 ++++++++++++------- .../python/PythonCompletionTest.java | 4 ++ 6 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 python/testData/completion/parameterFromUsages.after.py create mode 100644 python/testData/completion/parameterFromUsages.py 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 3e77defefca4..2a5c2b475f5d 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 @@ -53,6 +53,10 @@ public class TypeEvalContext { 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. * diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index 6812fc8bd4aa..d3284f7af980 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -3,6 +3,8 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; import com.intellij.openapi.extensions.Extensions; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.psi.stubs.IStubElementType; @@ -17,12 +19,14 @@ import com.jetbrains.python.PythonDialectsTokenSetProvider; import com.jetbrains.python.codeInsight.stdlib.PyStdlibTypeProvider; import com.jetbrains.python.documentation.StructuredDocString; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.stubs.PyNamedParameterStub; import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.Map; /** * @author yole @@ -224,11 +228,46 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl entry : mapping.getPlainMappedParams().entrySet()) { + if (entry.getValue() == this) { + final PyExpression argument = entry.getKey(); + if (argument != null) { + final PyType type = context.getType(argument); + if (type != null) { + return PyUnionType.createWeakType(type); + } + } + } + } + } + } + } + } + return null; + } + + @Nullable + private static PyCallExpression findFirstLocalCall(@NotNull PyFunction function) { + final PsiFile file = function.getContainingFile(); + final String name = function.getName(); + if (file != null && name != null) { + // Text search is faster than ReferencesSearch in LocalSearchScope + final String text = file.getText(); + for (int pos = text.indexOf(name); pos != -1; pos = text.indexOf(name, pos + 1)) { + final PsiReference ref = file.findReferenceAt(pos); + if (ref != null && ref.isReferenceTo(function)) { + return PsiTreeUtil.getParentOfType(ref.getElement(), PyCallExpression.class); + } } } return null; diff --git a/python/testData/completion/parameterFromUsages.after.py b/python/testData/completion/parameterFromUsages.after.py new file mode 100644 index 000000000000..70c64419a207 --- /dev/null +++ b/python/testData/completion/parameterFromUsages.after.py @@ -0,0 +1,6 @@ +def foo(bar): + bar.append() + + +def baz(): + foo(['hello', 'world']) diff --git a/python/testData/completion/parameterFromUsages.py b/python/testData/completion/parameterFromUsages.py new file mode 100644 index 000000000000..088cbf5eef63 --- /dev/null +++ b/python/testData/completion/parameterFromUsages.py @@ -0,0 +1,6 @@ +def foo(bar): + bar.app + + +def baz(): + foo(['hello', 'world']) diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index dfa9789ba325..2e950c23da36 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -6,6 +6,7 @@ import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; import com.jetbrains.python.psi.types.*; +import org.jetbrains.annotations.NotNull; /** * @author yole @@ -218,9 +219,9 @@ public class PyTypeTest extends PyTestCase { " if c:\n" + " return 1\n" + " return x\n" + - "expr = f(1, 2)\n"; + "expr = f(1, g())\n"; PyExpression expr = parseExpr(text); - PyType t = TypeEvalContext.userInitiated(null).getType(expr); + PyType t = getTypeEvalContext(expr).getType(expr); assertTrue(PyTypeChecker.isUnknown(t)); doTest("int", text); } @@ -234,7 +235,7 @@ public class PyTypeTest extends PyTestCase { " return foo(x)\n" + "expr = xyzzy(a, b)"; PyExpression expr = parseExpr(text); - PyType t = TypeEvalContext.userInitiated(null).getType(expr); + PyType t = getTypeEvalContext(expr).getType(expr); assertInstanceOf(t, PyTypeReference.class); } @@ -296,7 +297,7 @@ public class PyTypeTest extends PyTestCase { public void testSOEOnRecursiveCall() { PyExpression expr = parseExpr("def foo(x): return foo(x)\n" + "expr = foo(1)"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); assertFalse(actual.isBuiltin(context)); } @@ -310,7 +311,7 @@ public class PyTypeTest extends PyTestCase { " return x\n" + "\n" + "expr = f(1)\n"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); assertNotNull(actual); assertEquals("int", actual.getName()); @@ -325,7 +326,7 @@ public class PyTypeTest extends PyTestCase { " return x\n" + "\n" + "expr = f(1)\n"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); assertNotNull(actual); assertEquals("int", actual.getName()); @@ -335,7 +336,7 @@ public class PyTypeTest extends PyTestCase { public void testYieldType() { PyExpression expr = parseExpr("def f():\n" + " expr = yield 2\n"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); assertNull(actual); } @@ -344,7 +345,7 @@ public class PyTypeTest extends PyTestCase { public void testYieldParensType() { PyExpression expr = parseExpr("def f():\n" + " expr = (yield 2)\n"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); assertNull(actual); } @@ -389,7 +390,7 @@ public class PyTypeTest extends PyTestCase { "\n" + "x = f()\n" + "expr = x.start\n"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); assertNull(actual); } @@ -401,7 +402,7 @@ public class PyTypeTest extends PyTestCase { "\n" + "x = C()\n" + "expr = type(x)\n"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType type = context.getType(expr); assertInstanceOf(type, PyClassType.class); assertTrue("Got instance type instead of class type", ((PyClassType)type).isDefinition()); @@ -413,7 +414,7 @@ public class PyTypeTest extends PyTestCase { " pass\n" + "\n" + "expr = type(C)\n"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType type = context.getType(expr); assertInstanceOf(type, PyClassType.class); assertEquals(type.getName(), "type"); @@ -423,7 +424,7 @@ public class PyTypeTest extends PyTestCase { public void testReturnTypeOfTypeForUnknown() { PyExpression expr = parseExpr("def f(x):\n" + " expr = type(x)\n"); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType type = context.getType(expr); assertNull(type); } @@ -453,7 +454,7 @@ public class PyTypeTest extends PyTestCase { // PY-7020 public void testListComprehensionType() { final PyExpression expr = parseExpr("expr = [str(x) for x in range(10)]\n"); - final TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + final TypeEvalContext context = getTypeEvalContext(expr); final PyType type = context.getType(expr); assertNotNull(type); assertInstanceOf(type, PyCollectionType.class); @@ -467,7 +468,7 @@ public class PyTypeTest extends PyTestCase { // PY-7021 public void testGeneratorComprehensionType() { final PyExpression expr = parseExpr("expr = (str(x) for x in range(10))\n"); - final TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + final TypeEvalContext context = getTypeEvalContext(expr); final PyType type = context.getType(expr); assertNotNull(type); assertInstanceOf(type, PyCollectionType.class); @@ -558,11 +559,23 @@ public class PyTypeTest extends PyTestCase { public void testDefaultParameterIgnoreNone() { final PyExpression expr = parseExpr("def f(x=None):\n" + " expr = x\n"); - final TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + final TypeEvalContext context = getTypeEvalContext(expr); final PyType type = context.getType(expr); assertNull(type); } + public void testParameterFromUsages() { + doTest("int", + "def foo(bar):\n" + + " expr = bar\n" + + "def use_foo():\n" + + " foo(3)\n"); + } + + private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) { + return TypeEvalContext.userInitiated(element.getContainingFile()).withTracing(); + } + private PyExpression parseExpr(String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); return myFixture.findElementByText("expr", PyExpression.class); @@ -576,7 +589,7 @@ public class PyTypeTest extends PyTestCase { private void doTest(final String expectedType, final String text) { PyExpression expr = parseExpr(text); - TypeEvalContext context = TypeEvalContext.userInitiated(null).withTracing(); + TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); PyType expected = PyTypeParser.getTypeByName(expr, expectedType); if (expected != null) { diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 62c0e93c9e60..9b38c391a6bd 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -632,4 +632,8 @@ public class PythonCompletionTest extends PyTestCase { public void testNoUnderscoredBuiltin() { doTest(); } + + public void testParameterFromUsages() { + doTest(); + } }