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: [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 ++++++++++++++++ .../2023-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 bea7303..76a06df 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -813,6 +813,22 @@ Annotations must be either a name, a function call, or a string. ) 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 0000000..600c265 --- /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 d182e5e..d67479c 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4289,6 +4289,7 @@ class IndentStack: StateKeeper = Callable[[str | None], None] +ConverterArgs = dict[str, Any] class DSLParser: def __init__(self, clinic: Clinic) -> None: @@ -5016,10 +5017,10 @@ class DSLParser: 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 @@ class DSLParser: 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( -- cgit v0.12