From f3b07316d1b5dfabfc5e9d453bc806982a963855 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Tue, 14 Aug 2012 14:27:32 +0200 Subject: [PATCH] keyword argument completion refactored a bit --- .../psi/impl/PyKeywordArgumentProvider.java | 3 +- .../setupPy/SetupKeywordArgumentProvider.java | 7 +- .../KeywordArgumentCompletionUtil.java | 199 ++++++++++++++++++ .../psi/impl/references/PyReferenceImpl.java | 3 +- 4 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java diff --git a/python/psi-api/src/com/jetbrains/python/psi/impl/PyKeywordArgumentProvider.java b/python/psi-api/src/com/jetbrains/python/psi/impl/PyKeywordArgumentProvider.java index 94529229f70b..995ec9f5d042 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/impl/PyKeywordArgumentProvider.java +++ b/python/psi-api/src/com/jetbrains/python/psi/impl/PyKeywordArgumentProvider.java @@ -1,6 +1,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.openapi.extensions.ExtensionPointName; +import com.jetbrains.python.psi.PyCallExpression; import com.jetbrains.python.psi.PyFunction; import java.util.List; @@ -13,5 +14,5 @@ import java.util.List; public interface PyKeywordArgumentProvider { ExtensionPointName EP_NAME = ExtensionPointName.create("Pythonid.keywordArgumentProvider"); - List getKeywordArguments(PyFunction function); + List getKeywordArguments(PyFunction function, PyCallExpression callExpr); } diff --git a/python/src/com/jetbrains/python/packaging/setupPy/SetupKeywordArgumentProvider.java b/python/src/com/jetbrains/python/packaging/setupPy/SetupKeywordArgumentProvider.java index 7984ee878a46..fb3f22f2b689 100644 --- a/python/src/com/jetbrains/python/packaging/setupPy/SetupKeywordArgumentProvider.java +++ b/python/src/com/jetbrains/python/packaging/setupPy/SetupKeywordArgumentProvider.java @@ -2,10 +2,7 @@ package com.jetbrains.python.packaging.setupPy; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.PyTargetExpression; -import com.jetbrains.python.psi.PyUtil; +import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyKeywordArgumentProvider; import org.jetbrains.annotations.Nullable; @@ -17,7 +14,7 @@ import java.util.List; */ public class SetupKeywordArgumentProvider implements PyKeywordArgumentProvider { @Override - public List getKeywordArguments(PyFunction function) { + public List getKeywordArguments(PyFunction function, PyCallExpression callExpr) { if ("setup".equals(function.getName())) { final ScopeOwner scopeOwner = PsiTreeUtil.getParentOfType(function, ScopeOwner.class, true); if (scopeOwner instanceof PyFile) { diff --git a/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java b/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java new file mode 100644 index 000000000000..c97a4deffba3 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/references/KeywordArgumentCompletionUtil.java @@ -0,0 +1,199 @@ +package com.jetbrains.python.psi.impl.references; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.util.Comparing; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyKeywordArgumentProvider; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.resolve.QualifiedResolveResult; +import com.jetbrains.python.psi.search.PySuperMethodsSearch; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +public class KeywordArgumentCompletionUtil { + public static void collectFunctionArgNames(PyElement element, List ret) { + PyCallExpression callExpr = PsiTreeUtil.getParentOfType(element, PyCallExpression.class); + if (callExpr != null) { + PyExpression callee = callExpr.getCallee(); + if (callee instanceof PyReferenceExpression && element.getParent() == callExpr.getArgumentList()) { + final QualifiedResolveResult result = ((PyReferenceExpression)callee).followAssignmentsChain(PyResolveContext.defaultContext()); + PsiElement def = result.getElement(); + if (def instanceof PyFunction) { + addKeywordArgumentVariants((PyFunction)def, callExpr, ret); + } + else if (def instanceof PyClass) { + PyFunction init = ((PyClass)def).findMethodByName(PyNames.INIT, true); // search in superclasses + if (init != null) { + addKeywordArgumentVariants(init, callExpr, ret); + } + } + } + } + } + + public static void addKeywordArgumentVariants(PyFunction def, PyCallExpression callExpr, final List ret) { + addKeywordArgumentVariants(def, callExpr, ret, new HashSet()); + } + + public static void addKeywordArgumentVariants(PyFunction def, PyCallExpression callExpr, List ret, + Collection visited) { + if (visited.contains(def)) { + return; + } + visited.add(def); + boolean needSelf = def.getContainingClass() != null && def.getModifier() != PyFunction.Modifier.STATICMETHOD; + final KwArgParameterCollector collector = new KwArgParameterCollector(needSelf, ret); + def.getParameterList().acceptChildren(collector); + if (collector.hasKwArgs()) { + for (PyKeywordArgumentProvider provider : Extensions.getExtensions(PyKeywordArgumentProvider.EP_NAME)) { + final List arguments = provider.getKeywordArguments(def, callExpr); + for (String argument : arguments) { + ret.add(PyUtil.createNamedParameterLookup(argument)); + } + } + KwArgFromStatementCallCollector fromStatementCallCollector = new KwArgFromStatementCallCollector(ret, collector.getKwArgs()); + final PyStatementList statementList = def.getStatementList(); + if (statementList != null) + statementList.acceptChildren(fromStatementCallCollector); + + //if (collector.hasOnlySelfAndKwArgs()) { + // nothing interesting besides self and **kwargs, let's look at superclass (PY-778) + if (fromStatementCallCollector.isKwArgsTransit()) { + + final PsiElement superMethod = PySuperMethodsSearch.search(def).findFirst(); + if (superMethod instanceof PyFunction) { + addKeywordArgumentVariants((PyFunction)superMethod, callExpr, ret, visited); + } + } + } +//} + } + + public static class KwArgParameterCollector extends PyElementVisitor { + private int myCount; + private final boolean myNeedSelf; + private final List myRet; + private boolean myHasSelf = false; + private boolean myHasKwArgs = false; + private PyParameter kwArgsParam = null; + + public KwArgParameterCollector(boolean needSelf, List ret) { + myNeedSelf = needSelf; + myRet = ret; + } + + @Override + public void visitPyParameter(PyParameter par) { + myCount++; + if (myCount == 1 && myNeedSelf) { + myHasSelf = true; + return; + } + PyNamedParameter namedParam = par.getAsNamed(); + if (namedParam != null) { + if (!namedParam.isKeywordContainer() && !namedParam.isPositionalContainer()) { + final LookupElement item = PyUtil.createNamedParameterLookup(namedParam.getName()); + myRet.add(item); + } + else if (namedParam.isKeywordContainer()) { + myHasKwArgs = true; + kwArgsParam = namedParam; + } + } + else { + PyTupleParameter nestedTParam = par.getAsTuple(); + if (nestedTParam != null) { + for (PyParameter inner_par : nestedTParam.getContents()) inner_par.accept(this); + } + // else it's a lone star that can't contribute + } + } + + public PyParameter getKwArgs() { + return kwArgsParam; + } + + public boolean hasKwArgs() { + return myHasKwArgs; + } + + public boolean hasOnlySelfAndKwArgs() { + return myCount == 2 && myHasSelf && myHasKwArgs; + } + } + + public static class KwArgFromStatementCallCollector extends PyElementVisitor { + private final List myRet; + private final PyParameter myKwArgs; + private boolean kwArgsTransit = true; + + public KwArgFromStatementCallCollector(List ret, @NotNull PyParameter kwArgs) { + myRet = ret; + this.myKwArgs = kwArgs; + } + + @Override + public void visitPyElement(PyElement node) { + node.acceptChildren(this); + } + + @Override + public void visitPySubscriptionExpression(PySubscriptionExpression node) { + String operandName = node.getOperand().getName(); + processGet(operandName, node.getIndexExpression()); + } + + @Override + public void visitPyCallExpression(PyCallExpression node) { + if (node.isCalleeText("pop", "get", "getattr")) { + PyReferenceExpression child = PsiTreeUtil.getChildOfType(node.getCallee(), PyReferenceExpression.class); + if (child != null) { + String operandName = child.getName(); + if (node.getArguments().length > 0) { + PyExpression argument = node.getArguments()[0]; + processGet(operandName, argument); + } + } + } + else if (node.isCalleeText("__init__")) { + kwArgsTransit = false; + for (PyExpression e : node.getArguments()) { + if (e instanceof PyStarArgument) { + PyStarArgument kw = (PyStarArgument)e; + if (Comparing.equal(myKwArgs.getName(), kw.getFirstChild().getNextSibling().getText())) { + kwArgsTransit = true; + break; + } + } + } + } + super.visitPyCallExpression(node); + } + + private void processGet(String operandName, PyExpression argument) { + if (Comparing.equal(myKwArgs.getName(), operandName) && + argument instanceof PyStringLiteralExpression) { + String name = ((PyStringLiteralExpression)argument).getStringValue(); + if (PyUtil.isPythonIdentifier(name)) { + myRet.add(PyUtil.createNamedParameterLookup(name)); + } + } + } + + /** + * is name of kwargs parameter the same as transmitted to __init__ call + * + * @return + */ + public boolean isKwArgsTransit() { + return kwArgsTransit; + } + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java index 38d794e78f1d..b6b927948913 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java @@ -18,7 +18,6 @@ import com.intellij.util.ProcessingContext; import com.jetbrains.cython.CythonLanguageDialect; import com.jetbrains.cython.CythonResolveUtil; import com.jetbrains.cython.psi.*; -import com.jetbrains.django.util.PythonDataflowUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction; @@ -533,7 +532,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference } // in a call, include function's arg names - PythonDataflowUtil.collectFunctionArgNames(element, ret); + KeywordArgumentCompletionUtil.collectFunctionArgNames(element, ret); // include builtin names final PyFile builtinsFile = PyBuiltinCache.getInstance(element).getBuiltinsFile();