diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 6d3c42ddbd03fe..528e8836973530 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5303,6 +5303,12 @@ Test_property_set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'property' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } return_value = Test_property_set_impl((TestObj *)self, value); return return_value; @@ -5310,7 +5316,40 @@ Test_property_set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) static int Test_property_set_impl(TestObj *self, PyObject *value) -/*[clinic end generated code: output=49f925ab2a33b637 input=3bc3f46a23c83a88]*/ +/*[clinic end generated code: output=ec103a151cf51d25 input=3bc3f46a23c83a88]*/ + +/*[clinic input] +@setter +@deleter +Test.settable_and_deletable +[clinic start generated code]*/ + +#if !defined(Test_settable_and_deletable_DOCSTR) +# define Test_settable_and_deletable_DOCSTR NULL +#endif +#if defined(TEST_SETTABLE_AND_DELETABLE_GETSETDEF) +# undef TEST_SETTABLE_AND_DELETABLE_GETSETDEF +# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", (getter)Test_settable_and_deletable_get, (setter)Test_settable_and_deletable_set, Test_settable_and_deletable_DOCSTR}, +#else +# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", NULL, (setter)Test_settable_and_deletable_set, NULL}, +#endif + +static int +Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value); + +static int +Test_settable_and_deletable_set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + return_value = Test_settable_and_deletable_set_impl((TestObj *)self, value); + + return return_value; +} + +static int +Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value) +/*[clinic end generated code: output=479986d499b2f56d input=f5647f3511b9daea]*/ /*[clinic input] @setter @@ -5335,6 +5374,12 @@ Test_setter_first_with_docstr_set(PyObject *self, PyObject *value, void *Py_UNUS { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'setter_first_with_docstr' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } return_value = Test_setter_first_with_docstr_set_impl((TestObj *)self, value); return return_value; @@ -5342,7 +5387,7 @@ Test_setter_first_with_docstr_set(PyObject *self, PyObject *value, void *Py_UNUS static int Test_setter_first_with_docstr_set_impl(TestObj *self, PyObject *value) -/*[clinic end generated code: output=5aaf44373c0af545 input=31a045ce11bbe961]*/ +/*[clinic end generated code: output=eac8bafcaa50aa51 input=31a045ce11bbe961]*/ /*[clinic input] @getter diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 6f6e2cc3465264..91b7626fdec846 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -767,6 +767,102 @@ def test_ignore_preprocessor_in_comments(self): """) self.clinic.parse(raw) + def test_getset_in_ifdef(self): + block = """ + /*[clinic input] + output everything block + class Foo "FooObject *" "&Foo_Type" + [clinic start generated code]*/ + #ifdef CONDITION + /*[clinic input] + @getter + Foo.property + [clinic start generated code]*/ + /*[clinic input] + @setter + Foo.property + [clinic start generated code]*/ + #endif + """ + generated = self.clinic.parse(dedent(block)) + self.assertIn("#if defined(CONDITION)", generated) + # The getset is undefined if the condition is false. + self.assertIn("#ifndef FOO_PROPERTY_GETSETDEF\n" + " #define FOO_PROPERTY_GETSETDEF\n" + "#endif /* !defined(FOO_PROPERTY_GETSETDEF) */", + generated) + + def test_getset_duplicate(self): + for annotation in "@getter", "@setter": + with self.subTest(annotation=annotation): + self.clinic = _make_clinic(filename="test.c") + block = f""" + /*[clinic input] + class Foo "FooObject *" "&Foo_Type" + [clinic start generated code]*/ + /*[clinic input] + {annotation} + Foo.property + [clinic start generated code]*/ + /*[clinic input] + {annotation} + Foo.property + [clinic start generated code]*/ + """ + kind = 'setter' if annotation == '@setter' else 'getter' + err = f"Cannot apply @{kind} to 'Foo.property' twice" + self.expect_failure(block, err, lineno=10) + + def test_getset_different_c_basename(self): + block = """ + /*[clinic input] + class Foo "FooObject *" "&Foo_Type" + [clinic start generated code]*/ + /*[clinic input] + @getter + Foo.property as foo_get + [clinic start generated code]*/ + /*[clinic input] + @setter + Foo.property as foo_set + [clinic start generated code]*/ + """ + err = "The accessors of 'Foo.property' must have the same C basename" + self.expect_failure(block, err, lineno=10) + + def test_setter_deletion_check(self): + block = """ + /*[clinic input] + output everything block + class Foo "FooObject *" "&Foo_Type" + [clinic start generated code]*/ + /*[clinic input] + @setter + Foo.property + [clinic start generated code]*/ + """ + generated = self.clinic.parse(dedent(block)) + self.assertIn("if (value == NULL) {", generated) + self.assertIn("\"attribute 'property' of '%.100s' objects " + "cannot be deleted\"", generated) + + def test_deleter(self): + # @deleter means that the setter is called with NULL to delete + # the attribute, so it checks the value itself. + block = """ + /*[clinic input] + output everything block + class Foo "FooObject *" "&Foo_Type" + [clinic start generated code]*/ + /*[clinic input] + @setter + @deleter + Foo.property + [clinic start generated code]*/ + """ + generated = self.clinic.parse(dedent(block)) + self.assertNotIn("if (value == NULL) {", generated) + class ParseFileUnitTest(TestCase): def expect_parsing_failure( @@ -2528,7 +2624,7 @@ class Foo "" "" {annotation} Foo.property -> int """ - expected_error = f"{annotation} method cannot define a return type" + expected_error = "@getter and @setter methods cannot define a return type" self.expect_failure(block, expected_error, lineno=3) block = f""" @@ -2539,7 +2635,7 @@ class Foo "" "" obj: int / """ - expected_error = f"{annotation} methods cannot define parameters" + expected_error = "@getter and @setter methods cannot define parameters" self.expect_failure(block, expected_error) def test_setter_docstring(self): @@ -2582,9 +2678,51 @@ class Foo "" "" {dup[1]} Foo.property -> int """ - expected_error = "Cannot apply both @getter and @setter to the same function!" + expected_error = (f"Can't set {dup[1]}, " + f"function is not a normal callable") self.expect_failure(block, expected_error, lineno=3) + def test_deleter_without_setter(self): + block = """ + module foo + class Foo "" "" + @deleter + Foo.property + """ + expected_error = "Can't set @deleter, @setter is not applied" + self.expect_failure(block, expected_error, lineno=2) + + block = """ + module foo + class Foo "" "" + @deleter + @setter + Foo.property + """ + self.expect_failure(block, expected_error, lineno=2) + + def test_deleter_twice(self): + block = """ + module foo + class Foo "" "" + @setter + @deleter + @deleter + Foo.property + """ + expected_error = "Cannot apply @deleter twice to the same function!" + self.expect_failure(block, expected_error, lineno=4) + + def test_setter_and_deleter(self): + function = self.parse_function(""" + module foo + class Foo "" "" + @setter + @deleter + Foo.property + """, signatures_in_block=3, function_index=2) + self.assertEqual(function.kind, FunctionKind.SETTER_AND_DELETER) + def test_getset_no_class(self): for annotation in "@getter", "@setter": with self.subTest(annotation=annotation): diff --git a/Misc/NEWS.d/next/Library/2026-08-14-12-46-07.gh-issue-113318.DYGQjo.rst b/Misc/NEWS.d/next/Library/2026-08-14-12-46-07.gh-issue-113318.DYGQjo.rst new file mode 100644 index 00000000000000..4cd4acd0188636 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-14-12-46-07.gh-issue-113318.DYGQjo.rst @@ -0,0 +1,5 @@ +Fix crashes when deleting an attribute whose setter is generated by Argument +Clinic and is not prepared for deletion, among them +:attr:`frame.f_trace_opcodes` and the ``context``, ``owner`` and ``session`` +attributes of ``_ssl._SSLSocket``. +Deleting such attribute now raises :exc:`AttributeError`. diff --git a/Misc/NEWS.d/next/Tools-Demos/2026-08-14-12-45-57.gh-issue-113318.G1B0oH.rst b/Misc/NEWS.d/next/Tools-Demos/2026-08-14-12-45-57.gh-issue-113318.G1B0oH.rst new file mode 100644 index 00000000000000..3ea0a37288fe88 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2026-08-14-12-45-57.gh-issue-113318.G1B0oH.rst @@ -0,0 +1,6 @@ +Fix Argument Clinic for ``@getter`` and ``@setter`` in a preprocessor +conditional block. +It failed with an internal error. +Argument Clinic now also rejects the accessors of the same attribute with +different C basenames, and the same accessor defined twice, which silently +generated invalid or duplicated entries of :c:type:`PyGetSetDef`. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index e62f29a6b387ad..d20ecf308e419c 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1404,10 +1404,6 @@ _asyncio_Future__asyncio_future_blocking_set_impl(FutureObj *self, if (future_ensure_alive(self)) { return -1; } - if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); - return -1; - } int is_true = PyObject_IsTrue(value); if (is_true < 0) { @@ -1447,10 +1443,6 @@ static int _asyncio_Future__log_traceback_set_impl(FutureObj *self, PyObject *value) /*[clinic end generated code: output=9ce8e19504f42f54 input=30ac8217754b08c2]*/ { - if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); - return -1; - } int is_true = PyObject_IsTrue(value); if (is_true < 0) { return -1; @@ -1614,10 +1606,6 @@ static int _asyncio_Future__cancel_message_set_impl(FutureObj *self, PyObject *value) /*[clinic end generated code: output=0854b2f77bff2209 input=f461d17f2d891fad]*/ { - if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); - return -1; - } Py_INCREF(value); Py_XSETREF(self->fut_cancel_msg, value); return 0; @@ -2478,10 +2466,6 @@ static int _asyncio_Task__log_destroy_pending_set_impl(TaskObj *self, PyObject *value) /*[clinic end generated code: output=7ebc030bb92ec5ce input=49b759c97d1216a4]*/ { - if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); - return -1; - } int is_true = PyObject_IsTrue(value); if (is_true < 0) { return -1; diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index d152653d121b33..12fef3ea23fef9 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1502,10 +1502,6 @@ _ctypes_PyCArrayType_Type_raw_set_impl(CDataObject *self, PyObject *value) Py_ssize_t size; Py_buffer view; - if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); - return -1; - } if (PyObject_GetBuffer(value, &view, PyBUF_SIMPLE) < 0) return -1; size = view.len; @@ -1561,12 +1557,13 @@ _ctypes_PyCArrayType_Type_value_get_impl(CDataObject *self) /*[clinic input] @critical_section @setter +@deleter _ctypes.PyCArrayType_Type.value [clinic start generated code]*/ static int _ctypes_PyCArrayType_Type_value_set_impl(CDataObject *self, PyObject *value) -/*[clinic end generated code: output=39ad655636a28dd5 input=e2e6385fc6ab1a29]*/ +/*[clinic end generated code: output=39ad655636a28dd5 input=167f0935cbb8d489]*/ { const char *ptr; Py_ssize_t size; @@ -3664,12 +3661,13 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags, /*[clinic input] @critical_section @setter +@deleter _ctypes.CFuncPtr.errcheck [clinic start generated code]*/ static int _ctypes_CFuncPtr_errcheck_set_impl(PyCFuncPtrObject *self, PyObject *value) -/*[clinic end generated code: output=6580cf1ffdf3b9fb input=84930bb16c490b33]*/ +/*[clinic end generated code: output=6580cf1ffdf3b9fb input=bcd5d3ed1a0c36e9]*/ { if (value && !PyCallable_Check(value)) { PyErr_SetString(PyExc_TypeError, @@ -3701,13 +3699,14 @@ _ctypes_CFuncPtr_errcheck_get_impl(PyCFuncPtrObject *self) /*[clinic input] @setter +@deleter @critical_section _ctypes.CFuncPtr.restype [clinic start generated code]*/ static int _ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value) -/*[clinic end generated code: output=0be0a086abbabf18 input=683c3bef4562ccc6]*/ +/*[clinic end generated code: output=0be0a086abbabf18 input=ffc941a26dbb31f3]*/ { PyObject *checker; if (value == NULL) { @@ -3764,13 +3763,14 @@ _ctypes_CFuncPtr_restype_get_impl(PyCFuncPtrObject *self) /*[clinic input] @setter +@deleter @critical_section _ctypes.CFuncPtr.argtypes [clinic start generated code]*/ static int _ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value) -/*[clinic end generated code: output=596a36e2ae89d7d1 input=c4627573e980aa8b]*/ +/*[clinic end generated code: output=596a36e2ae89d7d1 input=fd012f1fd7cc35be]*/ { if (value == NULL || value == Py_None) { atomic_xsetref(&self->argtypes, NULL); @@ -5414,12 +5414,13 @@ class _ctypes.Simple "CDataObject *" "clinic_state()->Simple_Type" /*[clinic input] @critical_section @setter +@deleter _ctypes.Simple.value [clinic start generated code]*/ static int _ctypes_Simple_value_set_impl(CDataObject *self, PyObject *value) -/*[clinic end generated code: output=f267186118939863 input=977af9dc9e71e857]*/ +/*[clinic end generated code: output=f267186118939863 input=4e6c1143d17c2c3f]*/ { PyObject *result; diff --git a/Modules/_ctypes/clinic/_ctypes.c.h b/Modules/_ctypes/clinic/_ctypes.c.h index 92dfb8f83b7da6..62414cf1816df2 100644 --- a/Modules/_ctypes/clinic/_ctypes.c.h +++ b/Modules/_ctypes/clinic/_ctypes.c.h @@ -425,6 +425,12 @@ _ctypes_PyCArrayType_Type_raw_set(PyObject *self, PyObject *value, void *Py_UNUS { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'raw' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ctypes_PyCArrayType_Type_raw_set_impl((CDataObject *)self, value); Py_END_CRITICAL_SECTION(); @@ -1000,4 +1006,4 @@ Simple_from_outparm(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py } return Simple_from_outparm_impl(self, cls); } -/*[clinic end generated code: output=9fb75bf7e9a17df2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=349a8678c2782fc9 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index 8d59bda5f74b38..99a3eb9f260707 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -1325,10 +1325,16 @@ _io_TextIOWrapper__CHUNK_SIZE_set(PyObject *self, PyObject *value, void *Py_UNUS { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute '_CHUNK_SIZE' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _io_TextIOWrapper__CHUNK_SIZE_set_impl((textio *)self, value); Py_END_CRITICAL_SECTION(); return return_value; } -/*[clinic end generated code: output=8c571c9dba87d2b1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=cec74a7476964ae4 input=a9049054013a1b77]*/ diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 74775e035e47a4..4815aa26ab5986 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -3326,10 +3326,6 @@ _io_TextIOWrapper__CHUNK_SIZE_set_impl(textio *self, PyObject *value) { Py_ssize_t n; CHECK_ATTACHED_INT(self); - if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); - return -1; - } n = PyNumber_AsSsize_t(value, PyExc_ValueError); if (n == -1 && PyErr_Occurred()) return -1; diff --git a/Modules/_sqlite/clinic/cursor.c.h b/Modules/_sqlite/clinic/cursor.c.h index 3cad9f3aef5ecd..689466b1c2b85a 100644 --- a/Modules/_sqlite/clinic/cursor.c.h +++ b/Modules/_sqlite/clinic/cursor.c.h @@ -367,8 +367,14 @@ _sqlite3_Cursor_arraysize_set(PyObject *self, PyObject *value, void *Py_UNUSED(c { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'arraysize' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } return_value = _sqlite3_Cursor_arraysize_set_impl((pysqlite_Cursor *)self, value); return return_value; } -/*[clinic end generated code: output=a0e3ebba9e4d0ece input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e7b20358f8213fd7 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index f07a09df5ac7ae..d9202681492039 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -585,6 +585,12 @@ _asyncio_Future__asyncio_future_blocking_set(PyObject *self, PyObject *value, vo { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute '_asyncio_future_blocking' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _asyncio_Future__asyncio_future_blocking_set_impl((FutureObj *)self, value); Py_END_CRITICAL_SECTION(); @@ -635,6 +641,12 @@ _asyncio_Future__log_traceback_set(PyObject *self, PyObject *value, void *Py_UNU { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute '_log_traceback' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _asyncio_Future__log_traceback_set_impl((FutureObj *)self, value); Py_END_CRITICAL_SECTION(); @@ -810,6 +822,12 @@ _asyncio_Future__cancel_message_set(PyObject *self, PyObject *value, void *Py_UN { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute '_cancel_message' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _asyncio_Future__cancel_message_set_impl((FutureObj *)self, value); Py_END_CRITICAL_SECTION(); @@ -1002,6 +1020,12 @@ _asyncio_Task__log_destroy_pending_set(PyObject *self, PyObject *value, void *Py { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute '_log_destroy_pending' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _asyncio_Task__log_destroy_pending_set_impl((TaskObj *)self, value); Py_END_CRITICAL_SECTION(); @@ -2233,4 +2257,4 @@ _asyncio_future_discard_from_awaited_by(PyObject *module, PyObject *const *args, exit: return return_value; } -/*[clinic end generated code: output=32996fb47c48245b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=6a0d02bd421248aa input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 0f45b2f52c5090..d105500d176b15 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -316,6 +316,12 @@ _ssl__SSLSocket_context_set(PyObject *self, PyObject *value, void *Py_UNUSED(con { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'context' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLSocket_context_set_impl((PySSLSocket *)self, value); Py_END_CRITICAL_SECTION(); @@ -439,6 +445,12 @@ _ssl__SSLSocket_owner_set(PyObject *self, PyObject *value, void *Py_UNUSED(conte { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'owner' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLSocket_owner_set_impl((PySSLSocket *)self, value); Py_END_CRITICAL_SECTION(); @@ -735,6 +747,12 @@ _ssl__SSLSocket_session_set(PyObject *self, PyObject *value, void *Py_UNUSED(con { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'session' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLSocket_session_set_impl((PySSLSocket *)self, value); Py_END_CRITICAL_SECTION(); @@ -941,6 +959,12 @@ _ssl__SSLContext_verify_mode_set(PyObject *self, PyObject *value, void *Py_UNUSE { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'verify_mode' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext_verify_mode_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -991,6 +1015,12 @@ _ssl__SSLContext_verify_flags_set(PyObject *self, PyObject *value, void *Py_UNUS { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'verify_flags' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext_verify_flags_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -1042,6 +1072,12 @@ _ssl__SSLContext_minimum_version_set(PyObject *self, PyObject *value, void *Py_U { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'minimum_version' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext_minimum_version_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -1093,6 +1129,12 @@ _ssl__SSLContext_maximum_version_set(PyObject *self, PyObject *value, void *Py_U { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'maximum_version' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext_maximum_version_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -1150,6 +1192,12 @@ _ssl__SSLContext_num_tickets_set(PyObject *self, PyObject *value, void *Py_UNUSE { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'num_tickets' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext_num_tickets_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -1232,6 +1280,12 @@ _ssl__SSLContext_options_set(PyObject *self, PyObject *value, void *Py_UNUSED(co { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'options' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext_options_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -1282,6 +1336,12 @@ _ssl__SSLContext__host_flags_set(PyObject *self, PyObject *value, void *Py_UNUSE { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute '_host_flags' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext__host_flags_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -1332,6 +1392,12 @@ _ssl__SSLContext_check_hostname_set(PyObject *self, PyObject *value, void *Py_UN { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'check_hostname' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext_check_hostname_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -1866,6 +1932,12 @@ _ssl__SSLContext_sni_callback_set(PyObject *self, PyObject *value, void *Py_UNUS { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'sni_callback' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = _ssl__SSLContext_sni_callback_set_impl((PySSLContext *)self, value); Py_END_CRITICAL_SECTION(); @@ -2906,4 +2978,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=6b5d14b14e152522 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=8a0e36a31fccd58c input=a9049054013a1b77]*/ diff --git a/Objects/clinic/frameobject.c.h b/Objects/clinic/frameobject.c.h index 327896f4b97c68..7b8dab1e015a6b 100644 --- a/Objects/clinic/frameobject.c.h +++ b/Objects/clinic/frameobject.c.h @@ -265,6 +265,12 @@ frame_trace_opcodes_set(PyObject *self, PyObject *value, void *Py_UNUSED(context { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'f_trace_opcodes' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = frame_trace_opcodes_set_impl((PyFrameObject *)self, value); Py_END_CRITICAL_SECTION(); @@ -290,6 +296,12 @@ frame_lineno_set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) { int return_value; + if (value == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute 'f_lineno' of '%.100s' objects cannot be deleted", + Py_TYPE(self)->tp_name); + return -1; + } Py_BEGIN_CRITICAL_SECTION(self); return_value = frame_lineno_set_impl((PyFrameObject *)self, value); Py_END_CRITICAL_SECTION(); @@ -433,4 +445,4 @@ frame___sizeof__(PyObject *self, PyObject *Py_UNUSED(ignored)) return return_value; } -/*[clinic end generated code: output=74abf652547c0c11 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a42421e56faa7a80 input=a9049054013a1b77]*/ diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 833e283c17603c..93214399640873 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -352,12 +352,13 @@ BaseException_args_get_impl(PyBaseExceptionObject *self) /*[clinic input] @critical_section @setter +@deleter BaseException.args [clinic start generated code]*/ static int BaseException_args_set_impl(PyBaseExceptionObject *self, PyObject *value) -/*[clinic end generated code: output=331137e11d8f9e80 input=2400047ea5970a84]*/ +/*[clinic end generated code: output=331137e11d8f9e80 input=177ad350c8b45219]*/ { PyObject *seq; if (value == NULL) { @@ -391,13 +392,14 @@ BaseException___traceback___get_impl(PyBaseExceptionObject *self) /*[clinic input] @critical_section @setter +@deleter BaseException.__traceback__ [clinic start generated code]*/ static int BaseException___traceback___set_impl(PyBaseExceptionObject *self, PyObject *value) -/*[clinic end generated code: output=a82c86d9f29f48f0 input=12676035676badad]*/ +/*[clinic end generated code: output=a82c86d9f29f48f0 input=53a1df586023d786]*/ { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "__traceback__ may not be deleted"); @@ -436,13 +438,14 @@ BaseException___context___get_impl(PyBaseExceptionObject *self) /*[clinic input] @critical_section @setter +@deleter BaseException.__context__ [clinic start generated code]*/ static int BaseException___context___set_impl(PyBaseExceptionObject *self, PyObject *value) -/*[clinic end generated code: output=b4cb52dcca1da3bd input=c0971adf47fa1858]*/ +/*[clinic end generated code: output=b4cb52dcca1da3bd input=fe79e7c0a0854004]*/ { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "__context__ may not be deleted"); @@ -479,13 +482,14 @@ BaseException___cause___get_impl(PyBaseExceptionObject *self) /*[clinic input] @critical_section @setter +@deleter BaseException.__cause__ [clinic start generated code]*/ static int BaseException___cause___set_impl(PyBaseExceptionObject *self, PyObject *value) -/*[clinic end generated code: output=6161315398aaf541 input=e1b403c0bde3f62a]*/ +/*[clinic end generated code: output=6161315398aaf541 input=3fdd9a0d1674abc9]*/ { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted"); diff --git a/Objects/frameobject.c b/Objects/frameobject.c index b4597a937074b8..a73d9b485a76f2 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -1641,10 +1641,6 @@ frame_lineno_set_impl(PyFrameObject *self, PyObject *value) /*[clinic end generated code: output=e64c86ff6be64292 input=36ed3c896b27fb91]*/ { PyCodeObject *code = _PyFrame_GetCode(self->f_frame); - if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); - return -1; - } /* f_lineno must be an integer. */ if (!PyLong_CheckExact(value)) { PyErr_SetString(PyExc_ValueError, @@ -1856,12 +1852,13 @@ frame_trace_get_impl(PyFrameObject *self) /*[clinic input] @critical_section @setter +@deleter frame.f_trace as frame_trace [clinic start generated code]*/ static int frame_trace_set_impl(PyFrameObject *self, PyObject *value) -/*[clinic end generated code: output=d6fe08335cf76ae4 input=d96a18bda085707f]*/ +/*[clinic end generated code: output=d6fe08335cf76ae4 input=b652d64fdd1189f2]*/ { if (value == Py_None) { value = NULL; diff --git a/Objects/funcobject.c b/Objects/funcobject.c index b59a493e6234cb..412c7ec4d0a66e 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -941,12 +941,13 @@ function___annotate___get_impl(PyFunctionObject *self) /*[clinic input] @critical_section @setter +@deleter function.__annotate__ [clinic start generated code]*/ static int function___annotate___set_impl(PyFunctionObject *self, PyObject *value) -/*[clinic end generated code: output=05b7dfc07ada66cd input=eb6225e358d97448]*/ +/*[clinic end generated code: output=05b7dfc07ada66cd input=4bcfad0bdcfec768]*/ { if (value == NULL) { PyErr_SetString(PyExc_TypeError, @@ -995,12 +996,13 @@ function___annotations___get_impl(PyFunctionObject *self) /*[clinic input] @critical_section @setter +@deleter function.__annotations__ [clinic start generated code]*/ static int function___annotations___set_impl(PyFunctionObject *self, PyObject *value) -/*[clinic end generated code: output=a61795d4a95eede4 input=5302641f686f0463]*/ +/*[clinic end generated code: output=a61795d4a95eede4 input=71f6a58c00ac6745]*/ { if (value == Py_None) value = NULL; @@ -1040,12 +1042,13 @@ function___type_params___get_impl(PyFunctionObject *self) /*[clinic input] @critical_section @setter +@deleter function.__type_params__ [clinic start generated code]*/ static int function___type_params___set_impl(PyFunctionObject *self, PyObject *value) -/*[clinic end generated code: output=038b4cda220e56fb input=3862fbd4db2b70e8]*/ +/*[clinic end generated code: output=038b4cda220e56fb input=c0e33abc5901a2f5]*/ { /* Not legal to del f.__type_params__ or to set it to anything * other than a tuple object. */ diff --git a/Python/traceback.c b/Python/traceback.c index 48be6f5add352d..208689b1a76d40 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -177,12 +177,13 @@ tb_lineno_get(PyObject *op, void *Py_UNUSED(_)) /*[clinic input] @critical_section @setter +@deleter traceback.tb_next [clinic start generated code]*/ static int traceback_tb_next_set_impl(PyTracebackObject *self, PyObject *value) -/*[clinic end generated code: output=d4868cbc48f2adac input=ce66367f85e3c443]*/ +/*[clinic end generated code: output=d4868cbc48f2adac input=936201ff689c5700]*/ { if (!value) { PyErr_Format(PyExc_TypeError, "can't delete tb_next attribute"); diff --git a/Tools/clinic/libclinic/clanguage.py b/Tools/clinic/libclinic/clanguage.py index 3ee06307441fac..2e381477674df6 100644 --- a/Tools/clinic/libclinic/clanguage.py +++ b/Tools/clinic/libclinic/clanguage.py @@ -13,7 +13,8 @@ from libclinic.function import ( Module, Class, Function, Parameter, ParamTuple, permute_optional_groups, - GETTER, SETTER, METHOD_INIT) + GETTER, METHOD_INIT, + ACCESSORS, SETTERS) from libclinic.converters import self_converter from libclinic.parse_args import ParseArgsCodeGen if TYPE_CHECKING: @@ -463,12 +464,12 @@ def render_function( full_name = f.full_name template_dict = {'full_name': full_name} template_dict['name'] = f.displayname - if f.kind in {GETTER, SETTER}: + if f.kind in ACCESSORS: 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: + else: template_dict['c_basename'] = f.c_basename + "_set" # Implicitly add the setter value parameter. data.impl_parameters.append("PyObject *value") @@ -483,7 +484,7 @@ def render_function( for converter in converters: converter.set_template_dict(template_dict) - if f.kind not in {SETTER, METHOD_INIT}: + if f.kind not in SETTERS | {METHOD_INIT}: f.return_converter.render(f, data) template_dict['impl_return_type'] = f.return_converter.type diff --git a/Tools/clinic/libclinic/converters.py b/Tools/clinic/libclinic/converters.py index 64fc1e95007516..2756aa7370eb40 100644 --- a/Tools/clinic/libclinic/converters.py +++ b/Tools/clinic/libclinic/converters.py @@ -8,7 +8,7 @@ from libclinic.function import ( Function, Parameter, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW, - GETTER, SETTER) + ACCESSORS) from libclinic.codegen import CRenderData, TemplateDict from libclinic.converter import ( CConverter, legacy_converters, add_legacy_c_converter) @@ -1129,7 +1129,7 @@ def correct_name_for_self( f: Function, parser: bool = False ) -> tuple[str, str]: - if f.kind in {CALLABLE, METHOD_INIT, GETTER, SETTER}: + if f.kind in {CALLABLE, METHOD_INIT} | ACCESSORS: if f.cls: return "PyObject *", "self" return "PyObject *", "module" diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py index 6ead9bf2022833..ee8d09dee75470 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -18,7 +18,7 @@ Module, Class, Function, Parameter, FunctionKind, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW, - GETTER, SETTER) + ACCESSORS, SETTERS) from libclinic.converter import ( converters, legacy_converters) from libclinic.converters import ( @@ -439,21 +439,31 @@ def at_disable(self, *args: str) -> None: def at_getter(self) -> None: match self.kind: + case FunctionKind.CALLABLE: + self.kind = FunctionKind.GETTER 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 + fail("Can't set @getter, function is not a normal callable") def at_setter(self) -> None: match self.kind: - case FunctionKind.SETTER: + case FunctionKind.CALLABLE: + self.kind = FunctionKind.SETTER + case FunctionKind.SETTER | FunctionKind.SETTER_AND_DELETER: 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 + fail("Can't set @setter, function is not a normal callable") + + def at_deleter(self) -> None: + match self.kind: + case FunctionKind.SETTER: + # The setter is called with NULL to delete the attribute. + self.kind = FunctionKind.SETTER_AND_DELETER + case FunctionKind.SETTER_AND_DELETER: + fail("Cannot apply @deleter twice to the same function!") + case _: + fail("Can't set @deleter, @setter is not applied") def at_staticmethod(self) -> None: if self.kind is not CALLABLE: @@ -574,7 +584,7 @@ def normalize_function_kind(self, fullname: str) -> None: fail(f"{name!r} must be a normal method; got '{self.kind}'!") if name == '__new__' and (self.kind is not CLASS_METHOD or not cls): fail("'__new__' must be a class method!") - if self.kind in {GETTER, SETTER} and not cls: + if self.kind in ACCESSORS and not cls: fail("@getter and @setter must be methods") # Normalise self.kind. @@ -587,8 +597,8 @@ def resolve_return_converter( self, full_name: str, forced_converter: str ) -> CReturnConverter: if forced_converter: - if self.kind in {GETTER, SETTER}: - fail(f"@{self.kind.name.lower()} method cannot define a return type") + if self.kind in ACCESSORS: + fail("@getter and @setter methods cannot define a return type") if self.kind is METHOD_INIT: fail("__init__ methods cannot define a return type") ast_input = f"def x() -> {forced_converter}: pass" @@ -608,7 +618,7 @@ def resolve_return_converter( except ValueError: fail(f"Badly formed annotation for {full_name!r}: {forced_converter!r}") - if self.kind in {METHOD_INIT, SETTER}: + if self.kind in {METHOD_INIT} | SETTERS: return int_return_converter() return CReturnConverter() @@ -714,6 +724,22 @@ def state_modulename_name(self, line: str) -> None: self.next(self.state_parameters_start) def add_function(self, func: Function) -> None: + if func.kind in ACCESSORS: + # The accessors of the same attribute are rendered into a single + # PyGetSetDef entry, which is identified by the C basename, so + # they must share it. + for other in (func.cls or func.module).functions: + if (other.kind in ACCESSORS + and other.full_name == func.full_name): + if (other.kind is func.kind + or {other.kind, func.kind} <= SETTERS): + kind = 'setter' if func.kind in SETTERS else 'getter' + fail(f"Cannot apply @{kind} to " + f"{func.full_name!r} twice") + if other.c_basename != func.c_basename: + fail(f"The accessors of {func.full_name!r} " + f"must have the same C basename") + # Insert a self converter automatically. tp, name = correct_name_for_self(func) if func.cls and tp == "PyObject *": @@ -796,9 +822,8 @@ def state_parameters_start(self, line: str) -> None: return self.next(self.state_function_docstring, line) assert self.function is not None - if self.function.kind in {GETTER, SETTER}: - getset = self.function.kind.name.lower() - fail(f"@{getset} methods cannot define parameters") + if self.function.kind in ACCESSORS: + fail("@getter and @setter methods cannot define parameters") self.parameter_continuation = '' return self.next(self.state_parameter, line) @@ -1302,7 +1327,7 @@ def format_docstring_signature( lines.append(f.displayname) if f.forced_text_signature: lines.append(f.forced_text_signature) - elif f.kind in {GETTER, SETTER}: + elif f.kind in ACCESSORS: # @getter and @setter do not need signatures like a method or a function. return '' else: @@ -1473,7 +1498,7 @@ def format_docstring(self) -> str: assert self.function is not None f = self.function # For the following special cases, it does not make sense to render a docstring. - if f.kind in {METHOD_INIT, METHOD_NEW, GETTER, SETTER} and not f.docstring: + if f.kind in {METHOD_INIT, METHOD_NEW} | ACCESSORS and not f.docstring: return f.docstring # Enforce the summary line! diff --git a/Tools/clinic/libclinic/function.py b/Tools/clinic/libclinic/function.py index e80e2f5f13f648..8f9be33372d54a 100644 --- a/Tools/clinic/libclinic/function.py +++ b/Tools/clinic/libclinic/function.py @@ -60,6 +60,7 @@ class FunctionKind(enum.Enum): METHOD_NEW = enum.auto() GETTER = enum.auto() SETTER = enum.auto() + SETTER_AND_DELETER = enum.auto() @functools.cached_property def new_or_init(self) -> bool: @@ -76,6 +77,12 @@ def __repr__(self) -> str: METHOD_NEW: Final = FunctionKind.METHOD_NEW GETTER: Final = FunctionKind.GETTER SETTER: Final = FunctionKind.SETTER +SETTER_AND_DELETER: Final = FunctionKind.SETTER_AND_DELETER + +# The kinds which implement the setter of an entry of PyGetSetDef. +SETTERS: Final = frozenset({SETTER, SETTER_AND_DELETER}) +# The kinds which implement an entry of PyGetSetDef. +ACCESSORS: Final = SETTERS | {GETTER} @dc.dataclass(repr=False) @@ -161,7 +168,7 @@ def methoddef_flags(self) -> str | None: case FunctionKind.STATIC_METHOD: flags.append('METH_STATIC') case _ as kind: - acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER} + acceptable_kinds = {FunctionKind.CALLABLE} | ACCESSORS assert kind in acceptable_kinds, f"unknown kind: {kind!r}" if self.coexist: flags.append('METH_COEXIST') diff --git a/Tools/clinic/libclinic/parse_args.py b/Tools/clinic/libclinic/parse_args.py index 0e15d2f163b816..b4a359df8670f3 100644 --- a/Tools/clinic/libclinic/parse_args.py +++ b/Tools/clinic/libclinic/parse_args.py @@ -5,7 +5,8 @@ from libclinic import fail, warn from libclinic.function import ( Function, Parameter, - GETTER, SETTER, METHOD_NEW) + GETTER, SETTER, METHOD_NEW, + ACCESSORS, SETTERS) from libclinic.converter import CConverter from libclinic.converters import ( defining_class_converter, object_converter, self_converter) @@ -188,6 +189,21 @@ def declare_parser( #define {methoddef_name} #endif /* !defined({methoddef_name}) */ """) +GETSETDEF_PROTOTYPE_IFNDEF: Final[str] = libclinic.normalize_snippet(""" + #ifndef {getset_name}_GETSETDEF + #define {getset_name}_GETSETDEF + #endif /* !defined({getset_name}_GETSETDEF) */ +""") +# The setter is called with NULL to delete the attribute. Unless @deleter is +# applied to it, deletion is rejected before the implementation is called. +SETTER_PREAMBLE: Final[str] = libclinic.normalize_snippet(""" + if (value == NULL) {{ + PyErr_Format(PyExc_AttributeError, + "attribute '{name}' of '%.100s' objects cannot be deleted", + Py_TYPE({self_name})->tp_name); + return -1; + }} +""", indent=4) class ParseArgsCodeGen: @@ -315,7 +331,7 @@ def select_prototypes(self) -> None: self.methoddef_define = GETTERDEF_PROTOTYPE_DEFINE if self.func.docstring: self.docstring_definition = GETSET_DOCSTRING_PROTOTYPE_STRVAR - elif self.func.kind is SETTER: + elif self.func.kind in SETTERS: if self.func.docstring: fail("docstrings are only supported for @getter, not @setter") self.return_value_declaration = "int {return_value};" @@ -374,9 +390,12 @@ def parse_no_args(self) -> None: if self.func.kind is GETTER: self.parser_prototype = PARSER_PROTOTYPE_GETTER parser_code = [] - elif self.func.kind is SETTER: + elif self.func.kind in SETTERS: self.parser_prototype = PARSER_PROTOTYPE_SETTER - parser_code = [] + if self.func.kind is SETTER: + parser_code = [SETTER_PREAMBLE] + else: + parser_code = [] elif not self.requires_defining_class: # no self.parameters, METH_NOARGS self.flags = "METH_NOARGS" @@ -861,7 +880,10 @@ def process_methoddef(self, clang: CLanguage) -> None: self.cpp_endif = "#endif /* " + conditional + " */" if self.methoddef_define and self.codegen.add_ifndef_symbol(self.func.full_name): - self.methoddef_ifndef = METHODDEF_PROTOTYPE_IFNDEF + if self.func.kind in ACCESSORS: + self.methoddef_ifndef = GETSETDEF_PROTOTYPE_IFNDEF + else: + self.methoddef_ifndef = METHODDEF_PROTOTYPE_IFNDEF def finalize(self, clang: CLanguage) -> None: # add ';' to the end of self.parser_prototype and self.impl_prototype