diff --git a/doc/whatsnew/fragments/8570.false_positive b/doc/whatsnew/fragments/8570.false_positive new file mode 100644 index 0000000000..fec3a855e3 --- /dev/null +++ b/doc/whatsnew/fragments/8570.false_positive @@ -0,0 +1,3 @@ +Fix false positive for ``keyword-arg-before-vararg`` when a positional-only parameter with a default value precedes ``*args``. + +Closes #8570 diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 0c8c44a15a..1a628f2312 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1001,8 +1001,14 @@ def _compiled_generated_members(self) -> tuple[Pattern[str], ...]: @only_required_for_messages("keyword-arg-before-vararg") def visit_functiondef(self, node: nodes.FunctionDef) -> None: - # check for keyword arg before varargs + # check for keyword arg before varargs. + if node.args.vararg and node.args.defaults: + # When `positional-only` parameters are present then only + # `positional-or-keyword` parameters are checked. I.e: + # >>> def name(pos_only_params, /, pos_or_keyword_params, *args): ... + if node.args.posonlyargs and not node.args.args: + return self.add_message("keyword-arg-before-vararg", node=node, args=(node.name)) visit_asyncfunctiondef = visit_functiondef diff --git a/tests/functional/k/keyword_arg_before_vararg_positional_only.py b/tests/functional/k/keyword_arg_before_vararg_positional_only.py new file mode 100644 index 0000000000..70b5f9929f --- /dev/null +++ b/tests/functional/k/keyword_arg_before_vararg_positional_only.py @@ -0,0 +1,13 @@ +"""Test `keyword-arg-before-vararg` in the context of positional-only parameters""" + +# pylint: disable=missing-function-docstring, unused-argument + + +def name1(param1, /, param2=True, *args): ... # [keyword-arg-before-vararg] +def name2(param1=True, /, param2=True, *args): ... # [keyword-arg-before-vararg] +def name3(param1, param2=True, /, param3=True, *args): ... # [keyword-arg-before-vararg] +def name4(param1, /, *args): ... +def name5(param1=True, /, *args): ... +def name6(param1, /, *args, param2=True): ... +def name7(param1=True, /, *args, param2=True): ... +def name8(param1, param2=True, /, *args, param3=True): ... diff --git a/tests/functional/k/keyword_arg_before_vararg_positional_only.rc b/tests/functional/k/keyword_arg_before_vararg_positional_only.rc new file mode 100644 index 0000000000..85fc502b37 --- /dev/null +++ b/tests/functional/k/keyword_arg_before_vararg_positional_only.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.8 diff --git a/tests/functional/k/keyword_arg_before_vararg_positional_only.txt b/tests/functional/k/keyword_arg_before_vararg_positional_only.txt new file mode 100644 index 0000000000..18700e2e42 --- /dev/null +++ b/tests/functional/k/keyword_arg_before_vararg_positional_only.txt @@ -0,0 +1,3 @@ +keyword-arg-before-vararg:6:0:6:9:name1:Keyword argument before variable positional arguments list in the definition of name1 function:UNDEFINED +keyword-arg-before-vararg:7:0:7:9:name2:Keyword argument before variable positional arguments list in the definition of name2 function:UNDEFINED +keyword-arg-before-vararg:8:0:8:9:name3:Keyword argument before variable positional arguments list in the definition of name3 function:UNDEFINED