From 2e2de9e48ccb030c8b3b1faa4b516744e01ee0b8 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Wed, 10 Feb 2016 18:38:40 +0300 Subject: [PATCH] PY-5833 Fixed: No namedtuple autocompletion if field names passed in Try to resolve fieldNamesExpression if it is a reference --- .../codeInsight/stdlib/PyNamedTupleType.java | 29 ++++++++++++++++--- .../stdlib/PyStdlibTypeProvider.java | 10 ++++--- .../passedNamedTupleAttributes.after.py | 5 ++++ .../completion/passedNamedTupleAttributes.py | 5 ++++ .../python/PythonCompletionTest.java | 5 ++++ 5 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 python/testData/completion/passedNamedTupleAttributes.after.py create mode 100644 python/testData/completion/passedNamedTupleAttributes.py diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java index 3143334c4dfd..ab74f9189c1c 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java @@ -25,7 +25,9 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyElementImpl; import com.jetbrains.python.psi.impl.PyPsiUtils; +import com.jetbrains.python.psi.impl.PyReferenceExpressionImpl; import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.resolve.QualifiedResolveResult; import com.jetbrains.python.psi.resolve.RatedResolveResult; import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; @@ -113,10 +115,10 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType } @Nullable - public static PyType fromCall(PyCallExpression call, int level) { + public static PyType fromCall(@NotNull PyCallExpression call, @NotNull TypeEvalContext context, int level) { final String name = PyPsiUtils.strValue(call.getArgument(0, PyExpression.class)); - final PyExpression fieldNamesExpression = PyPsiUtils.flattenParens(call.getArgument(1, PyExpression.class)); - if (name == null || fieldNamesExpression == null) { + final PyExpression fieldNamesExpression = resolveFieldNamesExpression(call, context); + if (name == null || fieldNamesExpression == null || !context.maySwitchToAST(fieldNamesExpression)) { return null; } List fieldNames = null; @@ -138,7 +140,26 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType return null; } - private static List parseFieldNamesString(String fieldNamesString) { + @Nullable + private static PyExpression resolveFieldNamesExpression(@NotNull PyCallExpression call, @NotNull TypeEvalContext context) { + final PyExpression fieldNamesExpression = PyPsiUtils.flattenParens(call.getArgument(1, PyExpression.class)); + + if (fieldNamesExpression instanceof PyReferenceExpression) { + final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); + final QualifiedResolveResult resolveResult = ((PyReferenceExpression)fieldNamesExpression).followAssignmentsChain(resolveContext); + + final PsiElement resolvedFieldNamesExpression = resolveResult.getElement(); + + if (resolvedFieldNamesExpression instanceof PyExpression) { + return (PyExpression)resolvedFieldNamesExpression; + } + } + + return fieldNamesExpression; + } + + @NotNull + private static List parseFieldNamesString(@NotNull String fieldNamesString) { List result = new ArrayList(); for(String name: StringUtil.tokenize(fieldNamesString, ", ")) { result.add(name); diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index e9ed58c9425a..b7a3c49c3bf4 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -63,7 +63,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { if (type != null) { return type; } - type = getNamedTupleType(referenceTarget, anchor); + type = getNamedTupleType(referenceTarget, context, anchor); if (type != null) { return type; } @@ -226,7 +226,9 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getNamedTupleType(@NotNull PsiElement referenceTarget, @Nullable PsiElement anchor) { + private static PyType getNamedTupleType(@NotNull PsiElement referenceTarget, + @NotNull TypeEvalContext context, + @Nullable PsiElement anchor) { if (referenceTarget instanceof PyTargetExpression) { final PyTargetExpression target = (PyTargetExpression)referenceTarget; final QualifiedName calleeName = target.getCalleeName(); @@ -239,7 +241,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { if (callee != null) { final PyCallable callable = callee.getCallable(); if (PyNames.COLLECTIONS_NAMEDTUPLE.equals(callable.getQualifiedName())) { - return PyNamedTupleType.fromCall(call, 1); + return PyNamedTupleType.fromCall(call, context, 1); } } } @@ -248,7 +250,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { else if (referenceTarget instanceof PyFunction && anchor instanceof PyCallExpression) { final PyFunction function = (PyFunction)referenceTarget; if (PyNames.NAMEDTUPLE.equals(function.getName()) && PyNames.COLLECTIONS_NAMEDTUPLE.equals(function.getQualifiedName())) { - return PyNamedTupleType.fromCall((PyCallExpression)anchor, 2); + return PyNamedTupleType.fromCall((PyCallExpression)anchor, context, 2); } } return null; diff --git a/python/testData/completion/passedNamedTupleAttributes.after.py b/python/testData/completion/passedNamedTupleAttributes.after.py new file mode 100644 index 000000000000..37ba7c5ee02d --- /dev/null +++ b/python/testData/completion/passedNamedTupleAttributes.after.py @@ -0,0 +1,5 @@ +from collections import namedtuple +foofields = 'bar', 'xyzzy' +nt = namedtuple('foo', foofields) +o = nt(1, 2) +print o.xyzzy \ No newline at end of file diff --git a/python/testData/completion/passedNamedTupleAttributes.py b/python/testData/completion/passedNamedTupleAttributes.py new file mode 100644 index 000000000000..742ddfcd8208 --- /dev/null +++ b/python/testData/completion/passedNamedTupleAttributes.py @@ -0,0 +1,5 @@ +from collections import namedtuple +foofields = 'bar', 'xyzzy' +nt = namedtuple('foo', foofields) +o = nt(1, 2) +print o.xyz \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 33b0dce1968d..a1fddd101de9 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -823,6 +823,11 @@ public class PythonCompletionTest extends PyTestCase { assertUnderscoredMethodSpecialAttributesSuggested(); } + // PY-5833 + public void testPassedNamedTupleAttributes() { + doTest(); + } + private void assertUnderscoredFunctionAttributesSuggested() { final List suggested = doTestByFile(); assertNotNull(suggested);