mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Find unmapped parameters using the new PyCallExpressionHelper.mapArguments() implementation
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user