From 192476c80f1bb6038ea14908a5c0fad488e308cf Mon Sep 17 00:00:00 2001 From: Advait Dixit Date: Sat, 31 May 2025 15:22:32 -0700 Subject: [PATCH 1/2] [mypyc] Fixing condition for handling user-defined __del__. --- mypyc/codegen/emitclass.py | 23 +++++++++++++---------- mypyc/test-data/run-classes.test | 12 ++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index c5191e5fb9397..e79a8225c4bd1 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -214,12 +214,14 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None: if not cl.builtin_base: fields["tp_new"] = new_name - if generate_full: + # Populate .tp_finalize and generate a finalize method only if __del__ is defined for this class. + del_method = next((e.method for e in cl.vtable_entries if e.name == "__del__"), None) + # Del method is called from dealloc method. So, dealloc should be generated whenever there is a del method. + if generate_full or del_method: fields["tp_dealloc"] = f"(destructor){name_prefix}_dealloc" + if generate_full: fields["tp_traverse"] = f"(traverseproc){name_prefix}_traverse" fields["tp_clear"] = f"(inquiry){name_prefix}_clear" - # Populate .tp_finalize and generate a finalize method only if __del__ is defined for this class. - del_method = next((e.method for e in cl.vtable_entries if e.name == "__del__"), None) if del_method: fields["tp_finalize"] = f"(destructor){finalize_name}" if needs_getseters: @@ -300,13 +302,6 @@ def emit_line() -> None: emit_line() generate_traverse_for_class(cl, traverse_name, emitter) emit_line() - generate_clear_for_class(cl, clear_name, emitter) - emit_line() - generate_dealloc_for_class(cl, dealloc_name, clear_name, bool(del_method), emitter) - emit_line() - if del_method: - generate_finalize_for_class(del_method, finalize_name, emitter) - emit_line() if cl.allow_interpreted_subclasses: shadow_vtable_name: str | None = generate_vtables( @@ -317,6 +312,14 @@ def emit_line() -> None: shadow_vtable_name = None vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter, shadow=False) emit_line() + if generate_full or del_method: + generate_clear_for_class(cl, clear_name, emitter) + emit_line() + generate_dealloc_for_class(cl, dealloc_name, clear_name, bool(del_method), emitter) + emit_line() + if del_method: + generate_finalize_for_class(del_method, finalize_name, emitter) + emit_line() if needs_getseters: generate_getseter_declarations(cl, emitter) emit_line() diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 97bc063dd8ea6..d866356afd75c 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -2754,6 +2754,18 @@ def test_function(): assert(isinstance(d.fitem, ForwardDefinedClass)) assert(isinstance(d.fitems, ForwardDefinedClass)) +[case testDelForDictSubclass] +class DictSubclass(dict): + def __del__(self): + print("deleting DictSubclass...") + +[file driver.py] +import native +native.DictSubclass() + +[out] +deleting DictSubclass... + [case testDel] class A: def __del__(self): From 58017e64bec31db79090e8a95b5f077fbf4ba745 Mon Sep 17 00:00:00 2001 From: Advait Dixit Date: Sun, 1 Jun 2025 23:19:58 -0700 Subject: [PATCH 2/2] Revert some changes from previous commit. Add test for expected failure. --- mypyc/codegen/emitclass.py | 17 +++++++---------- mypyc/test-data/run-classes.test | 11 ++++++++++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index e79a8225c4bd1..9cb9074b9fc4f 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -214,14 +214,12 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None: if not cl.builtin_base: fields["tp_new"] = new_name - # Populate .tp_finalize and generate a finalize method only if __del__ is defined for this class. - del_method = next((e.method for e in cl.vtable_entries if e.name == "__del__"), None) - # Del method is called from dealloc method. So, dealloc should be generated whenever there is a del method. - if generate_full or del_method: - fields["tp_dealloc"] = f"(destructor){name_prefix}_dealloc" if generate_full: + fields["tp_dealloc"] = f"(destructor){name_prefix}_dealloc" fields["tp_traverse"] = f"(traverseproc){name_prefix}_traverse" fields["tp_clear"] = f"(inquiry){name_prefix}_clear" + # Populate .tp_finalize and generate a finalize method only if __del__ is defined for this class. + del_method = next((e.method for e in cl.vtable_entries if e.name == "__del__"), None) if del_method: fields["tp_finalize"] = f"(destructor){finalize_name}" if needs_getseters: @@ -302,6 +300,10 @@ def emit_line() -> None: emit_line() generate_traverse_for_class(cl, traverse_name, emitter) emit_line() + generate_clear_for_class(cl, clear_name, emitter) + emit_line() + generate_dealloc_for_class(cl, dealloc_name, clear_name, bool(del_method), emitter) + emit_line() if cl.allow_interpreted_subclasses: shadow_vtable_name: str | None = generate_vtables( @@ -312,11 +314,6 @@ def emit_line() -> None: shadow_vtable_name = None vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter, shadow=False) emit_line() - if generate_full or del_method: - generate_clear_for_class(cl, clear_name, emitter) - emit_line() - generate_dealloc_for_class(cl, dealloc_name, clear_name, bool(del_method), emitter) - emit_line() if del_method: generate_finalize_for_class(del_method, finalize_name, emitter) emit_line() diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index d866356afd75c..288f281c0a940 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -2754,7 +2754,10 @@ def test_function(): assert(isinstance(d.fitem, ForwardDefinedClass)) assert(isinstance(d.fitems, ForwardDefinedClass)) -[case testDelForDictSubclass] +[case testDelForDictSubclass-xfail] +# The crash in issue mypy#19175 is fixed. +# But, for classes that derive from built-in Python classes, user-defined __del__ method is not +# being invoked. class DictSubclass(dict): def __del__(self): print("deleting DictSubclass...") @@ -2786,6 +2789,12 @@ class C(B): class D(A): pass +# Just make sure that this class compiles (see issue mypy#19175). testDelForDictSubclass tests for +# correct output. +class NormDict(dict): + def __del__(self) -> None: + pass + [file driver.py] import native native.C()