mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Combine current main and validated mapping prerequisite
Assisted-by: OpenAI Codex: model identifier unavailable
This commit is contained in:
commit
63c96dcecf
36 files changed
+3173
-395
No files matched your search
Vendored
+25
-10
@@ -100,14 +100,14 @@ def copy(x):
|
||||
return _reconstruct(x, None, *rv)
|
||||
|
||||
|
||||
_copy_atomic_types = {types.NoneType, int, float, bool, complex, str, tuple,
|
||||
bytes, frozenset, type, range, slice, property,
|
||||
_copy_atomic_types = frozenset({types.NoneType, int, float, bool, complex, str, tuple,
|
||||
bytes, frozendict, frozenset, type, range, slice, property,
|
||||
types.BuiltinFunctionType, types.EllipsisType,
|
||||
types.NotImplementedType, types.FunctionType, types.CodeType,
|
||||
weakref.ref, super}
|
||||
_copy_builtin_containers = {list, dict, set, bytearray}
|
||||
weakref.ref, super})
|
||||
_copy_builtin_containers = frozenset({list, dict, set, bytearray})
|
||||
|
||||
def deepcopy(x, memo=None, _nil=[]):
|
||||
def deepcopy(x, memo=None):
|
||||
"""Deep copy operation on arbitrary Python objects.
|
||||
|
||||
See the module's __doc__ string for more info.
|
||||
@@ -122,8 +122,8 @@ def deepcopy(x, memo=None, _nil=[]):
|
||||
if memo is None:
|
||||
memo = {}
|
||||
else:
|
||||
y = memo.get(d, _nil)
|
||||
if y is not _nil:
|
||||
y = memo.get(d, None)
|
||||
if y is not None:
|
||||
return y
|
||||
|
||||
copier = _deepcopy_dispatch.get(cls)
|
||||
@@ -162,9 +162,9 @@ def deepcopy(x, memo=None, _nil=[]):
|
||||
_keep_alive(x, memo) # Make sure x lives at least as long as d
|
||||
return y
|
||||
|
||||
_atomic_types = {types.NoneType, types.EllipsisType, types.NotImplementedType,
|
||||
_atomic_types = frozenset({types.NoneType, types.EllipsisType, types.NotImplementedType,
|
||||
int, float, bool, complex, bytes, str, types.CodeType, type, range,
|
||||
types.BuiltinFunctionType, types.FunctionType, weakref.ref, property}
|
||||
types.BuiltinFunctionType, types.FunctionType, weakref.ref, property})
|
||||
|
||||
_deepcopy_dispatch = d = {}
|
||||
|
||||
@@ -203,6 +203,21 @@ def _deepcopy_dict(x, memo, deepcopy=deepcopy):
|
||||
return y
|
||||
d[dict] = _deepcopy_dict
|
||||
|
||||
def _deepcopy_frozendict(x, memo, deepcopy=deepcopy):
|
||||
y = {}
|
||||
for key, value in x.items():
|
||||
y[deepcopy(key, memo)] = deepcopy(value, memo)
|
||||
|
||||
# We're not going to put the frozendict in the memo, but it's still
|
||||
# important we check for it, in case the frozendict contains recursive
|
||||
# mutable structures.
|
||||
try:
|
||||
return memo[id(x)]
|
||||
except KeyError:
|
||||
pass
|
||||
return frozendict(y)
|
||||
d[frozendict] = _deepcopy_frozendict
|
||||
|
||||
def _deepcopy_method(x, memo): # Copy instance methods
|
||||
return type(x)(x.__func__, deepcopy(x.__self__, memo))
|
||||
d[types.MethodType] = _deepcopy_method
|
||||
@@ -230,7 +245,7 @@ def _reconstruct(x, memo, func, args,
|
||||
*, deepcopy=deepcopy):
|
||||
deep = memo is not None
|
||||
if deep and args:
|
||||
args = (deepcopy(arg, memo) for arg in args)
|
||||
args = [deepcopy(arg, memo) for arg in args]
|
||||
y = func(*args)
|
||||
if deep:
|
||||
memo[id(x)] = y
|
||||
|
||||
Vendored
-4
@@ -4394,8 +4394,6 @@ class AbstractPickleTests:
|
||||
def test_fast_save_enter_set(self):
|
||||
self.fast_save_enter(lambda i: set([i]))
|
||||
|
||||
# TODO: RUSTPYTHON; frozendict support: https://github.com/RustPython/RustPython/pull/8951
|
||||
@unittest.expectedFailure
|
||||
def test_fast_save_enter_frozendict(self):
|
||||
if self.py_version < (3, 15):
|
||||
self.skipTest('need frozendict')
|
||||
@@ -4443,8 +4441,6 @@ class AbstractPickleTests:
|
||||
depth=FAST_NESTING_LIMIT+1,
|
||||
compare_equal=False)
|
||||
|
||||
# TODO: RUSTPYTHON; frozendict support: https://github.com/RustPython/RustPython/pull/8951
|
||||
@unittest.expectedFailure
|
||||
def test_deep_nested_struct_frozendict(self):
|
||||
if self.py_version < (3, 15):
|
||||
self.skipTest('need frozendict')
|
||||
|
||||
Vendored
-2
@@ -951,7 +951,6 @@ class DictTest(unittest.TestCase):
|
||||
'f': None, 'g': None, 'h': None}
|
||||
d = {}
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_container_iterator(self):
|
||||
# Bug #3680: tp_traverse was not implemented for dictiter and
|
||||
# dictview objects.
|
||||
@@ -1264,7 +1263,6 @@ class DictTest(unittest.TestCase):
|
||||
support.check_free_after_iterating(self, lambda d: iter(d.values()), dict)
|
||||
support.check_free_after_iterating(self, lambda d: iter(d.items()), dict)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_equal_operator_modifying_operand(self):
|
||||
# test fix for seg fault reported in bpo-27945 part 3.
|
||||
class X():
|
||||
|
||||
+1
-1
@@ -742,7 +742,7 @@ plain ol' Python and is guaranteed to be available.
|
||||
|
||||
>>> import builtins
|
||||
>>> tests = doctest.DocTestFinder().find(builtins)
|
||||
>>> 750 < len(tests) < 800 # approximate number of objects with docstrings
|
||||
>>> 750 < len(tests) < 850 # approximate number of objects with docstrings
|
||||
True
|
||||
>>> real_tests = [t for t in tests if len(t.examples) > 0]
|
||||
>>> len(real_tests) # objects that actually have doctests
|
||||
|
||||
Vendored
+1
-1
@@ -135,7 +135,7 @@ class RebindBuiltinsTests(unittest.TestCase):
|
||||
self.assertEqual(foo(), 7)
|
||||
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@unittest.expectedFailureIf(sys.getrecursionlimit() == 256, "TODO: RUSTPYTHON; default debug recursion limit is 256")
|
||||
@skip_wasi_stack_overflow()
|
||||
def test_load_global_specialization_failure_keeps_oparg(self):
|
||||
# https://github.com/python/cpython/issues/91625
|
||||
|
||||
Vendored
+12
@@ -107,6 +107,10 @@ class PyPicklerTests(AbstractPickleTests, unittest.TestCase):
|
||||
def test_c_methods(self):
|
||||
return super().test_c_methods()
|
||||
|
||||
@unittest.expectedFailureIf(sys.getrecursionlimit() == 256, "TODO: RUSTPYTHON; default debug recursion limit is 256")
|
||||
def test_deep_nested_struct_frozendict(self):
|
||||
return super().test_deep_nested_struct_frozendict()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -408,6 +412,10 @@ if has_c_implementation:
|
||||
pickler = _pickle.Pickler
|
||||
unpickler = _pickle.Unpickler
|
||||
|
||||
@unittest.expectedSuccess # TODO: RUSTPYTHON; native pickler passes at the default debug recursion limit
|
||||
def test_deep_nested_struct_frozendict(self):
|
||||
return super().test_deep_nested_struct_frozendict()
|
||||
|
||||
class CPersPicklerTests(PyPersPicklerTests):
|
||||
pickler = _pickle.Pickler
|
||||
unpickler = _pickle.Unpickler
|
||||
@@ -421,6 +429,10 @@ if has_c_implementation:
|
||||
pickler = _pickle.Pickler
|
||||
unpickler = pickle._Unpickler
|
||||
|
||||
@unittest.expectedSuccess # TODO: RUSTPYTHON; native pickler passes at the default debug recursion limit
|
||||
def test_deep_nested_struct_frozendict(self):
|
||||
return super().test_deep_nested_struct_frozendict()
|
||||
|
||||
class DumpPickle_CLoadPickle(PyPicklerTests):
|
||||
pickler = pickle._Pickler
|
||||
unpickler = _pickle.Unpickler
|
||||
|
||||
@@ -45,7 +45,6 @@ class AnyTest(unittest.TestCase):
|
||||
mock.assert_called_with(ANY, foo=ANY)
|
||||
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_any_mock_calls_comparison_order(self):
|
||||
mock = Mock()
|
||||
class Foo(object):
|
||||
|
||||
@@ -3,10 +3,10 @@ use crate::pystate::with_vm;
|
||||
use crate::unicodeobject::decode_fsdefault_and_size;
|
||||
use crate::util::{CStrExt, FfiPtrExt};
|
||||
use core::ffi::{CStr, c_char, c_int};
|
||||
use rustpython_vm::builtins::{PyCode, PyDict};
|
||||
use rustpython_vm::builtins::{PyAnyDict, PyCode};
|
||||
use rustpython_vm::function::ArgMapping;
|
||||
use rustpython_vm::scope::Scope;
|
||||
use rustpython_vm::{AsObject, PyObject, TryFromObject};
|
||||
use rustpython_vm::{AsObject, PyObject, PyRef, TryFromObject};
|
||||
use rustpython_vm::{PyObjectRef, version};
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -32,12 +32,17 @@ pub unsafe extern "C" fn PyEval_EvalCode(
|
||||
) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let code = unsafe { co.assume_borrowed_and_cast::<PyCode>(vm) }?;
|
||||
let globals = unsafe { globals.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let globals = PyRef::<PyAnyDict>::try_from_object(
|
||||
vm,
|
||||
unsafe { globals.assume_borrowed() }.to_owned(),
|
||||
)?;
|
||||
let locals = unsafe { locals.assume_borrowed_or_opt() }
|
||||
.map(|obj| ArgMapping::try_from_object(vm, obj.to_owned()))
|
||||
.transpose()?;
|
||||
|
||||
let scope = Scope::with_builtins(locals, globals.to_owned(), vm);
|
||||
let locals = locals.or_else(|| Some(ArgMapping::from_anydict_exact(globals.clone())));
|
||||
// Unlike Python's eval/exec, this API does not insert __builtins__.
|
||||
let scope = Scope::new(locals, globals);
|
||||
|
||||
vm.run_code_obj(code.to_owned(), scope)
|
||||
})
|
||||
|
||||
@@ -4,9 +4,8 @@ use crate::pystate::with_vm;
|
||||
use crate::util::{CStrExt, FfiPtrExt};
|
||||
use core::ffi::{c_char, c_int};
|
||||
use core::ptr::NonNull;
|
||||
use rustpython_vm::AsObject;
|
||||
use rustpython_vm::PyPayload;
|
||||
use rustpython_vm::builtins::PyDict;
|
||||
use rustpython_vm::builtins::{PyAnyDict, PyDict, PyFrozenDict};
|
||||
use rustpython_vm::{AsObject, Py, PyPayload, PyResult, VirtualMachine};
|
||||
|
||||
define_py_check!(fn PyDict_Check, types.dict_type);
|
||||
define_py_check!(exact fn PyDict_CheckExact, types.dict_type);
|
||||
@@ -14,6 +13,27 @@ define_py_check!(fn PyDictKeys_Check, types.dict_keys_type);
|
||||
define_py_check!(fn PyDictValues_Check, types.dict_values_type);
|
||||
define_py_check!(fn PyDictItems_Check, types.dict_items_type);
|
||||
|
||||
fn any_dict<'a>(dict: &'a PyObject, vm: &VirtualMachine) -> PyResult<&'a PyAnyDict> {
|
||||
PyAnyDict::from_object(dict)
|
||||
.ok_or_else(|| vm.new_system_error("bad argument to internal function"))
|
||||
}
|
||||
|
||||
fn writable_dict<'a>(
|
||||
dict: &'a PyObject,
|
||||
operation: &str,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<&'a Py<PyDict>> {
|
||||
dict.downcast_ref::<PyDict>().ok_or_else(|| {
|
||||
if dict.downcast_ref::<PyFrozenDict>().is_some() {
|
||||
vm.new_type_error(format!(
|
||||
"frozendict object does not support item {operation}"
|
||||
))
|
||||
} else {
|
||||
vm.new_system_error("bad argument to internal function")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn PyDict_New() -> *mut PyObject {
|
||||
with_vm(|vm| vm.ctx.new_dict())
|
||||
@@ -21,10 +41,10 @@ pub extern "C" fn PyDict_New() -> *mut PyObject {
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_Clear(dict: *mut PyObject) {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
dict.clear();
|
||||
Ok(())
|
||||
with_vm(|_vm| {
|
||||
if let Some(dict) = unsafe { dict.assume_borrowed() }.downcast_ref::<PyDict>() {
|
||||
dict.clear();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,7 +55,7 @@ pub unsafe extern "C" fn PyDict_SetItem(
|
||||
val: *mut PyObject,
|
||||
) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = writable_dict(unsafe { dict.assume_borrowed() }, "assignment", vm)?;
|
||||
let key = unsafe { key.assume_borrowed() };
|
||||
let value = unsafe { val.assume_borrowed() }.to_owned();
|
||||
dict.inner_setitem(key, value, vm)
|
||||
@@ -49,8 +69,8 @@ pub unsafe extern "C" fn PyDict_SetItemString(
|
||||
val: *mut PyObject,
|
||||
) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let key = unsafe { key.try_as_str(vm) }?;
|
||||
let dict = writable_dict(unsafe { dict.assume_borrowed() }, "assignment", vm)?;
|
||||
let value = unsafe { val.assume_borrowed() }.to_owned();
|
||||
dict.inner_setitem(key, value, vm)
|
||||
})
|
||||
@@ -59,12 +79,14 @@ pub unsafe extern "C" fn PyDict_SetItemString(
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_GetItem(dict: *mut PyObject, key: *mut PyObject) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let Some(dict) = PyAnyDict::from_object(unsafe { dict.assume_borrowed() }) else {
|
||||
return core::ptr::null_mut();
|
||||
};
|
||||
let key = unsafe { key.assume_borrowed() };
|
||||
|
||||
match dict.inner_getitem_opt(key, vm) {
|
||||
Ok(Some(value)) => Ok(value.as_object().as_raw().cast_mut()),
|
||||
Ok(None) | Err(_) => Ok(core::ptr::null_mut()),
|
||||
Ok(Some(value)) => value.as_object().as_raw().cast_mut(),
|
||||
Ok(None) | Err(_) => core::ptr::null_mut(),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -75,7 +97,7 @@ pub unsafe extern "C" fn PyDict_GetItemWithError(
|
||||
key: *mut PyObject,
|
||||
) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = any_dict(unsafe { dict.assume_borrowed() }, vm)?;
|
||||
let key = unsafe { key.assume_borrowed() };
|
||||
|
||||
if let Some(value) = dict.inner_getitem_opt(key, vm)? {
|
||||
@@ -92,12 +114,16 @@ pub unsafe extern "C" fn PyDict_GetItemString(
|
||||
key: *const c_char,
|
||||
) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let key = unsafe { key.try_as_str(vm) }?;
|
||||
let Ok(key) = (unsafe { key.try_as_str(vm) }) else {
|
||||
return core::ptr::null_mut();
|
||||
};
|
||||
let Some(dict) = PyAnyDict::from_object(unsafe { dict.assume_borrowed() }) else {
|
||||
return core::ptr::null_mut();
|
||||
};
|
||||
|
||||
match dict.inner_getitem_opt(key, vm)? {
|
||||
Some(value) => Ok(value.as_object().as_raw().cast_mut()),
|
||||
None => Ok(core::ptr::null_mut()),
|
||||
match dict.inner_getitem_opt(key, vm) {
|
||||
Ok(Some(value)) => value.as_object().as_raw().cast_mut(),
|
||||
Ok(None) | Err(_) => core::ptr::null_mut(),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -112,8 +138,8 @@ pub unsafe extern "C" fn PyDict_GetItemStringRef(
|
||||
unsafe {
|
||||
*result = core::ptr::null_mut();
|
||||
}
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let key = unsafe { key.try_as_str(vm) }?;
|
||||
let dict = any_dict(unsafe { dict.assume_borrowed() }, vm)?;
|
||||
|
||||
if let Some(value) = dict.inner_getitem_opt(key, vm)? {
|
||||
unsafe {
|
||||
@@ -134,7 +160,7 @@ pub unsafe extern "C" fn PyDict_GetItemRef(
|
||||
) -> c_int {
|
||||
with_vm(|vm| {
|
||||
unsafe { *result = core::ptr::null_mut() };
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = any_dict(unsafe { dict.assume_borrowed() }, vm)?;
|
||||
let key = unsafe { key.assume_borrowed() };
|
||||
|
||||
if let Some(value) = dict.inner_getitem_opt(key, vm)? {
|
||||
@@ -165,7 +191,7 @@ pub unsafe extern "C" fn PyDict_SetDefaultRef(
|
||||
result.write(core::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = writable_dict(unsafe { dict.assume_borrowed() }, "assignment", vm)?;
|
||||
let key = unsafe { key.assume_borrowed() };
|
||||
|
||||
if let Some(value) = dict.inner_getitem_opt(key, vm)? {
|
||||
@@ -191,7 +217,9 @@ pub unsafe extern "C" fn PyDict_SetDefaultRef(
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_Size(dict: *mut PyObject) -> isize {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = unsafe { dict.assume_borrowed_or_opt() }
|
||||
.ok_or_else(|| vm.new_system_error("bad argument to internal function"))?;
|
||||
let dict = any_dict(dict, vm)?;
|
||||
Ok(dict.__len__())
|
||||
})
|
||||
}
|
||||
@@ -199,7 +227,7 @@ pub unsafe extern "C" fn PyDict_Size(dict: *mut PyObject) -> isize {
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_Contains(dict: *mut PyObject, key: *mut PyObject) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = any_dict(unsafe { dict.assume_borrowed() }, vm)?;
|
||||
let key = unsafe { key.assume_borrowed() };
|
||||
Ok(dict.inner_getitem_opt(key, vm)?.is_some())
|
||||
})
|
||||
@@ -208,7 +236,9 @@ pub unsafe extern "C" fn PyDict_Contains(dict: *mut PyObject, key: *mut PyObject
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_Copy(dict: *mut PyObject) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = unsafe { dict.assume_borrowed_or_opt() }
|
||||
.and_then(|dict| dict.downcast_ref::<PyDict>())
|
||||
.ok_or_else(|| vm.new_system_error("bad argument to internal function"))?;
|
||||
Ok(dict.copy().into_ref(&vm.ctx))
|
||||
})
|
||||
}
|
||||
@@ -216,8 +246,12 @@ pub unsafe extern "C" fn PyDict_Copy(dict: *mut PyObject) -> *mut PyObject {
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_DelItem(dict: *mut PyObject, key: *mut PyObject) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = unsafe { dict.assume_borrowed() };
|
||||
let key = unsafe { key.assume_borrowed() };
|
||||
if dict.downcast_ref::<PyFrozenDict>().is_some() {
|
||||
PyFrozenDict::key_hash(key, vm)?;
|
||||
}
|
||||
let dict = writable_dict(dict, "deletion", vm)?;
|
||||
dict.del_item(key, vm)
|
||||
})
|
||||
}
|
||||
@@ -225,8 +259,8 @@ pub unsafe extern "C" fn PyDict_DelItem(dict: *mut PyObject, key: *mut PyObject)
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_DelItemString(dict: *mut PyObject, key: *const c_char) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let key = unsafe { key.try_as_str(vm) }?;
|
||||
let dict = writable_dict(unsafe { dict.assume_borrowed() }, "deletion", vm)?;
|
||||
dict.del_item(key, vm)
|
||||
})
|
||||
}
|
||||
@@ -234,7 +268,9 @@ pub unsafe extern "C" fn PyDict_DelItemString(dict: *mut PyObject, key: *const c
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_Items(dict: *mut PyObject) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = unsafe { dict.assume_borrowed_or_opt() }
|
||||
.ok_or_else(|| vm.new_system_error("bad argument to internal function"))?;
|
||||
let dict = any_dict(dict, vm)?;
|
||||
let items = dict
|
||||
.items_vec()
|
||||
.into_iter()
|
||||
@@ -247,7 +283,9 @@ pub unsafe extern "C" fn PyDict_Items(dict: *mut PyObject) -> *mut PyObject {
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_Keys(dict: *mut PyObject) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = unsafe { dict.assume_borrowed_or_opt() }
|
||||
.ok_or_else(|| vm.new_system_error("bad argument to internal function"))?;
|
||||
let dict = any_dict(dict, vm)?;
|
||||
Ok(vm.ctx.new_list(dict.keys_vec()))
|
||||
})
|
||||
}
|
||||
@@ -255,7 +293,9 @@ pub unsafe extern "C" fn PyDict_Keys(dict: *mut PyObject) -> *mut PyObject {
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_Values(dict: *mut PyObject) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = unsafe { dict.assume_borrowed_or_opt() }
|
||||
.ok_or_else(|| vm.new_system_error("bad argument to internal function"))?;
|
||||
let dict = any_dict(dict, vm)?;
|
||||
Ok(vm.ctx.new_list(dict.values_vec()))
|
||||
})
|
||||
}
|
||||
@@ -267,7 +307,7 @@ pub unsafe extern "C" fn PyDict_Merge(
|
||||
override_: c_int,
|
||||
) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = writable_dict(unsafe { dict.assume_borrowed() }, "assignment", vm)?;
|
||||
let other = unsafe { other.assume_borrowed() }.to_owned();
|
||||
if override_ != 0 {
|
||||
dict.merge_object(other, vm)
|
||||
@@ -280,7 +320,7 @@ pub unsafe extern "C" fn PyDict_Merge(
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyDict_Update(dict: *mut PyObject, other: *mut PyObject) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = writable_dict(unsafe { dict.assume_borrowed() }, "assignment", vm)?;
|
||||
let other = unsafe { other.assume_borrowed() }.to_owned();
|
||||
dict.merge_object(other, vm)
|
||||
})
|
||||
@@ -293,7 +333,7 @@ pub unsafe extern "C" fn PyDict_MergeFromSeq2(
|
||||
override_: c_int,
|
||||
) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
let dict = writable_dict(unsafe { dict.assume_borrowed() }, "assignment", vm)?;
|
||||
let seq2 = unsafe { seq2.assume_borrowed() };
|
||||
dict.merge_from_seq2(seq2, override_ != 0, vm)
|
||||
})
|
||||
@@ -306,8 +346,10 @@ pub unsafe extern "C" fn PyDict_Next(
|
||||
key: *mut *mut PyObject,
|
||||
value: *mut *mut PyObject,
|
||||
) -> c_int {
|
||||
with_vm(|vm| {
|
||||
let dict = unsafe { dict.assume_borrowed_and_cast::<PyDict>(vm) }?;
|
||||
with_vm(|_vm| {
|
||||
let Some(dict) = PyAnyDict::from_object(unsafe { dict.assume_borrowed() }) else {
|
||||
return false;
|
||||
};
|
||||
let index = unsafe { *pos } as usize;
|
||||
|
||||
if let Some((next_pos, k, v)) = dict.next_entry(index) {
|
||||
@@ -320,9 +362,9 @@ pub unsafe extern "C" fn PyDict_Next(
|
||||
value.write(v.as_object().as_raw().cast_mut());
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
true
|
||||
} else {
|
||||
Ok(false)
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ use crate::PyObject;
|
||||
use crate::pystate::with_vm;
|
||||
use crate::util::FfiPtrExt;
|
||||
use core::ffi::c_int;
|
||||
use rustpython_vm::Py;
|
||||
use rustpython_vm::builtins::PyCode;
|
||||
use rustpython_vm::frame::FrameObject;
|
||||
use rustpython_vm::{Py, PyObjectRef};
|
||||
|
||||
pub type PyFrameObject = Py<FrameObject>;
|
||||
pub type PyCodeObject = Py<PyCode>;
|
||||
@@ -26,7 +26,7 @@ pub unsafe extern "C" fn PyFrame_GetLocals(frame: *mut PyFrameObject) -> *mut Py
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn PyFrame_GetGlobals(frame: *mut PyFrameObject) -> *mut PyObject {
|
||||
with_vm(|_vm| Ok(unsafe { frame.assume_borrowed() }.f_globals()))
|
||||
with_vm(|_vm| PyObjectRef::from(unsafe { frame.assume_borrowed() }.f_globals()))
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
|
||||
+904
-169
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,7 @@
|
||||
|
||||
*/
|
||||
|
||||
use super::{PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyIntRef};
|
||||
use super::{PyAnyDict, PyAsyncGen, PyCode, PyCoroutine, PyGenerator, PyIntRef};
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
class::PyClassImpl,
|
||||
@@ -668,7 +668,7 @@ impl Py<FrameObject> {
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
pub fn f_globals(&self) -> PyDictRef {
|
||||
pub fn f_globals(&self) -> PyRef<PyAnyDict> {
|
||||
self.iframe().globals().to_owned()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
mod jit;
|
||||
|
||||
use super::{
|
||||
PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyList, PyModule, PyStr, PyStrRef,
|
||||
PyTuple, PyTupleRef, PyType, object,
|
||||
PyAnyDict, PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyList, PyModule, PyStr,
|
||||
PyStrRef, PyTuple, PyTupleRef, PyType, object,
|
||||
};
|
||||
use crate::common::hash::PyHash;
|
||||
use crate::common::lock::PyMutex;
|
||||
@@ -66,7 +66,7 @@ fn format_missing_args(
|
||||
pub struct PyFunction {
|
||||
pub(crate) code: PyAtomicRef<PyCode>,
|
||||
#[pymember(name = "__globals__")]
|
||||
pub(crate) globals: PyDictRef,
|
||||
pub(crate) globals: PyRef<PyAnyDict>,
|
||||
#[pymember(name = "__builtins__")]
|
||||
pub(crate) builtins: PyObjectRef,
|
||||
#[pymember(name = "__closure__")]
|
||||
@@ -179,15 +179,17 @@ impl PyFunction {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
code: PyRef<PyCode>,
|
||||
globals: PyDictRef,
|
||||
globals: PyRef<PyAnyDict>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Self> {
|
||||
let name = PyMutex::new(code.obj_name.to_owned());
|
||||
let module = vm.unwrap_or_none(globals.get_item_opt(identifier!(vm, __name__), vm)?);
|
||||
let builtins = globals.get_item("__builtins__", vm).unwrap_or_else(|_| {
|
||||
// If not in globals, inherit from current execution context
|
||||
crate::frame::current_builtins().unwrap_or_else(|| vm.builtins.dict().into())
|
||||
});
|
||||
let module = vm.unwrap_or_none(globals.inner_getitem_opt(identifier!(vm, __name__), vm)?);
|
||||
let builtins = globals
|
||||
.inner_getitem_opt(identifier!(vm, __builtins__), vm)?
|
||||
.unwrap_or_else(|| {
|
||||
// If not in globals, inherit from current execution context.
|
||||
crate::frame::current_builtins().unwrap_or_else(|| vm.builtins.dict().into())
|
||||
});
|
||||
// If builtins is a module, use its __dict__ instead
|
||||
let builtins = if let Some(module) = builtins.downcast_ref::<PyModule>() {
|
||||
module.dict().into()
|
||||
@@ -621,7 +623,7 @@ impl Py<PyFunction> {
|
||||
} else if let Some(locals) = locals {
|
||||
Some(locals)
|
||||
} else {
|
||||
Some(ArgMapping::from_dict_exact(self.globals.clone()))
|
||||
Some(ArgMapping::from_anydict_exact(self.globals.clone()))
|
||||
};
|
||||
let use_datastack = !is_gen && !is_coro && !is_async_gen;
|
||||
let frame = FrameObject::new_ref(
|
||||
@@ -654,7 +656,7 @@ impl Py<PyFunction> {
|
||||
} else if let Some(locals) = locals {
|
||||
crate::frame::FrameLocals::with_locals(locals)
|
||||
} else {
|
||||
crate::frame::FrameLocals::with_locals(crate::function::ArgMapping::from_dict_exact(
|
||||
crate::frame::FrameLocals::with_locals(crate::function::ArgMapping::from_anydict_exact(
|
||||
self.globals.clone(),
|
||||
))
|
||||
};
|
||||
@@ -805,7 +807,7 @@ impl Py<PyFunction> {
|
||||
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
|
||||
None
|
||||
} else {
|
||||
Some(ArgMapping::from_dict_exact(self.globals.clone()))
|
||||
Some(ArgMapping::from_anydict_exact(self.globals.clone()))
|
||||
};
|
||||
|
||||
let frame = FrameObject::new_ref(
|
||||
@@ -862,7 +864,7 @@ impl Py<PyFunction> {
|
||||
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
|
||||
None
|
||||
} else {
|
||||
Some(ArgMapping::from_dict_exact(self.globals.clone()))
|
||||
Some(ArgMapping::from_anydict_exact(self.globals.clone()))
|
||||
};
|
||||
|
||||
// Heap-backed: the frame outlives the call that made it.
|
||||
@@ -897,7 +899,7 @@ impl Py<PyFunction> {
|
||||
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
|
||||
crate::frame::FrameLocals::lazy()
|
||||
} else {
|
||||
crate::frame::FrameLocals::with_locals(ArgMapping::from_dict_exact(
|
||||
crate::frame::FrameLocals::with_locals(ArgMapping::from_anydict_exact(
|
||||
self.globals.clone(),
|
||||
))
|
||||
};
|
||||
@@ -1318,7 +1320,7 @@ impl Constructor for PyFunction {
|
||||
None
|
||||
};
|
||||
|
||||
let mut func = Self::new(args.code.clone(), args.globals.clone(), vm)?;
|
||||
let mut func = Self::new(args.code.clone(), args.globals.clone().into(), vm)?;
|
||||
// Set function name if provided
|
||||
if let Some(name) = args.name.into_option() {
|
||||
*func.name.lock() = name;
|
||||
|
||||
@@ -68,6 +68,12 @@ impl Constructor for PyMappingProxy {
|
||||
}
|
||||
|
||||
impl PyMappingProxy {
|
||||
pub(crate) fn from_any_dict(dict: PyRef<super::PyAnyDict>) -> Self {
|
||||
Self {
|
||||
mapping: MappingProxyInner::Mapping(ArgMapping::new(dict.into())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_object(mapping: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
|
||||
if mapping.mapping_unchecked().check()
|
||||
&& !mapping.downcastable::<PyList>()
|
||||
|
||||
@@ -20,7 +20,7 @@ pub use complex::PyComplex;
|
||||
pub(crate) mod coroutine;
|
||||
pub use coroutine::PyCoroutine;
|
||||
pub(crate) mod dict;
|
||||
pub use dict::{PyDict, PyDictRef};
|
||||
pub use dict::{PyAnyDict, PyDict, PyDictRef, PyFrozenDict};
|
||||
pub(crate) mod enumerate;
|
||||
pub use enumerate::PyEnumerate;
|
||||
pub(crate) mod filter;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
PyClassMethod, PyDict, PyDictRef, PyList, PyStaticMethod, PyStr, PyStrInterned, PyStrRef,
|
||||
PyTupleRef, PyUtf8StrRef, PyWeak, mappingproxy::PyMappingProxy, object, union_,
|
||||
PyAnyDict, PyClassMethod, PyDict, PyDictRef, PyList, PyStaticMethod, PyStr, PyStrInterned,
|
||||
PyStrRef, PyTupleRef, PyUtf8StrRef, PyWeak, mappingproxy::PyMappingProxy, object, union_,
|
||||
};
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyAtomicRef, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
|
||||
@@ -1798,25 +1798,15 @@ impl Constructor for PyType {
|
||||
}));
|
||||
}
|
||||
|
||||
let (name, bases, dict, kwargs): (PyStrRef, PyTupleRef, PyDictRef, KwArgs) =
|
||||
let (name, bases, dict, kwargs): (PyStrRef, PyTupleRef, PyObjectRef, KwArgs) =
|
||||
args.clone().bind_for(vm, Self::NAME)?;
|
||||
|
||||
// A mapping that is not an exact dict (e.g. OrderedDict) keeps its
|
||||
// own iteration order; copy via the mapping protocol so that order
|
||||
// lands in the type dict.
|
||||
let dict = if args.args[2].class().is(vm.ctx.types.dict_type) {
|
||||
dict
|
||||
} else {
|
||||
let copied = vm.ctx.new_dict();
|
||||
copied.merge_object(args.args[2].clone(), vm)?;
|
||||
copied
|
||||
let Some(dict) = PyAnyDict::from_object(&dict) else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"type.__new__() argument 3 must be dict or frozendict, not {}",
|
||||
dict.class().fully_qualified_name(vm)?
|
||||
)));
|
||||
};
|
||||
|
||||
if name.as_bytes().contains(&0) {
|
||||
return Err(vm.new_value_error("type name must not contain null characters"));
|
||||
}
|
||||
let name = name.try_into_utf8(vm)?;
|
||||
|
||||
let (metatype, base, bases, base_is_type) = if bases.as_slice().is_empty() {
|
||||
let base = vm.ctx.types.object_type.to_owned();
|
||||
let bases = PyTuple::new_ref_typed(vec![base.clone()], &vm.ctx);
|
||||
@@ -1856,6 +1846,25 @@ impl Constructor for PyType {
|
||||
(metatype, base.to_owned(), bases, base_is_type)
|
||||
};
|
||||
|
||||
// Copy only after resolving the metaclass: its __new__ receives the
|
||||
// original namespace. A subclass with a custom iterator (e.g.
|
||||
// OrderedDict) keeps its own mapping order in the mutable type dict.
|
||||
let dict = if let Some(dict) = dict.as_object().downcast_ref_if_exact::<PyDict>(vm) {
|
||||
dict.to_owned()
|
||||
} else {
|
||||
let copied = vm.ctx.new_dict();
|
||||
// Like dict.copy(), an empty table bypasses subclass hooks.
|
||||
if !dict.is_empty() {
|
||||
copied.merge_object(dict.to_owned().into(), vm)?;
|
||||
}
|
||||
copied
|
||||
};
|
||||
|
||||
if name.as_bytes().contains(&0) {
|
||||
return Err(vm.new_value_error("type name must not contain null characters"));
|
||||
}
|
||||
let name = name.try_into_utf8(vm)?;
|
||||
|
||||
let qualname = dict
|
||||
.get_item_opt(identifier!(vm, __qualname__), vm)?
|
||||
.map(|obj| downcast_qualname(obj, vm))
|
||||
|
||||
+61
-44
@@ -6,10 +6,10 @@ use crate::{
|
||||
AsObject, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, PyStackRef,
|
||||
TryFromObject, VirtualMachine,
|
||||
builtins::{
|
||||
PyBaseException, PyBaseExceptionRef, PyBaseObject, PyBytes, PyCode, PyCoroutine, PyDict,
|
||||
PyDictRef, PyFloat, PyFrozenSet, PyGenerator, PyInt, PyInterpolation, PyList, PyModule,
|
||||
PyProperty, PySet, PySlice, PyStr, PyStrInterned, PyTemplate, PyTraceback, PyType,
|
||||
PyUtf8Str,
|
||||
PyAnyDict, PyBaseException, PyBaseExceptionRef, PyBaseObject, PyBytes, PyCode, PyCoroutine,
|
||||
PyDict, PyDictRef, PyFloat, PyFrozenSet, PyGenerator, PyInt, PyInterpolation, PyList,
|
||||
PyModule, PyProperty, PySet, PySlice, PyStr, PyStrInterned, PyTemplate, PyTraceback,
|
||||
PyType, PyUtf8Str,
|
||||
builtin_func::PyNativeFunction,
|
||||
descriptor::{PyMemberDescriptor, PyMethodDescriptor},
|
||||
frame::stack_analysis,
|
||||
@@ -108,7 +108,7 @@ pub fn current_thread_frame_materialize(vm: &VirtualMachine) -> Option<FrameObje
|
||||
/// Read the globals dict from the topmost frame on this thread's chain.
|
||||
/// Returns `None` if the chain is empty.
|
||||
#[must_use]
|
||||
pub fn current_globals() -> Option<PyDictRef> {
|
||||
pub fn current_globals() -> Option<PyRef<PyAnyDict>> {
|
||||
let ptr = crate::vm::thread::get_current_frame();
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
@@ -923,7 +923,7 @@ pub struct InterpreterFrame {
|
||||
// Borrowed pointers — owned by FrameObject or by PyFunction on caller's stack.
|
||||
pub(crate) code: *const Py<PyCode>,
|
||||
pub(crate) func_obj: *const PyObject, // nullable
|
||||
pub(crate) globals: *const Py<PyDict>,
|
||||
pub(crate) globals: *const PyAnyDict,
|
||||
pub(crate) builtins: *const PyObject,
|
||||
|
||||
/// Unified storage for local variables and evaluation stack.
|
||||
@@ -983,7 +983,7 @@ impl InterpreterFrame {
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn new(
|
||||
code: &Py<PyCode>,
|
||||
globals: &Py<PyDict>,
|
||||
globals: &PyAnyDict,
|
||||
builtins: &PyObject,
|
||||
func_obj: Option<&PyObject>,
|
||||
localsplus: LocalsPlus,
|
||||
@@ -1027,7 +1027,7 @@ impl InterpreterFrame {
|
||||
Some(obj) => obj as *const PyObject,
|
||||
None => core::ptr::null(),
|
||||
},
|
||||
globals: globals as *const Py<PyDict>,
|
||||
globals: globals as *const PyAnyDict,
|
||||
builtins: builtins as *const PyObject,
|
||||
localsplus,
|
||||
locals,
|
||||
@@ -1060,7 +1060,7 @@ impl InterpreterFrame {
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn new_on_datastack<'a>(
|
||||
code: &Py<PyCode>,
|
||||
globals: &Py<PyDict>,
|
||||
globals: &PyAnyDict,
|
||||
builtins: &PyObject,
|
||||
func_obj: Option<&PyObject>,
|
||||
locals: FrameLocals,
|
||||
@@ -1266,7 +1266,7 @@ impl InterpreterFrame {
|
||||
// Create a full FrameObject with its own InterpreterFrame copy.
|
||||
// The FrameObject owns references to the same objects (code, globals, etc).
|
||||
let code: PyRef<PyCode> = self.code().to_owned();
|
||||
let globals: PyDictRef = self.globals().to_owned();
|
||||
let globals = self.globals().to_owned();
|
||||
let builtins: PyObjectRef = self.builtins().to_owned();
|
||||
let func_obj: Option<PyObjectRef> = self.func_obj().map(|o| o.to_owned());
|
||||
|
||||
@@ -1362,7 +1362,7 @@ impl InterpreterFrame {
|
||||
#[cold]
|
||||
fn materialize_slow_chain(&self, vm: &VirtualMachine) -> FrameObjectRef {
|
||||
let code: PyRef<PyCode> = self.code().to_owned();
|
||||
let globals: PyDictRef = self.globals().to_owned();
|
||||
let globals = self.globals().to_owned();
|
||||
let builtins: PyObjectRef = self.builtins().to_owned();
|
||||
let func_obj: Option<PyObjectRef> = self.func_obj().map(|o| o.to_owned());
|
||||
|
||||
@@ -1419,7 +1419,7 @@ impl InterpreterFrame {
|
||||
|
||||
/// Borrowed globals dict.
|
||||
#[inline(always)]
|
||||
pub fn globals(&self) -> &Py<PyDict> {
|
||||
pub fn globals(&self) -> &PyAnyDict {
|
||||
// SAFETY: established by `new` / `init_iframe_ptrs`.
|
||||
unsafe { &*self.globals }
|
||||
}
|
||||
@@ -1534,7 +1534,7 @@ pub struct FrameObject {
|
||||
// raw pointers. Wrapped in Option so Traverse::clear can release them,
|
||||
// allowing GC cycle collection to reclaim referenced objects.
|
||||
pub(crate) owned_code: Option<PyRef<PyCode>>,
|
||||
pub(crate) owned_globals: Option<PyDictRef>,
|
||||
pub(crate) owned_globals: Option<PyRef<PyAnyDict>>,
|
||||
pub(crate) owned_builtins: Option<PyObjectRef>,
|
||||
pub(crate) owned_func_obj: Option<PyObjectRef>,
|
||||
|
||||
@@ -1817,7 +1817,7 @@ impl FrameObject {
|
||||
let locals = match scope.locals {
|
||||
Some(locals) => FrameLocals::with_locals(locals),
|
||||
None if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) => FrameLocals::lazy(),
|
||||
None => FrameLocals::with_locals(ArgMapping::from_dict_exact(scope.globals.clone())),
|
||||
None => FrameLocals::with_locals(ArgMapping::from_anydict_exact(scope.globals.clone())),
|
||||
};
|
||||
|
||||
// Pointers are initially set from owned fields' references but will be
|
||||
@@ -1853,7 +1853,7 @@ impl FrameObject {
|
||||
fn init_iframe_ptrs(self_: &Py<Self>) {
|
||||
let iframe = unsafe { self_.iframe_mut() };
|
||||
iframe.code = &**self_.owned_code.as_ref().unwrap() as *const Py<PyCode>;
|
||||
iframe.globals = &**self_.owned_globals.as_ref().unwrap() as *const Py<PyDict>;
|
||||
iframe.globals = &**self_.owned_globals.as_ref().unwrap() as *const PyAnyDict;
|
||||
iframe.builtins = &**self_.owned_builtins.as_ref().unwrap() as *const PyObject;
|
||||
iframe.func_obj = match &self_.owned_func_obj {
|
||||
Some(obj) => &**obj as *const PyObject,
|
||||
@@ -2497,7 +2497,7 @@ impl Py<FrameObject> {
|
||||
},
|
||||
)
|
||||
};
|
||||
let builtins_dict = if globals.class().is(vm.ctx.types.dict_type) {
|
||||
let builtins_dict = if globals.as_object().class().is(vm.ctx.types.dict_type) {
|
||||
builtins
|
||||
.downcast_ref_if_exact::<PyDict>(vm)
|
||||
// SAFETY: downcast_ref_if_exact already verified exact type
|
||||
@@ -2739,7 +2739,7 @@ fn exec_iframe<'a>(
|
||||
},
|
||||
)
|
||||
};
|
||||
let builtins_dict = if globals.class().is(vm.ctx.types.dict_type) {
|
||||
let builtins_dict = if globals.as_object().class().is(vm.ctx.types.dict_type) {
|
||||
builtins
|
||||
.downcast_ref_if_exact::<PyDict>(vm)
|
||||
.map(|d| unsafe { PyExact::ref_unchecked(d) })
|
||||
@@ -2931,7 +2931,7 @@ pub(crate) struct ExecutingFrame<'a> {
|
||||
code: &'a Py<PyCode>,
|
||||
localsplus: &'a mut LocalsPlus,
|
||||
locals: &'a FrameLocals,
|
||||
globals: &'a Py<PyDict>,
|
||||
globals: &'a PyAnyDict,
|
||||
builtins: &'a PyObject,
|
||||
/// Cached downcast of builtins to PyDict for fast LOAD_GLOBAL.
|
||||
/// Only set when both globals and builtins are exact dict types (not
|
||||
@@ -4651,15 +4651,10 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Instruction::DeleteName { namei: idx } => {
|
||||
let name = self.code.names[idx.get(arg) as usize];
|
||||
let res = self.locals.mapping(vm).ass_subscript(name, None, vm);
|
||||
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => {
|
||||
return Err(name_error(name, vm));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
self.locals
|
||||
.mapping(vm)
|
||||
.ass_subscript(name, None, vm)
|
||||
.map_err(|_| name_error(name, vm))?;
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::DeleteSubscr => self.execute_delete_subscript(vm),
|
||||
@@ -5254,7 +5249,7 @@ impl ExecutingFrame<'_> {
|
||||
match result {
|
||||
Ok(x) => self.push_value(x),
|
||||
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => {
|
||||
self.push_value(self.load_global_or_builtin(name, vm)?);
|
||||
self.push_value(self.load_name_global_or_builtin(name, vm)?);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
@@ -8043,9 +8038,9 @@ impl ExecutingFrame<'_> {
|
||||
let cached_version = self.code.instructions.read_cache_u16(cache_base + 1);
|
||||
let cached_index = self.code.instructions.read_cache_u16(cache_base + 3);
|
||||
if cached_version != 0
|
||||
&& let Some(x) = self
|
||||
.globals
|
||||
.get_item_by_index_and_keys_version(cached_version, cached_index)
|
||||
&& let Some(globals) = self.globals.as_mutable()
|
||||
&& let Some(x) =
|
||||
globals.get_item_by_index_and_keys_version(cached_version, cached_index)
|
||||
{
|
||||
self.push_value(x);
|
||||
if (oparg & 1) != 0 {
|
||||
@@ -8069,7 +8064,8 @@ impl ExecutingFrame<'_> {
|
||||
let cached_index = self.code.instructions.read_cache_u16(cache_base + 3);
|
||||
if cached_globals_ver != 0
|
||||
&& cached_builtins_ver != 0
|
||||
&& let Ok(current_globals_ver) = u16::try_from(self.globals.keys_version())
|
||||
&& let Some(globals) = self.globals.as_mutable()
|
||||
&& let Ok(current_globals_ver) = u16::try_from(globals.keys_version())
|
||||
&& cached_globals_ver == current_globals_ver
|
||||
&& let Some(builtins_dict) = self.builtins.downcast_ref_if_exact::<PyDict>(vm)
|
||||
&& let Some(x) = builtins_dict
|
||||
@@ -8541,7 +8537,12 @@ impl ExecutingFrame<'_> {
|
||||
if let Some(builtins_dict) = self.builtins_dict {
|
||||
// Fast path: both globals and builtins are exact dicts
|
||||
// SAFETY: builtins_dict is only set when globals is also exact dict
|
||||
let globals_exact = unsafe { PyExact::ref_unchecked(self.globals) };
|
||||
let globals = self
|
||||
.globals
|
||||
.as_object()
|
||||
.downcast_ref::<PyDict>()
|
||||
.expect("exact dict globals");
|
||||
let globals_exact = unsafe { PyExact::ref_unchecked(globals) };
|
||||
globals_exact
|
||||
.get_chain_exact(builtins_dict, name, vm)?
|
||||
.ok_or_else(|| {
|
||||
@@ -8552,16 +8553,29 @@ impl ExecutingFrame<'_> {
|
||||
if let Some(value) = self.globals.get_item_opt(name, vm)? {
|
||||
return Ok(value);
|
||||
}
|
||||
self.builtins.get_item(name, vm).map_err(|e| {
|
||||
if e.fast_isinstance(vm.ctx.exceptions.key_error) {
|
||||
vm.new_name_error(format!("name '{name}' is not defined"), name.to_owned())
|
||||
} else {
|
||||
e
|
||||
}
|
||||
})
|
||||
self.load_builtin(name, vm)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_name_global_or_builtin(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
|
||||
// LOAD_NAME reads the stored global entries even for dict subclasses;
|
||||
// LOAD_GLOBAL instead honors their __getitem__ implementation.
|
||||
if let Some(value) = self.globals.inner_getitem_opt(name, vm)? {
|
||||
return Ok(value);
|
||||
}
|
||||
self.load_builtin(name, vm)
|
||||
}
|
||||
|
||||
fn load_builtin(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
|
||||
self.builtins.get_item(name, vm).map_err(|e| {
|
||||
if e.fast_isinstance(vm.ctx.exceptions.key_error) {
|
||||
vm.new_name_error(format!("name '{name}' is not defined"), name.to_owned())
|
||||
} else {
|
||||
e
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "flame-it", flame("FrameObject"))]
|
||||
fn import(&mut self, vm: &VirtualMachine, module_name: Option<&Py<PyStr>>) -> PyResult<()> {
|
||||
let module_name = module_name.unwrap_or(vm.ctx.empty_str);
|
||||
@@ -11786,7 +11800,7 @@ impl ExecutingFrame<'_> {
|
||||
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
|
||||
FrameLocals::lazy()
|
||||
} else {
|
||||
FrameLocals::with_locals(crate::function::ArgMapping::from_dict_exact(
|
||||
FrameLocals::with_locals(crate::function::ArgMapping::from_anydict_exact(
|
||||
func.globals.clone(),
|
||||
))
|
||||
};
|
||||
@@ -11852,7 +11866,7 @@ impl ExecutingFrame<'_> {
|
||||
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
|
||||
FrameLocals::lazy()
|
||||
} else {
|
||||
FrameLocals::with_locals(crate::function::ArgMapping::from_dict_exact(
|
||||
FrameLocals::with_locals(crate::function::ArgMapping::from_anydict_exact(
|
||||
func.globals.clone(),
|
||||
))
|
||||
};
|
||||
@@ -11938,8 +11952,11 @@ impl ExecutingFrame<'_> {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let Some(globals) = self.globals.as_mutable() else {
|
||||
return;
|
||||
};
|
||||
let name = self.code.names[(oparg >> 1) as usize];
|
||||
let Ok(globals_version @ 1..) = u16::try_from(self.globals.assign_keys_version(vm)) else {
|
||||
let Ok(globals_version @ 1..) = u16::try_from(globals.assign_keys_version(vm)) else {
|
||||
unsafe {
|
||||
self.code.instructions.write_adaptive_counter(
|
||||
cache_base,
|
||||
@@ -11951,7 +11968,7 @@ impl ExecutingFrame<'_> {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(Some(globals_hint)) = self.globals.hint_for_key(name, vm) {
|
||||
if let Ok(Some(globals_hint)) = globals.hint_for_key(name, vm) {
|
||||
unsafe {
|
||||
self.code
|
||||
.instructions
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::IntoFuncArgs;
|
||||
use crate::{
|
||||
AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
|
||||
builtins::{PyDictRef, iter::PySequenceIterator},
|
||||
AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
|
||||
builtins::{PyAnyDict, PyDictRef, iter::PySequenceIterator},
|
||||
convert::ToPyObject,
|
||||
object::{Traverse, TraverseFn},
|
||||
protocol::{PyIter, PyIterIter, PyMapping},
|
||||
@@ -155,6 +155,12 @@ impl ArgMapping {
|
||||
Self { obj: dict.into() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn from_anydict_exact(dict: PyRef<PyAnyDict>) -> Self {
|
||||
Self { obj: dict.into() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn obj(&self) -> &PyObject {
|
||||
|
||||
@@ -2666,6 +2666,7 @@ mod ref_target_sealed {
|
||||
impl<T: super::PyPayload> Sealed for T {}
|
||||
impl Sealed for super::PyObject {}
|
||||
impl Sealed for crate::builtins::set::PyAnySet {}
|
||||
impl Sealed for crate::builtins::PyAnyDict {}
|
||||
impl<T: super::PyTypedRefTarget> Sealed for super::PyTuple<super::PyRef<T>> {}
|
||||
}
|
||||
|
||||
@@ -2709,6 +2710,11 @@ pub trait PyTypedRefTarget: PyRefTarget {}
|
||||
|
||||
impl<T: PyRefTarget<Target = Py<T>>> PyTypedRefTarget for T {}
|
||||
impl PyTypedRefTarget for crate::builtins::set::PyAnySet {}
|
||||
impl PyTypedRefTarget for crate::builtins::PyAnyDict {}
|
||||
|
||||
impl PyRefTarget for crate::builtins::PyAnyDict {
|
||||
type Target = Self;
|
||||
}
|
||||
|
||||
impl PyRefTarget for crate::builtins::set::PyAnySet {
|
||||
type Target = Self;
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
builtins::{PyBoundMethod, PyFunction},
|
||||
function::{FuncArgs, IntoFuncArgs},
|
||||
types::{GenericMethod, VectorCallFunc},
|
||||
{PyObject, PyObjectRef, PyResult, VirtualMachine},
|
||||
{AsObject, PyObject, PyObjectRef, PyResult, VirtualMachine},
|
||||
};
|
||||
|
||||
impl PyObject {
|
||||
@@ -78,6 +78,15 @@ impl<'a> PyCallable<'a> {
|
||||
|
||||
pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let args = args.into_args(vm);
|
||||
// The frozendict identity optimization belongs to the ordinary call path, not
|
||||
// to __new__ or an explicit type.__call__. CALL_FUNCTION_EX reaches this path.
|
||||
if self.obj.is(vm.ctx.types.frozendict_type)
|
||||
&& args.kwargs.is_empty()
|
||||
&& self.vectorcall.is_some()
|
||||
{
|
||||
let nargs = args.args.len();
|
||||
return self.invoke_vectorcall(args.args, nargs, None, vm);
|
||||
}
|
||||
if !vm.use_tracing.get() {
|
||||
return (self.call)(self.obj, args, vm);
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ impl PyMapping<'_> {
|
||||
|
||||
pub fn keys(self, vm: &VirtualMachine) -> PyResult {
|
||||
if let Some(dict) = self.obj.downcast_ref_if_exact::<PyDict>(vm) {
|
||||
PyDictKeys::new(dict.to_owned()).to_pyresult(vm)
|
||||
PyDictKeys::new(dict.to_owned().into()).to_pyresult(vm)
|
||||
} else {
|
||||
self.method_output_as_list(identifier!(vm, keys), vm)
|
||||
}
|
||||
@@ -192,7 +192,7 @@ impl PyMapping<'_> {
|
||||
|
||||
pub fn values(self, vm: &VirtualMachine) -> PyResult {
|
||||
if let Some(dict) = self.obj.downcast_ref_if_exact::<PyDict>(vm) {
|
||||
PyDictValues::new(dict.to_owned()).to_pyresult(vm)
|
||||
PyDictValues::new(dict.to_owned().into()).to_pyresult(vm)
|
||||
} else {
|
||||
self.method_output_as_list(identifier!(vm, values), vm)
|
||||
}
|
||||
@@ -200,7 +200,7 @@ impl PyMapping<'_> {
|
||||
|
||||
pub fn items(self, vm: &VirtualMachine) -> PyResult {
|
||||
if let Some(dict) = self.obj.downcast_ref_if_exact::<PyDict>(vm) {
|
||||
PyDictItems::new(dict.to_owned()).to_pyresult(vm)
|
||||
PyDictItems::new(dict.to_owned().into()).to_pyresult(vm)
|
||||
} else {
|
||||
self.method_output_as_list(identifier!(vm, items), vm)
|
||||
}
|
||||
|
||||
+11
-4
@@ -1,10 +1,14 @@
|
||||
use crate::{VirtualMachine, builtins::PyDictRef, function::ArgMapping};
|
||||
use crate::{
|
||||
PyRef, VirtualMachine,
|
||||
builtins::{PyAnyDict, PyDictRef},
|
||||
function::ArgMapping,
|
||||
};
|
||||
use alloc::fmt;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Scope {
|
||||
pub locals: Option<ArgMapping>,
|
||||
pub globals: PyDictRef,
|
||||
pub globals: PyRef<PyAnyDict>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Scope {
|
||||
@@ -17,8 +21,11 @@ impl fmt::Debug for Scope {
|
||||
impl Scope {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn new(locals: Option<ArgMapping>, globals: PyDictRef) -> Self {
|
||||
Self { locals, globals }
|
||||
pub fn new(locals: Option<ArgMapping>, globals: impl Into<PyRef<PyAnyDict>>) -> Self {
|
||||
Self {
|
||||
locals,
|
||||
globals: globals.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_builtins(
|
||||
|
||||
@@ -1125,11 +1125,16 @@ fn expandtabs(input: &str, tab_size: usize) -> String {
|
||||
expanded
|
||||
}
|
||||
|
||||
type CodeNamespaces = (PyRef<PyCode>, Option<PyRef<PyDict>>, Option<PyRef<PyDict>>);
|
||||
type CodeNamespaces = (
|
||||
PyRef<PyCode>,
|
||||
Option<PyRef<crate::builtins::PyAnyDict>>,
|
||||
Option<PyRef<crate::builtins::PyAnyDict>>,
|
||||
);
|
||||
|
||||
fn code_or_function(obj: &PyObject, vm: &VirtualMachine) -> PyResult<CodeNamespaces> {
|
||||
if let Ok(func) = obj.to_owned().downcast::<PyFunction>() {
|
||||
let builtins = func.builtins.clone().downcast::<PyDict>().ok();
|
||||
let builtins =
|
||||
crate::builtins::PyAnyDict::from_object(&func.builtins).map(ToOwned::to_owned);
|
||||
return Ok((
|
||||
(*func.code).to_owned(),
|
||||
Some(func.globals.clone()),
|
||||
@@ -1142,19 +1147,22 @@ fn code_or_function(obj: &PyObject, vm: &VirtualMachine) -> PyResult<CodeNamespa
|
||||
Err(vm.new_type_error("argument must be a code object or a function"))
|
||||
}
|
||||
|
||||
fn optional_dict(
|
||||
fn optional_dict<T: From<PyRef<PyDict>>>(
|
||||
override_ns: OptionalArg<PyObjectRef>,
|
||||
default: Option<PyRef<PyDict>>,
|
||||
default: Option<T>,
|
||||
name: &str,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyRef<PyDict>>> {
|
||||
) -> PyResult<Option<T>> {
|
||||
match override_ns.into_option() {
|
||||
Some(obj) => obj.downcast::<PyDict>().map(Some).map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"expected a dict for \"{name}\", got {}",
|
||||
obj.class().name()
|
||||
))
|
||||
}),
|
||||
Some(obj) => obj
|
||||
.downcast::<PyDict>()
|
||||
.map(|dict| Some(dict.into()))
|
||||
.map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"expected a dict for \"{name}\", got {}",
|
||||
obj.class().name()
|
||||
))
|
||||
}),
|
||||
None => Ok(default),
|
||||
}
|
||||
}
|
||||
@@ -1280,8 +1288,8 @@ fn set_unbound_var_counts(
|
||||
counts: &mut VarCounts,
|
||||
globalnames: Option<PyObjectRef>,
|
||||
attrnames: Option<PyObjectRef>,
|
||||
globalsns: Option<&Py<PyDict>>,
|
||||
builtinsns: Option<&Py<PyDict>>,
|
||||
globalsns: Option<&crate::builtins::PyAnyDict>,
|
||||
builtinsns: Option<&crate::builtins::PyAnyDict>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let globalnames = optional_set(globalnames, "globalnames", vm)?;
|
||||
@@ -1317,8 +1325,8 @@ fn identify_unbound_names(
|
||||
code: &Py<PyCode>,
|
||||
globalnames: Option<PyRef<PySet>>,
|
||||
attrnames: Option<PyRef<PySet>>,
|
||||
globalsns: Option<&Py<PyDict>>,
|
||||
builtinsns: Option<&Py<PyDict>>,
|
||||
globalsns: Option<&crate::builtins::PyAnyDict>,
|
||||
builtinsns: Option<&crate::builtins::PyAnyDict>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<(UnboundCounts, i32)> {
|
||||
let mut seen_globals = HashSet::new();
|
||||
|
||||
@@ -11,7 +11,7 @@ mod builtins {
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
|
||||
VirtualMachine,
|
||||
builtins::{
|
||||
PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType,
|
||||
PyAnyDict, PyByteArray, PyBytes, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType,
|
||||
PyUtf8StrRef,
|
||||
enumerate::PyReverseSequenceIterator,
|
||||
function::{PyCell, PyCellRef, PyFunction},
|
||||
@@ -436,21 +436,23 @@ mod builtins {
|
||||
vm: &VirtualMachine,
|
||||
func_name: &'static str,
|
||||
) -> PyResult<()> {
|
||||
if !globals.fast_isinstance(vm.ctx.types.dict_type) {
|
||||
if !globals.fast_isinstance(vm.ctx.types.dict_type)
|
||||
&& !globals.fast_isinstance(vm.ctx.types.frozendict_type)
|
||||
{
|
||||
return Err(match func_name {
|
||||
"eval" => {
|
||||
let is_mapping = globals.mapping_unchecked().check();
|
||||
vm.new_type_error(if is_mapping {
|
||||
"globals must be a real dict; try eval(expr, {}, mapping)"
|
||||
"globals must be a real dict or a frozendict; try eval(expr, {}, mapping)"
|
||||
} else {
|
||||
"globals must be a dict"
|
||||
"globals must be a dict or a frozendict"
|
||||
})
|
||||
}
|
||||
"exec" => vm.new_type_error(format!(
|
||||
"exec() globals must be a dict, not {}",
|
||||
"exec() globals must be a dict or a frozendict, not {}",
|
||||
globals.class().name()
|
||||
)),
|
||||
_ => vm.new_type_error("globals must be a dict"),
|
||||
_ => vm.new_type_error("globals must be a dict or a frozendict"),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
@@ -460,15 +462,11 @@ mod builtins {
|
||||
Some(globals) => {
|
||||
validate_globals_dict(&globals, vm, func_name)?;
|
||||
|
||||
let globals = PyDictRef::try_from_object(vm, globals)?;
|
||||
if !globals.contains_key(identifier!(vm, __builtins__), vm) {
|
||||
let builtins_dict = vm.builtins.dict().into();
|
||||
globals.set_item(identifier!(vm, __builtins__), builtins_dict, vm)?;
|
||||
}
|
||||
let globals = PyRef::<PyAnyDict>::try_from_object(vm, globals)?;
|
||||
(
|
||||
globals.clone(),
|
||||
self.locals
|
||||
.unwrap_or_else(|| ArgMapping::from_dict_exact(globals.clone())),
|
||||
.unwrap_or_else(|| ArgMapping::from_anydict_exact(globals.clone())),
|
||||
)
|
||||
}
|
||||
None => (
|
||||
@@ -481,7 +479,27 @@ mod builtins {
|
||||
),
|
||||
};
|
||||
|
||||
let scope = crate::scope::Scope::with_builtins(Some(locals), globals, vm);
|
||||
let builtins = globals.inner_getitem_opt(identifier!(vm, __builtins__), vm)?;
|
||||
if builtins.is_none() {
|
||||
if globals.is_frozen() {
|
||||
let builtins = crate::frame::current_builtins()
|
||||
.unwrap_or_else(|| vm.builtins.dict().into());
|
||||
globals
|
||||
.as_object()
|
||||
.set_item(identifier!(vm, __builtins__), builtins, vm)
|
||||
.map_err(|_| {
|
||||
vm.new_type_error("cannot assign __builtins__ to frozendict globals")
|
||||
})?;
|
||||
} else {
|
||||
globals.set_item(
|
||||
identifier!(vm, __builtins__),
|
||||
vm.builtins.dict().into(),
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let scope = crate::scope::Scope::new(Some(locals), globals);
|
||||
Ok(scope)
|
||||
}
|
||||
}
|
||||
@@ -650,6 +668,7 @@ mod builtins {
|
||||
func: &str,
|
||||
closure: Option<PyRef<PyTuple<PyCellRef>>>,
|
||||
) -> PyResult {
|
||||
let from_string = matches!(&source, Either::B(_));
|
||||
// Determine code object:
|
||||
let code_obj = match source {
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
@@ -675,6 +694,20 @@ mod builtins {
|
||||
)));
|
||||
}
|
||||
|
||||
// The source-string path repeats the stored-builtins check through the
|
||||
// dictionary API, unlike direct evaluation of an existing code object.
|
||||
if from_string
|
||||
&& scope.globals.is_frozen()
|
||||
&& scope
|
||||
.globals
|
||||
.inner_getitem_opt(identifier!(vm, __builtins__), vm)?
|
||||
.is_none()
|
||||
{
|
||||
scope
|
||||
.globals
|
||||
.set_item(identifier!(vm, __builtins__), vm.builtins.dict().into(), vm)?;
|
||||
}
|
||||
|
||||
// Run the code:
|
||||
vm.run_code_obj_with_closure(code_obj, scope, closure)
|
||||
}
|
||||
@@ -721,7 +754,7 @@ mod builtins {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn globals(vm: &VirtualMachine) -> PyDictRef {
|
||||
fn globals(vm: &VirtualMachine) -> PyRef<PyAnyDict> {
|
||||
vm.current_globals()
|
||||
}
|
||||
|
||||
@@ -1490,6 +1523,7 @@ pub fn init_module(vm: &VirtualMachine, module: &Py<PyModule>) {
|
||||
"dict" => ctx.types.dict_type.to_owned(),
|
||||
"enumerate" => ctx.types.enumerate_type.to_owned(),
|
||||
"float" => ctx.types.float_type.to_owned(),
|
||||
"frozendict" => ctx.types.frozendict_type.to_owned(),
|
||||
"frozenset" => ctx.types.frozenset_type.to_owned(),
|
||||
"filter" => ctx.types.filter_type.to_owned(),
|
||||
"int" => ctx.types.int_type.to_owned(),
|
||||
|
||||
@@ -40,6 +40,7 @@ pub struct TypeZoo {
|
||||
pub float_type: &'static Py<PyType>,
|
||||
pub frame_type: &'static Py<PyType>,
|
||||
pub frame_locals_proxy_type: &'static Py<PyType>,
|
||||
pub frozendict_type: &'static Py<PyType>,
|
||||
pub frozenset_type: &'static Py<PyType>,
|
||||
pub generator_type: &'static Py<PyType>,
|
||||
pub int_type: &'static Py<PyType>,
|
||||
@@ -139,6 +140,7 @@ impl TypeZoo {
|
||||
dict_type: dict::PyDict::init_builtin_type(),
|
||||
enumerate_type: enumerate::PyEnumerate::init_builtin_type(),
|
||||
float_type: float::PyFloat::init_builtin_type(),
|
||||
frozendict_type: dict::PyFrozenDict::init_builtin_type(),
|
||||
frozenset_type: set::PyFrozenSet::init_builtin_type(),
|
||||
filter_type: filter::PyFilter::init_builtin_type(),
|
||||
list_type: list::PyList::init_builtin_type(),
|
||||
|
||||
@@ -340,7 +340,7 @@ impl SharedValue {
|
||||
// Stateless functions have no globals, so `__main__` is used,
|
||||
// just like for builtins such as exec().
|
||||
let globals = vm.main_namespace()?;
|
||||
Ok(PyFunction::new(code, globals, vm)?.into_pyobject(vm))
|
||||
Ok(PyFunction::new(code, globals.into(), vm)?.into_pyobject(vm))
|
||||
}
|
||||
Self::Pickled(data) => pickle_loads(&data, vm),
|
||||
}
|
||||
@@ -763,7 +763,12 @@ pub(crate) fn walk_instructions(
|
||||
|
||||
/// `_PyFunction_VerifyStateless`.
|
||||
fn verify_stateless_function(func: &Py<PyFunction>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
// `__globals__` is a dict by construction, so only the builtins are checked.
|
||||
let globals = func.globals.as_mutable().ok_or_else(|| {
|
||||
vm.new_type_error(format!(
|
||||
"unsupported globals {}",
|
||||
render_repr(func.globals.as_object(), vm)
|
||||
))
|
||||
})?;
|
||||
let builtins = func.builtins.downcast_ref::<PyDict>().ok_or_else(|| {
|
||||
vm.new_type_error(format!(
|
||||
"unsupported builtins {}",
|
||||
@@ -786,7 +791,7 @@ fn verify_stateless_function(func: &Py<PyFunction>, vm: &VirtualMachine) -> PyRe
|
||||
{
|
||||
return Err(vm.new_value_error("closures not supported"));
|
||||
}
|
||||
verify_stateless(&func.code, Some((&func.globals, builtins)), vm)
|
||||
verify_stateless(&func.code, Some((globals, builtins)), vm)
|
||||
}
|
||||
|
||||
/// `verify_script`: a script takes no arguments and returns only None.
|
||||
|
||||
@@ -24,7 +24,7 @@ mod vm_ops;
|
||||
use crate::{
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
|
||||
builtins::{
|
||||
self, PyBaseExceptionRef, PyBaseObject, PyDict, PyDictRef, PyFrozenSet, PyInt, PyList,
|
||||
self, PyAnyDict, PyBaseExceptionRef, PyBaseObject, PyDict, PyFrozenSet, PyInt, PyList,
|
||||
PyModule, PySet, PyStr, PyStrInterned, PyStrRef, PyTypeRef, PyUtf8Str, PyUtf8StrInterned,
|
||||
PyWeak,
|
||||
code::PyCode,
|
||||
@@ -3113,7 +3113,7 @@ impl VirtualMachine {
|
||||
.locals(self)
|
||||
}
|
||||
|
||||
pub fn current_globals(&self) -> PyDictRef {
|
||||
pub fn current_globals(&self) -> PyRef<PyAnyDict> {
|
||||
let ptr = crate::vm::thread::get_current_frame();
|
||||
if !ptr.is_null() {
|
||||
return unsafe { (*ptr).globals().to_owned() };
|
||||
@@ -3226,7 +3226,7 @@ impl VirtualMachine {
|
||||
// Locals fallback: use the heavy frame if available, otherwise
|
||||
// use globals as locals (light frame locals are on the data stack).
|
||||
let locals_mapping = self.current_frame().map_or_else(
|
||||
|| ArgMapping::from_dict_exact(globals.clone()),
|
||||
|| ArgMapping::from_anydict_exact(globals.clone()),
|
||||
|f| f.iframe().locals.clone_mapping(self),
|
||||
);
|
||||
(Some(locals_mapping), Some(globals))
|
||||
|
||||
@@ -83,15 +83,16 @@ impl VirtualMachine {
|
||||
}
|
||||
|
||||
pub fn new_scope_with_main(&self) -> PyResult<Scope> {
|
||||
let scope = self.new_scope_with_builtins();
|
||||
let main_module = self.new_module("__main__", scope.globals.clone(), None);
|
||||
let globals = self.ctx.new_dict();
|
||||
let scope = Scope::with_builtins(None, globals.clone(), self);
|
||||
let main_module = self.new_module("__main__", globals.clone(), None);
|
||||
|
||||
self.sys_module.get_attr("modules", self)?.set_item(
|
||||
"__main__",
|
||||
main_module.into(),
|
||||
self,
|
||||
)?;
|
||||
self.set_main_builtin_importer(&scope.globals)?;
|
||||
self.set_main_builtin_importer(&globals)?;
|
||||
|
||||
Ok(scope)
|
||||
}
|
||||
|
||||
+12
-7
@@ -762,12 +762,19 @@ fn setup_context(
|
||||
d.downcast::<crate::builtins::PyDict>()
|
||||
.map_err(|_| vm.new_type_error("sys.__dict__ is not a dictionary"))
|
||||
})?;
|
||||
(globals, vm.ctx.intern_str("<sys>"), 0)
|
||||
(globals.into(), vm.ctx.intern_str("<sys>"), 0)
|
||||
};
|
||||
|
||||
let registry = match globals.get_item("__warningregistry__", vm) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
let get_global = |name: &str| {
|
||||
if globals.is_frozen() {
|
||||
globals.inner_getitem_opt(name, vm)
|
||||
} else {
|
||||
Ok(globals.get_item(name, vm).ok())
|
||||
}
|
||||
};
|
||||
let registry = match get_global("__warningregistry__")? {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
let r = vm.ctx.new_dict();
|
||||
globals.set_item("__warningregistry__", r.clone().into(), vm)?;
|
||||
r.into()
|
||||
@@ -775,8 +782,6 @@ fn setup_context(
|
||||
};
|
||||
|
||||
// Setup module.
|
||||
let module = globals
|
||||
.get_item("__name__", vm)
|
||||
.unwrap_or_else(|_| vm.new_pyobj("<string>"));
|
||||
let module = get_global("__name__")?.unwrap_or_else(|| vm.new_pyobj("<string>"));
|
||||
Ok((filename.to_owned(), lineno, Some(module), registry))
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,6 +8,7 @@ Usage:
|
||||
python scripts/update_lib migrate cpython/Lib/test/test_foo.py
|
||||
python scripts/update_lib patches --from Lib/test/foo.py --to cpython/Lib/test/foo.py
|
||||
python scripts/update_lib auto-mark Lib/test/test_foo.py
|
||||
python scripts/update_lib copy-test SOURCE DESTINATION --test ClassName.method_name --source-sha256 HASH --destination-sha256 HASH
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -49,6 +50,11 @@ def main(argv: list[str] | None = None) -> int:
|
||||
help="Copy library file/directory from CPython (delete existing first)",
|
||||
add_help=False,
|
||||
)
|
||||
subparsers.add_parser(
|
||||
"copy-test",
|
||||
help="Copy one complete canonical test from hash-pinned inputs",
|
||||
add_help=False,
|
||||
)
|
||||
subparsers.add_parser(
|
||||
"deps",
|
||||
help="Show dependency information for a module",
|
||||
@@ -72,6 +78,11 @@ def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
return copy_lib_main(remaining)
|
||||
|
||||
if args.command == "copy-test":
|
||||
from update_lib.cmd_copy_test import main as copy_test_main
|
||||
|
||||
return copy_test_main(remaining)
|
||||
|
||||
if args.command == "migrate":
|
||||
from update_lib.cmd_migrate import main as migrate_main
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Copy one complete canonical test definition without refreshing its module."""
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from update_lib.patch_spec import _iter_class_methods, _iter_module_classes
|
||||
|
||||
|
||||
def _definition(contents: str, qualified_name: str):
|
||||
parts = qualified_name.split(".")
|
||||
if len(parts) != 2 or not all(part.isidentifier() for part in parts):
|
||||
raise ValueError("test name must be ClassName.method_name")
|
||||
tree = ast.parse(contents)
|
||||
classes = [node for node in _iter_module_classes(tree) if node.name == parts[0]]
|
||||
if len(classes) != 1:
|
||||
raise ValueError(f"Expected one class named {parts[0]}, found {len(classes)}")
|
||||
methods = [
|
||||
node for node in _iter_class_methods(classes[0]) if node.name == parts[1]
|
||||
]
|
||||
if len(methods) != 1:
|
||||
raise ValueError(
|
||||
f"Expected one method named {qualified_name}, found {len(methods)}"
|
||||
)
|
||||
method = methods[0]
|
||||
parents = {
|
||||
child: parent
|
||||
for parent in ast.walk(tree)
|
||||
for child in ast.iter_child_nodes(parent)
|
||||
}
|
||||
guards = []
|
||||
child = method
|
||||
while child in parents:
|
||||
parent = parents[child]
|
||||
if isinstance(parent, ast.If):
|
||||
branch = "body" if child in parent.body else "orelse"
|
||||
guards.append((ast.dump(parent.test), branch))
|
||||
elif not isinstance(parent, (ast.Module, ast.ClassDef)):
|
||||
raise ValueError("Only module/class scopes and if guards are supported")
|
||||
child = parent
|
||||
start = min([method.lineno, *(node.lineno for node in method.decorator_list)]) - 1
|
||||
return method, guards, start, method.end_lineno
|
||||
|
||||
|
||||
def copy_test(source: str, destination: str, qualified_name: str) -> str:
|
||||
"""Replace an unambiguous whole definition, preserving all surrounding bytes.
|
||||
|
||||
Guard, signature, decorator and indentation differences require a broader
|
||||
migration rather than silently changing the selected test's execution context.
|
||||
"""
|
||||
src, src_guards, src_start, src_end = _definition(source, qualified_name)
|
||||
dst, dst_guards, dst_start, dst_end = _definition(destination, qualified_name)
|
||||
if src_guards != dst_guards:
|
||||
raise ValueError("Source and destination test guards differ")
|
||||
|
||||
def signature(node):
|
||||
return (
|
||||
ast.dump(node.args),
|
||||
ast.dump(node.returns) if node.returns is not None else None,
|
||||
[ast.dump(param) for param in getattr(node, "type_params", [])],
|
||||
)
|
||||
|
||||
if type(src) is not type(dst) or signature(src) != signature(dst):
|
||||
raise ValueError("Source and destination test signatures differ")
|
||||
if [ast.dump(node) for node in src.decorator_list] != [
|
||||
ast.dump(node) for node in dst.decorator_list
|
||||
]:
|
||||
raise ValueError(
|
||||
"Source and destination decorators differ; preserve markers explicitly"
|
||||
)
|
||||
if src.col_offset != dst.col_offset:
|
||||
raise ValueError("Source and destination indentation differs")
|
||||
source_lines = source.splitlines(keepends=True)
|
||||
destination_lines = destination.splitlines(keepends=True)
|
||||
# AST equality omits comments, including project marker rationales. Require
|
||||
# the complete decorator block to match before replacing the definition.
|
||||
if (
|
||||
source_lines[src_start : src.lineno - 1]
|
||||
!= destination_lines[dst_start : dst.lineno - 1]
|
||||
):
|
||||
raise ValueError(
|
||||
"Source and destination decorator text differs; preserve markers explicitly"
|
||||
)
|
||||
definition = source_lines[src_start:src_end]
|
||||
result = "".join(
|
||||
destination_lines[:dst_start] + definition + destination_lines[dst_end:]
|
||||
)
|
||||
_, _, result_start, result_end = _definition(result, qualified_name)
|
||||
if result.splitlines(keepends=True)[result_start:result_end] != definition:
|
||||
raise ValueError("Copied definition is not byte-identical to the source")
|
||||
return result
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("source", type=Path)
|
||||
parser.add_argument("destination", type=Path)
|
||||
parser.add_argument("--test", required=True, help="Exact ClassName.method_name")
|
||||
parser.add_argument("--source-sha256", required=True)
|
||||
parser.add_argument("--destination-sha256", required=True)
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
source = args.source.read_bytes()
|
||||
destination = args.destination.read_bytes()
|
||||
for label, contents, expected in (
|
||||
("source", source, args.source_sha256),
|
||||
("destination", destination, args.destination_sha256),
|
||||
):
|
||||
if hashlib.sha256(contents).hexdigest() != expected:
|
||||
raise ValueError(f"{label} SHA256 does not match the pinned input")
|
||||
result = copy_test(
|
||||
source.decode("utf-8"), destination.decode("utf-8"), args.test
|
||||
).encode("utf-8")
|
||||
# Recheck the destination immediately before writing so stale invocations
|
||||
# fail instead of overwriting an intervening edit.
|
||||
if args.destination.read_bytes() != destination:
|
||||
raise ValueError("Destination changed during test preparation")
|
||||
args.destination.write_bytes(result)
|
||||
except (OSError, UnicodeError, SyntaxError, ValueError) as exc:
|
||||
parser.exit(1, f"Error: {exc}\n")
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"test": args.test,
|
||||
"source_sha256": args.source_sha256,
|
||||
"destination_before_sha256": args.destination_sha256,
|
||||
"destination_after_sha256": hashlib.sha256(result).hexdigest(),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
@@ -197,7 +197,10 @@ class PatchEntryVisitor(ast.NodeVisitor):
|
||||
patch = patch._replace(test_name="__self__")
|
||||
self.patches.append(patch)
|
||||
|
||||
self.generic_visit(node)
|
||||
# Keep conditional methods in this namespace, but do not enter
|
||||
# nested classes or the bodies of methods.
|
||||
for item in _iter_class_methods(node):
|
||||
self.visit(item)
|
||||
|
||||
|
||||
class PatchEntry(typing.NamedTuple):
|
||||
@@ -228,22 +231,35 @@ class PatchEntry(typing.NamedTuple):
|
||||
yield from visitor.patches
|
||||
|
||||
|
||||
def _iter_module_classes(node: ast.AST) -> "Iterator[ast.ClassDef]":
|
||||
"""Find module-scope classes, including conditional definitions.
|
||||
|
||||
Function-local and class-nested definitions have different namespaces and
|
||||
cannot be addressed by the patch format's unqualified class names.
|
||||
"""
|
||||
for child in ast.iter_child_nodes(node):
|
||||
if isinstance(child, ast.ClassDef):
|
||||
yield child
|
||||
elif not isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
|
||||
yield from _iter_module_classes(child)
|
||||
|
||||
|
||||
def _iter_class_methods(
|
||||
node: ast.AST,
|
||||
) -> "Iterator[ast.FunctionDef | ast.AsyncFunctionDef]":
|
||||
for child in ast.iter_child_nodes(node):
|
||||
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
yield child
|
||||
elif not isinstance(child, (ast.ClassDef, ast.Lambda)):
|
||||
yield from _iter_class_methods(child)
|
||||
|
||||
|
||||
def iter_tests(
|
||||
tree: ast.Module,
|
||||
) -> "Iterator[tuple[ast.ClassDef, ast.FunctionDef | ast.AsyncFunctionDef]]":
|
||||
for key, nodes in ast.iter_fields(tree):
|
||||
if key != "body":
|
||||
continue
|
||||
|
||||
for cls_node in nodes:
|
||||
if not isinstance(cls_node, ast.ClassDef):
|
||||
continue
|
||||
|
||||
for fn_node in cls_node.body:
|
||||
if not isinstance(fn_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
continue
|
||||
|
||||
yield (cls_node, fn_node)
|
||||
for cls_node in _iter_module_classes(tree):
|
||||
for fn_node in _iter_class_methods(cls_node):
|
||||
yield (cls_node, fn_node)
|
||||
|
||||
|
||||
def iter_patches(contents: str) -> "Iterator[PatchEntry]":
|
||||
@@ -286,24 +302,28 @@ def _iter_patch_lines(
|
||||
# Track class bases for inherited async method lookup
|
||||
class_bases: dict[str, list[str]] = {}
|
||||
all_classes = set()
|
||||
all_class_nodes = []
|
||||
for node in tree.body:
|
||||
if not isinstance(node, ast.ClassDef):
|
||||
continue
|
||||
|
||||
all_class_nodes = list(_iter_module_classes(tree))
|
||||
for node in all_class_nodes:
|
||||
if node.name in all_classes:
|
||||
raise ValueError(f"Ambiguous module-scope class name: {node.name}")
|
||||
all_classes.add(node.name)
|
||||
all_class_nodes.append(node)
|
||||
method_counts = collections.Counter(
|
||||
item.name for item in _iter_class_methods(node)
|
||||
)
|
||||
for method in patches.get(node.name, {}):
|
||||
if method_counts[method] > 1:
|
||||
raise ValueError(f"Ambiguous method name: {node.name}.{method}")
|
||||
|
||||
for node in tree.body:
|
||||
for node in all_class_nodes:
|
||||
if isinstance(node, ast.ClassDef):
|
||||
cache[node.name] = node.end_lineno
|
||||
cache[node.name] = (node.end_lineno, node.col_offset + len(DEFAULT_INDENT))
|
||||
class_bases[node.name] = [
|
||||
base.id
|
||||
for base in node.bases
|
||||
if isinstance(base, ast.Name) and base.id in all_classes
|
||||
]
|
||||
cls_async: set[str] = set()
|
||||
for item in node.body:
|
||||
for item in _iter_class_methods(node):
|
||||
if isinstance(item, ast.AsyncFunctionDef):
|
||||
cls_async.add(item.name)
|
||||
if cls_async:
|
||||
@@ -319,10 +339,11 @@ def _iter_patch_lines(
|
||||
|
||||
# Phase 2: Iterate and mark inherited tests
|
||||
for cls_name, tests in sorted(patches.items()):
|
||||
lineno = cache.get(cls_name)
|
||||
if not lineno:
|
||||
location = cache.get(cls_name)
|
||||
if not location:
|
||||
print(f"WARNING: {cls_name} does not exist in remote file", file=sys.stderr)
|
||||
continue
|
||||
lineno, indent = location
|
||||
|
||||
for test_name, specs in sorted(tests.items()):
|
||||
if test_name == "__self__":
|
||||
@@ -356,7 +377,7 @@ async def {test_name}(self):
|
||||
def {test_name}(self):
|
||||
{DEFAULT_INDENT}return super().{test_name}()
|
||||
""".rstrip()
|
||||
yield (lineno, textwrap.indent(patch_lines, DEFAULT_INDENT))
|
||||
yield (lineno, textwrap.indent(patch_lines, " " * indent))
|
||||
|
||||
# Phase 3: Mark the class itself
|
||||
for cls_node in all_class_nodes:
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
"""Tests for bounded, hash-pinned canonical test copying."""
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
import hashlib
|
||||
import io
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from update_lib.cmd_copy_test import copy_test, main
|
||||
|
||||
|
||||
class TestCopyTest(unittest.TestCase):
|
||||
destination = '''# Destination-only header
|
||||
class Finder:
|
||||
if int.__doc__:
|
||||
def example():
|
||||
r"""Count is below 800. π\\n"""
|
||||
|
||||
def untouched(self):
|
||||
return "destination"
|
||||
'''
|
||||
source = '''# Canonical header must not be copied
|
||||
class Finder:
|
||||
if int.__doc__:
|
||||
def example():
|
||||
r"""Count is below 850. π\\n"""
|
||||
|
||||
def untouched(self):
|
||||
return "canonical"
|
||||
'''
|
||||
|
||||
def test_complete_definition_and_surroundings(self):
|
||||
result = copy_test(self.source, self.destination, "Finder.example")
|
||||
self.assertEqual(result, self.destination.replace("below 800", "below 850"))
|
||||
self.assertIn('return "destination"', result)
|
||||
source_method = ast.parse(self.source).body[0].body[0].body[0]
|
||||
result_method = ast.parse(result).body[0].body[0].body[0]
|
||||
self.assertEqual(
|
||||
ast.get_source_segment(self.source, source_method),
|
||||
ast.get_source_segment(result, result_method),
|
||||
)
|
||||
|
||||
def test_guard_mismatch_is_rejected(self):
|
||||
with self.assertRaisesRegex(ValueError, "guards differ"):
|
||||
copy_test(
|
||||
self.source.replace("if int.__doc__", "if enabled"),
|
||||
self.destination,
|
||||
"Finder.example",
|
||||
)
|
||||
|
||||
def test_missing_or_ambiguous_definition_is_rejected(self):
|
||||
for source in ("class Other: pass\n", self.source + self.source):
|
||||
with self.subTest(source=source):
|
||||
with self.assertRaisesRegex(ValueError, "Expected one class"):
|
||||
copy_test(source, self.destination, "Finder.example")
|
||||
duplicate = self.source.replace(
|
||||
" def untouched(self):",
|
||||
" def example():\n pass\n\n def untouched(self):",
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "Expected one method"):
|
||||
copy_test(duplicate, self.destination, "Finder.example")
|
||||
|
||||
def test_decorator_and_signature_changes_are_rejected(self):
|
||||
decorated = self.source.replace(
|
||||
" def example():", " @decorator\n def example():"
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "decorators differ"):
|
||||
copy_test(decorated, self.destination, "Finder.example")
|
||||
with self.assertRaisesRegex(ValueError, "signatures differ"):
|
||||
copy_test(
|
||||
self.source.replace("def example():", "def example(arg):"),
|
||||
self.destination,
|
||||
"Finder.example",
|
||||
)
|
||||
|
||||
def test_nested_local_scope_is_not_selected(self):
|
||||
local = "def factory():\n" + "".join(
|
||||
" " + line for line in self.source.splitlines(True)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "Expected one class"):
|
||||
copy_test(local, self.destination, "Finder.example")
|
||||
|
||||
def test_decorator_comments_must_match_exactly(self):
|
||||
for source_marker, destination_marker in (
|
||||
(
|
||||
"@unittest.expectedFailure",
|
||||
"@unittest.expectedFailure # TODO: RUSTPYTHON; reason",
|
||||
),
|
||||
(
|
||||
"@unittest.expectedFailure # original reason",
|
||||
"@unittest.expectedFailure # changed reason",
|
||||
),
|
||||
(
|
||||
"@unittest.expectedFailure\n @other",
|
||||
"@unittest.expectedFailure\n # marker rationale\n @other",
|
||||
),
|
||||
(
|
||||
"@unittest.expectedFailureIf(\n condition,\n 'reason',\n )",
|
||||
"@unittest.expectedFailureIf(\n condition, # project limitation\n 'reason',\n )",
|
||||
),
|
||||
):
|
||||
with self.subTest(destination_marker=destination_marker):
|
||||
source = self.source.replace(
|
||||
" def example():",
|
||||
f" {source_marker}\n def example():",
|
||||
)
|
||||
destination = self.destination.replace(
|
||||
" def example():",
|
||||
f" {destination_marker}\n def example():",
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "decorator text differs"):
|
||||
copy_test(source, destination, "Finder.example")
|
||||
|
||||
def test_identical_decorator_comments_are_preserved(self):
|
||||
prefix = (
|
||||
" @unittest.expectedFailure # TODO: RUSTPYTHON; reason\n"
|
||||
" # Keep this rationale with the marker.\n"
|
||||
" def example():"
|
||||
)
|
||||
for declaration in ("def example():", "async def example():"):
|
||||
with self.subTest(declaration=declaration):
|
||||
source = self.source.replace(" def example():", prefix)
|
||||
destination = self.destination.replace(" def example():", prefix)
|
||||
source = source.replace("def example():", declaration)
|
||||
destination = destination.replace("def example():", declaration)
|
||||
self.assertEqual(
|
||||
copy_test(source, destination, "Finder.example"),
|
||||
destination.replace("below 800", "below 850"),
|
||||
)
|
||||
|
||||
def test_cli_does_not_write_on_decorator_comment_mismatch(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
source = Path(temp) / "source.py"
|
||||
destination = Path(temp) / "destination.py"
|
||||
source.write_text(
|
||||
self.source.replace(
|
||||
" def example():",
|
||||
" @unittest.expectedFailure\n def example():",
|
||||
)
|
||||
)
|
||||
destination.write_text(
|
||||
self.destination.replace(
|
||||
" def example():",
|
||||
" @unittest.expectedFailure # TODO: RUSTPYTHON; reason\n"
|
||||
" def example():",
|
||||
)
|
||||
)
|
||||
before = destination.read_bytes()
|
||||
with (
|
||||
contextlib.redirect_stderr(io.StringIO()) as errors,
|
||||
self.assertRaises(SystemExit),
|
||||
):
|
||||
main(
|
||||
[
|
||||
str(source),
|
||||
str(destination),
|
||||
"--test",
|
||||
"Finder.example",
|
||||
"--source-sha256",
|
||||
hashlib.sha256(source.read_bytes()).hexdigest(),
|
||||
"--destination-sha256",
|
||||
hashlib.sha256(before).hexdigest(),
|
||||
]
|
||||
)
|
||||
self.assertIn("decorator text differs", errors.getvalue())
|
||||
self.assertEqual(destination.read_bytes(), before)
|
||||
|
||||
def test_return_annotation_and_type_parameters_are_rejected(self):
|
||||
for declaration in ("def example() -> int:", "def example[T]():"):
|
||||
with self.subTest(declaration=declaration):
|
||||
source = self.source.replace("def example():", declaration)
|
||||
with self.assertRaisesRegex(ValueError, "signatures differ"):
|
||||
copy_test(source, self.destination, "Finder.example")
|
||||
|
||||
def test_cli_hashes_and_no_write_on_stale_input(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
source = Path(temp) / "source.py"
|
||||
destination = Path(temp) / "destination.py"
|
||||
source.write_bytes(self.source.encode())
|
||||
destination.write_bytes(self.destination.encode())
|
||||
args = [
|
||||
str(source),
|
||||
str(destination),
|
||||
"--test",
|
||||
"Finder.example",
|
||||
"--source-sha256",
|
||||
hashlib.sha256(source.read_bytes()).hexdigest(),
|
||||
"--destination-sha256",
|
||||
"0" * 64,
|
||||
]
|
||||
with (
|
||||
contextlib.redirect_stderr(io.StringIO()),
|
||||
self.assertRaises(SystemExit),
|
||||
):
|
||||
main(args)
|
||||
self.assertEqual(destination.read_bytes(), self.destination.encode())
|
||||
args[-1] = hashlib.sha256(destination.read_bytes()).hexdigest()
|
||||
with contextlib.redirect_stdout(io.StringIO()) as output:
|
||||
self.assertEqual(main(args), 0)
|
||||
self.assertIn('"source_sha256"', output.getvalue())
|
||||
self.assertEqual(
|
||||
destination.read_text(),
|
||||
self.destination.replace("below 800", "below 850"),
|
||||
)
|
||||
@@ -67,6 +67,38 @@ class TestAsync(unittest.TestCase):
|
||||
class TestExtractPatches(unittest.TestCase):
|
||||
"""Tests for extract_patches function."""
|
||||
|
||||
def test_class_conditional_method_markers_are_preserved(self):
|
||||
code = """class Owner:
|
||||
if enabled:
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_value(self):
|
||||
return 42
|
||||
"""
|
||||
patches = extract_patches(code)
|
||||
self.assertIn("test_value", patches["Owner"])
|
||||
clean = code.replace(
|
||||
" @unittest.expectedFailure # TODO: RUSTPYTHON\n", ""
|
||||
)
|
||||
result = apply_patches(clean, patches)
|
||||
self.assertIn(" @unittest.expectedFailure", result)
|
||||
method = list(iter_tests(ast.parse(result)))[0][1]
|
||||
self.assertEqual(method.col_offset, 8)
|
||||
self.assertEqual(ast.dump(method.body[0]), "Return(value=Constant(value=42))")
|
||||
|
||||
def test_nested_class_markers_do_not_target_module_names(self):
|
||||
code = """class Container:
|
||||
class Target:
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_value(self):
|
||||
pass
|
||||
def factory():
|
||||
class Target:
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_value(self):
|
||||
pass
|
||||
"""
|
||||
self.assertEqual(extract_patches(code), {})
|
||||
|
||||
def test_extract_expected_failure(self):
|
||||
"""Test extracting @unittest.expectedFailure decorator."""
|
||||
code = f"""
|
||||
@@ -177,6 +209,106 @@ class TestFoo(unittest.TestCase):
|
||||
class TestApplyPatches(unittest.TestCase):
|
||||
"""Tests for apply_patches function."""
|
||||
|
||||
def test_conditional_class_existing_method(self):
|
||||
code = """import unittest
|
||||
if enabled:
|
||||
class TestConditional:
|
||||
def test_value(self):
|
||||
assert 1 + 1 == 2
|
||||
"""
|
||||
result = apply_patches(
|
||||
code,
|
||||
{
|
||||
"TestConditional": {
|
||||
"test_value": [
|
||||
PatchSpec(
|
||||
UtMethod.ExpectedFailureIf, "condition", "known limitation"
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
)
|
||||
original = list(iter_tests(ast.parse(code)))[0][1]
|
||||
updated = list(iter_tests(ast.parse(result)))[0][1]
|
||||
self.assertEqual(ast.dump(original.body[0]), ast.dump(updated.body[0]))
|
||||
self.assertEqual(updated.col_offset, 8)
|
||||
self.assertIn(" @unittest.expectedFailureIf", result)
|
||||
|
||||
def test_conditional_class_inherited_override(self):
|
||||
code = """import unittest
|
||||
class Parent:
|
||||
async def test_value(self):
|
||||
return 42
|
||||
if enabled:
|
||||
class Child(Parent):
|
||||
pass
|
||||
"""
|
||||
result = apply_patches(
|
||||
code, {"Child": {"test_value": [PatchSpec(UtMethod.ExpectedSuccess)]}}
|
||||
)
|
||||
methods = list(iter_tests(ast.parse(result)))
|
||||
self.assertEqual([cls.name for cls, _ in methods], ["Parent", "Child"])
|
||||
self.assertIsInstance(methods[1][1], ast.AsyncFunctionDef)
|
||||
self.assertIn(" return await super().test_value()", result)
|
||||
self.assertIn(" @unittest.expectedSuccess", result)
|
||||
|
||||
def test_duplicate_conditional_class_names_are_rejected(self):
|
||||
code = """if enabled:
|
||||
class Duplicate:
|
||||
pass
|
||||
else:
|
||||
class Duplicate:
|
||||
pass
|
||||
"""
|
||||
with self.assertRaisesRegex(ValueError, "Ambiguous.*Duplicate"):
|
||||
apply_patches(
|
||||
code,
|
||||
{"Duplicate": {"test_value": [PatchSpec(UtMethod.ExpectedFailure)]}},
|
||||
)
|
||||
|
||||
def test_local_class_names_do_not_shadow_module_class(self):
|
||||
code = """import unittest
|
||||
class Target:
|
||||
def test_value(self):
|
||||
return 1
|
||||
class Container:
|
||||
class Target:
|
||||
def test_value(self):
|
||||
return 2
|
||||
def factory():
|
||||
class Target:
|
||||
def test_value(self):
|
||||
return 3
|
||||
"""
|
||||
result = apply_patches(
|
||||
code, {"Target": {"test_value": [PatchSpec(UtMethod.ExpectedFailure)]}}
|
||||
)
|
||||
tree = ast.parse(result)
|
||||
targets = [
|
||||
node
|
||||
for node in ast.walk(tree)
|
||||
if isinstance(node, ast.ClassDef) and node.name == "Target"
|
||||
]
|
||||
self.assertEqual(
|
||||
[len(node.body[0].decorator_list) for node in targets], [1, 0, 0]
|
||||
)
|
||||
|
||||
def test_duplicate_conditional_method_names_are_rejected(self):
|
||||
code = """class Owner:
|
||||
if enabled:
|
||||
def test_value(self):
|
||||
return 1
|
||||
else:
|
||||
def test_value(self):
|
||||
return 2
|
||||
"""
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "Ambiguous method name: Owner.test_value"
|
||||
):
|
||||
apply_patches(
|
||||
code, {"Owner": {"test_value": [PatchSpec(UtMethod.ExpectedFailure)]}}
|
||||
)
|
||||
|
||||
def test_apply_expected_failure(self):
|
||||
"""Test applying @unittest.expectedFailure."""
|
||||
code = """import unittest
|
||||
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
#![cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||
use rustpython_vm::{
|
||||
AsObject, PyResult, TryFromObject, VirtualMachine,
|
||||
builtins::{PyDictRef, PyStrRef},
|
||||
AsObject, PyRef, PyResult, TryFromObject, VirtualMachine,
|
||||
builtins::{PyAnyDict, PyStrRef},
|
||||
function::ArgIterable,
|
||||
identifier,
|
||||
};
|
||||
|
||||
pub(super) struct ShellHelper<'vm> {
|
||||
vm: &'vm VirtualMachine,
|
||||
globals: PyDictRef,
|
||||
globals: PyRef<PyAnyDict>,
|
||||
}
|
||||
|
||||
fn reverse_string(s: &mut String) {
|
||||
@@ -54,7 +54,7 @@ fn split_idents_on_dot(line: &str) -> Option<(usize, Vec<String>)> {
|
||||
}
|
||||
|
||||
impl<'vm> ShellHelper<'vm> {
|
||||
pub(super) const fn new(vm: &'vm VirtualMachine, globals: PyDictRef) -> Self {
|
||||
pub(super) const fn new(vm: &'vm VirtualMachine, globals: PyRef<PyAnyDict>) -> Self {
|
||||
ShellHelper { vm, globals }
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user