Skip to content

Commit 5f20152

Browse files
miss-islingtonerlend-aaslandAlexWaygood
authored
[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 0da4c88) Co-authored-by: Erlend E. Aasland <[email protected]> Co-authored-by: Alex Waygood <[email protected]>
1 parent 5e85604 commit 5f20152

File tree

3 files changed

+27
-7
lines changed

3 files changed

+27
-7
lines changed

Lib/test/test_clinic.py

+16
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,22 @@ def test_other_bizarre_things_in_annotations_fail(self):
813813
)
814814
self.assertEqual(s, expected_failure_message)
815815

816+
def test_kwarg_splats_disallowed_in_function_call_annotations(self):
817+
expected_error_msg = (
818+
"Error on line 0:\n"
819+
"Cannot use a kwarg splat in a function-call annotation\n"
820+
)
821+
dataset = (
822+
'module fo\nfo.barbaz\n o: bool(**{None: "bang!"})',
823+
'module fo\nfo.barbaz -> bool(**{None: "bang!"})',
824+
'module fo\nfo.barbaz -> bool(**{"bang": 42})',
825+
'module fo\nfo.barbaz\n o: bool(**{"bang": None})',
826+
)
827+
for fn in dataset:
828+
with self.subTest(fn=fn):
829+
out = self.parse_function_should_fail(fn)
830+
self.assertEqual(out, expected_error_msg)
831+
816832
def test_unused_param(self):
817833
block = self.parse("""
818834
module foo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Argument Clinic now explicitly forbids "kwarg splats" in function calls used as
2+
annotations.

Tools/clinic/clinic.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -4289,6 +4289,7 @@ def dedent(self, line):
42894289

42904290

42914291
StateKeeper = Callable[[str | None], None]
4292+
ConverterArgs = dict[str, Any]
42924293

42934294
class DSLParser:
42944295
def __init__(self, clinic: Clinic) -> None:
@@ -5016,21 +5017,22 @@ def bad_node(self, node):
50165017
key = f"{parameter_name}_as_{c_name}" if c_name else parameter_name
50175018
self.function.parameters[key] = p
50185019

5019-
KwargDict = dict[str | None, Any]
5020-
50215020
@staticmethod
5022-
def parse_converter(annotation: ast.expr | None) -> tuple[str, bool, KwargDict]:
5021+
def parse_converter(
5022+
annotation: ast.expr | None
5023+
) -> tuple[str, bool, ConverterArgs]:
50235024
match annotation:
50245025
case ast.Constant(value=str() as value):
50255026
return value, True, {}
50265027
case ast.Name(name):
50275028
return name, False, {}
50285029
case ast.Call(func=ast.Name(name)):
50295030
symbols = globals()
5030-
kwargs = {
5031-
node.arg: eval_ast_expr(node.value, symbols)
5032-
for node in annotation.keywords
5033-
}
5031+
kwargs: ConverterArgs = {}
5032+
for node in annotation.keywords:
5033+
if not isinstance(node.arg, str):
5034+
fail("Cannot use a kwarg splat in a function-call annotation")
5035+
kwargs[node.arg] = eval_ast_expr(node.value, symbols)
50345036
return name, False, kwargs
50355037
case _:
50365038
fail(

0 commit comments

Comments
 (0)