Convert native base references without consulting the Python class (#9090)

PyRef::into_base and Py::to_base cast the pointer directly instead of
going through a class-based downcast. into_base no longer reaches
unreachable_unchecked when the object's MRO omits the native base, and
to_base drops the equivalent debug assertion.

Extracted from #8963.

Assisted-by: Codex:gpt-6
Assisted-by: Claude:claude-opus-5-5

Co-authored-by: 1ndahous3 <9205230+1ndahous3@users.noreply.github.com>
This commit is contained in:
Jeong, YunWonand1ndahous3 authored and GitHub committed 2026-10-13 20:50:13 +09:00
1 parent 7c3c7a73c7
commit 0803a55df5
1 file changed
+38 -10
+38 -10
View File
@@ -2904,16 +2904,14 @@ where
T::Base: core::fmt::Debug,
{
/// Converts this reference to the base type (ownership transfer).
/// # Safety
/// T and T::Base must have compatible layouts in size_of::<T::Base>() bytes.
#[inline]
#[must_use]
pub fn into_base(self) -> PyRef<T::Base> {
let obj: PyObjectRef = self.into();
match obj.downcast() {
Ok(base_ref) => base_ref,
Err(_) => unsafe { core::hint::unreachable_unchecked() },
}
// PySubclass establishes the physical prefix; Python may have changed
// the object's class or MRO since this typed reference was constructed.
let ptr = self.ptr.cast();
core::mem::forget(self);
PyRef { ptr }
}
#[inline]
#[must_use]
@@ -2934,9 +2932,8 @@ impl<T: crate::class::PySubclass> Py<T> {
/// Converts `&Py<T>` to `&Py<T::Base>`.
#[inline]
pub fn to_base(&self) -> &Py<T::Base> {
debug_assert!(self.as_object().downcast_ref::<T::Base>().is_some());
// SAFETY: T is #[repr(transparent)] over T::Base,
// so Py<T> and Py<T::Base> have the same layout.
// SAFETY: PySubclass guarantees a valid base prefix with matching
// payload offsets and compatible object alignment.
unsafe { &*(self as *const Self as *const Py<T::Base>) }
}
@@ -3410,6 +3407,37 @@ mod tests {
}
}
#[test]
fn native_base_conversions_ignore_python_mro() {
crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
let class = vm.ctx.new_class(
None,
"HiddenOSErrorBase",
vm.ctx.exceptions.os_error.to_owned(),
Default::default(),
);
let marker: PyObjectRef = vm.ctx.new_int(42).into();
let error = vm.new_os_subtype_error(class.clone(), None, marker.clone());
// A custom MRO can omit native bases without changing the allocation.
*class.mro.write() = vec![class.clone(), vm.ctx.types.object_type.to_owned()];
assert!(
!error
.as_object()
.downcastable::<crate::exceptions::types::PyException>()
);
assert!(error.to_base().to_base().get_arg(0).unwrap().is(&marker));
assert!(
error
.into_base()
.into_base()
.get_arg(0)
.unwrap()
.is(&marker)
);
});
}
#[test]
fn native_type_basicsize_includes_payload_padding() {
use crate::class::PyClassDef;