From 7f5cc0e35c433921829211794f2a4a76bd69c4e9 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Mon, 19 Oct 2026 04:34:04 -0700 Subject: [PATCH] Align import, numeric and warning edge cases with Python 3.15 (#9210) * Align import, numeric and warning edge cases with Python 3.15 Carry four independent compatibility fixes from the 3.15 upgrade: preserve import cache lookup errors, handle surrogate module names, qualify numeric conversion diagnostics, and accept frozen warning globals. Match pinned CPython d220409a13b77de7a6c30abe16ea90db01528edc. Assisted-by: OpenAI Codex:model version unavailable * Preserve the integer contract of builtin module detection Return -1 for the separately initialized sys/builtins core modules, 1 for registered definitions and 0 for absent or unencodable names, matching pinned CPython import.c and its initialization table. Assisted-by: OpenAI Codex:model version unavailable * Normalize successful index conversions to exact integers Match PyNumber_Index after its internal protocol call: preserve exceptions and warning timing, then copy bool or int-subclass payloads to an exact int without calling __index__ again. Assisted-by: OpenAI Codex:model version unavailable * Retire passing surrogate-import expected failures Canonical cases now pass on the repaired import paths, including hosted Windows validation. Preserve their bodies and platform guards through update_lib. Assisted-by: OpenAI Codex:model version unavailable --- Lib/test/test_cmd_line.py | 1 - Lib/test/test_import/__init__.py | 1 - crates/vm/src/import.rs | 6 ++++- crates/vm/src/protocol/number.rs | 41 ++++++++++++++++++------------- crates/vm/src/stdlib/_imp.rs | 20 +++++++++------ crates/vm/src/stdlib/_warnings.rs | 8 +++--- 6 files changed, 46 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index b9d1b9493..0def1da45 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -1284,7 +1284,6 @@ class CmdLineTest(unittest.TestCase): assert_python_failure('-X', 'importtime=-1', '-c', code) assert_python_failure('-X', 'importtime=3', '-c', code) - @unittest.expectedFailure # TODO: RUSTPYTHON; -X importtime=2 timing output and surrogate module import exception semantics are not implemented correctly def test_import_time_unencodable_module_name(self): code = textwrap.dedent(""" import sys, types diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 5d8e764b5..3827d0e6d 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2028,7 +2028,6 @@ class ImportTracebackTests(unittest.TestCase): finally: del importlib.SourceLoader.exec_module - @unittest.expectedFailure # TODO: RUSTPYTHON; subprocess fails on Windows @unittest.skipUnless(TESTFN_UNENCODABLE, 'need TESTFN_UNENCODABLE') def test_unencodable_filename(self): # Issue #11619: The Python parser and the import machinery must not diff --git a/crates/vm/src/import.rs b/crates/vm/src/import.rs index 02c1048f6..fe9b449dd 100644 --- a/crates/vm/src/import.rs +++ b/crates/vm/src/import.rs @@ -573,7 +573,11 @@ pub(crate) fn import_module_level( crate::lazy_import::clear_submodule(&abs_name, true, vm)?; m } - _ => find_and_load(&abs_name, "_find_and_load", vm)?, + Ok(_) => find_and_load(&abs_name, "_find_and_load", vm)?, + Err(error) if error.fast_isinstance(vm.ctx.exceptions.key_error) => { + find_and_load(&abs_name, "_find_and_load", vm)? + } + Err(error) => return Err(error), }; // Handle fromlist diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index db14a08d4..ce8251b61 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -53,7 +53,14 @@ impl PyObject { } else if let Some(i) = self.downcast_ref::() { Some(Ok(vm.ctx.new_bigint(i.as_bigint()))) } else { - self.number().index(vm) + self.number().index(vm).map(|result| { + let result = result?; + if result.class().is(vm.ctx.types.int_type) { + Ok(result) + } else { + Ok(vm.ctx.new_bigint(result.as_bigint())) + } + }) } } @@ -703,21 +710,22 @@ and may be removed in a future version of Python.", } let ret_class = ret.class().to_owned(); + let message = format!( + "{}.__index__() must return an int, not {}", + self.class().fully_qualified_name(vm)?, + ret_class.fully_qualified_name(vm)? + ); if let Some(ret) = ret.downcast_ref::() { let msg = format!( - "__index__ returned non-int (type {}). \ + "{message}. \ The ability to return an instance of a strict subclass of int is deprecated, \ -and may be removed in a future version of Python.", - ret_class.slot_name() +and may be removed in a future version of Python." ); _warnings::warn(vm.ctx.exceptions.deprecation_warning, msg, 1, vm)?; Ok(ret.to_owned()) } else { - Err(vm.new_type_error(format!( - "__index__ returned non-int (type {})", - ret_class.slot_name() - ))) + Err(vm.new_type_error(message)) } }) } @@ -732,23 +740,22 @@ and may be removed in a future version of Python.", } let ret_class = ret.class().to_owned(); + let message = format!( + "{}.__float__() must return a float, not {}", + self.class().fully_qualified_name(vm)?, + ret_class.fully_qualified_name(vm)? + ); if let Some(ret) = ret.downcast_ref::() { let msg = format!( - "{}.__float__ returned non-float (type {}). \ + "{message}. \ The ability to return an instance of a strict subclass of float is deprecated, \ -and may be removed in a future version of Python.", - self.class().slot_name(), - ret_class.slot_name() +and may be removed in a future version of Python." ); _warnings::warn(vm.ctx.exceptions.deprecation_warning, msg, 1, vm)?; Ok(ret.to_owned()) } else { - Err(vm.new_type_error(format!( - "{}.__float__ returned non-float (type {})", - self.class().slot_name(), - ret_class.slot_name() - ))) + Err(vm.new_type_error(message)) } }) } diff --git a/crates/vm/src/stdlib/_imp.rs b/crates/vm/src/stdlib/_imp.rs index 54357d63c..338cd567f 100644 --- a/crates/vm/src/stdlib/_imp.rs +++ b/crates/vm/src/stdlib/_imp.rs @@ -243,13 +243,18 @@ mod _imp { } #[pyfunction] - fn is_builtin(name: PyUtf8StrRef, vm: &VirtualMachine) -> bool { - vm.state.module_defs.contains_key(name.as_str()) + fn is_builtin(name: PyStrRef, vm: &VirtualMachine) -> i32 { + match name.to_str() { + Some("sys" | "builtins") => -1, + Some(name) => i32::from(vm.state.module_defs.contains_key(name)), + None => 0, + } } #[pyfunction] - fn is_frozen(name: PyUtf8StrRef, vm: &VirtualMachine) -> bool { - super::find_frozen(name.as_str(), vm).is_ok() + fn is_frozen(name: PyStrRef, vm: &VirtualMachine) -> bool { + name.to_str() + .is_some_and(|name| super::find_frozen(name, vm).is_ok()) } #[pyfunction] @@ -481,7 +486,7 @@ mod _imp { #[derive(FromArgs)] struct FindFrozenArgs { #[pyarg(positional)] - name: PyUtf8StrRef, + name: PyStrRef, #[pyarg(named, default)] withdata: bool, } @@ -493,8 +498,9 @@ mod _imp { vm: &VirtualMachine, ) -> PyResult>, bool, Option)>> { let FindFrozenArgs { name, withdata } = args; - - let name_str = name.as_str(); + let Some(name_str) = name.to_str() else { + return Ok(None); + }; let info = match super::find_frozen(name_str, vm) { Ok(info) => info, Err(FrozenError::NotFound | FrozenError::Disabled | FrozenError::BadName) => { diff --git a/crates/vm/src/stdlib/_warnings.rs b/crates/vm/src/stdlib/_warnings.rs index ddbb38bbd..c92fd559b 100644 --- a/crates/vm/src/stdlib/_warnings.rs +++ b/crates/vm/src/stdlib/_warnings.rs @@ -21,7 +21,7 @@ pub fn warn( mod _warnings { use crate::{ AsObject, PyObject, PyObjectRef, PyResult, VirtualMachine, - builtins::{PyDict, PyDictRef, PyListRef, PyStrRef, PyTupleRef, PyTypeRef}, + builtins::{PyAnyDict, PyDictRef, PyListRef, PyStrRef, PyTupleRef, PyTypeRef}, convert::TryFromObject, function::OptionalArg, }; @@ -180,10 +180,10 @@ mod _warnings { let source_line = if let Some(mg) = args.module_globals.into_option() { if vm.is_none(&mg) { None - } else if mg.downcast_ref::().is_none() { + } else if PyAnyDict::from_object(&mg).is_none() { return Err(vm.new_type_error(format!( - "module_globals must be a dict, not '{}'", - mg.class().name() + "module_globals must be a dict or a frozendict, not {}", + mg.class().fully_qualified_name(vm)? ))); } else { crate::warn::get_source_line(&mg, args.lineno, vm)?