Repository navigation
New subclass payload layout #6319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
e05cf53
6a76024
695d32d
d384d06
33bb7c4
5a35dcf
38159d4
d7a563b
0379914
99a3b04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -350,6 +350,18 @@ fn generate_class_def( | |
| false | ||
| } | ||
| }); | ||
| // Check if the type has #[repr(transparent)] - only then we can safely | ||
| // generate PySubclass impl (requires same memory layout as base type) | ||
| let is_repr_transparent = attrs.iter().any(|attr| { | ||
| attr.path().is_ident("repr") | ||
| && if let Ok(Meta::List(l)) = attr.parse_meta() { | ||
| l.nested | ||
| .into_iter() | ||
| .any(|n| n.get_ident().is_some_and(|p| p == "transparent")) | ||
| } else { | ||
| false | ||
| } | ||
| }); | ||
| if base.is_some() && is_pystruct { | ||
| bail_span!(ident, "PyStructSequence cannot have `base` class attr",); | ||
| } | ||
|
|
@@ -379,12 +391,40 @@ fn generate_class_def( | |
| } | ||
| }); | ||
|
|
||
| let base_or_object = if let Some(base) = base { | ||
| let base_or_object = if let Some(ref base) = base { | ||
| quote! { #base } | ||
| } else { | ||
| quote! { ::rustpython_vm::builtins::PyBaseObject } | ||
| }; | ||
|
|
||
| // Generate PySubclass impl for #[repr(transparent)] types with base class | ||
| // (tuple struct assumed, so &self.0 works) | ||
| let subclass_impl = if !is_pystruct && is_repr_transparent { | ||
| base.as_ref().map(|typ| { | ||
| quote! { | ||
| impl ::rustpython_vm::class::PySubclass for #ident { | ||
| type Base = #typ; | ||
|
|
||
| #[inline] | ||
| fn as_base(&self) -> &Self::Base { | ||
| &self.0 | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| // Generate PySubclassTransparent marker for #[repr(transparent)] types | ||
| let transparent_impl = if !is_pystruct && is_repr_transparent && base.is_some() { | ||
| Some(quote! { | ||
| impl ::rustpython_vm::class::PySubclassTransparent for #ident {} | ||
| }) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| let tokens = quote! { | ||
| impl ::rustpython_vm::class::PyClassDef for #ident { | ||
| const NAME: &'static str = #name; | ||
|
|
@@ -409,6 +449,9 @@ fn generate_class_def( | |
|
|
||
| #base_class | ||
| } | ||
|
|
||
| #subclass_impl | ||
| #transparent_impl | ||
| }; | ||
| Ok(tokens) | ||
| } | ||
|
|
@@ -430,7 +473,7 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok | |
| ident, | ||
| &class_name, | ||
| module_name.as_deref(), | ||
| base, | ||
| base.clone(), | ||
| metaclass, | ||
| unhashable, | ||
| attrs, | ||
|
|
@@ -485,19 +528,46 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok | |
| } | ||
| }; | ||
|
|
||
| let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? { | ||
| let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span | ||
| // Generate PyPayload impl based on whether base exists | ||
| #[allow(clippy::collapsible_else_if)] | ||
| let impl_payload = if let Some(base_type) = &base { | ||
| let class_fn = if let Some(ctx_type_name) = class_meta.ctx_name()? { | ||
| let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); | ||
| quote! { ctx.types.#ctx_type_ident } | ||
| } else { | ||
| quote! { <Self as ::rustpython_vm::class::StaticType>::static_type() } | ||
| }; | ||
|
|
||
| // We need this to make extend mechanism work: | ||
| quote! { | ||
| impl ::rustpython_vm::PyPayload for #ident { | ||
| #[inline] | ||
| fn payload_type_id() -> ::std::any::TypeId { | ||
| <#base_type as ::rustpython_vm::PyPayload>::payload_type_id() | ||
| } | ||
|
|
||
| #[inline] | ||
| fn validate_downcastable_from(obj: &::rustpython_vm::PyObject) -> bool { | ||
| <Self as ::rustpython_vm::class::PyClassDef>::BASICSIZE <= obj.class().slots.basicsize && obj.class().fast_issubclass(<Self as ::rustpython_vm::class::StaticType>::static_type()) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I found another way to do with more tricks. Let's check it with OSError |
||
| } | ||
|
|
||
| fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { | ||
| ctx.types.#ctx_type_ident | ||
| #class_fn | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| quote! {} | ||
| if let Some(ctx_type_name) = class_meta.ctx_name()? { | ||
| let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); | ||
| quote! { | ||
| impl ::rustpython_vm::PyPayload for #ident { | ||
| fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { | ||
| ctx.types.#ctx_type_ident | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| quote! {} | ||
| } | ||
| }; | ||
|
|
||
| let empty_impl = if let Some(attrs) = class_meta.impl_attrs()? { | ||
|
|
@@ -536,26 +606,6 @@ pub(crate) fn impl_pyexception(attr: PunctuatedNestedMeta, item: Item) -> Result | |
| let class_name = class_meta.class_name()?; | ||
|
|
||
| let base_class_name = class_meta.base()?; | ||
| let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? { | ||
| let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span | ||
|
|
||
| // We need this to make extend mechanism work: | ||
| quote! { | ||
| impl ::rustpython_vm::PyPayload for #ident { | ||
| fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { | ||
| ctx.exceptions.#ctx_type_ident | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| quote! { | ||
| impl ::rustpython_vm::PyPayload for #ident { | ||
| fn class(_ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { | ||
| <Self as ::rustpython_vm::class::StaticType>::static_type() | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| let impl_pyclass = if class_meta.has_impl()? { | ||
| quote! { | ||
| #[pyexception] | ||
|
|
@@ -568,7 +618,6 @@ pub(crate) fn impl_pyexception(attr: PunctuatedNestedMeta, item: Item) -> Result | |
| let ret = quote! { | ||
| #[pyclass(module = false, name = #class_name, base = #base_class_name)] | ||
| #item | ||
| #impl_payload | ||
| #impl_pyclass | ||
| }; | ||
| Ok(ret) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,7 @@ | ||
| use super::{PyInt, PyStrRef, PyType, PyTypeRef}; | ||
| use crate::common::format::FormatSpec; | ||
| use crate::{ | ||
| AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, | ||
| VirtualMachine, | ||
| AsObject, Context, Py, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine, | ||
| class::PyClassImpl, | ||
| convert::{IntoPyException, ToPyObject, ToPyResult}, | ||
| function::{FuncArgs, OptionalArg}, | ||
|
|
@@ -81,15 +80,9 @@ impl PyObjectRef { | |
| } | ||
| } | ||
|
|
||
| #[pyclass(name = "bool", module = false, base = PyInt)] | ||
| pub struct PyBool; | ||
|
|
||
| impl PyPayload for PyBool { | ||
| #[inline] | ||
| fn class(ctx: &Context) -> &'static Py<PyType> { | ||
| ctx.types.bool_type | ||
| } | ||
| } | ||
| #[pyclass(name = "bool", module = false, base = PyInt, ctx = "bool_type")] | ||
| #[repr(transparent)] | ||
| pub struct PyBool(pub PyInt); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now we have to manually define the base layout to be always leading field. What I didn't check yet: Can using #[repr(C)] helps? Is there any other repr options to keep the first field as same as its original layout? |
||
|
|
||
| impl Debug for PyBool { | ||
| fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1070,6 +1070,19 @@ impl<T: PyPayload + std::fmt::Debug> PyRef<T> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| impl<T: crate::class::PySubclassTransparent + std::fmt::Debug> PyRef<T> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| T::Base: std::fmt::Debug, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Converts this reference to the base type (ownership transfer). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Only available for `#[repr(transparent)]` types. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[inline] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn into_base_ref(self) -> PyRef<T::Base> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SAFETY: #[repr(transparent)] guarantees same memory layout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unsafe { std::mem::transmute(self) } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1076
to
+1103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe Both Consider using pub fn into_base(self) -> PyRef<T::Base> {
let obj: PyObjectRef = self.into();
match obj.downcast() {
Ok(base_ref) => base_ref,
- Err(_) => unsafe { std::hint::unreachable_unchecked() },
+ Err(_) => unreachable!("PySubclass invariant violated: into_base downcast failed"),
}
}
#[inline]
pub fn upcast<U: PyPayload + StaticType>(self) -> PyRef<U>
where
T: StaticType,
{
debug_assert!(T::static_type().is_subtype(U::static_type()));
let obj: PyObjectRef = self.into();
match obj.downcast::<U>() {
Ok(upcast_ref) => upcast_ref,
- Err(_) => unsafe { std::hint::unreachable_unchecked() },
+ Err(_) => unreachable!("PySubclass invariant violated: upcast to {} failed", std::any::type_name::<U>()),
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| impl<T> Borrow<PyObject> for PyRef<T> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| T: PyPayload, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PySubclass::as_base()assumes tuple structs (self.0) but validate_base_field also allows named-field structs.For
#[repr(transparent)] struct S { base: Base }this will generate invalid code. Either (a) restrict repr(transparent)+base to tuple structs, or (b) generateas_base()using the actual first field (named vs unnamed) discovered during validation.🤖 Prompt for AI Agents