Fix mapping arguments to parameters for functions with positional-only parameters (PEP 570) (PY-35512)

GitOrigin-RevId: 7306430f4a9562855a958b91250ce65c29b3dba3
This commit is contained in:
Semyon Proshev
2019-06-04 19:13:52 +03:00
committed by intellij-monorepo-bot
parent 2ddb4c886c
commit 221ecaea49
4 changed files with 105 additions and 11 deletions
@@ -8,6 +8,7 @@ import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Conditions;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.ResolveResult;
@@ -86,7 +87,9 @@ public class PyArgumentListInspection extends PyInspection {
else { // possible unfilled params
for (int i = firstParamOffset; i < params.size(); i++) {
final PyCallableParameter parameter = params.get(i);
if (parameter.getParameter() instanceof PySingleStarParameter) continue;
if (parameter.getParameter() instanceof PySingleStarParameter || parameter.getParameter() instanceof PySlashParameter) {
continue;
}
// param tuples, non-starred or non-default won't do
if (!parameter.isKeywordContainer() && !parameter.isPositionalContainer() && !parameter.hasDefaultValue()) {
final String parameterName = parameter.getName();
@@ -859,6 +859,7 @@ public class PyCallExpressionHelper {
@NotNull
private static ArgumentMappingResults analyzeArguments(@NotNull List<PyExpression> arguments,
@NotNull List<PyCallableParameter> parameters) {
boolean positionalOnlyMode = ContainerUtil.exists(parameters, p -> p.getParameter() instanceof PySlashParameter);
boolean seenSingleStar = false;
boolean mappedVariadicArgumentsToParameters = false;
final Map<PyExpression, PyCallableParameter> mappedParameters = new LinkedHashMap<>();
@@ -920,7 +921,17 @@ public class PyCallExpressionHelper {
}
}
else {
if (allPositionalArguments.isEmpty()) {
if (positionalOnlyMode) {
final PyExpression positionalArgument = next(allPositionalArguments);
if (positionalArgument != null) {
mappedParameters.put(positionalArgument, parameter);
}
else if (!parameter.hasDefaultValue()) {
unmappedParameters.add(parameter);
}
}
else if (allPositionalArguments.isEmpty()) {
final PyKeywordArgument keywordArgument = removeKeywordArgument(keywordArguments, parameterName);
if (keywordArgument != null) {
mappedParameters.put(keywordArgument, parameter);
@@ -970,6 +981,9 @@ public class PyCallExpressionHelper {
mappedVariadicArgumentsToParameters = true;
}
}
else if (psi instanceof PySlashParameter) {
positionalOnlyMode = false;
}
else if (psi instanceof PySingleStarParameter) {
seenSingleStar = true;
}
@@ -1285,15 +1299,6 @@ public class PyCallExpressionHelper {
return list.isEmpty() ? null : list.remove(0);
}
@NotNull
private static List<PyCallableParameter> dropImplicitParameters(@NotNull List<PyCallableParameter> parameters, int offset) {
final ArrayList<PyCallableParameter> results = new ArrayList<>(parameters);
for (int i = 0; i < offset && !results.isEmpty(); i++) {
results.remove(0);
}
return results;
}
@NotNull
private static List<PyCallableParameter> filterExplicitParameters(@NotNull List<PyCallableParameter> parameters,
@Nullable PyCallable callable,
@@ -0,0 +1,81 @@
def foo(a, /, b, *args, c, **kwargs):
print(a, b, args, c, kwargs)
# valid
# b as positional
foo(1, 2, c=3)
foo(1, 2, 3, c=3)
foo(1, 2, 3, c=3, d=4)
# b as keyword
foo(1, b=2, c=3)
foo(1, c=3, b=2)
# invalid
foo(<warning descr="Parameter 'a' unfilled"><warning descr="Parameter 'b' unfilled"><warning descr="Parameter 'c' unfilled">)</warning></warning></warning>
foo(c=3<warning descr="Parameter 'a' unfilled"><warning descr="Parameter 'b' unfilled">)</warning></warning>
foo(b=2, c=3<warning descr="Parameter 'a' unfilled">)</warning>
foo(a=1, b=2, c=3<warning descr="Parameter 'a' unfilled">)</warning>
def foo2(a, /, b=2, *args, c, **kwargs):
print(a, b, args, c, kwargs)
# valid
# b as positional
foo2(1, 2, c=3)
foo2(1, 2, 3, c=3)
foo2(1, 2, 3, c=3, d=4)
# b as keyword
foo2(1, b=2, c=3)
foo2(1, c=3, b=2)
# no b
foo2(1, c=3)
# invalid
foo2(<warning descr="Parameter 'a' unfilled"><warning descr="Parameter 'c' unfilled">)</warning></warning>
foo2(c=3<warning descr="Parameter 'a' unfilled">)</warning>
foo2(b=2, c=3<warning descr="Parameter 'a' unfilled">)</warning>
foo2(a=1, b=2, c=3<warning descr="Parameter 'a' unfilled">)</warning>
def foo3(a=1, /, b=2, *args, c, **kwargs):
print(a, b, args, c, kwargs)
# valid
# b as positional
foo3(1, 2, c=3)
foo3(1, 2, 3, c=3)
foo3(1, 2, 3, c=3, d=4)
# b as keyword
foo3(1, b=2, c=3)
foo3(1, c=3, b=2)
# no b
foo3(1, c=3)
# no a, b as positional
foo3(2, c=3)
foo3(2, 3, c=3)
foo3(2, 3, c=3, d=4)
# no a, b as keyword
foo3(b=2, c=3)
foo3(c=3, b=2)
# no a, no b
foo3(c=3)
foo3(a=1, b=2, c=3) # a goes to kwargs
# invalid
foo3(<warning descr="Parameter 'c' unfilled">)</warning>
def foo4(a=1, /, b=2, *args, c):
print(a, b, args, c)
# invalid
foo4(<warning descr="Unexpected argument">a=1</warning>, b=2, c=3)
@@ -376,4 +376,9 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase {
public void testAnotherMethodIsWrappedIntoStatic() {
doMultiFileTest();
}
// PY-35512
public void testPositionalOnlyParameters() {
runWithLanguageLevel(LanguageLevel.PYTHON38, this::doTest);
}
}