Infer parameter types from file-local function usages during code completion

This commit is contained in:
Andrey Vlasovskikh
2013-05-13 22:41:41 +04:00
parent fd0476b073
commit d3525c55f9
6 changed files with 89 additions and 17 deletions
@@ -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.
*
@@ -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<PyNamedParame
}
}
}
for(PyTypeProvider provider: Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
PyType result = provider.getParameterType(this, func, context);
if (result != null) return result;
}
// Guess the type from file-local usages
if (context.allowLocalUsages(this)) {
final PyCallExpression call = findFirstLocalCall(func);
if (call != null) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
final CallArgumentsMapping mapping = call.getArgumentList().analyzeCall(resolveContext);
for (Map.Entry<PyExpression, PyNamedParameter> 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;
@@ -0,0 +1,6 @@
def foo(bar):
bar.append()
def baz():
foo(['hello', 'world'])
@@ -0,0 +1,6 @@
def foo(bar):
bar.app<caret>
def baz():
foo(['hello', 'world'])
@@ -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) {
@@ -632,4 +632,8 @@ public class PythonCompletionTest extends PyTestCase {
public void testNoUnderscoredBuiltin() {
doTest();
}
public void testParameterFromUsages() {
doTest();
}
}