Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-104683: clinic.py: refactor Parameter and Function as dataclasses #106477

Merged
merged 3 commits into from
Jul 7, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 57 additions & 86 deletions Tools/clinic/clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2439,6 +2439,8 @@ def __repr__(self) -> str:
ParamDict = dict[str, "Parameter"]
ReturnConverterType = Callable[..., "CReturnConverter"]


@dc.dataclass(repr=False)
class Function:
"""
Mutable duck type for inspect.Function.
Expand All @@ -2450,49 +2452,34 @@ class Function:
It will always be true that
(not docstring) or ((not docstring[0].isspace()) and (docstring.rstrip() == docstring))
"""
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 = 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 __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
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()
Expand All @@ -2517,49 +2504,30 @@ def methoddef_flags(self) -> str | None:
def __repr__(self) -> str:
return '<clinic.Function ' + self.name + '>'

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()
}
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 '<clinic.Parameter ' + self.name + '>'
Expand All @@ -2576,18 +2544,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:
Expand Down Expand Up @@ -2761,7 +2730,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,
Expand Down Expand Up @@ -2800,7 +2769,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

Expand All @@ -2810,7 +2781,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

Expand Down Expand Up @@ -2870,7 +2841,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.
Expand Down Expand Up @@ -5246,7 +5217,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():
Expand Down
Loading