Align mapping boundaries with Python 3.15 final (#9128)

Support frozen mappings in str.maketrans and native JSON encoding, preserve integer translation keys, and protect mappingproxy dictionaries from unknown reflected comparisons.

Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-15 10:31:45 +09:00
1 parent ba54cce23a
commit 3ab5c0c2f2
5 files changed
+43 -24

No files matched your search

+10 -7
View File
@@ -5,7 +5,8 @@ mod _json {
use crate::vm::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{
PyBaseExceptionRef, PyDict, PyFloat, PyInt, PyList, PyStr, PyStrRef, PyTuple, PyType,
PyAnyDict, PyBaseExceptionRef, PyDict, PyFloat, PyInt, PyList, PyStr, PyStrRef,
PyTuple, PyType,
},
convert::ToPyResult,
function::{IntoFuncArgs, OptionalArg},
@@ -1126,18 +1127,20 @@ mod _json {
Ok(())
}
/// Fetch `dct.items()` for encoding. For an exact `dict` this reads
/// Fetch `dct.items()` for encoding. For an exact `dict` or `frozendict` this reads
/// the native table directly (matching CPython's fast path); for a
/// `dict` subclass (e.g. `collections.OrderedDict`) it calls the
/// subclass (e.g. `collections.OrderedDict`) it calls the
/// real (possibly overridden) `.items()` method instead, so a
/// subclass's custom ordering/behavior is respected.
fn dict_items(
&self,
obj: &PyObject,
dict: &Py<PyDict>,
dict: &PyAnyDict,
vm: &VirtualMachine,
) -> PyResult<Vec<(PyObjectRef, PyObjectRef)>> {
if obj.class().is(vm.ctx.types.dict_type) {
if obj.class().is(vm.ctx.types.dict_type)
|| obj.class().is(vm.ctx.types.frozendict_type)
{
return Ok(dict.items_vec());
}
let items_obj = obj.get_attr("items", vm)?.call((), vm)?;
@@ -1155,7 +1158,7 @@ mod _json {
fn encode_dict(
&self,
obj: &PyObject,
dict: &Py<PyDict>,
dict: &PyAnyDict,
level: isize,
out: &mut Wtf8Buf,
vm: &VirtualMachine,
@@ -1289,7 +1292,7 @@ mod _json {
} else if let Some(tuple) = obj.downcast_ref::<PyTuple>() {
let slice = tuple.as_slice();
self.encode_list(obj, level, out, vm, || slice.len(), |i| slice[i].clone())
} else if let Some(dict) = obj.downcast_ref::<PyDict>() {
} else if let Some(dict) = PyAnyDict::from_object(obj) {
self.encode_dict(obj, dict, level, out, vm)
} else {
self.encode_default(obj, level, out, vm)
+20 -3
View File
@@ -2,12 +2,13 @@ use super::{PyDict, PyDictRef, PyGenericAlias, PyList, PyTuple, PyType, PyTypeRe
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
atomic_func,
class::PyClassImpl,
class::{PyClassImpl, StaticType},
common::{hash, lock::LazyLock},
convert::ToPyObject,
function::{ArgMapping, Either, OptionalArg, PyComparisonValue},
object::{Traverse, TraverseFn},
protocol::{PyMappingMethods, PyNumberMethods, PySequenceMethods},
stdlib::PyOrderedDict,
types::{
AsMapping, AsNumber, AsSequence, Constructor, Hashable, Iterable, PyComparisonOp,
Representable,
@@ -202,8 +203,24 @@ impl Py<PyMappingProxy> {
if !matches!(op, PyComparisonOp::Eq | PyComparisonOp::Ne) {
return Ok(Either::B(PyComparisonValue::NotImplemented));
}
let obj = zelf.to_object(vm)?;
// Equality comparisons preserve the underlying mapping's result.
let mut obj = zelf.to_object(vm)?;
// Unknown comparison methods must not receive a mutable dictionary
// protected by the proxy. Class namespaces are already copied by
// to_object; other exact dicts need the same protection here.
if matches!(&zelf.mapping, MappingProxyInner::Mapping(_))
&& obj.class().is(vm.ctx.types.dict_type)
&& !(other.class().is(vm.ctx.types.dict_type)
|| other.class().is(vm.ctx.types.frozendict_type)
|| other.class().is(vm.ctx.types.mappingproxy_type)
|| (other.downcastable::<PyOrderedDict>()
&& other.class().is(PyOrderedDict::static_type())))
{
obj = obj
.downcast_ref::<PyDict>()
.unwrap()
.copy()
.into_pyobject(vm);
}
obj.rich_compare(other.to_owned(), op, vm).map(Either::A)
}
+11 -12
View File
@@ -1,5 +1,6 @@
use super::{
PositionIterInternal, PyBytesRef, PyDict, PyList, PyTuple, PyTupleRef, PyType, PyTypeRef,
PositionIterInternal, PyAnyDict, PyBytesRef, PyDict, PyList, PyTuple, PyTupleRef, PyType,
PyTypeRef,
int::PyInt,
iter::{IterStatus, builtins_iter},
};
@@ -1580,17 +1581,15 @@ impl Py<PyStr> {
)),
}
} else {
// dict_str must be a dict
match dict_or_str.downcast::<PyDict>() {
Ok(dict) => {
// The single-argument form accepts exact dict and frozendict objects.
match PyAnyDict::from_object(&dict_or_str).filter(|_| {
dict_or_str.class().is(vm.ctx.types.dict_type)
|| dict_or_str.class().is(vm.ctx.types.frozendict_type)
}) {
Some(dict) => {
for (key, val) in dict {
// FIXME: ints are key-compatible
if let Some(num) = key.downcast_ref::<PyInt>() {
new_dict.set_item(
&*num.as_bigint().to_i32().to_pyobject(vm),
val,
vm,
)?;
if key.downcast_ref::<PyInt>().is_some() {
new_dict.set_item(&*key, val, vm)?;
} else if let Some(string) = key.downcast_ref::<PyStr>() {
if string.len() == 1 {
let num_value =
@@ -1609,7 +1608,7 @@ impl Py<PyStr> {
}
Ok(new_dict.to_pyobject(vm))
}
_ => Err(vm.new_value_error(
_ => Err(vm.new_type_error(
"if you give only one argument to maketrans it must be a dict",
)),
}
@@ -138,7 +138,7 @@ pub(crate) mod ordered_dict {
traverse = "manual"
)]
#[derive(Debug)]
struct PyOrderedDict {
pub(crate) struct PyOrderedDict {
dict: PyDict,
links: PyMutex<ODictLinks>,
}
+1 -1
View File
@@ -3,7 +3,7 @@ mod _abc;
pub(crate) mod _ast;
mod _codecs;
mod _collections;
pub(crate) use _collections::ordered_dict::ordered_dict::PyOrderedDictItems;
pub(crate) use _collections::ordered_dict::ordered_dict::{PyOrderedDict, PyOrderedDictItems};
mod _functools;
mod _imp;
pub mod _io;