gh-112205: Support @getter annotation from AC (gh-112396)
https://github.com/python/cpython/commit/7eeea13403882af63a71226433c9a13b80c... commit: 7eeea13403882af63a71226433c9a13b80c22564 branch: main author: Donghee Na <donghee.na@python.org> committer: corona10 <donghee.na92@gmail.com> date: 2023-11-30T19:40:53+09:00 summary: gh-112205: Support @getter annotation from AC (gh-112396) files: M Lib/test/clinic.test.c M Lib/test/test_clinic.py M Modules/_io/bufferedio.c M Modules/_io/clinic/bufferedio.c.h M Tools/clinic/clinic.py diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 81f88c4d1535c..ee4a4228fd28b 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4951,6 +4951,27 @@ static PyObject * Test_meth_coexist_impl(TestObj *self) /*[clinic end generated code: output=808a293d0cd27439 input=2a1d75b5e6fec6dd]*/ +/*[clinic input] +@getter +Test.property +[clinic start generated code]*/ + +#define TEST_PROPERTY_GETTERDEF \ + {"property", (getter)Test_property_get, NULL, NULL}, + +static PyObject * +Test_property_get_impl(TestObj *self); + +static PyObject * +Test_property_get(TestObj *self, void *Py_UNUSED(context)) +{ + return Test_property_get_impl(self); +} + +static PyObject * +Test_property_get_impl(TestObj *self) +/*[clinic end generated code: output=892b6fb351ff85fd input=2d92b3449fbc7d2b]*/ + /*[clinic input] output push diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index da957fcebaa29..f53e948108310 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -638,7 +638,7 @@ class C "void *" "" C.__init__ = C.meth [clinic start generated code]*/ """ - err = "'__init__' must be a normal method, not a class or static method" + err = "'__init__' must be a normal method; got 'FunctionKind.CLASS_METHOD'!" self.expect_failure(block, err, lineno=8) def test_validate_cloned_new(self): @@ -2180,14 +2180,22 @@ class Foo "" "" self.expect_failure(block, err, lineno=2) def test_init_must_be_a_normal_method(self): - err = "'__init__' must be a normal method, not a class or static method!" - block = """ - module foo - class Foo "" "" - @classmethod - Foo.__init__ - """ - self.expect_failure(block, err, lineno=3) + err_template = "'__init__' must be a normal method; got 'FunctionKind.{}'!" + annotations = { + "@classmethod": "CLASS_METHOD", + "@staticmethod": "STATIC_METHOD", + "@getter": "GETTER", + } + for annotation, invalid_kind in annotations.items(): + with self.subTest(annotation=annotation, invalid_kind=invalid_kind): + block = f""" + module foo + class Foo "" "" + {annotation} + Foo.__init__ + """ + expected_error = err_template.format(invalid_kind) + self.expect_failure(block, expected_error, lineno=3) def test_duplicate_coexist(self): err = "Called @coexist twice" diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 4f3786676d131..679626863c385 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -10,7 +10,6 @@ #include "Python.h" #include "pycore_bytesobject.h" // _PyBytes_Join() #include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION() #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_pyerrors.h" // _Py_FatalErrorFormat() #include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() @@ -518,25 +517,20 @@ buffered_closed(buffered *self) return closed; } +/*[clinic input] +@critical_section +@getter +_io._Buffered.closed +[clinic start generated code]*/ + static PyObject * -buffered_closed_get_impl(buffered *self, void *context) +_io__Buffered_closed_get_impl(buffered *self) +/*[clinic end generated code: output=f08ce57290703a1a input=18eddefdfe4a3d2f]*/ { CHECK_INITIALIZED(self) return PyObject_GetAttr(self->raw, &_Py_ID(closed)); } -static PyObject * -buffered_closed_get(buffered *self, void *context) -{ - PyObject *return_value = NULL; - - Py_BEGIN_CRITICAL_SECTION(self); - return_value = buffered_closed_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - - return return_value; -} - /*[clinic input] @critical_section _io._Buffered.close @@ -662,44 +656,35 @@ _io__Buffered_writable_impl(buffered *self) return PyObject_CallMethodNoArgs(self->raw, &_Py_ID(writable)); } + +/*[clinic input] +@critical_section +@getter +_io._Buffered.name +[clinic start generated code]*/ + static PyObject * -buffered_name_get_impl(buffered *self, void *context) +_io__Buffered_name_get_impl(buffered *self) +/*[clinic end generated code: output=d2adf384051d3d10 input=6b84a0e6126f545e]*/ { CHECK_INITIALIZED(self) return PyObject_GetAttr(self->raw, &_Py_ID(name)); } -static PyObject * -buffered_name_get(buffered *self, void *context) -{ - PyObject *return_value = NULL; - - Py_BEGIN_CRITICAL_SECTION(self); - return_value = buffered_name_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - - return return_value; -} +/*[clinic input] +@critical_section +@getter +_io._Buffered.mode +[clinic start generated code]*/ static PyObject * -buffered_mode_get_impl(buffered *self, void *context) +_io__Buffered_mode_get_impl(buffered *self) +/*[clinic end generated code: output=0feb205748892fa4 input=0762d5e28542fd8c]*/ { CHECK_INITIALIZED(self) return PyObject_GetAttr(self->raw, &_Py_ID(mode)); } -static PyObject * -buffered_mode_get(buffered *self, void *context) -{ - PyObject *return_value = NULL; - - Py_BEGIN_CRITICAL_SECTION(self); - return_value = buffered_mode_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - - return return_value; -} - /* Lower-level APIs */ /*[clinic input] @@ -2541,9 +2526,9 @@ static PyMemberDef bufferedreader_members[] = { }; static PyGetSetDef bufferedreader_getset[] = { - {"closed", (getter)buffered_closed_get, NULL, NULL}, - {"name", (getter)buffered_name_get, NULL, NULL}, - {"mode", (getter)buffered_mode_get, NULL, NULL}, + _IO__BUFFERED_CLOSED_GETTERDEF + _IO__BUFFERED_NAME_GETTERDEF + _IO__BUFFERED_MODE_GETTERDEF {NULL} }; @@ -2601,9 +2586,9 @@ static PyMemberDef bufferedwriter_members[] = { }; static PyGetSetDef bufferedwriter_getset[] = { - {"closed", (getter)buffered_closed_get, NULL, NULL}, - {"name", (getter)buffered_name_get, NULL, NULL}, - {"mode", (getter)buffered_mode_get, NULL, NULL}, + _IO__BUFFERED_CLOSED_GETTERDEF + _IO__BUFFERED_NAME_GETTERDEF + _IO__BUFFERED_MODE_GETTERDEF {NULL} }; @@ -2719,9 +2704,9 @@ static PyMemberDef bufferedrandom_members[] = { }; static PyGetSetDef bufferedrandom_getset[] = { - {"closed", (getter)buffered_closed_get, NULL, NULL}, - {"name", (getter)buffered_name_get, NULL, NULL}, - {"mode", (getter)buffered_mode_get, NULL, NULL}, + _IO__BUFFERED_CLOSED_GETTERDEF + _IO__BUFFERED_NAME_GETTERDEF + _IO__BUFFERED_MODE_GETTERDEF {NULL} }; diff --git a/Modules/_io/clinic/bufferedio.c.h b/Modules/_io/clinic/bufferedio.c.h index 20833a1013968..69d28ad00c2ad 100644 --- a/Modules/_io/clinic/bufferedio.c.h +++ b/Modules/_io/clinic/bufferedio.c.h @@ -327,6 +327,24 @@ _io__Buffered_simple_flush(buffered *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#define _IO__BUFFERED_CLOSED_GETTERDEF \ + {"closed", (getter)_io__Buffered_closed_get, NULL, NULL}, + +static PyObject * +_io__Buffered_closed_get_impl(buffered *self); + +static PyObject * +_io__Buffered_closed_get(buffered *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io__Buffered_closed_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(_io__Buffered_close__doc__, "close($self, /)\n" "--\n" @@ -442,6 +460,42 @@ _io__Buffered_writable(buffered *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#define _IO__BUFFERED_NAME_GETTERDEF \ + {"name", (getter)_io__Buffered_name_get, NULL, NULL}, + +static PyObject * +_io__Buffered_name_get_impl(buffered *self); + +static PyObject * +_io__Buffered_name_get(buffered *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io__Buffered_name_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#define _IO__BUFFERED_MODE_GETTERDEF \ + {"mode", (getter)_io__Buffered_mode_get, NULL, NULL}, + +static PyObject * +_io__Buffered_mode_get_impl(buffered *self); + +static PyObject * +_io__Buffered_mode_get(buffered *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io__Buffered_mode_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(_io__Buffered_fileno__doc__, "fileno($self, /)\n" "--\n" @@ -1164,4 +1218,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=e8ad39a45531d7f2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f21ed03255032b43 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index c0830864175ad..54962c9e1c92f 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -846,6 +846,10 @@ class CLanguage(Language): static PyObject * {c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored)) """) + PARSER_PROTOTYPE_GETTER: Final[str] = normalize_snippet(""" + static PyObject * + {c_basename}({self_type}{self_name}, void *Py_UNUSED(context)) + """) METH_O_PROTOTYPE: Final[str] = normalize_snippet(""" static PyObject * {c_basename}({impl_parameters}) @@ -865,6 +869,10 @@ class CLanguage(Language): #define {methoddef_name} \ {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}}, """) + GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + #define {getter_name} \ + {{"{name}", (getter){c_basename}, NULL, NULL}}, + """) METHODDEF_PROTOTYPE_IFNDEF: Final[str] = normalize_snippet(""" #ifndef {methoddef_name} #define {methoddef_name} @@ -1161,6 +1169,9 @@ def output_templates( methoddef_define = self.METHODDEF_PROTOTYPE_DEFINE if new_or_init and not f.docstring: docstring_prototype = docstring_definition = '' + elif f.kind is GETTER: + methoddef_define = self.GETTERDEF_PROTOTYPE_DEFINE + docstring_prototype = docstring_definition = '' else: docstring_prototype = self.DOCSTRING_PROTOTYPE_VAR docstring_definition = self.DOCSTRING_PROTOTYPE_STRVAR @@ -1217,7 +1228,11 @@ def parser_body( parsearg: str | None if not parameters: parser_code: list[str] | None - if not requires_defining_class: + if f.kind is GETTER: + flags = "" # This should end up unused + parser_prototype = self.PARSER_PROTOTYPE_GETTER + parser_code = [] + elif not requires_defining_class: # no parameters, METH_NOARGS flags = "METH_NOARGS" parser_prototype = self.PARSER_PROTOTYPE_NOARGS @@ -1670,6 +1685,8 @@ def parser_body( methoddef_cast_end = "" if flags in ('METH_NOARGS', 'METH_O', 'METH_VARARGS'): methoddef_cast = "(PyCFunction)" + elif f.kind is GETTER: + methoddef_cast = "" # This should end up unused elif limited_capi: methoddef_cast = "(PyCFunction)(void(*)(void))" else: @@ -1927,8 +1944,12 @@ def render_function( full_name = f.full_name template_dict = {'full_name': full_name} template_dict['name'] = f.displayname - template_dict['c_basename'] = f.c_basename - template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF" + if f.kind is GETTER: + template_dict['getter_name'] = f.c_basename.upper() + "_GETTERDEF" + template_dict['c_basename'] = f.c_basename + "_get" + else: + template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF" + template_dict['c_basename'] = f.c_basename template_dict['docstring'] = self.docstring_for_c_string(f) @@ -2932,6 +2953,7 @@ class FunctionKind(enum.Enum): CLASS_METHOD = enum.auto() METHOD_INIT = enum.auto() METHOD_NEW = enum.auto() + GETTER = enum.auto() @functools.cached_property def new_or_init(self) -> bool: @@ -2947,6 +2969,7 @@ def __repr__(self) -> str: CLASS_METHOD: Final = FunctionKind.CLASS_METHOD METHOD_INIT: Final = FunctionKind.METHOD_INIT METHOD_NEW: Final = FunctionKind.METHOD_NEW +GETTER: Final = FunctionKind.GETTER ParamDict = dict[str, "Parameter"] ReturnConverterType = Callable[..., "CReturnConverter"] @@ -3033,7 +3056,8 @@ def methoddef_flags(self) -> str | None: case FunctionKind.STATIC_METHOD: flags.append('METH_STATIC') case _ as kind: - assert kind is FunctionKind.CALLABLE, f"unknown kind: {kind!r}" + acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER} + assert kind in acceptable_kinds, f"unknown kind: {kind!r}" if self.coexist: flags.append('METH_COEXIST') return '|'.join(flags) @@ -4678,7 +4702,7 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st def correct_name_for_self( f: Function ) -> tuple[str, str]: - if f.kind in (CALLABLE, METHOD_INIT): + if f.kind in {CALLABLE, METHOD_INIT, GETTER}: if f.cls: return "PyObject *", "self" return "PyObject *", "module" @@ -5310,6 +5334,9 @@ def at_critical_section(self, *args: str) -> None: self.target_critical_section.extend(args) self.critical_section = True + def at_getter(self) -> None: + self.kind = GETTER + def at_staticmethod(self) -> None: if self.kind is not CALLABLE: fail("Can't set @staticmethod, function is not a normal callable") @@ -5419,14 +5446,20 @@ def update_function_kind(self, fullname: str) -> None: _, cls = self.clinic._module_and_class(fields) if name in unsupported_special_methods: fail(f"{name!r} is a special method and cannot be converted to Argument Clinic!") + if name == '__new__': - if (self.kind is not CLASS_METHOD) or (not cls): + if (self.kind is CLASS_METHOD) and cls: + self.kind = METHOD_NEW + else: 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 + if (self.kind is CALLABLE) and cls: + self.kind = METHOD_INIT + else: + fail( + "'__init__' must be a normal method; " + f"got '{self.kind}'!" + ) def state_modulename_name(self, line: str) -> None: # looking for declaration, which establishes the leftmost column
participants (1)
-
corona10