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
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-19 20:34:04 +09:00
1 parent 75e77c30e3
commit 7f5cc0e35c
6 files changed
+46 -31

No files matched your search

-1
View File
@@ -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
-1
View File
@@ -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
+5 -1
View File
@@ -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
+24 -17
View File
@@ -53,7 +53,14 @@ impl PyObject {
} else if let Some(i) = self.downcast_ref::<PyInt>() {
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::<PyInt>() {
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::<PyFloat>() {
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))
}
})
}
+13 -7
View File
@@ -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<Option<(Option<PyRef<PyMemoryView>>, bool, Option<PyStrRef>)>> {
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) => {
+4 -4
View File
@@ -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::<PyDict>().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)?