mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
Infer parameter types from file-local function usages during code completion
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user