Skip to content

Commit ba070b6

Browse files
[3.14] gh-135256: Simplify parsing parameters in Argument Clinic (GH-135257) (121914136635)
(cherry picked from commit b74fb8e) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 348e22c commit ba070b6

File tree

2 files changed

+17
-59
lines changed

2 files changed

+17
-59
lines changed

Lib/test/test_clinic.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,20 +1277,16 @@ def test_base_invalid_syntax(self):
12771277
os.stat
12781278
invalid syntax: int = 42
12791279
"""
1280-
err = dedent(r"""
1281-
Function 'stat' has an invalid parameter declaration:
1282-
\s+'invalid syntax: int = 42'
1283-
""").strip()
1284-
with self.assertRaisesRegex(ClinicError, err):
1285-
self.parse_function(block)
1280+
err = "Function 'stat' has an invalid parameter declaration: 'invalid syntax: int = 42'"
1281+
self.expect_failure(block, err, lineno=2)
12861282

12871283
def test_param_default_invalid_syntax(self):
12881284
block = """
12891285
module os
12901286
os.stat
12911287
x: int = invalid syntax
12921288
"""
1293-
err = r"Syntax error: 'x = invalid syntax\n'"
1289+
err = "Function 'stat' has an invalid parameter declaration:"
12941290
self.expect_failure(block, err, lineno=2)
12951291

12961292
def test_cloning_nonexistent_function_correctly_fails(self):
@@ -2510,7 +2506,7 @@ def test_cannot_specify_pydefault_without_default(self):
25102506
self.expect_failure(block, err, lineno=1)
25112507

25122508
def test_vararg_cannot_take_default_value(self):
2513-
err = "Vararg can't take a default value!"
2509+
err = "Function 'fn' has an invalid parameter declaration:"
25142510
block = """
25152511
fn
25162512
*args: tuple = None

Tools/clinic/libclinic/dsl_parser.py

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -877,43 +877,16 @@ def parse_parameter(self, line: str) -> None:
877877

878878
# handle "as" for parameters too
879879
c_name = None
880-
name, have_as_token, trailing = line.partition(' as ')
881-
if have_as_token:
882-
name = name.strip()
883-
if ' ' not in name:
884-
fields = trailing.strip().split(' ')
885-
if not fields:
886-
fail("Invalid 'as' clause!")
887-
c_name = fields[0]
888-
if c_name.endswith(':'):
889-
name += ':'
890-
c_name = c_name[:-1]
891-
fields[0] = name
892-
line = ' '.join(fields)
893-
894-
default: str | None
895-
base, equals, default = line.rpartition('=')
896-
if not equals:
897-
base = default
898-
default = None
899-
900-
module = None
880+
m = re.match(r'(?:\* *)?\w+( +as +(\w+))', line)
881+
if m:
882+
c_name = m[2]
883+
line = line[:m.start(1)] + line[m.end(1):]
884+
901885
try:
902-
ast_input = f"def x({base}): pass"
886+
ast_input = f"def x({line}\n): pass"
903887
module = ast.parse(ast_input)
904888
except SyntaxError:
905-
try:
906-
# the last = was probably inside a function call, like
907-
# c: int(accept={str})
908-
# so assume there was no actual default value.
909-
default = None
910-
ast_input = f"def x({line}): pass"
911-
module = ast.parse(ast_input)
912-
except SyntaxError:
913-
pass
914-
if not module:
915-
fail(f"Function {self.function.name!r} has an invalid parameter declaration:\n\t",
916-
repr(line))
889+
fail(f"Function {self.function.name!r} has an invalid parameter declaration: {line!r}")
917890

918891
function = module.body[0]
919892
assert isinstance(function, ast.FunctionDef)
@@ -922,9 +895,6 @@ def parse_parameter(self, line: str) -> None:
922895
if len(function_args.args) > 1:
923896
fail(f"Function {self.function.name!r} has an "
924897
f"invalid parameter declaration (comma?): {line!r}")
925-
if function_args.defaults or function_args.kw_defaults:
926-
fail(f"Function {self.function.name!r} has an "
927-
f"invalid parameter declaration (default value?): {line!r}")
928898
if function_args.kwarg:
929899
fail(f"Function {self.function.name!r} has an "
930900
f"invalid parameter declaration (**kwargs?): {line!r}")
@@ -944,7 +914,7 @@ def parse_parameter(self, line: str) -> None:
944914
name = 'varpos_' + name
945915

946916
value: object
947-
if not default:
917+
if not function_args.defaults:
948918
if is_vararg:
949919
value = NULL
950920
else:
@@ -955,17 +925,13 @@ def parse_parameter(self, line: str) -> None:
955925
if 'py_default' in kwargs:
956926
fail("You can't specify py_default without specifying a default value!")
957927
else:
958-
if is_vararg:
959-
fail("Vararg can't take a default value!")
928+
expr = function_args.defaults[0]
929+
default = ast_input[expr.col_offset: expr.end_col_offset].strip()
960930

961931
if self.parameter_state is ParamState.REQUIRED:
962932
self.parameter_state = ParamState.OPTIONAL
963-
default = default.strip()
964933
bad = False
965-
ast_input = f"x = {default}"
966934
try:
967-
module = ast.parse(ast_input)
968-
969935
if 'c_default' not in kwargs:
970936
# we can only represent very simple data values in C.
971937
# detect whether default is okay, via a denylist
@@ -992,13 +958,14 @@ def bad_node(self, node: ast.AST) -> None:
992958
visit_Starred = bad_node
993959

994960
denylist = DetectBadNodes()
995-
denylist.visit(module)
961+
denylist.visit(expr)
996962
bad = denylist.bad
997963
else:
998964
# if they specify a c_default, we can be more lenient about the default value.
999965
# but at least make an attempt at ensuring it's a valid expression.
966+
code = compile(ast.Expression(expr), '<expr>', 'eval')
1000967
try:
1001-
value = eval(default)
968+
value = eval(code)
1002969
except NameError:
1003970
pass # probably a named constant
1004971
except Exception as e:
@@ -1010,9 +977,6 @@ def bad_node(self, node: ast.AST) -> None:
1010977
if bad:
1011978
fail(f"Unsupported expression as default value: {default!r}")
1012979

1013-
assignment = module.body[0]
1014-
assert isinstance(assignment, ast.Assign)
1015-
expr = assignment.value
1016980
# mild hack: explicitly support NULL as a default value
1017981
c_default: str | None
1018982
if isinstance(expr, ast.Name) and expr.id == 'NULL':
@@ -1064,8 +1028,6 @@ def bad_node(self, node: ast.AST) -> None:
10641028
else:
10651029
c_default = py_default
10661030

1067-
except SyntaxError as e:
1068-
fail(f"Syntax error: {e.text!r}")
10691031
except (ValueError, AttributeError):
10701032
value = unknown
10711033
c_default = kwargs.get("c_default")

0 commit comments

Comments
 (0)