keyword argument completion refactored a bit

This commit is contained in:
Dmitry Jemerov
2012-08-14 14:27:32 +02:00
parent 7800296dbe
commit f3b07316d1
4 changed files with 204 additions and 8 deletions
@@ -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<PyKeywordArgumentProvider> EP_NAME = ExtensionPointName.create("Pythonid.keywordArgumentProvider");
List<String> getKeywordArguments(PyFunction function);
List<String> getKeywordArguments(PyFunction function, PyCallExpression callExpr);
}
@@ -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<String> getKeywordArguments(PyFunction function) {
public List<String> getKeywordArguments(PyFunction function, PyCallExpression callExpr) {
if ("setup".equals(function.getName())) {
final ScopeOwner scopeOwner = PsiTreeUtil.getParentOfType(function, ScopeOwner.class, true);
if (scopeOwner instanceof PyFile) {
@@ -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<LookupElement> 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<LookupElement> ret) {
addKeywordArgumentVariants(def, callExpr, ret, new HashSet<PyFunction>());
}
public static void addKeywordArgumentVariants(PyFunction def, PyCallExpression callExpr, List<LookupElement> ret,
Collection<PyFunction> 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<String> 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<LookupElement> myRet;
private boolean myHasSelf = false;
private boolean myHasKwArgs = false;
private PyParameter kwArgsParam = null;
public KwArgParameterCollector(boolean needSelf, List<LookupElement> 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<LookupElement> myRet;
private final PyParameter myKwArgs;
private boolean kwArgsTransit = true;
public KwArgFromStatementCallCollector(List<LookupElement> 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;
}
}
}
@@ -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();