Skip to content

Commit 6c6033a

Browse files
committed
Argument clinic: Modernise AST-parsing methods using pattern-matching
1 parent 663c049 commit 6c6033a

File tree

1 file changed

+83
-62
lines changed

1 file changed

+83
-62
lines changed

Tools/clinic/clinic.py

Lines changed: 83 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4796,59 +4796,77 @@ def bad_node(self, node):
47964796
if bad:
47974797
fail("Unsupported expression as default value: " + repr(default))
47984798

4799-
expr = module.body[0].value
4800-
# mild hack: explicitly support NULL as a default value
4801-
if isinstance(expr, ast.Name) and expr.id == 'NULL':
4802-
value = NULL
4803-
py_default = '<unrepresentable>'
4804-
c_default = "NULL"
4805-
elif (isinstance(expr, ast.BinOp) or
4806-
(isinstance(expr, ast.UnaryOp) and
4807-
not (isinstance(expr.operand, ast.Constant) and
4808-
type(expr.operand.value) in {int, float, complex})
4809-
)):
4799+
def get_c_default(
4800+
default: str, default_kind: str, expr: ast.expr | None = None
4801+
) -> str:
48104802
c_default = kwargs.get("c_default")
48114803
if not (isinstance(c_default, str) and c_default):
4812-
fail("When you specify an expression (" + repr(default) + ") as your default value,\nyou MUST specify a valid c_default." + ast.dump(expr))
4813-
py_default = default
4814-
value = unknown
4815-
elif isinstance(expr, ast.Attribute):
4816-
a = []
4817-
n = expr
4818-
while isinstance(n, ast.Attribute):
4819-
a.append(n.attr)
4820-
n = n.value
4821-
if not isinstance(n, ast.Name):
4822-
fail("Unsupported default value " + repr(default) + " (looked like a Python constant)")
4823-
a.append(n.id)
4824-
py_default = ".".join(reversed(a))
4825-
4826-
c_default = kwargs.get("c_default")
4827-
if not (isinstance(c_default, str) and c_default):
4828-
fail("When you specify a named constant (" + repr(py_default) + ") as your default value,\nyou MUST specify a valid c_default.")
4829-
4830-
try:
4831-
value = eval(py_default)
4832-
except NameError:
4804+
msg = (
4805+
f"When you specify {default_kind} ({default!r}) "
4806+
f"as your default value,\n"
4807+
f"you MUST specify a valid c_default."
4808+
)
4809+
if expr:
4810+
msg += f"\n{ast.dump(expr)}"
4811+
fail(msg)
4812+
return c_default
4813+
4814+
def is_numeric(node: ast.expr) -> bool:
4815+
match node:
4816+
case ast.Constant(value=value):
4817+
return type(value) in {int, float, complex}
4818+
case _:
4819+
return False
4820+
4821+
match expr := module.body[0].value:
4822+
# mild hack: explicitly support NULL as a default value
4823+
case ast.Name('NULL'):
4824+
value = NULL
4825+
py_default = '<unrepresentable>'
4826+
c_default = "NULL"
4827+
case ast.BinOp():
4828+
c_default = get_c_default(default, 'an expression', expr)
4829+
py_default = default
48334830
value = unknown
4834-
else:
4835-
value = ast.literal_eval(expr)
4836-
py_default = repr(value)
4837-
if isinstance(value, (bool, None.__class__)):
4838-
c_default = "Py_" + py_default
4839-
elif isinstance(value, str):
4840-
c_default = c_repr(value)
4841-
else:
4842-
c_default = py_default
4831+
case ast.UnaryOp(operand=operand) if is_numeric(operand):
4832+
c_default = get_c_default(default, 'an expression', expr)
4833+
py_default = default
4834+
value = unknown
4835+
case ast.Attribute():
4836+
a = []
4837+
n = expr
4838+
while isinstance(n, ast.Attribute):
4839+
a.append(n.attr)
4840+
n = n.value
4841+
if not isinstance(n, ast.Name):
4842+
fail(
4843+
f"Unsupported default value {default!r} (looked like a Python constant)"
4844+
)
4845+
a.append(n.id)
4846+
py_default = ".".join(reversed(a))
4847+
c_default = get_c_default(default, 'a named constant')
4848+
4849+
try:
4850+
value = eval(py_default)
4851+
except NameError:
4852+
value = unknown
4853+
case _:
4854+
value = ast.literal_eval(expr)
4855+
py_default = repr(value)
4856+
match value:
4857+
case True | False | None:
4858+
c_default = f"Py_{py_default}"
4859+
case str():
4860+
c_default = c_repr(value)
4861+
case _:
4862+
c_default = py_default
48434863

48444864
except SyntaxError as e:
4845-
fail("Syntax error: " + repr(e.text))
4865+
fail(f"Syntax error: {e.text!r}")
48464866
except (ValueError, AttributeError):
48474867
value = unknown
4848-
c_default = kwargs.get("c_default")
48494868
py_default = default
4850-
if not (isinstance(c_default, str) and c_default):
4851-
fail("When you specify a named constant (" + repr(py_default) + ") as your default value,\nyou MUST specify a valid c_default.")
4869+
c_default = get_c_default(py_default, 'a named constant')
48524870

48534871
kwargs.setdefault('c_default', c_default)
48544872
kwargs.setdefault('py_default', py_default)
@@ -4899,29 +4917,32 @@ def bad_node(self, node):
48994917

49004918
names = [k.name for k in self.function.parameters.values()]
49014919
if parameter_name in names[1:]:
4902-
fail("You can't have two parameters named " + repr(parameter_name) + "!")
4920+
fail(f"You can't have two parameters named {parameter_name!r}!")
49034921
elif names and parameter_name == names[0] and c_name is None:
49044922
fail(f"Parameter '{parameter_name}' requires a custom C name")
49054923

49064924
key = f"{parameter_name}_as_{c_name}" if c_name else parameter_name
49074925
self.function.parameters[key] = p
49084926

4909-
def parse_converter(self, annotation):
4910-
if (isinstance(annotation, ast.Constant) and
4911-
type(annotation.value) is str):
4912-
return annotation.value, True, {}
4913-
4914-
if isinstance(annotation, ast.Name):
4915-
return annotation.id, False, {}
4916-
4917-
if not isinstance(annotation, ast.Call):
4918-
fail("Annotations must be either a name, a function call, or a string.")
4919-
4920-
name = annotation.func.id
4921-
symbols = globals()
4922-
4923-
kwargs = {node.arg: eval_ast_expr(node.value, symbols) for node in annotation.keywords}
4924-
return name, False, kwargs
4927+
def parse_converter(
4928+
self, annotation: ast.AST
4929+
) -> tuple[str, bool, dict[str | None, object]]:
4930+
match annotation:
4931+
case ast.Constant(value=str() as value):
4932+
return value, True, {}
4933+
case ast.Name(id):
4934+
return id, False, {}
4935+
case ast.Call(func=ast.Name(name)):
4936+
symbols = globals()
4937+
kwargs = {
4938+
node.arg: eval_ast_expr(node.value, symbols)
4939+
for node in annotation.keywords
4940+
}
4941+
return name, False, kwargs
4942+
case _:
4943+
fail(
4944+
"Annotations must be either a name, a function call, or a string."
4945+
)
49254946

49264947
def parse_special_symbol(self, symbol):
49274948
if symbol == '*':

0 commit comments

Comments
 (0)