🌐 AI搜索 & 代理 主页
Skip to content
Draft
148 changes: 110 additions & 38 deletions crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",);
}
Expand Down Expand Up @@ -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;
Expand All @@ -409,6 +449,9 @@ fn generate_class_def(

#base_class
}

#subclass_impl
#transparent_impl
};
Ok(tokens)
}
Expand All @@ -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,
Expand Down Expand Up @@ -485,19 +528,47 @@ 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! {
// static_assertions::const_assert!(std::mem::size_of::<#base_type>() <= std::mem::size_of::<#ident>());
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())
Copy link
Member Author

Choose a reason for hiding this comment

The 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()? {
Expand Down Expand Up @@ -536,26 +607,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]
Expand All @@ -568,7 +619,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)
Expand All @@ -579,14 +629,30 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R
return Ok(item.into_token_stream());
};

if !attr.is_empty() {
return Err(syn::Error::new_spanned(
&attr[0],
"#[pyexception] impl doesn't allow attrs. Use #[pyclass] instead.",
));
// Check if with(Constructor) is specified
let mut has_constructor_trait = false;
let mut extra_attrs = Vec::new();
for nested in &attr {
if let NestedMeta::Meta(Meta::List(MetaList { path, nested, .. })) = nested {
if path.is_ident("with") {
// Check if Constructor is in the list
for meta in nested {
if let NestedMeta::Meta(Meta::Path(p)) = meta
&& p.is_ident("Constructor")
{
has_constructor_trait = true;
}
}
}
extra_attrs.push(NestedMeta::Meta(Meta::List(MetaList {
path: path.clone(),
paren_token: Default::default(),
nested: nested.clone(),
})));
}
}

let mut has_slot_new = false;
let mut has_slot_new = has_constructor_trait; // If Constructor trait is used, don't generate slot_new
let mut has_slot_init = false;
let syn::ItemImpl {
generics,
Expand Down Expand Up @@ -646,8 +712,15 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R
}
}
};

let extra_attrs_tokens = if extra_attrs.is_empty() {
quote!()
} else {
quote!(, #(#extra_attrs),*)
};

Ok(quote! {
#[pyclass(flags(BASETYPE, HAS_DICT))]
#[pyclass(flags(BASETYPE, HAS_DICT) #extra_attrs_tokens)]
impl #generics #self_ty {
#(#items)*

Expand Down Expand Up @@ -1053,9 +1126,8 @@ impl GetSetNursery {
item_ident: Ident,
) -> Result<()> {
assert!(!self.validated, "new item is not allowed after validation");
if !matches!(kind, GetSetItemKind::Get) && !cfgs.is_empty() {
bail_span!(item_ident, "Only the getter can have #[cfg]",);
}
// Note: Both getter and setter can have #[cfg], but they must have matching cfgs
// since the map key is (name, cfgs). This ensures getter and setter are paired correctly.
let entry = self.map.entry((name.clone(), cfgs)).or_default();
let func = match kind {
GetSetItemKind::Get => &mut entry.0,
Expand Down
64 changes: 59 additions & 5 deletions crates/derive-impl/src/pystructseq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,8 +446,10 @@ pub(crate) fn impl_pystruct_sequence(
};

let output = quote! {
// The Python type struct (user-defined, possibly empty)
#pytype_vis struct #pytype_ident;
// The Python type struct - newtype wrapping PyTuple
#[derive(Debug)]
#[repr(transparent)]
#pytype_vis struct #pytype_ident(pub ::rustpython_vm::builtins::PyTuple);

// PyClassDef for Python type
impl ::rustpython_vm::class::PyClassDef for #pytype_ident {
Expand Down Expand Up @@ -476,16 +478,68 @@ pub(crate) fn impl_pystruct_sequence(
}
}

// MaybeTraverse (empty - no GC fields in empty struct)
// PyPayload - following PyBool pattern (use base type's payload_type_id)
impl ::rustpython_vm::PyPayload for #pytype_ident {
#[inline]
fn payload_type_id() -> ::std::any::TypeId {
<::rustpython_vm::builtins::PyTuple as ::rustpython_vm::PyPayload>::payload_type_id()
}

#[inline]
fn validate_downcastable_from(obj: &::rustpython_vm::PyObject) -> bool {
obj.class().fast_issubclass(<Self as ::rustpython_vm::class::StaticType>::static_type())
}

fn class(_ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
<Self as ::rustpython_vm::class::StaticType>::static_type()
}
}

// MaybeTraverse - delegate to inner PyTuple
impl ::rustpython_vm::object::MaybeTraverse for #pytype_ident {
fn try_traverse(&self, _traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) {
// Empty struct has no fields to traverse
fn try_traverse(&self, traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) {
self.0.try_traverse(traverse_fn)
}
}

// Deref to access inner PyTuple
impl ::std::ops::Deref for #pytype_ident {
type Target = ::rustpython_vm::builtins::PyTuple;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

// PySubclass for proper inheritance
impl ::rustpython_vm::class::PySubclass for #pytype_ident {
type Base = ::rustpython_vm::builtins::PyTuple;

#[inline]
fn as_base(&self) -> &Self::Base {
&self.0
}
}

impl ::rustpython_vm::class::PySubclassTransparent for #pytype_ident {}

// PyStructSequence trait for Python type
impl ::rustpython_vm::types::PyStructSequence for #pytype_ident {
type Data = #data_ident;

// fn from_data(data: Self::Data, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTupleRef {
// let tuple = <Self::Data as ::rustpython_vm::types::PyStructSequenceData>::into_tuple(data, vm);
// // Create Self(tuple) as the payload
// let wrapped = #pytype_ident(tuple);
// let result = ::rustpython_vm::PyRef::new_ref(
// wrapped,
// <Self as ::rustpython_vm::class::StaticType>::static_type().to_owned(),
// None
// );
// // SAFETY: PyRef<Self> has same layout as PyRef<PyTuple> due to #[repr(transparent)]
// unsafe { ::std::mem::transmute(result) }
// }
}

// ToPyObject for Data struct - uses PyStructSequence::from_data
Expand Down
10 changes: 5 additions & 5 deletions crates/stdlib/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ mod array {
atomic_func,
builtins::{
PositionIterInternal, PyByteArray, PyBytes, PyBytesRef, PyDictRef, PyFloat,
PyGenericAlias, PyInt, PyList, PyListRef, PyStr, PyStrRef, PyTupleRef, PyTypeRef,
PyGenericAlias, PyInt, PyList, PyListRef, PyStr, PyStrRef, PyTupleRef, PyType,
PyTypeRef,
},
class_or_notimplemented,
convert::{ToPyObject, ToPyResult, TryFromBorrowedObject, TryFromObject},
Expand Down Expand Up @@ -651,10 +652,10 @@ mod array {
type Args = (ArrayNewArgs, KwArgs);

fn py_new(
cls: PyTypeRef,
cls: &Py<PyType>,
(ArrayNewArgs { spec, init }, kwargs): Self::Args,
vm: &VirtualMachine,
) -> PyResult {
) -> PyResult<Self> {
let spec = spec.as_str().chars().exactly_one().map_err(|_| {
vm.new_type_error("array() argument 1 must be a unicode character, not str")
})?;
Expand Down Expand Up @@ -701,8 +702,7 @@ mod array {
}
}

let zelf = Self::from(array).into_ref_with_type(vm, cls)?;
Ok(zelf.into())
Ok(Self::from(array))
}
}

Expand Down
Loading
Loading