forked from python/cpython
-
Notifications
You must be signed in to change notification settings - Fork 0
Argument Clinic: Remove METHOD_INIT and METHOD_NEW function kinds #23
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
Open
erlend-aasland
wants to merge
2
commits into
main
Choose a base branch
from
clinic/new-and-init
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -998,7 +998,7 @@ def output_templates( | |||||
|
|
||||||
| has_option_groups = parameters and (parameters[0].group or parameters[-1].group) | ||||||
| default_return_converter = f.return_converter.type == 'PyObject *' | ||||||
| new_or_init = f.kind.new_or_init | ||||||
| new_or_init = f.new_or_init | ||||||
|
|
||||||
| vararg: int | str = NO_VARARG | ||||||
| pos_only = min_pos = max_pos = min_kw_only = pseudo_args = 0 | ||||||
|
|
@@ -1405,7 +1405,7 @@ def parser_body( | |||||
| if new_or_init: | ||||||
| methoddef_define = '' | ||||||
|
|
||||||
| if f.kind is METHOD_NEW: | ||||||
| if f.name == "__new__": | ||||||
| parser_prototype = self.PARSER_PROTOTYPE_KEYWORD | ||||||
| else: | ||||||
| return_value_declaration = "int return_value = -1;" | ||||||
|
|
@@ -1629,7 +1629,7 @@ def render_function( | |||||
| last_group = 0 | ||||||
| first_optional = len(selfless) | ||||||
| positional = selfless and selfless[-1].is_positional_only() | ||||||
| new_or_init = f.kind.new_or_init | ||||||
| new_or_init = f.new_or_init | ||||||
| has_option_groups = False | ||||||
|
|
||||||
| # offset i by -1 because first_optional needs to ignore self | ||||||
|
|
@@ -2604,12 +2604,6 @@ class FunctionKind(enum.Enum): | |||||
| CALLABLE = enum.auto() | ||||||
| STATIC_METHOD = enum.auto() | ||||||
| CLASS_METHOD = enum.auto() | ||||||
| METHOD_INIT = enum.auto() | ||||||
| METHOD_NEW = enum.auto() | ||||||
|
|
||||||
| @functools.cached_property | ||||||
| def new_or_init(self) -> bool: | ||||||
| return self in {FunctionKind.METHOD_INIT, FunctionKind.METHOD_NEW} | ||||||
|
|
||||||
| def __repr__(self) -> str: | ||||||
| return f"<clinic.FunctionKind.{self.name}>" | ||||||
|
|
@@ -2619,8 +2613,6 @@ def __repr__(self) -> str: | |||||
| CALLABLE: Final = FunctionKind.CALLABLE | ||||||
| STATIC_METHOD: Final = FunctionKind.STATIC_METHOD | ||||||
| CLASS_METHOD: Final = FunctionKind.CLASS_METHOD | ||||||
| METHOD_INIT: Final = FunctionKind.METHOD_INIT | ||||||
| METHOD_NEW: Final = FunctionKind.METHOD_NEW | ||||||
|
|
||||||
| ParamDict = dict[str, "Parameter"] | ||||||
| ReturnConverterType = Callable[..., "CReturnConverter"] | ||||||
|
|
@@ -2661,10 +2653,14 @@ def __post_init__(self) -> None: | |||||
| self.self_converter: self_converter | None = None | ||||||
| self.__render_parameters__: list[Parameter] | None = None | ||||||
|
|
||||||
| @functools.cached_property | ||||||
| def new_or_init(self) -> bool: | ||||||
| return self.name in ("__new__", "__init__") | ||||||
|
|
||||||
| @functools.cached_property | ||||||
| def displayname(self) -> str: | ||||||
| """Pretty-printable name.""" | ||||||
| if self.kind.new_or_init: | ||||||
| if self.new_or_init: | ||||||
| assert isinstance(self.cls, Class) | ||||||
| return self.cls.name | ||||||
| else: | ||||||
|
|
@@ -2673,7 +2669,7 @@ def displayname(self) -> str: | |||||
| @functools.cached_property | ||||||
| def fulldisplayname(self) -> str: | ||||||
| parent: Class | Module | Clinic | None | ||||||
| if self.kind.new_or_init: | ||||||
| if self.new_or_init: | ||||||
| parent = getattr(self.cls, "parent", None) | ||||||
| else: | ||||||
| parent = self.parent | ||||||
|
|
@@ -2696,7 +2692,7 @@ def render_parameters(self) -> list[Parameter]: | |||||
|
|
||||||
| @property | ||||||
| def methoddef_flags(self) -> str | None: | ||||||
| if self.kind.new_or_init: | ||||||
| if self.new_or_init: | ||||||
| return None | ||||||
| flags = [] | ||||||
| match self.kind: | ||||||
|
|
@@ -4127,21 +4123,21 @@ def parse_arg(self, argname: str, displayname: str) -> str | None: | |||||
| def correct_name_for_self( | ||||||
| f: Function | ||||||
| ) -> tuple[str, str]: | ||||||
| if f.kind in (CALLABLE, METHOD_INIT): | ||||||
| if f.kind is CALLABLE or f.name == "__init__": | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| if f.cls: | ||||||
| return "PyObject *", "self" | ||||||
| return "PyObject *", "module" | ||||||
| if f.kind is STATIC_METHOD: | ||||||
| return "void *", "null" | ||||||
| if f.kind in (CLASS_METHOD, METHOD_NEW): | ||||||
| if f.kind is CLASS_METHOD: | ||||||
| return "PyTypeObject *", "type" | ||||||
| raise AssertionError(f"Unhandled type of function f: {f.kind!r}") | ||||||
|
|
||||||
| def required_type_for_self_for_parser( | ||||||
| f: Function | ||||||
| ) -> str | None: | ||||||
| type, _ = correct_name_for_self(f) | ||||||
| if f.kind in (METHOD_INIT, METHOD_NEW, STATIC_METHOD, CLASS_METHOD): | ||||||
| if f.kind in (STATIC_METHOD, CLASS_METHOD) or f.name == "__init__": | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reads slightly nicer. |
||||||
| return type | ||||||
| return None | ||||||
|
|
||||||
|
|
@@ -4165,13 +4161,13 @@ def pre_render(self) -> None: | |||||
|
|
||||||
| kind = self.function.kind | ||||||
|
|
||||||
| if kind is STATIC_METHOD or kind.new_or_init: | ||||||
| if kind is STATIC_METHOD or f.new_or_init: | ||||||
| self.show_in_signature = False | ||||||
|
|
||||||
| # tp_new (METHOD_NEW) functions are of type newfunc: | ||||||
| # tp_new functions are of type newfunc: | ||||||
| # typedef PyObject *(*newfunc)(PyTypeObject *, PyObject *, PyObject *); | ||||||
| # | ||||||
| # tp_init (METHOD_INIT) functions are of type initproc: | ||||||
| # tp_init functions are of type initproc: | ||||||
| # typedef int (*initproc)(PyObject *, PyObject *, PyObject *); | ||||||
| # | ||||||
| # All other functions generated by Argument Clinic are stored in | ||||||
|
|
@@ -4189,18 +4185,18 @@ def pre_render(self) -> None: | |||||
| # | ||||||
| # * The type of the first parameter to the impl will always be of self.type. | ||||||
| # | ||||||
| # * If the function is neither tp_new (METHOD_NEW) nor tp_init (METHOD_INIT): | ||||||
| # * If the function is neither tp_new nor tp_init: | ||||||
| # * The type of the first parameter to the parsing function is also self.type. | ||||||
| # This means that if you step into the parsing function, your "self" parameter | ||||||
| # is of the correct type, which may make debugging more pleasant. | ||||||
| # | ||||||
| # * Else if the function is tp_new (METHOD_NEW): | ||||||
| # * Else if the function is tp_new: | ||||||
| # * The type of the first parameter to the parsing function is "PyTypeObject *", | ||||||
| # so the type signature of the function call is an exact match. | ||||||
| # * If self.type != "PyTypeObject *", we cast the first parameter to self.type | ||||||
| # in the impl call. | ||||||
| # | ||||||
| # * Else if the function is tp_init (METHOD_INIT): | ||||||
| # * Else if the function is tp_init: | ||||||
| # * The type of the first parameter to the parsing function is "PyObject *", | ||||||
| # so the type signature of the function call is an exact match. | ||||||
| # * If self.type != "PyObject *", we cast the first parameter to self.type | ||||||
|
|
@@ -4233,11 +4229,10 @@ def render(self, parameter: Parameter, data: CRenderData) -> None: | |||||
| def set_template_dict(self, template_dict: TemplateDict) -> None: | ||||||
| template_dict['self_name'] = self.name | ||||||
| template_dict['self_type'] = self.parser_type | ||||||
| kind = self.function.kind | ||||||
| cls = self.function.cls | ||||||
|
|
||||||
| if kind.new_or_init and cls and cls.typedef: | ||||||
| if kind is METHOD_NEW: | ||||||
| if self.function.new_or_init and cls and cls.typedef: | ||||||
| if self.function.name == "__new__": | ||||||
| type_check = ( | ||||||
| '({0} == base_tp || {0}->tp_init == base_tp->tp_init)' | ||||||
| ).format(self.name) | ||||||
|
|
@@ -4862,11 +4857,9 @@ def update_function_kind(self, fullname: str) -> None: | |||||
| if name == '__new__': | ||||||
| if (self.kind is not CLASS_METHOD) or (not cls): | ||||||
| fail("'__new__' must be a class method!") | ||||||
| self.kind = METHOD_NEW | ||||||
| elif name == '__init__': | ||||||
| if (self.kind is not CALLABLE) or (not cls): | ||||||
| fail("'__init__' must be a normal method, not a class or static method!") | ||||||
| self.kind = METHOD_INIT | ||||||
|
|
||||||
| def state_modulename_name(self, line: str) -> None: | ||||||
| # looking for declaration, which establishes the leftmost column | ||||||
|
|
@@ -4920,16 +4913,15 @@ def state_modulename_name(self, line: str) -> None: | |||||
| "c_basename": c_basename, | ||||||
| "docstring": "", | ||||||
| } | ||||||
| if not (existing_function.kind is self.kind and | ||||||
| # Allow __new__ or __init__ methods. | ||||||
| if existing_function.new_or_init: | ||||||
| overrides["kind"] = self.kind | ||||||
| # Future enhancement: allow custom return converters | ||||||
| overrides["return_converter"] = CReturnConverter() | ||||||
| elif not (existing_function.kind is self.kind and | ||||||
| existing_function.coexist == self.coexist): | ||||||
| # Allow __new__ or __init__ methods. | ||||||
| if existing_function.kind.new_or_init: | ||||||
| overrides["kind"] = self.kind | ||||||
| # Future enhancement: allow custom return converters | ||||||
| overrides["return_converter"] = CReturnConverter() | ||||||
| else: | ||||||
| fail("'kind' of function and cloned function don't match! " | ||||||
| "(@classmethod/@staticmethod/@coexist)") | ||||||
| fail("'kind' of function and cloned function don't match! " | ||||||
| "(@classmethod/@staticmethod/@coexist)") | ||||||
| function = existing_function.copy(**overrides) | ||||||
| self.function = function | ||||||
| self.block.signatures.append(function) | ||||||
|
|
@@ -4965,7 +4957,7 @@ def state_modulename_name(self, line: str) -> None: | |||||
| module, cls = self.clinic._module_and_class(fields) | ||||||
|
|
||||||
| self.update_function_kind(full_name) | ||||||
| if self.kind is METHOD_INIT and not return_converter: | ||||||
| if function_name == "__init__" and not return_converter: | ||||||
| return_converter = init_return_converter() | ||||||
|
|
||||||
| if not return_converter: | ||||||
|
|
@@ -5729,7 +5721,7 @@ def format_docstring_parameters(params: list[Parameter]) -> str: | |||||
| def format_docstring(self) -> str: | ||||||
| assert self.function is not None | ||||||
| f = self.function | ||||||
| if f.kind.new_or_init and not f.docstring: | ||||||
| if f.new_or_init and not f.docstring: | ||||||
| # don't render a docstring at all, no signature, nothing. | ||||||
| return f.docstring | ||||||
|
|
||||||
|
|
||||||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to be a
cached_propertyanymore; we can just assign it eagerly in__post_init__:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that's fine. But what do you think of this refactor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I did not mean to be dismissive! Thanks for the suggestion :)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, you're right; the overall concept is more important!
I think it looks good. Conceptually, I think these aren't really distinct categories of methods; they're subcategories of (respectively)
CALLABLEandCLASS_METHOD. So we're simplifying some code while also making it conceptually cleaner, which is nice.The benefit doesn't seem huge to me right now, but I'll trust you when you say that this clears the way for further refactorings :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try to formulate my motivation for doing this. I just need to find time to jot it down :)