]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-112205: Support @getter annotation from AC (gh-112396)
authorDonghee Na <donghee.na@python.org>
Thu, 30 Nov 2023 10:40:53 +0000 (10:40 +0000)
committerGitHub <noreply@github.com>
Thu, 30 Nov 2023 10:40:53 +0000 (19:40 +0900)
Lib/test/clinic.test.c
Lib/test/test_clinic.py
Modules/_io/bufferedio.c
Modules/_io/clinic/bufferedio.c.h
Tools/clinic/clinic.py

index 81f88c4d1535ce6195c626bc0019701700db2f56..ee4a4228fd28beaecd56ff19b93c69eff025d367 100644 (file)
@@ -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
index da957fcebaa2961d61e5ed3c742e4f5c89160440..f53e9481083106a0be3d3bdc5e39413db12cd06c 100644 (file)
@@ -638,7 +638,7 @@ class ClinicWholeFileTest(TestCase):
             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 ClinicParserTest(TestCase):
         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"
index 4f3786676d131f6405632d11f8db19f4de6b5ba6..679626863c385c108c669d3b64b4f191d093ad3d 100644 (file)
@@ -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}
 };
 
index 20833a10139681f175515f78a91ad4f0bdb3d7f1..69d28ad00c2ad50a62876fdc24238b80d1200c5e 100644 (file)
@@ -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 @@ skip_optional_pos:
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=e8ad39a45531d7f2 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=f21ed03255032b43 input=a9049054013a1b77]*/
index c0830864175adf9a6c33f655fdd2abe439a9e968..54962c9e1c92f96c39f590d27bdd27cd7c598328 100755 (executable)
@@ -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 @@ class CLanguage(Language):
         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 @@ class CLanguage(Language):
         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 @@ class CLanguage(Language):
         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 @@ class CLanguage(Language):
         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 @@ STATIC_METHOD: Final = FunctionKind.STATIC_METHOD
 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 @@ class Function:
             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 @@ class Py_buffer_converter(CConverter):
 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 @@ class DSLParser:
         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 @@ class DSLParser:
         _, 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