Fully integrate PyTupleTyped into PyTuple

This commit is contained in:
Jeong YunWon committed 2025-07-14 14:27:48 +09:00
1 parent 14ce76e6c8
commit 6342ad4fa7
5 files changed
+92 -151

No files matched your search

+4 -4
View File
@@ -2,8 +2,8 @@
mod jit;
use super::{
PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef, PyTupleRef, PyType,
PyTypeRef, tuple::PyTupleTyped,
PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef, PyTuple, PyTupleRef,
PyType, PyTypeRef,
};
#[cfg(feature = "jit")]
use crate::common::lock::OnceCell;
@@ -31,7 +31,7 @@ pub struct PyFunction {
code: PyRef<PyCode>,
globals: PyDictRef,
builtins: PyObjectRef,
closure: Option<PyRef<PyTupleTyped<PyCellRef>>>,
closure: Option<PyRef<PyTuple<PyCellRef>>>,
defaults_and_kwdefaults: PyMutex<(Option<PyTupleRef>, Option<PyDictRef>)>,
name: PyMutex<PyStrRef>,
qualname: PyMutex<PyStrRef>,
@@ -59,7 +59,7 @@ impl PyFunction {
pub(crate) fn new(
code: PyRef<PyCode>,
globals: PyDictRef,
closure: Option<PyRef<PyTupleTyped<PyCellRef>>>,
closure: Option<PyRef<PyTuple<PyCellRef>>>,
defaults: Option<PyTupleRef>,
kw_only_defaults: Option<PyDictRef>,
qualname: PyStrRef,
+79 -134
View File
@@ -3,7 +3,6 @@ use crate::common::{
hash::{PyHash, PyUHash},
lock::PyMutex,
};
use crate::object::{MaybeTraverse, Traverse, TraverseFn};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
atomic_func,
@@ -22,14 +21,14 @@ use crate::{
utils::collection_repr,
vm::VirtualMachine,
};
use std::{fmt, marker::PhantomData, sync::LazyLock};
use std::{fmt, sync::LazyLock};
#[pyclass(module = false, name = "tuple", traverse)]
pub struct PyTuple {
elements: Box<[PyObjectRef]>,
pub struct PyTuple<R = PyObjectRef> {
elements: Box<[R]>,
}
impl fmt::Debug for PyTuple {
impl<R> fmt::Debug for PyTuple<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: implement more informational, non-recursive Debug formatter
f.write_str("tuple")
@@ -140,39 +139,60 @@ impl Constructor for PyTuple {
}
}
impl AsRef<[PyObjectRef]> for PyTuple {
fn as_ref(&self) -> &[PyObjectRef] {
self.as_slice()
impl<R> AsRef<[R]> for PyTuple<R> {
fn as_ref(&self) -> &[R] {
&self.elements
}
}
impl std::ops::Deref for PyTuple {
type Target = [PyObjectRef];
impl<R> std::ops::Deref for PyTuple<R> {
type Target = [R];
fn deref(&self) -> &[PyObjectRef] {
self.as_slice()
fn deref(&self) -> &[R] {
&self.elements
}
}
impl<'a> std::iter::IntoIterator for &'a PyTuple {
type Item = &'a PyObjectRef;
type IntoIter = std::slice::Iter<'a, PyObjectRef>;
impl<'a, R> std::iter::IntoIterator for &'a PyTuple<R> {
type Item = &'a R;
type IntoIter = std::slice::Iter<'a, R>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> std::iter::IntoIterator for &'a Py<PyTuple> {
type Item = &'a PyObjectRef;
type IntoIter = std::slice::Iter<'a, PyObjectRef>;
impl<'a, R> std::iter::IntoIterator for &'a Py<PyTuple<R>> {
type Item = &'a R;
type IntoIter = std::slice::Iter<'a, R>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl PyTuple {
impl<R> PyTuple<R> {
pub const fn as_slice(&self) -> &[R] {
&self.elements
}
#[inline]
pub fn len(&self) -> usize {
self.elements.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
#[inline]
pub fn iter(&self) -> std::slice::Iter<'_, R> {
self.elements.iter()
}
}
impl PyTuple<PyObjectRef> {
pub fn new_ref(elements: Vec<PyObjectRef>, ctx: &Context) -> PyRef<Self> {
if elements.is_empty() {
ctx.empty_tuple.clone()
@@ -189,10 +209,6 @@ impl PyTuple {
Self { elements }
}
pub const fn as_slice(&self) -> &[PyObjectRef] {
&self.elements
}
fn repeat(zelf: PyRef<Self>, value: isize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
Ok(if zelf.elements.is_empty() || value == 0 {
vm.ctx.empty_tuple.clone()
@@ -214,6 +230,18 @@ impl PyTuple {
}
}
impl<T> PyTuple<PyRef<T>> {
pub fn new_ref_typed(elements: Vec<PyRef<T>>, ctx: &Context) -> PyRef<PyTuple<PyRef<T>>> {
// SAFETY: PyRef<T> has the same layout as PyObjectRef
unsafe {
let elements: Vec<PyObjectRef> =
std::mem::transmute::<Vec<PyRef<T>>, Vec<PyObjectRef>>(elements);
let tuple = PyTuple::<PyObjectRef>::new_ref(elements, ctx);
std::mem::transmute::<PyRef<PyTuple>, PyRef<PyTuple<PyRef<T>>>>(tuple)
}
}
}
#[pyclass(
flags(BASETYPE),
with(
@@ -272,11 +300,6 @@ impl PyTuple {
self.elements.len()
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.elements.is_empty()
}
#[pymethod(name = "__rmul__")]
#[pymethod]
fn __mul__(zelf: PyRef<Self>, value: ArgSize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
@@ -449,21 +472,38 @@ impl Representable for PyTuple {
}
}
impl PyRef<PyTuple> {
impl PyRef<PyTuple<PyObjectRef>> {
pub fn try_into_typed<T: PyPayload>(
self,
vm: &VirtualMachine,
) -> PyResult<PyRef<PyTupleTyped<PyRef<T>>>> {
PyRef::<PyTupleTyped<PyRef<T>>>::try_from_untyped(self, vm)
) -> PyResult<PyRef<PyTuple<PyRef<T>>>> {
// Check that all elements are of the correct type
for elem in self.as_slice() {
<PyRef<T> as TransmuteFromObject>::check(vm, elem)?;
}
// SAFETY: We just verified all elements are of type T
Ok(unsafe { std::mem::transmute::<PyRef<PyTuple>, PyRef<PyTuple<PyRef<T>>>>(self) })
}
/// # Safety
///
/// The caller must ensure that all elements in the tuple are valid instances
/// of type `T` before calling this method. This is typically verified by
/// calling `try_into_typed` first.
unsafe fn into_typed_unchecked<T: PyPayload>(self) -> PyRef<PyTupleTyped<PyRef<T>>> {
let obj: PyObjectRef = self.into();
unsafe { obj.downcast_unchecked::<PyTupleTyped<PyRef<T>>>() }
}
impl<T: PyPayload> PyRef<PyTuple<PyRef<T>>> {
pub fn into_untyped(self) -> PyRef<PyTuple> {
// SAFETY: PyTuple<PyRef<T>> has the same layout as PyTuple
unsafe { std::mem::transmute::<PyRef<PyTuple<PyRef<T>>>, PyRef<PyTuple>>(self) }
}
}
impl<T: PyPayload> Py<PyTuple<PyRef<T>>> {
pub fn as_untyped(&self) -> &Py<PyTuple> {
// SAFETY: PyTuple<PyRef<T>> has the same layout as PyTuple
unsafe { std::mem::transmute::<&Py<PyTuple<PyRef<T>>>, &Py<PyTuple>>(self) }
}
}
impl<T: PyPayload> From<PyRef<PyTuple<PyRef<T>>>> for PyTupleRef {
#[inline]
fn from(tup: PyRef<PyTuple<PyRef<T>>>) -> Self {
tup.into_untyped()
}
}
@@ -518,101 +558,6 @@ pub(crate) fn init(context: &Context) {
PyTupleIterator::extend_class(context, context.types.tuple_iterator_type);
}
#[repr(transparent)]
pub struct PyTupleTyped<R> {
// SAFETY INVARIANT: T must be repr(transparent) over PyObjectRef, and the
// elements must be logically valid when transmuted to T
tuple: PyTuple,
_marker: PhantomData<R>,
}
unsafe impl<R> Traverse for PyTupleTyped<R>
where
R: TransmuteFromObject,
{
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.tuple.traverse(tracer_fn);
}
}
impl<R: TransmuteFromObject + Traverse> MaybeTraverse for PyTupleTyped<R> {
const IS_TRACE: bool = true;
fn try_traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.traverse(tracer_fn);
}
}
impl<T: PyPayload> PyTupleTyped<PyRef<T>> {
pub fn new_ref(elements: Vec<PyRef<T>>, ctx: &Context) -> PyRef<Self> {
// SAFETY: PyRef<T> has the same layout as PyObjectRef
unsafe {
let elements: Vec<PyObjectRef> =
std::mem::transmute::<Vec<PyRef<T>>, Vec<PyObjectRef>>(elements);
let tuple = PyTuple::new_ref(elements, ctx);
tuple.into_typed_unchecked::<T>()
}
}
}
impl<T: PyPayload> PyRef<PyTupleTyped<PyRef<T>>> {
pub fn into_untyped(self) -> PyRef<PyTuple> {
// SAFETY: PyTupleTyped is transparent over PyTuple
unsafe { std::mem::transmute::<PyRef<PyTupleTyped<PyRef<T>>>, PyRef<PyTuple>>(self) }
}
pub fn try_from_untyped(tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult<Self> {
// Check that all elements are of the correct type
for elem in tuple.as_slice() {
<PyRef<T> as TransmuteFromObject>::check(vm, elem)?;
}
// SAFETY: We just verified all elements are of type T, and PyTupleTyped has the same layout as PyTuple
Ok(unsafe { std::mem::transmute::<PyRef<PyTuple>, PyRef<PyTupleTyped<PyRef<T>>>>(tuple) })
}
}
impl<T: PyPayload> Py<PyTupleTyped<PyRef<T>>> {
pub fn as_untyped(&self) -> &Py<PyTuple> {
// SAFETY: PyTupleTyped is transparent over PyTuple
unsafe { std::mem::transmute::<&Py<PyTupleTyped<PyRef<T>>>, &Py<PyTuple>>(self) }
}
}
impl<T: PyPayload> AsRef<[PyRef<T>]> for PyTupleTyped<PyRef<T>> {
fn as_ref(&self) -> &[PyRef<T>] {
self.as_slice()
}
}
impl<T: PyPayload> PyTupleTyped<PyRef<T>> {
#[inline]
pub fn as_slice(&self) -> &[PyRef<T>] {
unsafe { &*(self.tuple.as_slice() as *const [PyObjectRef] as *const [PyRef<T>]) }
}
#[inline]
pub fn len(&self) -> usize {
self.tuple.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.tuple.is_empty()
}
}
impl<R: fmt::Debug> fmt::Debug for PyTupleTyped<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.tuple.as_slice().fmt(f)
}
}
impl<T: PyPayload> From<PyRef<PyTupleTyped<PyRef<T>>>> for PyTupleRef {
#[inline]
fn from(tup: PyRef<PyTupleTyped<PyRef<T>>>) -> Self {
tup.into_untyped()
}
}
pub(super) fn tuple_hash(elements: &[PyObjectRef], vm: &VirtualMachine) -> PyResult<PyHash> {
#[cfg(target_pointer_width = "64")]
const PRIME1: PyUHash = 11400714785074694791;
+5 -5
View File
@@ -1,5 +1,5 @@
use super::{
PyClassMethod, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTuple, PyTupleRef, PyWeak,
PyClassMethod, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTupleRef, PyWeak,
mappingproxy::PyMappingProxy, object, union_,
};
use crate::{
@@ -12,7 +12,7 @@ use crate::{
PyMemberDescriptor,
},
function::PyCellRef,
tuple::{IntoPyTuple, PyTupleTyped},
tuple::{IntoPyTuple, PyTuple},
},
class::{PyClassImpl, StaticType},
common::{
@@ -62,7 +62,7 @@ unsafe impl crate::object::Traverse for PyType {
pub struct HeapTypeExt {
pub name: PyRwLock<PyStrRef>,
pub qualname: PyRwLock<PyStrRef>,
pub slots: Option<PyRef<PyTupleTyped<PyStrRef>>>,
pub slots: Option<PyRef<PyTuple<PyStrRef>>>,
pub sequence_methods: PySequenceMethods,
pub mapping_methods: PyMappingMethods,
}
@@ -1041,11 +1041,11 @@ impl Constructor for PyType {
// TODO: Flags is currently initialized with HAS_DICT. Should be
// updated when __slots__ are supported (toggling the flag off if
// a class has __slots__ defined).
let heaptype_slots: Option<PyRef<PyTupleTyped<PyStrRef>>> =
let heaptype_slots: Option<PyRef<PyTuple<PyStrRef>>> =
if let Some(x) = attributes.get(identifier!(vm, __slots__)) {
let slots = if x.class().is(vm.ctx.types.str_type) {
let x = unsafe { x.downcast_unchecked_ref::<PyStr>() };
PyTupleTyped::new_ref(vec![x.to_owned()], &vm.ctx)
PyTuple::new_ref_typed(vec![x.to_owned()], &vm.ctx)
} else {
let iter = x.get_iter(vm)?;
let elements = {
+1 -2
View File
@@ -11,7 +11,6 @@ use crate::{
},
getset::PyGetSet,
object, pystr,
tuple::PyTupleTyped,
type_::PyAttributes,
},
class::{PyClassImpl, StaticType},
@@ -375,7 +374,7 @@ impl Context {
}
#[inline]
pub fn empty_tuple_typed<T>(&self) -> &Py<PyTupleTyped<T>> {
pub fn empty_tuple_typed<T>(&self) -> &Py<PyTuple<T>> {
let py: &Py<PyTuple> = &self.empty_tuple;
unsafe { std::mem::transmute(py) }
}
+3 -6
View File
@@ -20,10 +20,7 @@ use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
builtins::{
PyBaseExceptionRef, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned, PyStrRef,
PyTypeRef,
code::PyCode,
pystr::AsPyStr,
tuple::{PyTuple, PyTupleTyped},
PyTypeRef, code::PyCode, pystr::AsPyStr, tuple::PyTuple,
},
codecs::CodecsRegistry,
common::{hash::HashSecret, lock::PyMutex, rc::PyRc},
@@ -609,7 +606,7 @@ impl VirtualMachine {
pub fn import_from<'a>(
&self,
module_name: impl AsPyStr<'a>,
from_list: &Py<PyTupleTyped<PyStrRef>>,
from_list: &Py<PyTuple<PyStrRef>>,
level: usize,
) -> PyResult {
let module_name = module_name.as_pystr(&self.ctx);
@@ -619,7 +616,7 @@ impl VirtualMachine {
fn import_inner(
&self,
module: &Py<PyStr>,
from_list: &Py<PyTupleTyped<PyStrRef>>,
from_list: &Py<PyTuple<PyStrRef>>,
level: usize,
) -> PyResult {
// if the import inputs seem weird, e.g a package import or something, rather than just