[3.14] gh-135256: Simplify parsing parameters in Argument Clinic (GH-135257) (121914136635)

(cherry picked from commit b74fb8e220)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Miss Islington (bot) 2025-07-13 22:52:02 +02:00 committed by GitHub
parent 348e22cf06
commit ba070b6b07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 16 additions and 58 deletions

View file

@ -1277,12 +1277,8 @@ def test_base_invalid_syntax(self):
os.stat
invalid syntax: int = 42
"""
err = dedent(r"""
Function 'stat' has an invalid parameter declaration:
\s+'invalid syntax: int = 42'
""").strip()
with self.assertRaisesRegex(ClinicError, err):
self.parse_function(block)
err = "Function 'stat' has an invalid parameter declaration: 'invalid syntax: int = 42'"
self.expect_failure(block, err, lineno=2)
def test_param_default_invalid_syntax(self):
block = """
@ -1290,7 +1286,7 @@ def test_param_default_invalid_syntax(self):
os.stat
x: int = invalid syntax
"""
err = r"Syntax error: 'x = invalid syntax\n'"
err = "Function 'stat' has an invalid parameter declaration:"
self.expect_failure(block, err, lineno=2)
def test_cloning_nonexistent_function_correctly_fails(self):
@ -2510,7 +2506,7 @@ def test_cannot_specify_pydefault_without_default(self):
self.expect_failure(block, err, lineno=1)
def test_vararg_cannot_take_default_value(self):
err = "Vararg can't take a default value!"
err = "Function 'fn' has an invalid parameter declaration:"
block = """
fn
*args: tuple = None

View file

@ -877,43 +877,16 @@ def parse_parameter(self, line: str) -> None:
# handle "as" for parameters too
c_name = None
name, have_as_token, trailing = line.partition(' as ')
if have_as_token:
name = name.strip()
if ' ' not in name:
fields = trailing.strip().split(' ')
if not fields:
fail("Invalid 'as' clause!")
c_name = fields[0]
if c_name.endswith(':'):
name += ':'
c_name = c_name[:-1]
fields[0] = name
line = ' '.join(fields)
m = re.match(r'(?:\* *)?\w+( +as +(\w+))', line)
if m:
c_name = m[2]
line = line[:m.start(1)] + line[m.end(1):]
default: str | None
base, equals, default = line.rpartition('=')
if not equals:
base = default
default = None
module = None
try:
ast_input = f"def x({base}): pass"
ast_input = f"def x({line}\n): pass"
module = ast.parse(ast_input)
except SyntaxError:
try:
# the last = was probably inside a function call, like
# c: int(accept={str})
# so assume there was no actual default value.
default = None
ast_input = f"def x({line}): pass"
module = ast.parse(ast_input)
except SyntaxError:
pass
if not module:
fail(f"Function {self.function.name!r} has an invalid parameter declaration:\n\t",
repr(line))
fail(f"Function {self.function.name!r} has an invalid parameter declaration: {line!r}")
function = module.body[0]
assert isinstance(function, ast.FunctionDef)
@ -922,9 +895,6 @@ def parse_parameter(self, line: str) -> None:
if len(function_args.args) > 1:
fail(f"Function {self.function.name!r} has an "
f"invalid parameter declaration (comma?): {line!r}")
if function_args.defaults or function_args.kw_defaults:
fail(f"Function {self.function.name!r} has an "
f"invalid parameter declaration (default value?): {line!r}")
if function_args.kwarg:
fail(f"Function {self.function.name!r} has an "
f"invalid parameter declaration (**kwargs?): {line!r}")
@ -944,7 +914,7 @@ def parse_parameter(self, line: str) -> None:
name = 'varpos_' + name
value: object
if not default:
if not function_args.defaults:
if is_vararg:
value = NULL
else:
@ -955,17 +925,13 @@ def parse_parameter(self, line: str) -> None:
if 'py_default' in kwargs:
fail("You can't specify py_default without specifying a default value!")
else:
if is_vararg:
fail("Vararg can't take a default value!")
expr = function_args.defaults[0]
default = ast_input[expr.col_offset: expr.end_col_offset].strip()
if self.parameter_state is ParamState.REQUIRED:
self.parameter_state = ParamState.OPTIONAL
default = default.strip()
bad = False
ast_input = f"x = {default}"
try:
module = ast.parse(ast_input)
if 'c_default' not in kwargs:
# we can only represent very simple data values in C.
# detect whether default is okay, via a denylist
@ -992,13 +958,14 @@ def bad_node(self, node: ast.AST) -> None:
visit_Starred = bad_node
denylist = DetectBadNodes()
denylist.visit(module)
denylist.visit(expr)
bad = denylist.bad
else:
# if they specify a c_default, we can be more lenient about the default value.
# but at least make an attempt at ensuring it's a valid expression.
code = compile(ast.Expression(expr), '<expr>', 'eval')
try:
value = eval(default)
value = eval(code)
except NameError:
pass # probably a named constant
except Exception as e:
@ -1010,9 +977,6 @@ def bad_node(self, node: ast.AST) -> None:
if bad:
fail(f"Unsupported expression as default value: {default!r}")
assignment = module.body[0]
assert isinstance(assignment, ast.Assign)
expr = assignment.value
# mild hack: explicitly support NULL as a default value
c_default: str | None
if isinstance(expr, ast.Name) and expr.id == 'NULL':
@ -1064,8 +1028,6 @@ def bad_node(self, node: ast.AST) -> None:
else:
c_default = py_default
except SyntaxError as e:
fail(f"Syntax error: {e.text!r}")
except (ValueError, AttributeError):
value = unknown
c_default = kwargs.get("c_default")