Skip to content

Commit 0da4c88

Browse files
pythongh-106359: Fix corner case bugs in Argument Clinic converter parser (python#106361)
DSLParser.parse_converter() could return unusable kwdicts in some rare cases Co-authored-by: Alex Waygood <[email protected]>
1 parent 60e41a0 commit 0da4c88

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
@@ -4291,6 +4291,7 @@ def dedent(self, line):
42914291

42924292

42934293
StateKeeper = Callable[[str | None], None]
4294+
ConverterArgs = dict[str, Any]
42944295

42954296
class DSLParser:
42964297
function: Function | None
@@ -5033,21 +5034,22 @@ def bad_node(self, node):
50335034
key = f"{parameter_name}_as_{c_name}" if c_name else parameter_name
50345035
self.function.parameters[key] = p
50355036

5036-
KwargDict = dict[str | None, Any]
5037-
50385037
@staticmethod
5039-
def parse_converter(annotation: ast.expr | None) -> tuple[str, bool, KwargDict]:
5038+
def parse_converter(
5039+
annotation: ast.expr | None
5040+
) -> tuple[str, bool, ConverterArgs]:
50405041
match annotation:
50415042
case ast.Constant(value=str() as value):
50425043
return value, True, {}
50435044
case ast.Name(name):
50445045
return name, False, {}
50455046
case ast.Call(func=ast.Name(name)):
50465047
symbols = globals()
5047-
kwargs = {
5048-
node.arg: eval_ast_expr(node.value, symbols)
5049-
for node in annotation.keywords
5050-
}
5048+
kwargs: ConverterArgs = {}
5049+
for node in annotation.keywords:
5050+
if not isinstance(node.arg, str):
5051+
fail("Cannot use a kwarg splat in a function-call annotation")
5052+
kwargs[node.arg] = eval_ast_expr(node.value, symbols)
50515053
return name, False, kwargs
50525054
case _:
50535055
fail(

0 commit comments

Comments
 (0)