From fd8866b594eaa3fcbb4585adf0b7829ed5e8ddef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=A7=80=EC=9B=85?= Date: Wed, 15 Jul 2026 23:36:43 +0900 Subject: [PATCH] module: honor module-level __dir__ (PEP 562) (#8271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * module: honor module-level __dir__ (PEP 562) dir(module) ignored a module-level __dir__ function and returned the raw __dict__ keys. Look up __dir__ in the module dict and, when present, call it and return its result — matching CPython's module___dir___impl. The existing expectedFailure markers on test_module_dir/test_module_dir_errors are removed since both now pass. Fixes #8231 Assisted-by: Claude Code:claude-opus-4-8 * test: unmark zoneinfo test_dir_contains_all (PEP 562 __dir__) Honoring a module-level __dir__ makes dir(zoneinfo) contain everything in __all__, so test_dir_contains_all now passes. Remove its stale expectedFailure marker to avoid an unexpected-success CI failure. Assisted-by: Claude Code:claude-opus-4-8 * test: unmark test_support.test_check__all__ (PEP 562 __dir__) check__all__ iterates dir(module); honoring a module-level __dir__ fixes dir(unittest), so test_check__all__ now passes. Remove its stale expectedFailure marker to avoid an unexpected-success CI failure. Assisted-by: Claude Code:claude-opus-4-8 --- Lib/test/test_module/__init__.py | 2 -- Lib/test/test_support.py | 1 - Lib/test/test_zoneinfo/test_zoneinfo.py | 1 - crates/vm/src/builtins/module.rs | 4 ++++ 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_module/__init__.py b/Lib/test/test_module/__init__.py index d4ed61648..22132b01c 100644 --- a/Lib/test/test_module/__init__.py +++ b/Lib/test/test_module/__init__.py @@ -151,13 +151,11 @@ a = A(destroyed)""" if 'test.test_module.bad_getattr2' in sys.modules: del sys.modules['test.test_module.bad_getattr2'] - @unittest.expectedFailure # TODO: RUSTPYTHON def test_module_dir(self): import test.test_module.good_getattr as gga self.assertEqual(dir(gga), ['a', 'b', 'c']) del sys.modules['test.test_module.good_getattr'] - @unittest.expectedFailure # TODO: RUSTPYTHON def test_module_dir_errors(self): import test.test_module.bad_getattr as bga from test.test_module import bad_getattr2 diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 6b3aa466d..19ea6fafc 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -458,7 +458,6 @@ class TestSupport(unittest.TestCase): self.OtherClass, self.RefClass, ignore=ignore) self.assertEqual(set(), missing_items) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_check__all__(self): extra = {'tempdir'} not_exported = {'template'} diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index e9516c0b1..638b4c52a 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1970,7 +1970,6 @@ class TestModule(ZoneInfoTestBase): with self.assertRaises(AttributeError): self.module.NOATTRIBUTE - @unittest.expectedFailure # TODO: RUSTPYTHON; dir(self.module) should at least contain everything in __all__. def test_dir_contains_all(self): """dir(self.module) should at least contain everything in __all__.""" module_all_set = set(self.module.__all__) diff --git a/crates/vm/src/builtins/module.rs b/crates/vm/src/builtins/module.rs index e62967558..8fa5259f7 100644 --- a/crates/vm/src/builtins/module.rs +++ b/crates/vm/src/builtins/module.rs @@ -303,6 +303,10 @@ impl PyModule { let dict = dict_attr .downcast::() .map_err(|_| vm.new_type_error(".__dict__ is not a dictionary"))?; + // PEP 562: honor a module-level __dir__ if one is defined + if let Some(dir_func) = dict.get_item_opt(identifier!(vm, __dir__), vm)? { + return dir_func.call((), vm)?.try_to_value(vm); + } let attrs = dict.into_iter().map(|(k, _v)| k).collect(); Ok(attrs) }