From 1126bd34083c2dac250c1980ac64c5d68644df84 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Thu, 6 Jul 2023 11:25:33 +0100 Subject: [PATCH 1/2] clinic.py: refactor `Parameter` and `Function` as dataclasses --- Tools/clinic/clinic.py | 146 +++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 87 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 7ada7e9d917b38..1832f85d720e98 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -13,6 +13,7 @@ import contextlib import copy import cpp +import dataclasses as dc import enum import functools import hashlib @@ -2463,6 +2464,8 @@ def __repr__(self) -> str: ParamDict = dict[str, "Parameter"] ReturnConverterType = Callable[..., "CReturnConverter"] + +@dc.dataclass(repr=False) class Function: """ Mutable duck type for inspect.Function. @@ -2474,49 +2477,34 @@ class Function: It will always be true that (not docstring) or ((not docstring[0].isspace()) and (docstring.rstrip() == docstring)) """ - - def __init__( - self, - parameters: ParamDict | None = None, - *, - name: str, - module: Module, - cls: Class | None = None, - c_basename: str | None = None, - full_name: str | None = None, - return_converter: CReturnConverter, - return_annotation = inspect.Signature.empty, - docstring: str | None = None, - kind: str = CALLABLE, - coexist: bool = False, - docstring_only: bool = False - ) -> None: - self.parameters = parameters or {} - self.return_annotation = return_annotation - self.name = name - self.full_name = full_name - self.module = module - self.cls = cls - self.parent = cls or module - self.c_basename = c_basename - self.return_converter = return_converter - self.docstring = docstring or '' - self.kind = kind - self.coexist = coexist + parameters: ParamDict = dc.field(default_factory=dict) + _: dc.KW_ONLY + name: str + module: Module + cls: Class | None = None + c_basename: str | None = None + full_name: str | None + return_converter: CReturnConverter + return_annotation: object = inspect.Signature.empty + docstring: str = '' + kind: str = CALLABLE + coexist: bool = False + # docstring_only means "don't generate a machine-readable + # signature, just a normal docstring". it's True for + # functions with optional groups because we can't represent + # those accurately with inspect.Signature in 3.4. + docstring_only: bool = False + + def __post_init__(self) -> None: + self.parent: Class | Module = self.cls or self.module self.self_converter: self_converter | None = None - # docstring_only means "don't generate a machine-readable - # signature, just a normal docstring". it's True for - # functions with optional groups because we can't represent - # those accurately with inspect.Signature in 3.4. - self.docstring_only = docstring_only - - self.rendered_parameters = None + self.__render_parameters__: list[Parameter] | None = None - __render_parameters__ = None @property - def render_parameters(self): + def render_parameters(self) -> list[Parameter]: if not self.__render_parameters__: - self.__render_parameters__ = l = [] + l: list[Parameter] = [] + self.__render_parameters__ = l for p in self.parameters.values(): p = p.copy() p.converter.pre_render() @@ -2541,17 +2529,8 @@ def methoddef_flags(self) -> str | None: def __repr__(self) -> str: return '' - def copy(self, **overrides) -> "Function": - kwargs = { - 'name': self.name, 'module': self.module, 'parameters': self.parameters, - 'cls': self.cls, 'c_basename': self.c_basename, - 'full_name': self.full_name, - 'return_converter': self.return_converter, 'return_annotation': self.return_annotation, - 'docstring': self.docstring, 'kind': self.kind, 'coexist': self.coexist, - 'docstring_only': self.docstring_only, - } - kwargs.update(overrides) - f = Function(**kwargs) + def copy(self, **overrides: Any) -> Function: + f = dc.replace(self, **overrides) f.parameters = { name: value.copy(function=f) for name, value in f.parameters.items() @@ -2559,31 +2538,21 @@ def copy(self, **overrides) -> "Function": return f +@dc.dataclass(repr=False, slots=True) class Parameter: """ Mutable duck type of inspect.Parameter. """ - - def __init__( - self, - name: str, - kind: inspect._ParameterKind, - *, - default = inspect.Parameter.empty, - function: Function, - converter: "CConverter", - annotation = inspect.Parameter.empty, - docstring: str | None = None, - group: int = 0 - ) -> None: - self.name = name - self.kind = kind - self.default = default - self.function = function - self.converter = converter - self.annotation = annotation - self.docstring = docstring or '' - self.group = group + name: str + kind: inspect._ParameterKind + _: dc.KW_ONLY + default: object = inspect.Parameter.empty + function: Function + converter: CConverter + annotation: object = inspect.Parameter.empty + docstring: str = '' + group: int = 0 + right_bracket_count: int = dc.field(init=False, default=0) def __repr__(self) -> str: return '' @@ -2600,18 +2569,19 @@ def is_vararg(self) -> bool: def is_optional(self) -> bool: return not self.is_vararg() and (self.default is not unspecified) - def copy(self, **overrides) -> "Parameter": - kwargs = { - 'name': self.name, 'kind': self.kind, 'default':self.default, - 'function': self.function, 'converter': self.converter, 'annotation': self.annotation, - 'docstring': self.docstring, 'group': self.group, - } - kwargs.update(overrides) - if 'converter' not in overrides: + def copy( + self, + /, + *, + converter: CConverter | None = None, + function: Function | None = None, + **overrides: Any + ) -> Parameter: + function = function or self.function + if not converter: converter = copy.copy(self.converter) - converter.function = kwargs['function'] - kwargs['converter'] = converter - return Parameter(**kwargs) + converter.function = function + return dc.replace(self, **overrides, function=function, converter=converter) def get_displayname(self, i: int) -> str: if i == 0: @@ -2788,7 +2758,7 @@ def __init__(self, # Positional args: name: str, py_name: str, - function, + function: Function, default: object = unspecified, *, # Keyword only args: c_default: str | None = None, @@ -2827,7 +2797,9 @@ def __init__(self, # about the function in the init. # (that breaks if we get cloned.) # so after this change we will noisily fail. - self.function = LandMine("Don't access members of self.function inside converter_init!") + self.function: Function | LandMine = LandMine( + "Don't access members of self.function inside converter_init!" + ) self.converter_init(**kwargs) self.function = function @@ -2837,7 +2809,7 @@ def converter_init(self): def is_optional(self) -> bool: return (self.default is not unspecified) - def _render_self(self, parameter: str, data: CRenderData) -> None: + def _render_self(self, parameter: Parameter, data: CRenderData) -> None: self.parameter = parameter name = self.parser_name @@ -2897,7 +2869,7 @@ def _render_non_self(self, parameter, data): if cleanup: data.cleanup.append('/* Cleanup for ' + name + ' */\n' + cleanup.rstrip() + "\n") - def render(self, parameter: str, data: CRenderData) -> None: + def render(self, parameter: Parameter, data: CRenderData) -> None: """ parameter is a clinic.Parameter instance. data is a CRenderData instance. @@ -5273,7 +5245,7 @@ def format_docstring(self): assert isinstance(parameters[0].converter, self_converter) # self is always positional-only. assert parameters[0].is_positional_only() - parameters[0].right_bracket_count = 0 + assert parameters[0].right_bracket_count == 0 positional_only = True for p in parameters[1:]: if not p.is_positional_only(): From 3d5f073fa7e44739ee5170db8e865dc5d973180a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 7 Jul 2023 13:43:24 +0100 Subject: [PATCH 2/2] fix oversight --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 38b6dfc8734982..19c5f573920cb9 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2458,7 +2458,7 @@ class Function: module: Module cls: Class | None = None c_basename: str | None = None - full_name: str | None + full_name: str | None = None return_converter: CReturnConverter return_annotation: object = inspect.Signature.empty docstring: str = ''