Skip to content

Commit

Permalink
fix error with complex enums with many fields (PyO3#4832)
Browse files Browse the repository at this point in the history
  • Loading branch information
Icxolu authored and JeanArhancet committed Jan 17, 2025
1 parent 6e2dd9c commit ede56ce
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 25 deletions.
1 change: 1 addition & 0 deletions newsfragments/4832.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`#[pyclass]` complex enums support more than 12 variant fields.
47 changes: 23 additions & 24 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ use crate::method::{FnArg, FnSpec, PyArg, RegularArg};
use crate::pyfunction::ConstructorAttribute;
use crate::pyimpl::{gen_py_const, get_cfg_attributes, PyClassMethodsType};
use crate::pymethod::{
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__, __STR__,
impl_py_class_attribute, impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef,
MethodAndSlotDef, PropertyType, SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__,
__RICHCMP__, __STR__,
};
use crate::pyversions::is_abi3_before;
use crate::utils::{self, apply_renaming_rule, Ctx, LitCStr, PythonDoc};
Expand Down Expand Up @@ -1185,34 +1186,30 @@ fn impl_complex_enum_variant_cls(
}

fn impl_complex_enum_variant_match_args(
ctx: &Ctx,
ctx @ Ctx { pyo3_path, .. }: &Ctx,
variant_cls_type: &syn::Type,
field_names: &mut Vec<Ident>,
) -> (MethodAndMethodDef, syn::ImplItemConst) {
) -> syn::Result<(MethodAndMethodDef, syn::ImplItemFn)> {
let ident = format_ident!("__match_args__");
let match_args_const_impl: syn::ImplItemConst = {
let args_tp = field_names.iter().map(|_| {
quote! { &'static str }
});
let mut match_args_impl: syn::ImplItemFn = {
parse_quote! {
#[allow(non_upper_case_globals)]
const #ident: ( #(#args_tp,)* ) = (
#(stringify!(#field_names),)*
);
#[classattr]
fn #ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'_, #pyo3_path::types::PyTuple>> {
#pyo3_path::types::PyTuple::new::<&str, _>(py, [
#(stringify!(#field_names),)*
])
}
}
};

let spec = ConstSpec {
rust_ident: ident,
attributes: ConstAttributes {
is_class_attr: true,
name: None,
},
};

let variant_match_args = gen_py_const(variant_cls_type, &spec, ctx);
let spec = FnSpec::parse(
&mut match_args_impl.sig,
&mut match_args_impl.attrs,
Default::default(),
)?;
let variant_match_args = impl_py_class_attribute(variant_cls_type, &spec, ctx)?;

(variant_match_args, match_args_const_impl)
Ok((variant_match_args, match_args_impl))
}

fn impl_complex_enum_struct_variant_cls(
Expand Down Expand Up @@ -1260,14 +1257,15 @@ fn impl_complex_enum_struct_variant_cls(
}

let (variant_match_args, match_args_const_impl) =
impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names);
impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names)?;

field_getters.push(variant_match_args);

let cls_impl = quote! {
#[doc(hidden)]
#[allow(non_snake_case)]
impl #variant_cls {
#[allow(clippy::too_many_arguments)]
fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#fields_with_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> {
let base_value = #enum_name::#variant_ident { #(#field_names,)* };
<#pyo3_path::PyClassInitializer<#enum_name> as ::std::convert::From<#enum_name>>::from(base_value).add_subclass(#variant_cls)
Expand Down Expand Up @@ -1434,14 +1432,15 @@ fn impl_complex_enum_tuple_variant_cls(
slots.push(variant_getitem);

let (variant_match_args, match_args_method_impl) =
impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names);
impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names)?;

field_getters.push(variant_match_args);

let cls_impl = quote! {
#[doc(hidden)]
#[allow(non_snake_case)]
impl #variant_cls {
#[allow(clippy::too_many_arguments)]
fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#field_names : #field_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> {
let base_value = #enum_name::#variant_ident ( #(#field_names,)* );
<#pyo3_path::PyClassInitializer<#enum_name> as ::std::convert::From<#enum_name>>::from(base_value).add_subclass(#variant_cls)
Expand Down
2 changes: 1 addition & 1 deletion pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ fn impl_clear_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> syn::Result
})
}

fn impl_py_class_attribute(
pub(crate) fn impl_py_class_attribute(
cls: &syn::Type,
spec: &FnSpec<'_>,
ctx: &Ctx,
Expand Down
38 changes: 38 additions & 0 deletions src/tests/hygiene/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,44 @@ pub enum TupleEnumEqOrd {
Variant2(u32),
}

#[crate::pyclass(crate = "crate")]
pub enum ComplexEnumManyVariantFields {
ManyStructFields {
field_1: u16,
field_2: u32,
field_3: u32,
field_4: i32,
field_5: u32,
field_6: u32,
field_7: u8,
field_8: u32,
field_9: i32,
field_10: u32,
field_11: u32,
field_12: u32,
field_13: u32,
field_14: i16,
field_15: u32,
},
ManyTupleFields(
u16,
u32,
u32,
i32,
u32,
u32,
u8,
u32,
i32,
u32,
u32,
u32,
u32,
i16,
u32,
),
}

#[crate::pyclass(str = "{x}, {y}, {z}")]
#[pyo3(crate = "crate")]
pub struct PointFmt {
Expand Down

0 comments on commit ede56ce

Please sign in to comment.