Skip to content

Commit 128a6c1

Browse files
authored
gh-104683: Argument clinic: use an enum to describe the different kinds of functions (#106721)
Argument clinic: use an enum to describe the different kinds of functions
1 parent ec45c51 commit 128a6c1

File tree

1 file changed

+44
-25
lines changed

1 file changed

+44
-25
lines changed

Tools/clinic/clinic.py

+44-25
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,7 @@ def output_templates(self, f):
807807
default_return_converter = (not f.return_converter or
808808
f.return_converter.type == 'PyObject *')
809809

810-
new_or_init = f.kind in (METHOD_NEW, METHOD_INIT)
810+
new_or_init = f.kind.new_or_init
811811

812812
vararg = NO_VARARG
813813
pos_only = min_pos = max_pos = min_kw_only = pseudo_args = 0
@@ -1250,7 +1250,7 @@ def parser_body(
12501250
if new_or_init:
12511251
methoddef_define = ''
12521252

1253-
if f.kind == METHOD_NEW:
1253+
if f.kind is METHOD_NEW:
12541254
parser_prototype = parser_prototype_keyword
12551255
else:
12561256
return_value_declaration = "int return_value = -1;"
@@ -1475,7 +1475,7 @@ def render_function(
14751475
last_group = 0
14761476
first_optional = len(selfless)
14771477
positional = selfless and selfless[-1].is_positional_only()
1478-
new_or_init = f.kind in (METHOD_NEW, METHOD_INIT)
1478+
new_or_init = f.kind.new_or_init
14791479
has_option_groups = False
14801480

14811481
# offset i by -1 because first_optional needs to ignore self
@@ -2441,9 +2441,28 @@ def __repr__(self) -> str:
24412441
""".strip().split())
24422442

24432443

2444-
INVALID, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW = """
2445-
INVALID, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW
2446-
""".replace(",", "").strip().split()
2444+
class FunctionKind(enum.Enum):
2445+
INVALID = enum.auto()
2446+
CALLABLE = enum.auto()
2447+
STATIC_METHOD = enum.auto()
2448+
CLASS_METHOD = enum.auto()
2449+
METHOD_INIT = enum.auto()
2450+
METHOD_NEW = enum.auto()
2451+
2452+
@functools.cached_property
2453+
def new_or_init(self) -> bool:
2454+
return self in {FunctionKind.METHOD_INIT, FunctionKind.METHOD_NEW}
2455+
2456+
def __repr__(self) -> str:
2457+
return f"<FunctionKind.{self.name}>"
2458+
2459+
2460+
INVALID: Final = FunctionKind.INVALID
2461+
CALLABLE: Final = FunctionKind.CALLABLE
2462+
STATIC_METHOD: Final = FunctionKind.STATIC_METHOD
2463+
CLASS_METHOD: Final = FunctionKind.CLASS_METHOD
2464+
METHOD_INIT: Final = FunctionKind.METHOD_INIT
2465+
METHOD_NEW: Final = FunctionKind.METHOD_NEW
24472466

24482467
ParamDict = dict[str, "Parameter"]
24492468
ReturnConverterType = Callable[..., "CReturnConverter"]
@@ -2471,7 +2490,7 @@ class Function:
24712490
return_converter: CReturnConverter
24722491
return_annotation: object = inspect.Signature.empty
24732492
docstring: str = ''
2474-
kind: str = CALLABLE
2493+
kind: FunctionKind = CALLABLE
24752494
coexist: bool = False
24762495
# docstring_only means "don't generate a machine-readable
24772496
# signature, just a normal docstring". it's True for
@@ -2497,15 +2516,16 @@ def render_parameters(self) -> list[Parameter]:
24972516

24982517
@property
24992518
def methoddef_flags(self) -> str | None:
2500-
if self.kind in (METHOD_INIT, METHOD_NEW):
2519+
if self.kind.new_or_init:
25012520
return None
25022521
flags = []
2503-
if self.kind == CLASS_METHOD:
2504-
flags.append('METH_CLASS')
2505-
elif self.kind == STATIC_METHOD:
2506-
flags.append('METH_STATIC')
2507-
else:
2508-
assert self.kind == CALLABLE, "unknown kind: " + repr(self.kind)
2522+
match self.kind:
2523+
case FunctionKind.CLASS_METHOD:
2524+
flags.append('METH_CLASS')
2525+
case FunctionKind.STATIC_METHOD:
2526+
flags.append('METH_STATIC')
2527+
case _ as kind:
2528+
assert kind is FunctionKind.CALLABLE, f"unknown kind: {kind!r}"
25092529
if self.coexist:
25102530
flags.append('METH_COEXIST')
25112531
return '|'.join(flags)
@@ -3888,7 +3908,7 @@ def correct_name_for_self(
38883908
if f.cls:
38893909
return "PyObject *", "self"
38903910
return "PyObject *", "module"
3891-
if f.kind == STATIC_METHOD:
3911+
if f.kind is STATIC_METHOD:
38923912
return "void *", "null"
38933913
if f.kind in (CLASS_METHOD, METHOD_NEW):
38943914
return "PyTypeObject *", "type"
@@ -3921,9 +3941,8 @@ def pre_render(self):
39213941
self.type = self.specified_type or self.type or default_type
39223942

39233943
kind = self.function.kind
3924-
new_or_init = kind in (METHOD_NEW, METHOD_INIT)
39253944

3926-
if (kind == STATIC_METHOD) or new_or_init:
3945+
if kind is STATIC_METHOD or kind.new_or_init:
39273946
self.show_in_signature = False
39283947

39293948
# tp_new (METHOD_NEW) functions are of type newfunc:
@@ -3973,7 +3992,7 @@ def render(self, parameter, data):
39733992
parameter is a clinic.Parameter instance.
39743993
data is a CRenderData instance.
39753994
"""
3976-
if self.function.kind == STATIC_METHOD:
3995+
if self.function.kind is STATIC_METHOD:
39773996
return
39783997

39793998
self._render_self(parameter, data)
@@ -3992,8 +4011,8 @@ def set_template_dict(self, template_dict):
39924011
kind = self.function.kind
39934012
cls = self.function.cls
39944013

3995-
if ((kind in (METHOD_NEW, METHOD_INIT)) and cls and cls.typedef):
3996-
if kind == METHOD_NEW:
4014+
if kind.new_or_init and cls and cls.typedef:
4015+
if kind is METHOD_NEW:
39974016
type_check = (
39984017
'({0} == base_tp || {0}->tp_init == base_tp->tp_init)'
39994018
).format(self.name)
@@ -4337,7 +4356,7 @@ class DSLParser:
43374356
parameter_state: int
43384357
seen_positional_with_default: bool
43394358
indent: IndentStack
4340-
kind: str
4359+
kind: FunctionKind
43414360
coexist: bool
43424361
parameter_continuation: str
43434362
preserve_output: bool
@@ -4626,7 +4645,7 @@ def state_modulename_name(self, line: str | None) -> None:
46264645
function_name = fields.pop()
46274646
module, cls = self.clinic._module_and_class(fields)
46284647

4629-
if not (existing_function.kind == self.kind and existing_function.coexist == self.coexist):
4648+
if not (existing_function.kind is self.kind and existing_function.coexist == self.coexist):
46304649
fail("'kind' of function and cloned function don't match! (@classmethod/@staticmethod/@coexist)")
46314650
function = existing_function.copy(
46324651
name=function_name, full_name=full_name, module=module,
@@ -4679,11 +4698,11 @@ def state_modulename_name(self, line: str | None) -> None:
46794698
fail(f"{fields[-1]} is a special method and cannot be converted to Argument Clinic! (Yet.)")
46804699

46814700
if fields[-1] == '__new__':
4682-
if (self.kind != CLASS_METHOD) or (not cls):
4701+
if (self.kind is not CLASS_METHOD) or (not cls):
46834702
fail("__new__ must be a class method!")
46844703
self.kind = METHOD_NEW
46854704
elif fields[-1] == '__init__':
4686-
if (self.kind != CALLABLE) or (not cls):
4705+
if (self.kind is not CALLABLE) or (not cls):
46874706
fail("__init__ must be a normal method, not a class or static method!")
46884707
self.kind = METHOD_INIT
46894708
if not return_converter:
@@ -5203,7 +5222,7 @@ def state_function_docstring(self, line):
52035222
def format_docstring(self):
52045223
f = self.function
52055224

5206-
new_or_init = f.kind in (METHOD_NEW, METHOD_INIT)
5225+
new_or_init = f.kind.new_or_init
52075226
if new_or_init and not f.docstring:
52085227
# don't render a docstring at all, no signature, nothing.
52095228
return f.docstring

0 commit comments

Comments
 (0)