diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index ee4a4228fd28bea..a6a21664bb82a1a 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4956,8 +4956,12 @@ Test_meth_coexist_impl(TestObj *self) Test.property [clinic start generated code]*/ -#define TEST_PROPERTY_GETTERDEF \ - {"property", (getter)Test_property_get, NULL, NULL}, +#if defined(TEST_PROPERTY_GETSETDEF) +# undef TEST_PROPERTY_GETSETDEF +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, (setter)Test_property_set, NULL}, +#else +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, NULL, NULL}, +#endif static PyObject * Test_property_get_impl(TestObj *self); @@ -4970,8 +4974,32 @@ Test_property_get(TestObj *self, void *Py_UNUSED(context)) static PyObject * Test_property_get_impl(TestObj *self) -/*[clinic end generated code: output=892b6fb351ff85fd input=2d92b3449fbc7d2b]*/ +/*[clinic end generated code: output=af8140b692e0e2f1 input=2d92b3449fbc7d2b]*/ + +/*[clinic input] +@setter +Test.property +[clinic start generated code]*/ + +#if defined(TEST_PROPERTY_GETSETDEF) +# undef TEST_PROPERTY_GETSETDEF +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, (setter)Test_property_set, NULL}, +#else +# define TEST_PROPERTY_GETSETDEF {"property", NULL, (setter)Test_property_set, NULL}, +#endif + +static int +Test_property_set_impl(TestObj *self, PyObject *value); + +static int +Test_property_set(TestObj *self, PyObject *value, void *Py_UNUSED(context)) +{ + return Test_property_set_impl(self, value); +} +static int +Test_property_set_impl(TestObj *self, PyObject *value) +/*[clinic end generated code: output=f3eba6487d7550e2 input=3bc3f46a23c83a88]*/ /*[clinic input] output push diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f53e9481083106a..d3dbde88dd82a9e 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -2197,6 +2197,58 @@ class Foo "" "" expected_error = err_template.format(invalid_kind) self.expect_failure(block, expected_error, lineno=3) + def test_invalid_getset(self): + annotations = ["@getter", "@setter"] + for annotation in annotations: + with self.subTest(annotation=annotation): + block = f""" + module foo + class Foo "" "" + {annotation} + Foo.property -> int + """ + expected_error = f"{annotation} method cannot define a return type" + self.expect_failure(block, expected_error, lineno=3) + + block = f""" + module foo + class Foo "" "" + {annotation} + Foo.property + obj: int + / + """ + expected_error = f"{annotation} method cannot define parameters" + self.expect_failure(block, expected_error) + + def test_duplicate_getset(self): + annotations = ["@getter", "@setter"] + for annotation in annotations: + with self.subTest(annotation=annotation): + block = f""" + module foo + class Foo "" "" + {annotation} + {annotation} + Foo.property -> int + """ + expected_error = f"Cannot apply {annotation} twice to the same function!" + self.expect_failure(block, expected_error, lineno=3) + + def test_getter_and_setter_disallowed_on_same_function(self): + dup_annotations = [("@getter", "@setter"), ("@setter", "@getter")] + for dup in dup_annotations: + with self.subTest(dup=dup): + block = f""" + module foo + class Foo "" "" + {dup[0]} + {dup[1]} + Foo.property -> int + """ + expected_error = "Cannot apply both @getter and @setter to the same function!" + self.expect_failure(block, expected_error, lineno=3) + def test_duplicate_coexist(self): err = "Called @coexist twice" block = """ diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 679626863c385c1..f02207ace9f3d26 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -2526,9 +2526,9 @@ static PyMemberDef bufferedreader_members[] = { }; static PyGetSetDef bufferedreader_getset[] = { - _IO__BUFFERED_CLOSED_GETTERDEF - _IO__BUFFERED_NAME_GETTERDEF - _IO__BUFFERED_MODE_GETTERDEF + _IO__BUFFERED_CLOSED_GETSETDEF + _IO__BUFFERED_NAME_GETSETDEF + _IO__BUFFERED_MODE_GETSETDEF {NULL} }; @@ -2586,9 +2586,9 @@ static PyMemberDef bufferedwriter_members[] = { }; static PyGetSetDef bufferedwriter_getset[] = { - _IO__BUFFERED_CLOSED_GETTERDEF - _IO__BUFFERED_NAME_GETTERDEF - _IO__BUFFERED_MODE_GETTERDEF + _IO__BUFFERED_CLOSED_GETSETDEF + _IO__BUFFERED_NAME_GETSETDEF + _IO__BUFFERED_MODE_GETSETDEF {NULL} }; @@ -2704,9 +2704,9 @@ static PyMemberDef bufferedrandom_members[] = { }; static PyGetSetDef bufferedrandom_getset[] = { - _IO__BUFFERED_CLOSED_GETTERDEF - _IO__BUFFERED_NAME_GETTERDEF - _IO__BUFFERED_MODE_GETTERDEF + _IO__BUFFERED_CLOSED_GETSETDEF + _IO__BUFFERED_NAME_GETSETDEF + _IO__BUFFERED_MODE_GETSETDEF {NULL} }; diff --git a/Modules/_io/clinic/bufferedio.c.h b/Modules/_io/clinic/bufferedio.c.h index 69d28ad00c2ad50..ec46d5409a3d824 100644 --- a/Modules/_io/clinic/bufferedio.c.h +++ b/Modules/_io/clinic/bufferedio.c.h @@ -327,8 +327,12 @@ _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}, +#if defined(_IO__BUFFERED_CLOSED_GETSETDEF) +# undef _IO__BUFFERED_CLOSED_GETSETDEF +# define _IO__BUFFERED_CLOSED_GETSETDEF {"closed", (getter)_io__Buffered_closed_get, (setter)_io__Buffered_closed_set, NULL}, +#else +# define _IO__BUFFERED_CLOSED_GETSETDEF {"closed", (getter)_io__Buffered_closed_get, NULL, NULL}, +#endif static PyObject * _io__Buffered_closed_get_impl(buffered *self); @@ -460,8 +464,12 @@ _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}, +#if defined(_IO__BUFFERED_NAME_GETSETDEF) +# undef _IO__BUFFERED_NAME_GETSETDEF +# define _IO__BUFFERED_NAME_GETSETDEF {"name", (getter)_io__Buffered_name_get, (setter)_io__Buffered_name_set, NULL}, +#else +# define _IO__BUFFERED_NAME_GETSETDEF {"name", (getter)_io__Buffered_name_get, NULL, NULL}, +#endif static PyObject * _io__Buffered_name_get_impl(buffered *self); @@ -478,8 +486,12 @@ _io__Buffered_name_get(buffered *self, void *Py_UNUSED(context)) return return_value; } -#define _IO__BUFFERED_MODE_GETTERDEF \ - {"mode", (getter)_io__Buffered_mode_get, NULL, NULL}, +#if defined(_IO__BUFFERED_MODE_GETSETDEF) +# undef _IO__BUFFERED_MODE_GETSETDEF +# define _IO__BUFFERED_MODE_GETSETDEF {"mode", (getter)_io__Buffered_mode_get, (setter)_io__Buffered_mode_set, NULL}, +#else +# define _IO__BUFFERED_MODE_GETSETDEF {"mode", (getter)_io__Buffered_mode_get, NULL, NULL}, +#endif static PyObject * _io__Buffered_mode_get_impl(buffered *self); @@ -1218,4 +1230,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=f21ed03255032b43 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0999c33f666dc692 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/stringio.c.h b/Modules/_io/clinic/stringio.c.h index ed505ae67589a89..fc2962d1c9c9a70 100644 --- a/Modules/_io/clinic/stringio.c.h +++ b/Modules/_io/clinic/stringio.c.h @@ -475,8 +475,12 @@ _io_StringIO___setstate__(stringio *self, PyObject *state) return return_value; } -#define _IO_STRINGIO_CLOSED_GETTERDEF \ - {"closed", (getter)_io_StringIO_closed_get, NULL, NULL}, +#if defined(_IO_STRINGIO_CLOSED_GETSETDEF) +# undef _IO_STRINGIO_CLOSED_GETSETDEF +# define _IO_STRINGIO_CLOSED_GETSETDEF {"closed", (getter)_io_StringIO_closed_get, (setter)_io_StringIO_closed_set, NULL}, +#else +# define _IO_STRINGIO_CLOSED_GETSETDEF {"closed", (getter)_io_StringIO_closed_get, NULL, NULL}, +#endif static PyObject * _io_StringIO_closed_get_impl(stringio *self); @@ -493,8 +497,12 @@ _io_StringIO_closed_get(stringio *self, void *Py_UNUSED(context)) return return_value; } -#define _IO_STRINGIO_LINE_BUFFERING_GETTERDEF \ - {"line_buffering", (getter)_io_StringIO_line_buffering_get, NULL, NULL}, +#if defined(_IO_STRINGIO_LINE_BUFFERING_GETSETDEF) +# undef _IO_STRINGIO_LINE_BUFFERING_GETSETDEF +# define _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {"line_buffering", (getter)_io_StringIO_line_buffering_get, (setter)_io_StringIO_line_buffering_set, NULL}, +#else +# define _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {"line_buffering", (getter)_io_StringIO_line_buffering_get, NULL, NULL}, +#endif static PyObject * _io_StringIO_line_buffering_get_impl(stringio *self); @@ -511,8 +519,12 @@ _io_StringIO_line_buffering_get(stringio *self, void *Py_UNUSED(context)) return return_value; } -#define _IO_STRINGIO_NEWLINES_GETTERDEF \ - {"newlines", (getter)_io_StringIO_newlines_get, NULL, NULL}, +#if defined(_IO_STRINGIO_NEWLINES_GETSETDEF) +# undef _IO_STRINGIO_NEWLINES_GETSETDEF +# define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines", (getter)_io_StringIO_newlines_get, (setter)_io_StringIO_newlines_set, NULL}, +#else +# define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines", (getter)_io_StringIO_newlines_get, NULL, NULL}, +#endif static PyObject * _io_StringIO_newlines_get_impl(stringio *self); @@ -528,4 +540,4 @@ _io_StringIO_newlines_get(stringio *self, void *Py_UNUSED(context)) return return_value; } -/*[clinic end generated code: output=3a92e8b6c322f61b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=27726751d98ab617 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index 675e0ed2eab75e1..a492f340c74c0dd 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -1047,4 +1047,48 @@ _io_TextIOWrapper_close(textio *self, PyObject *Py_UNUSED(ignored)) return return_value; } -/*[clinic end generated code: output=8781a91be6d99e2c input=a9049054013a1b77]*/ + +#if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF) +# undef _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper__CHUNK_SIZE_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper__CHUNK_SIZE_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper__CHUNK_SIZE_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF) +# undef _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", NULL, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, +#endif + +static int +_io_TextIOWrapper__CHUNK_SIZE_set_impl(textio *self, PyObject *value); + +static int +_io_TextIOWrapper__CHUNK_SIZE_set(textio *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper__CHUNK_SIZE_set_impl(self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} +/*[clinic end generated code: output=b312f2d2e2221580 input=a9049054013a1b77]*/ diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 74dcee237303069..06bc2679e8e2276 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -1037,15 +1037,15 @@ static struct PyMethodDef stringio_methods[] = { }; static PyGetSetDef stringio_getset[] = { - _IO_STRINGIO_CLOSED_GETTERDEF - _IO_STRINGIO_NEWLINES_GETTERDEF + _IO_STRINGIO_CLOSED_GETSETDEF + _IO_STRINGIO_NEWLINES_GETSETDEF /* (following comments straight off of the original Python wrapper:) XXX Cruft to support the TextIOWrapper API. This would only be meaningful if StringIO supported the buffer attribute. Hopefully, a better solution, than adding these pseudo-attributes, will be found. */ - _IO_STRINGIO_LINE_BUFFERING_GETTERDEF + _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {NULL} }; diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 545f467b7f0257b..c76d92cdd38b9a7 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -3238,33 +3238,37 @@ textiowrapper_errors_get(textio *self, void *context) return result; } +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper._CHUNK_SIZE +[clinic start generated code]*/ + static PyObject * -textiowrapper_chunk_size_get_impl(textio *self, void *context) +_io_TextIOWrapper__CHUNK_SIZE_get_impl(textio *self) +/*[clinic end generated code: output=039925cd2df375bc input=e9715b0e06ff0fa6]*/ { CHECK_ATTACHED(self); return PyLong_FromSsize_t(self->chunk_size); } -static PyObject * -textiowrapper_chunk_size_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_chunk_size_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@setter +_io.TextIOWrapper._CHUNK_SIZE +[clinic start generated code]*/ static int -textiowrapper_chunk_size_set_impl(textio *self, PyObject *arg, void *context) +_io_TextIOWrapper__CHUNK_SIZE_set_impl(textio *self, PyObject *value) +/*[clinic end generated code: output=edb86d2db660a5ab input=32fc99861db02a0a]*/ { Py_ssize_t n; CHECK_ATTACHED_INT(self); - if (arg == NULL) { + if (value == NULL) { PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); return -1; } - n = PyNumber_AsSsize_t(arg, PyExc_ValueError); + n = PyNumber_AsSsize_t(value, PyExc_ValueError); if (n == -1 && PyErr_Occurred()) return -1; if (n <= 0) { @@ -3276,16 +3280,6 @@ textiowrapper_chunk_size_set_impl(textio *self, PyObject *arg, void *context) return 0; } -static int -textiowrapper_chunk_size_set(textio *self, PyObject *arg, void *context) -{ - int result = 0; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_chunk_size_set_impl(self, arg, context); - Py_END_CRITICAL_SECTION(); - return result; -} - static PyMethodDef incrementalnewlinedecoder_methods[] = { _IO_INCREMENTALNEWLINEDECODER_DECODE_METHODDEF _IO_INCREMENTALNEWLINEDECODER_GETSTATE_METHODDEF @@ -3361,8 +3355,7 @@ static PyGetSetDef textiowrapper_getset[] = { */ {"newlines", (getter)textiowrapper_newlines_get, NULL, NULL}, {"errors", (getter)textiowrapper_errors_get, NULL, NULL}, - {"_CHUNK_SIZE", (getter)textiowrapper_chunk_size_get, - (setter)textiowrapper_chunk_size_set, NULL}, + _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {NULL} }; diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 816ce0e6efed610..5ec088765f3e01a 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -850,6 +850,10 @@ class CLanguage(Language): static PyObject * {c_basename}({self_type}{self_name}, void *Py_UNUSED(context)) """) + PARSER_PROTOTYPE_SETTER: Final[str] = normalize_snippet(""" + static int + {c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context)) + """) METH_O_PROTOTYPE: Final[str] = normalize_snippet(""" static PyObject * {c_basename}({impl_parameters}) @@ -870,8 +874,20 @@ class CLanguage(Language): {{"{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}}, + #if defined({getset_name}_GETSETDEF) + # undef {getset_name}_GETSETDEF + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, NULL}}, + #else + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, NULL}}, + #endif + """) + SETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + #if defined({getset_name}_GETSETDEF) + # undef {getset_name}_GETSETDEF + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, NULL}}, + #else + # define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}}, + #endif """) METHODDEF_PROTOTYPE_IFNDEF: Final[str] = normalize_snippet(""" #ifndef {methoddef_name} @@ -1172,6 +1188,10 @@ def output_templates( elif f.kind is GETTER: methoddef_define = self.GETTERDEF_PROTOTYPE_DEFINE docstring_prototype = docstring_definition = '' + elif f.kind is SETTER: + return_value_declaration = "int {return_value};" + methoddef_define = self.SETTERDEF_PROTOTYPE_DEFINE + docstring_prototype = docstring_prototype = docstring_definition = '' else: docstring_prototype = self.DOCSTRING_PROTOTYPE_VAR docstring_definition = self.DOCSTRING_PROTOTYPE_STRVAR @@ -1226,12 +1246,19 @@ def parser_body( limited_capi = False parsearg: str | None + if f.kind in {GETTER, SETTER} and parameters: + fail(f"@{f.kind.name.lower()} method cannot define parameters") + if not parameters: parser_code: list[str] | None if f.kind is GETTER: flags = "" # This should end up unused parser_prototype = self.PARSER_PROTOTYPE_GETTER parser_code = [] + elif f.kind is SETTER: + flags = "" + parser_prototype = self.PARSER_PROTOTYPE_SETTER + parser_code = [] elif not requires_defining_class: # no parameters, METH_NOARGS flags = "METH_NOARGS" @@ -1944,9 +1971,16 @@ def render_function( full_name = f.full_name template_dict = {'full_name': full_name} template_dict['name'] = f.displayname - if f.kind is GETTER: - template_dict['getter_name'] = f.c_basename.upper() + "_GETTERDEF" - template_dict['c_basename'] = f.c_basename + "_get" + if f.kind in {GETTER, SETTER}: + template_dict['getset_name'] = f.c_basename.upper() + template_dict['getset_basename'] = f.c_basename + if f.kind is GETTER: + template_dict['c_basename'] = f.c_basename + "_get" + elif f.kind is SETTER: + template_dict['c_basename'] = f.c_basename + "_set" + # Implicitly add the setter value parameter. + data.impl_parameters.append("PyObject *value") + data.impl_arguments.append("value") else: template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF" template_dict['c_basename'] = f.c_basename @@ -1959,7 +1993,11 @@ def render_function( converter.set_template_dict(template_dict) f.return_converter.render(f, data) - template_dict['impl_return_type'] = f.return_converter.type + if f.kind is SETTER: + # All setters return an int. + template_dict['impl_return_type'] = 'int' + else: + template_dict['impl_return_type'] = f.return_converter.type template_dict['declarations'] = format_escape("\n".join(data.declarations)) template_dict['initializers'] = "\n\n".join(data.initializers) @@ -2954,6 +2992,7 @@ class FunctionKind(enum.Enum): METHOD_INIT = enum.auto() METHOD_NEW = enum.auto() GETTER = enum.auto() + SETTER = enum.auto() @functools.cached_property def new_or_init(self) -> bool: @@ -2970,6 +3009,7 @@ def __repr__(self) -> str: METHOD_INIT: Final = FunctionKind.METHOD_INIT METHOD_NEW: Final = FunctionKind.METHOD_NEW GETTER: Final = FunctionKind.GETTER +SETTER: Final = FunctionKind.SETTER ParamDict = dict[str, "Parameter"] ReturnConverterType = Callable[..., "CReturnConverter"] @@ -3056,7 +3096,7 @@ def methoddef_flags(self) -> str | None: case FunctionKind.STATIC_METHOD: flags.append('METH_STATIC') case _ as kind: - acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER} + acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER} assert kind in acceptable_kinds, f"unknown kind: {kind!r}" if self.coexist: flags.append('METH_COEXIST') @@ -4702,7 +4742,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, GETTER}: + if f.kind in {CALLABLE, METHOD_INIT, GETTER, SETTER}: if f.cls: return "PyObject *", "self" return "PyObject *", "module" @@ -5335,7 +5375,22 @@ def at_critical_section(self, *args: str) -> None: self.critical_section = True def at_getter(self) -> None: - self.kind = GETTER + match self.kind: + case FunctionKind.GETTER: + fail("Cannot apply @getter twice to the same function!") + case FunctionKind.SETTER: + fail("Cannot apply both @getter and @setter to the same function!") + case _: + self.kind = FunctionKind.GETTER + + def at_setter(self) -> None: + match self.kind: + case FunctionKind.SETTER: + fail("Cannot apply @setter twice to the same function!") + case FunctionKind.GETTER: + fail("Cannot apply both @getter and @setter to the same function!") + case _: + self.kind = FunctionKind.SETTER def at_staticmethod(self) -> None: if self.kind is not CALLABLE: @@ -5536,6 +5591,8 @@ def state_modulename_name(self, line: str) -> None: return_converter = None if returns: + if self.kind in {GETTER, SETTER}: + fail(f"@{self.kind.name.lower()} method cannot define a return type") ast_input = f"def x() -> {returns}: pass" try: module_node = ast.parse(ast_input)