From 5f20152f01febf4ffd9572a8ab3e980a4660ef69 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 3 Jul 2023 06:42:20 -0700 Subject: [PATCH] [3.12] gh-106359: Fix corner case bugs in Argument Clinic converter parser (GH-106361) (#106364) gh-106359: Fix corner case bugs in Argument Clinic converter parser (GH-106361) DSLParser.parse_converter() could return unusable kwdicts in some rare cases (cherry picked from commit 0da4c883cf4185efe27b711c3e0a1e6e94397610) Co-authored-by: Erlend E. Aasland Co-authored-by: Alex Waygood --- Lib/test/test_clinic.py | 16 ++++++++++++++++ ...023-07-03-14-06-19.gh-issue-106359.RfJuR0.rst | 2 ++ Tools/clinic/clinic.py | 16 +++++++++------- 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2023-07-03-14-06-19.gh-issue-106359.RfJuR0.rst diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index bea7303805567f..76a06df5fe2e55 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -813,6 +813,22 @@ def test_other_bizarre_things_in_annotations_fail(self): ) self.assertEqual(s, expected_failure_message) + def test_kwarg_splats_disallowed_in_function_call_annotations(self): + expected_error_msg = ( + "Error on line 0:\n" + "Cannot use a kwarg splat in a function-call annotation\n" + ) + dataset = ( + 'module fo\nfo.barbaz\n o: bool(**{None: "bang!"})', + 'module fo\nfo.barbaz -> bool(**{None: "bang!"})', + 'module fo\nfo.barbaz -> bool(**{"bang": 42})', + 'module fo\nfo.barbaz\n o: bool(**{"bang": None})', + ) + for fn in dataset: + with self.subTest(fn=fn): + out = self.parse_function_should_fail(fn) + self.assertEqual(out, expected_error_msg) + def test_unused_param(self): block = self.parse(""" module foo diff --git a/Misc/NEWS.d/next/Tools-Demos/2023-07-03-14-06-19.gh-issue-106359.RfJuR0.rst b/Misc/NEWS.d/next/Tools-Demos/2023-07-03-14-06-19.gh-issue-106359.RfJuR0.rst new file mode 100644 index 00000000000000..600c265391ec5b --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2023-07-03-14-06-19.gh-issue-106359.RfJuR0.rst @@ -0,0 +1,2 @@ +Argument Clinic now explicitly forbids "kwarg splats" in function calls used as +annotations. diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index d182e5e7764e46..d67479c7f8ab88 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4289,6 +4289,7 @@ def dedent(self, line): StateKeeper = Callable[[str | None], None] +ConverterArgs = dict[str, Any] class DSLParser: def __init__(self, clinic: Clinic) -> None: @@ -5016,10 +5017,10 @@ def bad_node(self, node): key = f"{parameter_name}_as_{c_name}" if c_name else parameter_name self.function.parameters[key] = p - KwargDict = dict[str | None, Any] - @staticmethod - def parse_converter(annotation: ast.expr | None) -> tuple[str, bool, KwargDict]: + def parse_converter( + annotation: ast.expr | None + ) -> tuple[str, bool, ConverterArgs]: match annotation: case ast.Constant(value=str() as value): return value, True, {} @@ -5027,10 +5028,11 @@ def parse_converter(annotation: ast.expr | None) -> tuple[str, bool, KwargDict]: return name, False, {} case ast.Call(func=ast.Name(name)): symbols = globals() - kwargs = { - node.arg: eval_ast_expr(node.value, symbols) - for node in annotation.keywords - } + kwargs: ConverterArgs = {} + for node in annotation.keywords: + if not isinstance(node.arg, str): + fail("Cannot use a kwarg splat in a function-call annotation") + kwargs[node.arg] = eval_ast_expr(node.value, symbols) return name, False, kwargs case _: fail(