From 940fceb59a55513913c3c9c1eaf89a7f5ee774d6 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 15 Oct 2023 12:12:44 -0700 Subject: [PATCH] [mypyc] Fix direct __dict__ access on inner functions in new Python (#16084) Fixes #16077 --- mypyc/codegen/emitclass.py | 5 ++++- mypyc/test-data/run-functions.test | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index 62e1b4b2dea1..8dcf7212b694 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -217,7 +217,7 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None: fields["tp_name"] = f'"{name}"' generate_full = not cl.is_trait and not cl.builtin_base - needs_getseters = cl.needs_getseters or not cl.is_generated + needs_getseters = cl.needs_getseters or not cl.is_generated or cl.has_dict if not cl.builtin_base: fields["tp_new"] = new_name @@ -886,6 +886,9 @@ def generate_getseters_table(cl: ClassIR, name: str, emitter: Emitter) -> None: else: emitter.emit_line("NULL, NULL, NULL},") + if cl.has_dict: + emitter.emit_line('{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},') + emitter.emit_line("{NULL} /* Sentinel */") emitter.emit_line("};") diff --git a/mypyc/test-data/run-functions.test b/mypyc/test-data/run-functions.test index 21993891c4e3..bd8f1a9197dd 100644 --- a/mypyc/test-data/run-functions.test +++ b/mypyc/test-data/run-functions.test @@ -1256,3 +1256,33 @@ def foo(**kwargs: Unpack[Person]) -> None: foo(name='Jennifer', age=38) [out] Jennifer + +[case testNestedFunctionDunderDict312] +import sys + +def foo() -> None: + def inner() -> str: return "bar" + print(inner.__dict__) # type: ignore[attr-defined] + inner.__dict__.update({"x": 1}) # type: ignore[attr-defined] + print(inner.__dict__) # type: ignore[attr-defined] + print(inner.x) # type: ignore[attr-defined] + +if sys.version_info >= (3, 12): # type: ignore + foo() +[out] +[out version>=3.12] +{} +{'x': 1} +1 + +[case testFunctoolsUpdateWrapper] +import functools + +def bar() -> None: + def inner() -> str: return "bar" + functools.update_wrapper(inner, bar) # type: ignore + print(inner.__dict__) # type: ignore + +bar() +[out] +{'__module__': 'native', '__name__': 'bar', '__qualname__': 'bar', '__doc__': None, '__wrapped__': }