Skip to content

Commit

Permalink
[3.12] gh-106359: Fix corner case bugs in Argument Clinic converter p…
Browse files Browse the repository at this point in the history
…arser (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 0da4c88)

Co-authored-by: Erlend E. Aasland <erlend@python.org>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
3 people committed Jul 3, 2023
1 parent 5e85604 commit 5f20152
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 7 deletions.
16 changes: 16 additions & 0 deletions Lib/test/test_clinic.py
Expand Up @@ -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
Expand Down
@@ -0,0 +1,2 @@
Argument Clinic now explicitly forbids "kwarg splats" in function calls used as
annotations.
16 changes: 9 additions & 7 deletions Tools/clinic/clinic.py
Expand Up @@ -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:
Expand Down Expand Up @@ -5016,21 +5017,22 @@ 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, {}
case ast.Name(name):
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(
Expand Down

0 comments on commit 5f20152

Please sign in to comment.