diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java index 1a130c734f33..abed174f0abd 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java @@ -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 myMappedParameters; + @NotNull private final List myUnmappedParameters; + @NotNull private final List myUnmappedArguments; - public PyArgumentsMapping(@Nullable PyMarkedCallee markedCallee, @NotNull Map mappedParameters) { + public PyArgumentsMapping(@Nullable PyMarkedCallee markedCallee, @NotNull Map mappedParameters, + @NotNull List unmappedParameters, @NotNull List unmappedArguments) { myCallee = markedCallee; myMappedParameters = mappedParameters; + myUnmappedParameters = unmappedParameters; + myUnmappedArguments = unmappedArguments; } @Nullable @@ -153,6 +162,16 @@ public interface PyCallExpression extends PyCallSiteExpression { public Map getMappedParameters() { return myMappedParameters; } + + @NotNull + public List getUnmappedParameters() { + return myUnmappedParameters; + } + + @NotNull + public List getUnmappedArguments() { + return myUnmappedArguments; + } } /** diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java index 320ca265f72a..67c56ff4096e 100644 --- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java @@ -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())); } } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 00c122c7d11d..9d903d055e0f 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -634,87 +634,100 @@ public class PyCallExpressionHelper { @NotNull public static PyCallExpression.PyArgumentsMapping mapArguments(@NotNull PyCallExpression callExpression, - @NotNull PyResolveContext resolveContext) { - final Map mappedParameters = new LinkedHashMap(); + @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 allParameters = PyUtil.getParameters(markedCallee.getCallable(), context); - final List parameters = dropImplicitParameters(allParameters, markedCallee.getImplicitOffset()); - final List arguments = Arrays.asList(argumentList.getArguments()); + final PyCallExpression.PyMarkedCallee markedCallee = callExpression.resolveCallee(resolveContext, implicitOffset); - final List positionalArguments = filterPositionalArguments(arguments); - final List keywordArguments = filterKeywordArguments(arguments); - final List variadicPositionalArguments = filterVariadicPositionalArguments(arguments); - final List variadicKeywordArguments = filterVariadicKeywordArguments(arguments); + if (markedCallee == null || argumentList == null) { + return new PyCallExpression.PyArgumentsMapping(null, Collections.emptyMap(), + Collections.emptyList(), Collections.emptyList()); + } - boolean seenSingleStar = false; - final List unmappedParameters = new ArrayList(); - 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 mappedParameters = new LinkedHashMap(); + final List unmappedParameters = new ArrayList(); + + final List allParameters = PyUtil.getParameters(markedCallee.getCallable(), context); + final List parameters = dropImplicitParameters(allParameters, markedCallee.getImplicitOffset()); + final List arguments = new ArrayList(Arrays.asList(argumentList.getArguments())); + + final List positionalArguments = removePositionalElements(arguments, resolveContext); + final List keywordArguments = filterKeywordArguments(arguments); + final List variadicPositionalArguments = filterVariadicPositionalArguments(arguments); + final List 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 unmappedArguments = new ArrayList(); + 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 filterPositionalArguments(@NotNull List arguments) { + private static List removePositionalElements(@NotNull List arguments, + @NotNull PyResolveContext resolveContext) { final List results = new ArrayList(); - for (PyExpression argument : arguments) { + for (PyExpression argument : new ArrayList(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; diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java index f2011b372ddb..3067bdc5b524 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java @@ -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 diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java index 73cf915e0202..03207ee9c714 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java @@ -160,7 +160,13 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase 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 diff --git a/python/testData/inspections/PyArgumentListInspection/py3k.py b/python/testData/inspections/PyArgumentListInspection/py3k.py index a43c848f77d3..d60b09203c86 100644 --- a/python/testData/inspections/PyArgumentListInspection/py3k.py +++ b/python/testData/inspections/PyArgumentListInspection/py3k.py @@ -21,7 +21,7 @@ def a23(a, *b, c=1): a23(1,2,3, c=10) # pass a23(1,2,3, c=10, a=1) # fail a23(c=10, a=1) # pass -a23(c=10, 1) # fail +a23(c=10, 1) # fail a23(*args, a=1) # fail a23(*args, c=1) # pass