diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java index 467fdcab9b10..b07f695c0e07 100644 --- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java @@ -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(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index b27da4d605b7..569436edf529 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -859,6 +859,7 @@ public class PyCallExpressionHelper { @NotNull private static ArgumentMappingResults analyzeArguments(@NotNull List arguments, @NotNull List parameters) { + boolean positionalOnlyMode = ContainerUtil.exists(parameters, p -> p.getParameter() instanceof PySlashParameter); boolean seenSingleStar = false; boolean mappedVariadicArgumentsToParameters = false; final Map 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 dropImplicitParameters(@NotNull List parameters, int offset) { - final ArrayList results = new ArrayList<>(parameters); - for (int i = 0; i < offset && !results.isEmpty(); i++) { - results.remove(0); - } - return results; - } - @NotNull private static List filterExplicitParameters(@NotNull List parameters, @Nullable PyCallable callable, diff --git a/python/testData/inspections/PyArgumentListInspection/positionalOnlyParameters.py b/python/testData/inspections/PyArgumentListInspection/positionalOnlyParameters.py new file mode 100644 index 000000000000..09a0c948f1cf --- /dev/null +++ b/python/testData/inspections/PyArgumentListInspection/positionalOnlyParameters.py @@ -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() +foo(c=3) +foo(b=2, c=3) +foo(a=1, b=2, c=3) + + +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() +foo2(c=3) +foo2(b=2, c=3) +foo2(a=1, b=2, c=3) + + +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() + + +def foo4(a=1, /, b=2, *args, c): + print(a, b, args, c) + +# invalid +foo4(a=1, b=2, c=3) diff --git a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java index 3a57aa1462fe..744121a1e7c5 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java @@ -376,4 +376,9 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase { public void testAnotherMethodIsWrappedIntoStatic() { doMultiFileTest(); } + + // PY-35512 + public void testPositionalOnlyParameters() { + runWithLanguageLevel(LanguageLevel.PYTHON38, this::doTest); + } }