From 04d9990616b836da8ead2c8043d8cdbe524f71f6 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Mon, 5 Oct 2026 01:29:00 -0700 Subject: [PATCH] Add the eager-runtime hook required by Python 3.15 importlib (#8946) * Add the eager-runtime importlib lazy-attribute hook Expose _imp._set_lazy_attributes(modobj, name, /), used by CPython 3.15 importlib after eager loading. Match its empty pending-lazy-module registry behavior, including name type and string-subclass hashing checks. This does not implement lazy imports or change the target version or bundled libraries. Assisted-by: Codex:model-version-unavailable * Consolidate lazy import hook tests into stdlib_imp Move the distinct direct-hook argument and hashing contracts into the existing _imp snippet and remove the separate file. Preserve every pre-existing assertion and the CPython version guard; canonical 3.15 lazy-import tests already cover feature-level registry and reification behavior. Assisted-by: Codex:model-version-unavailable --- crates/vm/src/stdlib/_imp.rs | 15 +++++++++- extra_tests/snippets/stdlib_imp.py | 44 +++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/crates/vm/src/stdlib/_imp.rs b/crates/vm/src/stdlib/_imp.rs index a2bb2a0ab..b2b278311 100644 --- a/crates/vm/src/stdlib/_imp.rs +++ b/crates/vm/src/stdlib/_imp.rs @@ -193,7 +193,7 @@ fn find_frozen(name: &str, vm: &VirtualMachine) -> Result PyResult<()> { + // Even an empty set checks the hash of a str subclass. + name.as_object().hash(vm)?; + Ok(()) + } + #[pyfunction] fn is_builtin(name: PyUtf8StrRef, vm: &VirtualMachine) -> bool { vm.state.module_defs.contains_key(name.as_str()) diff --git a/extra_tests/snippets/stdlib_imp.py b/extra_tests/snippets/stdlib_imp.py index 9fd5f8a36..8153790c3 100644 --- a/extra_tests/snippets/stdlib_imp.py +++ b/extra_tests/snippets/stdlib_imp.py @@ -1,7 +1,7 @@ import _imp import time as import_time -from testutils import assert_raises +from testutils import assert_raises, skip_if_unsupported assert _imp.is_builtin("time") == True assert _imp.is_builtin("os") == False @@ -42,3 +42,45 @@ data, ispkg, origname = _imp.find_frozen("__hello__", withdata=True) assert ispkg is False assert origname == "__hello__" assert _imp.get_frozen_object("__hello__", data).co_name == "" + + +def test_lazy_attributes_eager_registry(): + # CPython accepts any first argument and a string name. In an eager-only + # interpreter there are no pending lazy module names to remove. + for modobj in (None, object(), {}, 42): + assert _imp._set_lazy_attributes(modobj, "package.module") is None + assert _imp._set_lazy_attributes(modobj, "\ud800") is None + + for name in (None, 1, b"package.module", [], {}): + with assert_raises(TypeError): + _imp._set_lazy_attributes(None, name) + + with assert_raises(TypeError): + _imp._set_lazy_attributes(modobj=None, name="package.module") + + class ModuleName(str): + hashes = 0 + + def __hash__(self): + self.hashes += 1 + return 42 + + name = ModuleName("package.module") + assert _imp._set_lazy_attributes(None, name) is None + assert name.hashes == 1 + + class UnhashableName(str): + __hash__ = None + + with assert_raises(TypeError): + _imp._set_lazy_attributes(None, UnhashableName("package.module")) + + class HashErrorName(str): + def __hash__(self): + raise RuntimeError("name hash failed") + + with assert_raises(RuntimeError): + _imp._set_lazy_attributes(None, HashErrorName("package.module")) + + +skip_if_unsupported(3, 15, test_lazy_attributes_eager_registry)