Skip to content

Commit 32f8ab1

Browse files
gh-114258: Refactor Argument Clinic function name parser (#114930)
Refactor state_modulename_name() of the parsing state machine, by adding helpers for the sections that deal with ...: 1. parsing the function name 2. normalizing "function kind" 3. dealing with cloned functions 4. resolving return converters 5. adding the function to the DSL parser
1 parent dc978f6 commit 32f8ab1

File tree

1 file changed

+123
-106
lines changed

1 file changed

+123
-106
lines changed

Tools/clinic/clinic.py

+123-106
Original file line numberDiff line numberDiff line change
@@ -5126,8 +5126,7 @@ def state_dsl_start(self, line: str) -> None:
51265126

51275127
self.next(self.state_modulename_name, line)
51285128

5129-
@staticmethod
5130-
def parse_function_names(line: str) -> FunctionNames:
5129+
def parse_function_names(self, line: str) -> FunctionNames:
51315130
left, as_, right = line.partition(' as ')
51325131
full_name = left.strip()
51335132
c_basename = right.strip()
@@ -5142,28 +5141,101 @@ def parse_function_names(line: str) -> FunctionNames:
51425141
fail(f"Illegal function name: {full_name!r}")
51435142
if not is_legal_c_identifier(c_basename):
51445143
fail(f"Illegal C basename: {c_basename!r}")
5145-
return FunctionNames(full_name=full_name, c_basename=c_basename)
5144+
names = FunctionNames(full_name=full_name, c_basename=c_basename)
5145+
self.normalize_function_kind(names.full_name)
5146+
return names
51465147

5147-
def update_function_kind(self, fullname: str) -> None:
5148+
def normalize_function_kind(self, fullname: str) -> None:
5149+
# Fetch the method name and possibly class.
51485150
fields = fullname.split('.')
51495151
name = fields.pop()
51505152
_, cls = self.clinic._module_and_class(fields)
5153+
5154+
# Check special method requirements.
51515155
if name in unsupported_special_methods:
51525156
fail(f"{name!r} is a special method and cannot be converted to Argument Clinic!")
5153-
5157+
if name == '__init__' and (self.kind is not CALLABLE or not cls):
5158+
fail(f"{name!r} must be a normal method; got '{self.kind}'!")
5159+
if name == '__new__' and (self.kind is not CLASS_METHOD or not cls):
5160+
fail("'__new__' must be a class method!")
5161+
if self.kind in {GETTER, SETTER} and not cls:
5162+
fail("@getter and @setter must be methods")
5163+
5164+
# Normalise self.kind.
51545165
if name == '__new__':
5155-
if (self.kind is CLASS_METHOD) and cls:
5156-
self.kind = METHOD_NEW
5157-
else:
5158-
fail("'__new__' must be a class method!")
5166+
self.kind = METHOD_NEW
51595167
elif name == '__init__':
5160-
if (self.kind is CALLABLE) and cls:
5161-
self.kind = METHOD_INIT
5168+
self.kind = METHOD_INIT
5169+
5170+
def resolve_return_converter(
5171+
self, full_name: str, forced_converter: str
5172+
) -> CReturnConverter:
5173+
if forced_converter:
5174+
if self.kind in {GETTER, SETTER}:
5175+
fail(f"@{self.kind.name.lower()} method cannot define a return type")
5176+
ast_input = f"def x() -> {forced_converter}: pass"
5177+
try:
5178+
module_node = ast.parse(ast_input)
5179+
except SyntaxError:
5180+
fail(f"Badly formed annotation for {full_name!r}: {forced_converter!r}")
5181+
function_node = module_node.body[0]
5182+
assert isinstance(function_node, ast.FunctionDef)
5183+
try:
5184+
name, legacy, kwargs = self.parse_converter(function_node.returns)
5185+
if legacy:
5186+
fail(f"Legacy converter {name!r} not allowed as a return converter")
5187+
if name not in return_converters:
5188+
fail(f"No available return converter called {name!r}")
5189+
return return_converters[name](**kwargs)
5190+
except ValueError:
5191+
fail(f"Badly formed annotation for {full_name!r}: {forced_converter!r}")
5192+
5193+
if self.kind is METHOD_INIT:
5194+
return init_return_converter()
5195+
return CReturnConverter()
5196+
5197+
def parse_cloned_function(self, names: FunctionNames, existing: str) -> None:
5198+
full_name, c_basename = names
5199+
fields = [x.strip() for x in existing.split('.')]
5200+
function_name = fields.pop()
5201+
module, cls = self.clinic._module_and_class(fields)
5202+
parent = cls or module
5203+
5204+
for existing_function in parent.functions:
5205+
if existing_function.name == function_name:
5206+
break
5207+
else:
5208+
print(f"{cls=}, {module=}, {existing=}", file=sys.stderr)
5209+
print(f"{(cls or module).functions=}", file=sys.stderr)
5210+
fail(f"Couldn't find existing function {existing!r}!")
5211+
5212+
fields = [x.strip() for x in full_name.split('.')]
5213+
function_name = fields.pop()
5214+
module, cls = self.clinic._module_and_class(fields)
5215+
5216+
overrides: dict[str, Any] = {
5217+
"name": function_name,
5218+
"full_name": full_name,
5219+
"module": module,
5220+
"cls": cls,
5221+
"c_basename": c_basename,
5222+
"docstring": "",
5223+
}
5224+
if not (existing_function.kind is self.kind and
5225+
existing_function.coexist == self.coexist):
5226+
# Allow __new__ or __init__ methods.
5227+
if existing_function.kind.new_or_init:
5228+
overrides["kind"] = self.kind
5229+
# Future enhancement: allow custom return converters
5230+
overrides["return_converter"] = CReturnConverter()
51625231
else:
5163-
fail(
5164-
"'__init__' must be a normal method; "
5165-
f"got '{self.kind}'!"
5166-
)
5232+
fail("'kind' of function and cloned function don't match! "
5233+
"(@classmethod/@staticmethod/@coexist)")
5234+
function = existing_function.copy(**overrides)
5235+
self.function = function
5236+
self.block.signatures.append(function)
5237+
(cls or module).functions.append(function)
5238+
self.next(self.state_function_docstring)
51675239

51685240
def state_modulename_name(self, line: str) -> None:
51695241
# looking for declaration, which establishes the leftmost column
@@ -5188,111 +5260,56 @@ def state_modulename_name(self, line: str) -> None:
51885260
# are we cloning?
51895261
before, equals, existing = line.rpartition('=')
51905262
if equals:
5191-
full_name, c_basename = self.parse_function_names(before)
51925263
existing = existing.strip()
51935264
if is_legal_py_identifier(existing):
51945265
# we're cloning!
5195-
fields = [x.strip() for x in existing.split('.')]
5196-
function_name = fields.pop()
5197-
module, cls = self.clinic._module_and_class(fields)
5198-
5199-
for existing_function in (cls or module).functions:
5200-
if existing_function.name == function_name:
5201-
break
5202-
else:
5203-
print(f"{cls=}, {module=}, {existing=}", file=sys.stderr)
5204-
print(f"{(cls or module).functions=}", file=sys.stderr)
5205-
fail(f"Couldn't find existing function {existing!r}!")
5206-
5207-
fields = [x.strip() for x in full_name.split('.')]
5208-
function_name = fields.pop()
5209-
module, cls = self.clinic._module_and_class(fields)
5210-
5211-
self.update_function_kind(full_name)
5212-
overrides: dict[str, Any] = {
5213-
"name": function_name,
5214-
"full_name": full_name,
5215-
"module": module,
5216-
"cls": cls,
5217-
"c_basename": c_basename,
5218-
"docstring": "",
5219-
}
5220-
if not (existing_function.kind is self.kind and
5221-
existing_function.coexist == self.coexist):
5222-
# Allow __new__ or __init__ methods.
5223-
if existing_function.kind.new_or_init:
5224-
overrides["kind"] = self.kind
5225-
# Future enhancement: allow custom return converters
5226-
overrides["return_converter"] = CReturnConverter()
5227-
else:
5228-
fail("'kind' of function and cloned function don't match! "
5229-
"(@classmethod/@staticmethod/@coexist)")
5230-
function = existing_function.copy(**overrides)
5231-
self.function = function
5232-
self.block.signatures.append(function)
5233-
(cls or module).functions.append(function)
5234-
self.next(self.state_function_docstring)
5235-
return
5266+
names = self.parse_function_names(before)
5267+
return self.parse_cloned_function(names, existing)
52365268

52375269
line, _, returns = line.partition('->')
52385270
returns = returns.strip()
52395271
full_name, c_basename = self.parse_function_names(line)
5240-
5241-
return_converter = None
5242-
if returns:
5243-
if self.kind in {GETTER, SETTER}:
5244-
fail(f"@{self.kind.name.lower()} method cannot define a return type")
5245-
ast_input = f"def x() -> {returns}: pass"
5246-
try:
5247-
module_node = ast.parse(ast_input)
5248-
except SyntaxError:
5249-
fail(f"Badly formed annotation for {full_name!r}: {returns!r}")
5250-
function_node = module_node.body[0]
5251-
assert isinstance(function_node, ast.FunctionDef)
5252-
try:
5253-
name, legacy, kwargs = self.parse_converter(function_node.returns)
5254-
if legacy:
5255-
fail(f"Legacy converter {name!r} not allowed as a return converter")
5256-
if name not in return_converters:
5257-
fail(f"No available return converter called {name!r}")
5258-
return_converter = return_converters[name](**kwargs)
5259-
except ValueError:
5260-
fail(f"Badly formed annotation for {full_name!r}: {returns!r}")
5272+
return_converter = self.resolve_return_converter(full_name, returns)
52615273

52625274
fields = [x.strip() for x in full_name.split('.')]
52635275
function_name = fields.pop()
52645276
module, cls = self.clinic._module_and_class(fields)
52655277

5266-
if self.kind in {GETTER, SETTER}:
5267-
if not cls:
5268-
fail("@getter and @setter must be methods")
5269-
5270-
self.update_function_kind(full_name)
5271-
if self.kind is METHOD_INIT and not return_converter:
5272-
return_converter = init_return_converter()
5273-
5274-
if not return_converter:
5275-
return_converter = CReturnConverter()
5276-
5277-
self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename,
5278-
return_converter=return_converter, kind=self.kind, coexist=self.coexist,
5279-
critical_section=self.critical_section,
5280-
target_critical_section=self.target_critical_section)
5281-
self.block.signatures.append(self.function)
5282-
5283-
# insert a self converter automatically
5284-
type, name = correct_name_for_self(self.function)
5285-
kwargs = {}
5286-
if cls and type == "PyObject *":
5287-
kwargs['type'] = cls.typedef
5288-
sc = self.function.self_converter = self_converter(name, name, self.function, **kwargs)
5289-
p_self = Parameter(name, inspect.Parameter.POSITIONAL_ONLY,
5290-
function=self.function, converter=sc)
5291-
self.function.parameters[name] = p_self
5292-
5293-
(cls or module).functions.append(self.function)
5278+
func = Function(
5279+
name=function_name,
5280+
full_name=full_name,
5281+
module=module,
5282+
cls=cls,
5283+
c_basename=c_basename,
5284+
return_converter=return_converter,
5285+
kind=self.kind,
5286+
coexist=self.coexist,
5287+
critical_section=self.critical_section,
5288+
target_critical_section=self.target_critical_section
5289+
)
5290+
self.add_function(func)
5291+
52945292
self.next(self.state_parameters_start)
52955293

5294+
def add_function(self, func: Function) -> None:
5295+
# Insert a self converter automatically.
5296+
tp, name = correct_name_for_self(func)
5297+
if func.cls and tp == "PyObject *":
5298+
func.self_converter = self_converter(name, name, func,
5299+
type=func.cls.typedef)
5300+
else:
5301+
func.self_converter = self_converter(name, name, func)
5302+
func.parameters[name] = Parameter(
5303+
name,
5304+
inspect.Parameter.POSITIONAL_ONLY,
5305+
function=func,
5306+
converter=func.self_converter
5307+
)
5308+
5309+
self.block.signatures.append(func)
5310+
self.function = func
5311+
(func.cls or func.module).functions.append(func)
5312+
52965313
# Now entering the parameters section. The rules, formally stated:
52975314
#
52985315
# * All lines must be indented with spaces only.

0 commit comments

Comments
 (0)