Skip to content

[mypyc] Call __delattr__ of a class that inherits __setattr__ - #22161

Merged
JukkaL merged 1 commit into
python:masterfrom
rheard:fix-mypyc-1241
Oct 15, 2026
Merged

JukkaL merged 1 commit into
python:masterfrom
rheard:fix-mypyc-1241

Conversation

@rheard

@rheard rheard commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Fixes mypyc/mypyc#1241

del obj.attr and delattr() go through tp_setattro. The wrapper for that slot was only generated for a class that defines __setattr__, and it calls __delattr__ only if that class has one. A subclass that defines __delattr__ and inherits __setattr__ also inherited the wrapper, so its __delattr__ was never called:

class Base:
    def __setattr__(self, name: str, value: object) -> None: ...

class Sub(Base):
    def __delattr__(self, name: str) -> None: ...  # not called by del or delattr()

Now a class that defines __delattr__ and inherits __setattr__ from a native class gets a wrapper of its own, which calls its __delattr__ and the inherited __setattr__. tp_setattro is filled for every class that has a wrapper, so the slot table is keyed on the wrapper instead of on __setattr__.

The errors for __setattr__ in a class that allows interpreted subclasses or inherits from a non-native class are still reported only where __setattr__ is defined.

A native class that inherits __setattr__ from a non-native class doesn't get a wrapper. The slot it inherits from the non-native class looks up both methods at runtime, so that case already worked.

@JukkaL JukkaL left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@JukkaL
JukkaL merged commit a0f70db into python:master Oct 15, 2026
18 checks passed
@ilevkivskyi

Copy link
Copy Markdown
Member

This broke master btw.

@ilevkivskyi

Copy link
Copy Markdown
Member

https://github.com/python/mypy/actions/runs/38714453707/job/116353613664

build/__native.c:152:20: error: initialization of ‘int (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘int (*)(struct _object *, struct _object *, struct _object *)’} from incompatible pointer type ‘PyObject * (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘struct _object * (*)(struct _object *, struct _object *, struct _object *)’} [-Werror=incompatible-pointer-types]
  152 |     .tp_setattro = CPyDunder___setattr____wrapperSetAttr,
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/__native.c:152:20: note: (near initialization for ‘CPyType_SetAttr_template_.tp_setattro’)
build/__native.c:464:20: error: initialization of ‘int (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘int (*)(struct _object *, struct _object *, struct _object *)’} from incompatible pointer type ‘PyObject * (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘struct _object * (*)(struct _object *, struct _object *, struct _object *)’} [-Werror=incompatible-pointer-types]
  464 |     .tp_setattro = CPyDunder___setattr____wrapperDelAttr,
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/__native.c:464:20: note: (near initialization for ‘CPyType_DelAttr_template_.tp_setattro’)
build/__native.c:743:20: error: initialization of ‘int (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘int (*)(struct _object *, struct _object *, struct _object *)’} from incompatible pointer type ‘PyObject * (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘struct _object * (*)(struct _object *, struct _object *, struct _object *)’} [-Werror=incompatible-pointer-types]
  743 |     .tp_setattro = CPyDunder___setattr____wrapperDelAttrOverridden,
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/__native.c:743:20: note: (near initialization for ‘CPyType_DelAttrOverridden_template_.tp_setattro’)
build/__native.c:892:20: error: initialization of ‘int (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘int (*)(struct _object *, struct _object *, struct _object *)’} from incompatible pointer type ‘PyObject * (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘struct _object * (*)(struct _object *, struct _object *, struct _object *)’} [-Werror=incompatible-pointer-types]
  892 |     .tp_setattro = CPyDunder___setattr____wrapperDelAttrInSubSubclass,
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/__native.c:892:20: note: (near initialization for ‘CPyType_DelAttrInSubSubclass_template_.tp_setattro’)
build/__native.c:1043:20: error: initialization of ‘int (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘int (*)(struct _object *, struct _object *, struct _object *)’} from incompatible pointer type ‘PyObject * (*)(PyObject *, PyObject *, PyObject *)’ {aka ‘struct _object * (*)(struct _object *, struct _object *, struct _object *)’} [-Werror=incompatible-pointer-types]
 1043 |     .tp_setattro = CPyDunder___setattr____wrapperSetAttrOverridden,
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/__native.c:1043:20: note: (near initialization for ‘CPyType_SetAttrOverridden_template_.tp_setattro’)

@JukkaL

JukkaL commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

#22209 should fix CI.

JukkaL added a commit that referenced this pull request Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

__delattr__ isn't called in a class that inherits __setattr__ from a native class

3 participants