gh-112205: Support @setter annotation from AC (gh-112922)

---------

Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Donghee Na 2023-12-13 14:00:34 +00:00 committed by GitHub
parent 9263173280
commit 498a096a51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 262 additions and 64 deletions

View file

@ -4956,8 +4956,12 @@ Test_meth_coexist_impl(TestObj *self)
Test.property Test.property
[clinic start generated code]*/ [clinic start generated code]*/
#define TEST_PROPERTY_GETTERDEF \ #if defined(TEST_PROPERTY_GETSETDEF)
{"property", (getter)Test_property_get, NULL, NULL}, # 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 * static PyObject *
Test_property_get_impl(TestObj *self); Test_property_get_impl(TestObj *self);
@ -4970,8 +4974,32 @@ Test_property_get(TestObj *self, void *Py_UNUSED(context))
static PyObject * static PyObject *
Test_property_get_impl(TestObj *self) 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] /*[clinic input]
output push output push

View file

@ -2197,6 +2197,58 @@ class ClinicParserTest(TestCase):
expected_error = err_template.format(invalid_kind) expected_error = err_template.format(invalid_kind)
self.expect_failure(block, expected_error, lineno=3) 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): def test_duplicate_coexist(self):
err = "Called @coexist twice" err = "Called @coexist twice"
block = """ block = """

View file

@ -2526,9 +2526,9 @@ static PyMemberDef bufferedreader_members[] = {
}; };
static PyGetSetDef bufferedreader_getset[] = { static PyGetSetDef bufferedreader_getset[] = {
_IO__BUFFERED_CLOSED_GETTERDEF _IO__BUFFERED_CLOSED_GETSETDEF
_IO__BUFFERED_NAME_GETTERDEF _IO__BUFFERED_NAME_GETSETDEF
_IO__BUFFERED_MODE_GETTERDEF _IO__BUFFERED_MODE_GETSETDEF
{NULL} {NULL}
}; };
@ -2586,9 +2586,9 @@ static PyMemberDef bufferedwriter_members[] = {
}; };
static PyGetSetDef bufferedwriter_getset[] = { static PyGetSetDef bufferedwriter_getset[] = {
_IO__BUFFERED_CLOSED_GETTERDEF _IO__BUFFERED_CLOSED_GETSETDEF
_IO__BUFFERED_NAME_GETTERDEF _IO__BUFFERED_NAME_GETSETDEF
_IO__BUFFERED_MODE_GETTERDEF _IO__BUFFERED_MODE_GETSETDEF
{NULL} {NULL}
}; };
@ -2704,9 +2704,9 @@ static PyMemberDef bufferedrandom_members[] = {
}; };
static PyGetSetDef bufferedrandom_getset[] = { static PyGetSetDef bufferedrandom_getset[] = {
_IO__BUFFERED_CLOSED_GETTERDEF _IO__BUFFERED_CLOSED_GETSETDEF
_IO__BUFFERED_NAME_GETTERDEF _IO__BUFFERED_NAME_GETSETDEF
_IO__BUFFERED_MODE_GETTERDEF _IO__BUFFERED_MODE_GETSETDEF
{NULL} {NULL}
}; };

View file

@ -327,8 +327,12 @@ _io__Buffered_simple_flush(buffered *self, PyObject *Py_UNUSED(ignored))
return return_value; return return_value;
} }
#define _IO__BUFFERED_CLOSED_GETTERDEF \ #if defined(_IO__BUFFERED_CLOSED_GETSETDEF)
{"closed", (getter)_io__Buffered_closed_get, NULL, NULL}, # 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 * static PyObject *
_io__Buffered_closed_get_impl(buffered *self); _io__Buffered_closed_get_impl(buffered *self);
@ -460,8 +464,12 @@ _io__Buffered_writable(buffered *self, PyObject *Py_UNUSED(ignored))
return return_value; return return_value;
} }
#define _IO__BUFFERED_NAME_GETTERDEF \ #if defined(_IO__BUFFERED_NAME_GETSETDEF)
{"name", (getter)_io__Buffered_name_get, NULL, NULL}, # 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 * static PyObject *
_io__Buffered_name_get_impl(buffered *self); _io__Buffered_name_get_impl(buffered *self);
@ -478,8 +486,12 @@ _io__Buffered_name_get(buffered *self, void *Py_UNUSED(context))
return return_value; return return_value;
} }
#define _IO__BUFFERED_MODE_GETTERDEF \ #if defined(_IO__BUFFERED_MODE_GETSETDEF)
{"mode", (getter)_io__Buffered_mode_get, NULL, NULL}, # 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 * static PyObject *
_io__Buffered_mode_get_impl(buffered *self); _io__Buffered_mode_get_impl(buffered *self);
@ -1218,4 +1230,4 @@ skip_optional_pos:
exit: exit:
return return_value; return return_value;
} }
/*[clinic end generated code: output=f21ed03255032b43 input=a9049054013a1b77]*/ /*[clinic end generated code: output=0999c33f666dc692 input=a9049054013a1b77]*/

View file

@ -475,8 +475,12 @@ _io_StringIO___setstate__(stringio *self, PyObject *state)
return return_value; return return_value;
} }
#define _IO_STRINGIO_CLOSED_GETTERDEF \ #if defined(_IO_STRINGIO_CLOSED_GETSETDEF)
{"closed", (getter)_io_StringIO_closed_get, NULL, NULL}, # 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 * static PyObject *
_io_StringIO_closed_get_impl(stringio *self); _io_StringIO_closed_get_impl(stringio *self);
@ -493,8 +497,12 @@ _io_StringIO_closed_get(stringio *self, void *Py_UNUSED(context))
return return_value; return return_value;
} }
#define _IO_STRINGIO_LINE_BUFFERING_GETTERDEF \ #if defined(_IO_STRINGIO_LINE_BUFFERING_GETSETDEF)
{"line_buffering", (getter)_io_StringIO_line_buffering_get, NULL, NULL}, # 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 * static PyObject *
_io_StringIO_line_buffering_get_impl(stringio *self); _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; return return_value;
} }
#define _IO_STRINGIO_NEWLINES_GETTERDEF \ #if defined(_IO_STRINGIO_NEWLINES_GETSETDEF)
{"newlines", (getter)_io_StringIO_newlines_get, NULL, NULL}, # 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 * static PyObject *
_io_StringIO_newlines_get_impl(stringio *self); _io_StringIO_newlines_get_impl(stringio *self);
@ -528,4 +540,4 @@ _io_StringIO_newlines_get(stringio *self, void *Py_UNUSED(context))
return return_value; return return_value;
} }
/*[clinic end generated code: output=3a92e8b6c322f61b input=a9049054013a1b77]*/ /*[clinic end generated code: output=27726751d98ab617 input=a9049054013a1b77]*/

View file

@ -1047,4 +1047,48 @@ _io_TextIOWrapper_close(textio *self, PyObject *Py_UNUSED(ignored))
return return_value; 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]*/

View file

@ -1037,15 +1037,15 @@ static struct PyMethodDef stringio_methods[] = {
}; };
static PyGetSetDef stringio_getset[] = { static PyGetSetDef stringio_getset[] = {
_IO_STRINGIO_CLOSED_GETTERDEF _IO_STRINGIO_CLOSED_GETSETDEF
_IO_STRINGIO_NEWLINES_GETTERDEF _IO_STRINGIO_NEWLINES_GETSETDEF
/* (following comments straight off of the original Python wrapper:) /* (following comments straight off of the original Python wrapper:)
XXX Cruft to support the TextIOWrapper API. This would only XXX Cruft to support the TextIOWrapper API. This would only
be meaningful if StringIO supported the buffer attribute. be meaningful if StringIO supported the buffer attribute.
Hopefully, a better solution, than adding these pseudo-attributes, Hopefully, a better solution, than adding these pseudo-attributes,
will be found. will be found.
*/ */
_IO_STRINGIO_LINE_BUFFERING_GETTERDEF _IO_STRINGIO_LINE_BUFFERING_GETSETDEF
{NULL} {NULL}
}; };

View file

@ -3238,33 +3238,37 @@ textiowrapper_errors_get(textio *self, void *context)
return result; return result;
} }
/*[clinic input]
@critical_section
@getter
_io.TextIOWrapper._CHUNK_SIZE
[clinic start generated code]*/
static PyObject * 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); CHECK_ATTACHED(self);
return PyLong_FromSsize_t(self->chunk_size); return PyLong_FromSsize_t(self->chunk_size);
} }
static PyObject * /*[clinic input]
textiowrapper_chunk_size_get(textio *self, void *context) @critical_section
{ @setter
PyObject *result = NULL; _io.TextIOWrapper._CHUNK_SIZE
Py_BEGIN_CRITICAL_SECTION(self); [clinic start generated code]*/
result = textiowrapper_chunk_size_get_impl(self, context);
Py_END_CRITICAL_SECTION();
return result;
}
static int 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; Py_ssize_t n;
CHECK_ATTACHED_INT(self); CHECK_ATTACHED_INT(self);
if (arg == NULL) { if (value == NULL) {
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
return -1; return -1;
} }
n = PyNumber_AsSsize_t(arg, PyExc_ValueError); n = PyNumber_AsSsize_t(value, PyExc_ValueError);
if (n == -1 && PyErr_Occurred()) if (n == -1 && PyErr_Occurred())
return -1; return -1;
if (n <= 0) { if (n <= 0) {
@ -3276,16 +3280,6 @@ textiowrapper_chunk_size_set_impl(textio *self, PyObject *arg, void *context)
return 0; 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[] = { static PyMethodDef incrementalnewlinedecoder_methods[] = {
_IO_INCREMENTALNEWLINEDECODER_DECODE_METHODDEF _IO_INCREMENTALNEWLINEDECODER_DECODE_METHODDEF
_IO_INCREMENTALNEWLINEDECODER_GETSTATE_METHODDEF _IO_INCREMENTALNEWLINEDECODER_GETSTATE_METHODDEF
@ -3361,8 +3355,7 @@ static PyGetSetDef textiowrapper_getset[] = {
*/ */
{"newlines", (getter)textiowrapper_newlines_get, NULL, NULL}, {"newlines", (getter)textiowrapper_newlines_get, NULL, NULL},
{"errors", (getter)textiowrapper_errors_get, NULL, NULL}, {"errors", (getter)textiowrapper_errors_get, NULL, NULL},
{"_CHUNK_SIZE", (getter)textiowrapper_chunk_size_get, _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF
(setter)textiowrapper_chunk_size_set, NULL},
{NULL} {NULL}
}; };

View file

@ -850,6 +850,10 @@ class CLanguage(Language):
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, void *Py_UNUSED(context)) {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(""" METH_O_PROTOTYPE: Final[str] = normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({impl_parameters}) {c_basename}({impl_parameters})
@ -870,8 +874,20 @@ class CLanguage(Language):
{{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}}, {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}},
""") """)
GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r"""
#define {getter_name} \ #if defined({getset_name}_GETSETDEF)
{{"{name}", (getter){c_basename}, NULL, NULL}}, # 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(""" METHODDEF_PROTOTYPE_IFNDEF: Final[str] = normalize_snippet("""
#ifndef {methoddef_name} #ifndef {methoddef_name}
@ -1172,6 +1188,10 @@ class CLanguage(Language):
elif f.kind is GETTER: elif f.kind is GETTER:
methoddef_define = self.GETTERDEF_PROTOTYPE_DEFINE methoddef_define = self.GETTERDEF_PROTOTYPE_DEFINE
docstring_prototype = docstring_definition = '' 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: else:
docstring_prototype = self.DOCSTRING_PROTOTYPE_VAR docstring_prototype = self.DOCSTRING_PROTOTYPE_VAR
docstring_definition = self.DOCSTRING_PROTOTYPE_STRVAR docstring_definition = self.DOCSTRING_PROTOTYPE_STRVAR
@ -1226,12 +1246,19 @@ class CLanguage(Language):
limited_capi = False limited_capi = False
parsearg: str | None parsearg: str | None
if f.kind in {GETTER, SETTER} and parameters:
fail(f"@{f.kind.name.lower()} method cannot define parameters")
if not parameters: if not parameters:
parser_code: list[str] | None parser_code: list[str] | None
if f.kind is GETTER: if f.kind is GETTER:
flags = "" # This should end up unused flags = "" # This should end up unused
parser_prototype = self.PARSER_PROTOTYPE_GETTER parser_prototype = self.PARSER_PROTOTYPE_GETTER
parser_code = [] parser_code = []
elif f.kind is SETTER:
flags = ""
parser_prototype = self.PARSER_PROTOTYPE_SETTER
parser_code = []
elif not requires_defining_class: elif not requires_defining_class:
# no parameters, METH_NOARGS # no parameters, METH_NOARGS
flags = "METH_NOARGS" flags = "METH_NOARGS"
@ -1944,9 +1971,16 @@ class CLanguage(Language):
full_name = f.full_name full_name = f.full_name
template_dict = {'full_name': full_name} template_dict = {'full_name': full_name}
template_dict['name'] = f.displayname template_dict['name'] = f.displayname
if f.kind is GETTER: if f.kind in {GETTER, SETTER}:
template_dict['getter_name'] = f.c_basename.upper() + "_GETTERDEF" template_dict['getset_name'] = f.c_basename.upper()
template_dict['c_basename'] = f.c_basename + "_get" 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: else:
template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF" template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF"
template_dict['c_basename'] = f.c_basename template_dict['c_basename'] = f.c_basename
@ -1959,7 +1993,11 @@ class CLanguage(Language):
converter.set_template_dict(template_dict) converter.set_template_dict(template_dict)
f.return_converter.render(f, data) 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['declarations'] = format_escape("\n".join(data.declarations))
template_dict['initializers'] = "\n\n".join(data.initializers) template_dict['initializers'] = "\n\n".join(data.initializers)
@ -2954,6 +2992,7 @@ class FunctionKind(enum.Enum):
METHOD_INIT = enum.auto() METHOD_INIT = enum.auto()
METHOD_NEW = enum.auto() METHOD_NEW = enum.auto()
GETTER = enum.auto() GETTER = enum.auto()
SETTER = enum.auto()
@functools.cached_property @functools.cached_property
def new_or_init(self) -> bool: def new_or_init(self) -> bool:
@ -2970,6 +3009,7 @@ CLASS_METHOD: Final = FunctionKind.CLASS_METHOD
METHOD_INIT: Final = FunctionKind.METHOD_INIT METHOD_INIT: Final = FunctionKind.METHOD_INIT
METHOD_NEW: Final = FunctionKind.METHOD_NEW METHOD_NEW: Final = FunctionKind.METHOD_NEW
GETTER: Final = FunctionKind.GETTER GETTER: Final = FunctionKind.GETTER
SETTER: Final = FunctionKind.SETTER
ParamDict = dict[str, "Parameter"] ParamDict = dict[str, "Parameter"]
ReturnConverterType = Callable[..., "CReturnConverter"] ReturnConverterType = Callable[..., "CReturnConverter"]
@ -3056,7 +3096,7 @@ class Function:
case FunctionKind.STATIC_METHOD: case FunctionKind.STATIC_METHOD:
flags.append('METH_STATIC') flags.append('METH_STATIC')
case _ as kind: 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}" assert kind in acceptable_kinds, f"unknown kind: {kind!r}"
if self.coexist: if self.coexist:
flags.append('METH_COEXIST') flags.append('METH_COEXIST')
@ -4702,7 +4742,7 @@ class Py_buffer_converter(CConverter):
def correct_name_for_self( def correct_name_for_self(
f: Function f: Function
) -> tuple[str, str]: ) -> tuple[str, str]:
if f.kind in {CALLABLE, METHOD_INIT, GETTER}: if f.kind in {CALLABLE, METHOD_INIT, GETTER, SETTER}:
if f.cls: if f.cls:
return "PyObject *", "self" return "PyObject *", "self"
return "PyObject *", "module" return "PyObject *", "module"
@ -5335,7 +5375,22 @@ class DSLParser:
self.critical_section = True self.critical_section = True
def at_getter(self) -> None: 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: def at_staticmethod(self) -> None:
if self.kind is not CALLABLE: if self.kind is not CALLABLE:
@ -5536,6 +5591,8 @@ class DSLParser:
return_converter = None return_converter = None
if returns: 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" ast_input = f"def x() -> {returns}: pass"
try: try:
module_node = ast.parse(ast_input) module_node = ast.parse(ast_input)