Find unmapped parameters using the new PyCallExpressionHelper.mapArguments() implementation

This commit is contained in:
Andrey Vlasovskikh
2015-08-19 20:10:29 +03:00
parent 08148e87d7
commit 313213c2d3
6 changed files with 145 additions and 77 deletions
@@ -22,6 +22,7 @@ import com.jetbrains.python.psi.resolve.PyResolveContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
@@ -119,6 +120,9 @@ public interface PyCallExpression extends PyCallSiteExpression {
@NotNull
PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext);
@NotNull
PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext, int implicitOffset);
/**
* Checks if the unqualified name of the callee matches any of the specified names
*
@@ -138,10 +142,15 @@ public interface PyCallExpression extends PyCallSiteExpression {
class PyArgumentsMapping {
@Nullable private final PyMarkedCallee myCallee;
@NotNull private final Map<PyExpression, PyNamedParameter> myMappedParameters;
@NotNull private final List<PyParameter> myUnmappedParameters;
@NotNull private final List<PyExpression> myUnmappedArguments;
public PyArgumentsMapping(@Nullable PyMarkedCallee markedCallee, @NotNull Map<PyExpression, PyNamedParameter> mappedParameters) {
public PyArgumentsMapping(@Nullable PyMarkedCallee markedCallee, @NotNull Map<PyExpression, PyNamedParameter> mappedParameters,
@NotNull List<PyParameter> unmappedParameters, @NotNull List<PyExpression> unmappedArguments) {
myCallee = markedCallee;
myMappedParameters = mappedParameters;
myUnmappedParameters = unmappedParameters;
myUnmappedArguments = unmappedArguments;
}
@Nullable
@@ -153,6 +162,16 @@ public interface PyCallExpression extends PyCallSiteExpression {
public Map<PyExpression, PyNamedParameter> getMappedParameters() {
return myMappedParameters;
}
@NotNull
public List<PyParameter> getUnmappedParameters() {
return myUnmappedParameters;
}
@NotNull
public List<PyExpression> getUnmappedArguments() {
return myUnmappedArguments;
}
}
/**
@@ -108,8 +108,14 @@ public class PyArgumentListInspection extends PyInspection {
public static void inspectPyArgumentList(PyArgumentList node, ProblemsHolder holder, final TypeEvalContext context, int implicitOffset) {
if (node.getParent() instanceof PyClass) return; // class Foo(object) is also an arg list
CallArgumentsMapping result = node.analyzeCall(PyResolveContext.noImplicits().withTypeEvalContext(context), implicitOffset);
final PyCallExpression.PyMarkedCallee callee = result.getMarkedCallee();
final PyCallExpression callExpr = node.getCallExpression();
if (callExpr == null) {
return;
}
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments(resolveContext, implicitOffset);
CallArgumentsMapping result = node.analyzeCall(resolveContext, implicitOffset);
final PyCallExpression.PyMarkedCallee callee = mapping.getMarkedCallee();
if (callee != null) {
final PyCallable callable = callee.getCallable();
// Decorate functions may have different parameter lists. We don't match arguments with parameters of decorators yet
@@ -118,7 +124,7 @@ public class PyArgumentListInspection extends PyInspection {
}
}
highlightIncorrectArguments(holder, result, context);
highlightMissingArguments(node, holder, result);
highlightMissingArguments(node, holder, mapping);
highlightStarArgumentTypeMismatch(node, holder, context);
}
@@ -197,13 +203,14 @@ public class PyArgumentListInspection extends PyInspection {
}
}
private static void highlightMissingArguments(PyArgumentList node, ProblemsHolder holder, CallArgumentsMapping result) {
private static void highlightMissingArguments(@NotNull PyArgumentList node, @NotNull ProblemsHolder holder,
@NotNull PyCallExpression.PyArgumentsMapping mapping) {
ASTNode our_node = node.getNode();
if (our_node != null) {
ASTNode close_paren = our_node.findChildByType(PyTokenTypes.RPAR);
if (close_paren != null) {
for (PyNamedParameter param : result.getUnmappedParams()) {
holder.registerProblem(close_paren.getPsi(), PyBundle.message("INSP.parameter.$0.unfilled", param.getName()));
for (PyParameter parameter : mapping.getUnmappedParameters()) {
holder.registerProblem(close_paren.getPsi(), PyBundle.message("INSP.parameter.$0.unfilled", parameter.getName()));
}
}
}
@@ -634,87 +634,100 @@ public class PyCallExpressionHelper {
@NotNull
public static PyCallExpression.PyArgumentsMapping mapArguments(@NotNull PyCallExpression callExpression,
@NotNull PyResolveContext resolveContext) {
final Map<PyExpression, PyNamedParameter> mappedParameters = new LinkedHashMap<PyExpression, PyNamedParameter>();
@NotNull PyResolveContext resolveContext, int implicitOffset) {
final PyArgumentList argumentList = callExpression.getArgumentList();
final PyCallExpression.PyMarkedCallee markedCallee = callExpression.resolveCallee(resolveContext);
if (markedCallee != null && argumentList != null) {
final TypeEvalContext context = resolveContext.getTypeEvalContext();
final List<PyParameter> allParameters = PyUtil.getParameters(markedCallee.getCallable(), context);
final List<PyParameter> parameters = dropImplicitParameters(allParameters, markedCallee.getImplicitOffset());
final List<PyExpression> arguments = Arrays.asList(argumentList.getArguments());
final PyCallExpression.PyMarkedCallee markedCallee = callExpression.resolveCallee(resolveContext, implicitOffset);
final List<PyExpression> positionalArguments = filterPositionalArguments(arguments);
final List<PyKeywordArgument> keywordArguments = filterKeywordArguments(arguments);
final List<PyExpression> variadicPositionalArguments = filterVariadicPositionalArguments(arguments);
final List<PyExpression> variadicKeywordArguments = filterVariadicKeywordArguments(arguments);
if (markedCallee == null || argumentList == null) {
return new PyCallExpression.PyArgumentsMapping(null, Collections.<PyExpression, PyNamedParameter>emptyMap(),
Collections.<PyParameter>emptyList(), Collections.<PyExpression>emptyList());
}
boolean seenSingleStar = false;
final List<PyParameter> unmappedParameters = new ArrayList<PyParameter>();
for (PyParameter parameter : parameters) {
if (parameter instanceof PyNamedParameter) {
final PyNamedParameter namedParameter = (PyNamedParameter)parameter;
final String parameterName = namedParameter.getName();
if (namedParameter.isPositionalContainer()) {
if (variadicPositionalArguments.size() == 1) {
mappedParameters.put(variadicPositionalArguments.remove(0), namedParameter);
}
else {
positionalArguments.clear();
variadicPositionalArguments.clear();
}
boolean seenSingleStar = false;
final TypeEvalContext context = resolveContext.getTypeEvalContext();
final Map<PyExpression, PyNamedParameter> mappedParameters = new LinkedHashMap<PyExpression, PyNamedParameter>();
final List<PyParameter> unmappedParameters = new ArrayList<PyParameter>();
final List<PyParameter> allParameters = PyUtil.getParameters(markedCallee.getCallable(), context);
final List<PyParameter> parameters = dropImplicitParameters(allParameters, markedCallee.getImplicitOffset());
final List<PyExpression> arguments = new ArrayList<PyExpression>(Arrays.asList(argumentList.getArguments()));
final List<PyExpression> positionalArguments = removePositionalElements(arguments, resolveContext);
final List<PyKeywordArgument> keywordArguments = filterKeywordArguments(arguments);
final List<PyExpression> variadicPositionalArguments = filterVariadicPositionalArguments(arguments);
final List<PyExpression> variadicKeywordArguments = filterVariadicKeywordArguments(arguments);
for (PyParameter parameter : parameters) {
if (parameter instanceof PyNamedParameter) {
final PyNamedParameter namedParameter = (PyNamedParameter)parameter;
final String parameterName = namedParameter.getName();
if (namedParameter.isPositionalContainer()) {
if (variadicPositionalArguments.size() == 1) {
mappedParameters.put(variadicPositionalArguments.remove(0), namedParameter);
}
else if (namedParameter.isKeywordContainer()) {
if (variadicKeywordArguments.size() == 1) {
mappedParameters.put(variadicKeywordArguments.remove(0), namedParameter);
}
else {
keywordArguments.clear();
variadicKeywordArguments.clear();
}
else {
positionalArguments.clear();
variadicPositionalArguments.clear();
}
else if (seenSingleStar) {
final PyExpression keywordArgument = removeKeywordArgument(keywordArguments, parameterName);
if (keywordArgument != null) {
mappedParameters.put(keywordArgument, namedParameter);
}
else if (namedParameter.isKeywordContainer()) {
if (variadicKeywordArguments.size() == 1) {
mappedParameters.put(variadicKeywordArguments.remove(0), namedParameter);
}
else {
keywordArguments.clear();
variadicKeywordArguments.clear();
}
}
else if (seenSingleStar) {
final PyExpression keywordArgument = removeKeywordArgument(keywordArguments, parameterName);
if (keywordArgument != null) {
mappedParameters.put(keywordArgument, namedParameter);
}
else if (variadicKeywordArguments.isEmpty() && !namedParameter.hasDefaultValue()) {
unmappedParameters.add(namedParameter);
}
}
else {
if (!positionalArguments.isEmpty()) {
final PyExpression positionalArgument = next(positionalArguments);
if (positionalArgument != null) {
mappedParameters.put(positionalArgument, namedParameter);
}
else if (variadicKeywordArguments.isEmpty()) {
else if (!namedParameter.hasDefaultValue()) {
unmappedParameters.add(namedParameter);
}
}
else {
if (!positionalArguments.isEmpty()) {
final PyExpression positionalArgument = next(positionalArguments);
if (positionalArgument != null) {
mappedParameters.put(positionalArgument, namedParameter);
}
else {
unmappedParameters.add(namedParameter);
}
final PyKeywordArgument keywordArgument = removeKeywordArgument(keywordArguments, parameterName);
if (keywordArgument != null) {
mappedParameters.put(keywordArgument, namedParameter);
}
else {
final PyKeywordArgument keywordArgument = removeKeywordArgument(keywordArguments, parameterName);
if (keywordArgument != null) {
mappedParameters.put(keywordArgument, namedParameter);
}
else if (variadicPositionalArguments.isEmpty() || variadicKeywordArguments.isEmpty()) {
unmappedParameters.add(namedParameter);
}
else if (variadicPositionalArguments.isEmpty() && variadicKeywordArguments.isEmpty() && !namedParameter.hasDefaultValue()) {
unmappedParameters.add(namedParameter);
}
}
}
else if (parameter instanceof PyTupleParameter) {
unmappedParameters.add(parameter);
}
else if (parameter instanceof PySingleStarParameter) {
seenSingleStar = true;
}
else {
unmappedParameters.add(parameter);
}
}
else if (parameter instanceof PyTupleParameter) {
// TODO: Handle tuple parameters
}
else if (parameter instanceof PySingleStarParameter) {
seenSingleStar = true;
}
else if (!parameter.hasDefaultValue()) {
unmappedParameters.add(parameter);
}
}
return new PyCallExpression.PyArgumentsMapping(markedCallee, mappedParameters);
final List<PyExpression> unmappedArguments = new ArrayList<PyExpression>();
unmappedArguments.addAll(positionalArguments);
unmappedArguments.addAll(keywordArguments);
unmappedArguments.addAll(variadicPositionalArguments);
unmappedArguments.addAll(variadicKeywordArguments);
return new PyCallExpression.PyArgumentsMapping(markedCallee, mappedParameters, unmappedParameters, unmappedArguments);
}
@Nullable
@@ -734,11 +747,28 @@ public class PyCallExpressionHelper {
}
@NotNull
private static List<PyExpression> filterPositionalArguments(@NotNull List<PyExpression> arguments) {
private static List<PyExpression> removePositionalElements(@NotNull List<PyExpression> arguments,
@NotNull PyResolveContext resolveContext) {
final List<PyExpression> results = new ArrayList<PyExpression>();
for (PyExpression argument : arguments) {
for (PyExpression argument : new ArrayList<PyExpression>(arguments)) {
if (isPositionalArgument(argument)) {
results.add(argument);
arguments.remove(argument);
}
else if (isVariadicPositionalArgument(argument)) {
final PsiElement expr = PyPsiUtils.flattenParens(PsiTreeUtil.getChildOfType(argument, PyExpression.class));
final PsiElement element;
if (expr instanceof PyReferenceExpression) {
element = ((PyReferenceExpression)expr).followAssignmentsChain(resolveContext).getElement();
}
else {
element = expr;
}
if (element instanceof PySequenceExpression) {
final PySequenceExpression sequenceExpr = (PySequenceExpression)element;
results.addAll(Arrays.asList(sequenceExpr.getElements()));
arguments.remove(argument);
}
}
}
return results;
@@ -105,7 +105,13 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress
@NotNull
@Override
public PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext) {
return PyCallExpressionHelper.mapArguments(this, resolveContext);
return PyCallExpressionHelper.mapArguments(this, resolveContext, 0);
}
@NotNull
@Override
public PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext, int implicitOffset) {
return PyCallExpressionHelper.mapArguments(this, resolveContext, implicitOffset);
}
@Override
@@ -160,7 +160,13 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
@NotNull
@Override
public PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext) {
return PyCallExpressionHelper.mapArguments(this, resolveContext);
return PyCallExpressionHelper.mapArguments(this, resolveContext, 0);
}
@NotNull
@Override
public PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext, int implicitOffset) {
return PyCallExpressionHelper.mapArguments(this, resolveContext, implicitOffset);
}
@Override
@@ -21,7 +21,7 @@ def a23(a, *b, c=1):
a23(1,2,3, c=10) # pass
a23(1,2,3, c=10, <warning descr="Duplicate argument">a=1</warning>) # fail
a23(c=10, a=1) # pass
a23(c=10, <error descr="Cannot appear past keyword arguments or *arg or **kwarg">1</error><warning descr="Parameter 'a' unfilled">)</warning> # fail
a23(c=10, <error descr="Cannot appear past keyword arguments or *arg or **kwarg">1</error>) # fail
a23(<warning descr="Multiple values resolve to positional parameter 'a'">*args</warning>, a=1) # fail
a23(*args, c=1) # pass