Adapt essential snippet contracts to Python 3.15 final

Preserve existing coverage while following verified final behavior for script metadata, builtin signatures and loading, AST fixture construction, memoryview diagnostics, mmap search bounds, and struct format errors. The unchanged final comparator exposes the earlier expectations; focused native baseline failures establish the required implementation changes.

Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
youknowdot committed 2026-10-10 00:53:45 +09:00
1 parent 6c2529800a
commit 519370bece
14 files changed
+91 -59

No files matched your search

+10 -3
View File
@@ -854,9 +854,16 @@ mod mmap {
fn get_find_range(&self, options: &FindOptions) -> (usize, usize) {
let size = self.__len__();
let start = options
.start
.map_or_else(|| self.pos(), |start| start.saturated_at(size));
let start = options.start.map_or_else(
|| self.pos(),
|start| {
if start < 0 {
start.saturated_at(size)
} else {
start as usize
}
},
);
let end = options.end.map_or(size, |end| end.saturated_at(size));
(start, end)
}
+11 -20
View File
@@ -30,33 +30,18 @@ pub(crate) mod _struct {
impl TryFromObject for IntoStructFormatBytes {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
// CPython turns str to bytes (via str.encode('ascii')) but we keep str.
// The error reporting for non-ASCII input still matches CPython:
// - str input with non-ASCII char: UnicodeEncodeError, the same exception
// str.encode('ascii') would produce.
// - bytes input with non-ASCII byte: struct.error("bad char in struct format"),
// matching CPython where bytes are passed through to the format parser.
// Keep ASCII formats as str and reject non-ASCII str or bytes with
// the same ValueError as CPython's format parser.
let fmt = match_class!(match obj {
s @ PyStr => {
if !s.isascii() {
let start = s
.as_wtf8()
.code_points()
.position(|cp| !cp.to_char().is_some_and(|c| c.is_ascii()))
.unwrap_or(0);
return Err(vm.new_unicode_encode_error(
vm.ctx.new_str("ascii"),
s,
start,
start + 1,
vm.ctx.new_str("ordinal not in range(128)"),
));
return Err(vm.new_value_error("non-ASCII character in struct format"));
}
s
}
b @ PyBytes => {
let ascii_str = ascii::AsciiStr::from_ascii(&b)
.map_err(|_| new_struct_error(vm, "bad char in struct format"))?;
.map_err(|_| vm.new_value_error("non-ASCII character in struct format"))?;
vm.ctx.new_str(ascii_str)
}
other =>
@@ -315,7 +300,13 @@ pub(crate) mod _struct {
#[pygetset]
fn format(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
Ok(zelf.ready(vm)?.format.clone())
zelf.inner
.read()
.as_ref()
.map(|inner| inner.format.clone())
.ok_or_else(|| {
vm.new_no_attribute_error(zelf.as_object().to_owned(), vm.ctx.new_str("format"))
})
}
// The size an uninitialized `Struct` reports, which no format has
+1 -1
View File
@@ -747,7 +747,7 @@ impl PyMemoryView {
let format_str = format.as_str();
let Some(dest_char) = Self::native_fmtchar(format_str) else {
return Err(vm.new_value_error(
"memoryview: destination format must be a native single character format prefixed with an optional '@'",
"memoryview: destination format must be a native format prefixed with an optional '@'",
));
};
// One side has to be bytes. Casting between two item types would
+40 -6
View File
@@ -194,7 +194,10 @@ fn find_frozen(name: &str, vm: &VirtualMachine) -> Result<FrozenModule, FrozenEr
mod _imp {
use crate::{
AsObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult, VirtualMachine,
builtins::{PyBytesRef, PyCode, PyDict, PyMemoryView, PyModule, PyStrRef, PyUtf8StrRef},
builtins::{
PyBytesRef, PyCode, PyDict, PyImportError, PyMemoryView, PyModule, PyStrRef,
PyUtf8StrRef,
},
import, version,
};
@@ -249,15 +252,46 @@ mod _imp {
#[pyfunction]
fn create_builtin(spec: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let sys_modules = vm.sys_module.get_attr("modules", vm).unwrap();
let name: PyUtf8StrRef = spec.get_attr("name", vm)?.try_into_value(vm)?;
let name_obj = spec.get_attr("name", vm)?;
let name: PyStrRef = name_obj.downcast().map_err(|obj| {
vm.new_type_error(format!(
"name must be string, not {:.200}",
obj.class().name()
))
})?;
if name.is_empty() {
return Err(vm.new_value_error("name must not be empty"));
}
// Builtin module names use ASCII even when an unknown name is supplied.
if !name.as_bytes().is_ascii() {
vm.state
.codec_registry
.encode_text(name.clone(), "ascii", None, vm)?;
}
let name_str = name.as_utf8().expect("ASCII module name").as_str();
if name_str.contains('\0') {
return Err(vm.new_value_error("embedded null character"));
}
if !matches!(name_str, "sys" | "builtins") && !vm.state.module_defs.contains_key(name_str) {
let exc = vm.new_payload_exception::<PyImportError>(
vm.ctx.exceptions.module_not_found_error.to_owned(),
vec![
vm.ctx
.new_str(format!("{name_str} module not found"))
.into(),
]
.into(),
)?;
exc.as_object().set_attr("name", name.clone(), vm)?;
return Err(exc.upcast());
}
// Check sys.modules first
let sys_modules = vm.sys_module.get_attr("modules", vm)?;
// Only registered builtins (or the two core modules) may use this cache.
if let Ok(module) = sys_modules.get_item(&*name, vm) {
return Ok(module);
}
let name_str = name.as_str();
if let Some(&def) = vm.state.module_defs.get(name_str) {
// Phase 1: Create module (use create slot if provided, else default creation)
let module = if let Some(create) = def.slots.create {
@@ -274,7 +308,7 @@ mod _imp {
module.__init_methods(vm)?;
// Add to sys.modules BEFORE exec (critical for circular import handling)
sys_modules.set_item(name.as_pystr(), module.clone().into(), vm)?;
sys_modules.set_item(&*name, module.clone().into(), vm)?;
// Phase 2: Call exec slot (can safely import other modules now)
if let Some(exec) = def.slots.exec {
+4 -4
View File
@@ -40,8 +40,8 @@ mod builtins {
"can't compile() to bytecode when the `codegen` feature of rustpython is disabled";
#[pyfunction]
fn abs(x: PyObjectRef, vm: &VirtualMachine) -> PyResult {
vm._abs(&x)
fn abs(number: PyObjectRef, vm: &VirtualMachine) -> PyResult {
vm._abs(&number)
}
#[pyfunction]
@@ -70,8 +70,8 @@ mod builtins {
}
#[pyfunction]
fn bin(number: ArgIndex) -> String {
let number = number.into_int_ref();
fn bin(integer: ArgIndex) -> String {
let number = integer.into_int_ref();
let x = number.as_bigint();
if x.is_negative() {
format!("-0b{:b}", x.abs())
+2 -4
View File
@@ -1607,7 +1607,7 @@ impl VirtualMachine {
/// ```no_run
/// use rustpython_vm::Interpreter;
/// Interpreter::without_stdlib(Default::default()).enter(|vm| {
/// let bytes = std::fs::read("__pycache__/<input>.rustpython-315.pyc").unwrap();
/// let bytes = std::fs::read("__pycache__/<input>.cpython-315.pyc").unwrap();
/// let main_scope = vm.new_scope_with_main().unwrap();
/// vm.run_pyc_bytes(&bytes, main_scope);
/// });
@@ -2258,17 +2258,15 @@ impl VirtualMachine {
self.ctx.new_str(path).into(),
self,
)?;
module_dict.set_item(identifier!(self, __cached__), self.ctx.none(), self)?;
}
let result = run(&module_dict);
self.flush_io();
// Cleanup __file__ and __cached__ after execution
// Cleanup __file__ after execution
if set_file_name {
let _ = module_dict.del_item(identifier!(self, __file__), self);
let _ = module_dict.del_item(identifier!(self, __cached__), self);
}
result
+1 -1
View File
@@ -2,4 +2,4 @@ import sys
main_module = sys.modules["__main__"]
assert main_module.__file__.endswith("builtin___main__.py")
assert main_module.__cached__ is None
assert not hasattr(main_module, "__cached__")
+1 -1
View File
@@ -399,7 +399,7 @@ def test_cast_rejects_non_native_format():
memoryview(b"").cast(fmt)
raise AssertionError(f"cast accepted {fmt!r}")
except ValueError as e:
assert "native single character format" in str(e), e
assert "native format" in str(e), e
assert memoryview(bytes(8)).cast("@i").itemsize == 4
+2 -2
View File
@@ -19,7 +19,7 @@ else:
raise AssertionError("len() should not accept keyword arguments")
assert str(inspect.signature(len)) == "(obj, /)"
assert str(inspect.signature(abs)) == "(x, /)"
assert str(inspect.signature(abs)) == "(number, /)"
assert str(inspect.signature(hash)) == "(obj, /)"
assert str(inspect.signature(chr)) == "(i, /)"
assert str(inspect.signature(callable)) == "(obj, /)"
@@ -37,7 +37,7 @@ assert breakpoint_kinds == [
], breakpoint_kinds
# Parameter names follow CPython, so signatures are directly comparable.
assert str(inspect.signature(bin)) == "(number, /)"
assert str(inspect.signature(bin)) == "(integer, /)"
assert str(inspect.signature(ord)) == "(character, /)"
assert str(inspect.signature(divmod)) == "(x, y, /)"
assert str(inspect.signature(hasattr)) == "(obj, name, /)"
+4 -3
View File
@@ -69,12 +69,13 @@ import warnings
def _cyclic_cases():
# Box<Expr> descents
u = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0)
u = ast.UnaryOp(op=ast.Not(), operand=None, lineno=0, col_offset=0)
u.operand = u
yield "UnaryOp", u
b = ast.BinOp(
op=ast.Add(),
left=None,
right=ast.Constant(value=0, lineno=0, col_offset=0),
lineno=0,
col_offset=0,
@@ -82,11 +83,11 @@ def _cyclic_cases():
b.left = b
yield "BinOp", b
c = ast.Call(args=[], keywords=[], lineno=0, col_offset=0)
c = ast.Call(func=None, args=[], keywords=[], lineno=0, col_offset=0)
c.func = c
yield "Call", c
a = ast.Attribute(attr="x", ctx=ast.Load(), lineno=0, col_offset=0)
a = ast.Attribute(value=None, attr="x", ctx=ast.Load(), lineno=0, col_offset=0)
a.value = a
yield "Attribute", a
+2 -1
View File
@@ -23,7 +23,8 @@ imp_time = _imp.create_builtin(A)
# assert imp_time.sleep == import_time.sleep
B = FakeSpec("not existing module")
assert _imp.create_builtin(B) == None
with assert_raises(ModuleNotFoundError):
_imp.create_builtin(B)
_imp.exec_builtin(imp_time) == 0
+3 -3
View File
@@ -25,9 +25,9 @@ assert mapped.find(b"") == 0
assert mapped.rfind(b"") == 3
assert mapped.find(b"", 1, 2) == 1
assert mapped.rfind(b"", 1, 2) == 2
# offsets past the end are clamped and negative ones count from the end
assert mapped.find(b"", 5) == 3
assert mapped.rfind(b"", 5) == 3
# starts past the end find nothing and negative offsets count from the end
assert mapped.find(b"", 5) == -1
assert mapped.rfind(b"", 5) == -1
assert mapped.find(b"", -1) == 2
assert mapped.rfind(b"", 0, -1) == 2
# an inverted range holds nothing, not even the empty subsequence
+9 -9
View File
@@ -78,19 +78,18 @@ assert struct.error.__module__ == "struct"
assert struct.error.__name__ == "error"
# Non-ASCII format string: error type matches CPython.
# str → UnicodeEncodeError (encoding='ascii')
# bytes → struct.error
# str and bytes → ValueError
try:
struct.Struct("\udc00")
except UnicodeEncodeError as e:
assert e.encoding == "ascii"
except ValueError as e:
assert str(e) == "non-ASCII character in struct format"
else:
raise AssertionError("expected UnicodeEncodeError")
raise AssertionError("expected ValueError")
with assert_raises(UnicodeEncodeError):
with assert_raises(ValueError):
struct.Struct("한")
with assert_raises(struct.error):
with assert_raises(ValueError):
struct.Struct(b"\xff")
@@ -128,7 +127,7 @@ assert s.unpack(b"\x00\x01\x00\x02") == (1, 2)
# A format that cannot be read leaves the Struct as it was.
for bad in ("\udc00", "$"):
with assert_raises((UnicodeEncodeError, struct.error)):
with assert_raises((ValueError, struct.error)):
s.__init__(bad)
assert s.format == ">hh"
assert s.pack(1, 2) == b"\x00\x01\x00\x02"
@@ -145,8 +144,9 @@ assert BigShort().pack(12345) == b"\x30\x39"
# Until __init__ runs there is no format to answer with.
blank = struct.Struct.__new__(struct.Struct)
assert blank.size == -1
with assert_raises(AttributeError):
blank.format
for call in (
lambda: blank.format,
lambda: blank.pack(1),
lambda: blank.unpack(b"aa"),
lambda: blank.unpack_from(b"aaaa"),
+1 -1
View File
@@ -29,7 +29,7 @@ if sys.implementation.name == "rustpython":
assert sys.version_info == (3, 15, 0, "alpha", 0)
assert sys.hexversion == 0x030F00A0
assert sys.version.startswith("3.15.0.alpha (")
assert sys.implementation.cache_tag == "rustpython-315"
assert sys.implementation.cache_tag == "cpython-315"
assert sys.getfilesystemencoding() == "utf-8"
assert sys.getfilesystemencodeerrors().startswith("surrogate")