From d4872f1c91d9b0b7c29e10e5fe48c0527bdfe095 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 12 Apr 2024 22:42:10 +0200 Subject: [PATCH 1/8] feat: support pyclass on tuple enums --- guide/src/class.md | 29 +-- pyo3-macros-backend/src/pyclass.rs | 254 +++++++++++++++++++++----- pytests/src/enums.rs | 30 +++ pytests/tests/test_enums.py | 23 +++ pytests/tests/test_enums_match.py | 19 ++ tests/ui/invalid_pyclass_enum.rs | 6 - tests/ui/invalid_pyclass_enum.stderr | 8 - tests/ui/invalid_pymethod_enum.rs | 16 ++ tests/ui/invalid_pymethod_enum.stderr | 25 +++ 9 files changed, 340 insertions(+), 70 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index f353cc4787e..4f7d9b28b01 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -52,18 +52,23 @@ enum HttpResponse { // ... } -// PyO3 also supports enums with non-unit variants -// These complex enums have sligtly different behavior from the simple enums above -// They are meant to work with instance checks and match statement patterns +/// PyO3 also supports enums with Struct and Tuple variants +/// These complex enums have sligtly different behavior from the simple enums above +/// They are meant to work with instance checks and match statement patterns +/// The variants can be mixed and matched +/// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ... +/// Apart from this both types are functionally identical #[pyclass] enum Shape { Circle { radius: f64 }, - Rectangle { width: f64, height: f64 }, - RegularPolygon { side_count: u32, radius: f64 }, - Nothing {}, + Rectangle{ height : f64, width : f64}, + Square(u32), + Nothing(), } ``` + + The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass`, `Number`, `MyEnum`, `HttpResponse`, and `Shape`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. ### Restrictions @@ -1140,7 +1145,7 @@ enum BadSubclass { An enum is complex if it has any non-unit (struct or tuple) variants. -Currently PyO3 supports only struct variants in a complex enum. Support for unit and tuple variants is planned. +Currently PyO3 supports only struct and tuple variants in a complex enum. Support for unit variants is planned. PyO3 adds a class attribute for each variant, which may be used to construct values and in match patterns. PyO3 also provides getter methods for all fields of each variant. @@ -1150,14 +1155,14 @@ PyO3 adds a class attribute for each variant, which may be used to construct val enum Shape { Circle { radius: f64 }, Rectangle { width: f64, height: f64 }, - RegularPolygon { side_count: u32, radius: f64 }, + RegularPolygon ( u32, f64 ), Nothing { }, } # #[cfg(Py_3_10)] Python::with_gil(|py| { let circle = Shape::Circle { radius: 10.0 }.into_py(py); - let square = Shape::RegularPolygon { side_count: 4, radius: 10.0 }.into_py(py); + let square = Shape::RegularPolygon ( 4, 10.0 ).into_py(py); let cls = py.get_type_bound::(); pyo3::py_run!(py, circle square cls, r#" assert isinstance(circle, cls) @@ -1166,8 +1171,8 @@ Python::with_gil(|py| { assert isinstance(square, cls) assert isinstance(square, cls.RegularPolygon) - assert square.side_count == 4 - assert square.radius == 10.0 + assert square._0 == 4 + assert square._1 == 10.0 def count_vertices(cls, shape): match shape: @@ -1175,7 +1180,7 @@ Python::with_gil(|py| { return 0 case cls.Rectangle(): return 4 - case cls.RegularPolygon(side_count=n): + case cls.RegularPolygon(_0=n): return n case cls.Nothing(): return 0 diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index aff23b879f5..3bd4551acbd 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -487,29 +487,29 @@ struct PyClassComplexEnum<'a> { impl<'a> PyClassComplexEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { let witness = enum_ - .variants - .iter() - .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) - .expect("complex enum has a non-unit variant") - .ident - .to_owned(); - + .variants + .iter() + .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) + .expect("complex enum has a non-unit variant") + .ident + .to_owned(); + let extract_variant_data = |variant: &'a mut syn::Variant| -> syn::Result> { use syn::Fields; let ident = &variant.ident; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; - + let variant = match &variant.fields { Fields::Unit => { bail_spanned!(variant.span() => format!( - "Unit variant `{ident}` is not yet supported in a complex enum\n\ - = help: change to a struct variant with no fields: `{ident} {{ }}`\n\ - = note: the enum is complex because of non-unit variant `{witness}`", - ident=ident, witness=witness)) - } - Fields::Named(fields) => { - let fields = fields + "Unit variant `{ident}` is not yet supported in a complex enum\n\ + = help: change to a struct variant with no fields: `{ident} {{ }}`\n\ + = note: the enum is complex because of non-unit variant `{witness}`", + ident=ident, witness=witness)) + } + Fields::Named(fields) => { + let fields = fields .named .iter() .map(|field| PyClassEnumVariantNamedField { @@ -518,41 +518,49 @@ impl<'a> PyClassComplexEnum<'a> { span: field.span(), }) .collect(); - + PyClassEnumVariant::Struct(PyClassEnumStructVariant { ident, fields, options, }) } - Fields::Unnamed(_) => { - bail_spanned!(variant.span() => format!( - "Tuple variant `{ident}` is not yet supported in a complex enum\n\ - = help: change to a struct variant with named fields: `{ident} {{ /* fields */ }}`\n\ - = note: the enum is complex because of non-unit variant `{witness}`", - ident=ident, witness=witness)) + Fields::Unnamed(types) => { + let fields = types.unnamed.iter().enumerate().map(|(_i, field)| { + PyClassEnumVariantUnnamedField { + ty: &field.ty, + span: field.span(), + } + }).collect(); + + PyClassEnumVariant::Tuple(PyClassEnumTupleVariant { + ident, + fields, + options, + }) } }; - + Ok(variant) }; - - let ident = &enum_.ident; - - let variants: Vec<_> = enum_ + + let ident = &enum_.ident; + + let variants: Vec<_> = enum_ .variants .iter_mut() .map(extract_variant_data) .collect::>()?; - + Ok(Self { ident, variants }) } } +#[derive(Debug)] enum PyClassEnumVariant<'a> { // TODO(mkovaxx): Unit(PyClassEnumUnitVariant<'a>), Struct(PyClassEnumStructVariant<'a>), - // TODO(mkovaxx): Tuple(PyClassEnumTupleVariant<'a>), + Tuple(PyClassEnumTupleVariant<'a>), } trait EnumVariant { @@ -580,12 +588,14 @@ impl<'a> EnumVariant for PyClassEnumVariant<'a> { fn get_ident(&self) -> &syn::Ident { match self { PyClassEnumVariant::Struct(struct_variant) => struct_variant.ident, + PyClassEnumVariant::Tuple(tuple_variant) => tuple_variant.ident, } } fn get_options(&self) -> &EnumVariantPyO3Options { match self { PyClassEnumVariant::Struct(struct_variant) => &struct_variant.options, + PyClassEnumVariant::Tuple(tuple_variant) => &tuple_variant.options } } } @@ -607,19 +617,35 @@ impl<'a> EnumVariant for PyClassEnumUnitVariant<'a> { } /// A struct variant has named fields +#[derive(Debug)] struct PyClassEnumStructVariant<'a> { ident: &'a syn::Ident, fields: Vec>, options: EnumVariantPyO3Options, } +#[derive(Debug)] +struct PyClassEnumTupleVariant<'a> { + ident: &'a syn::Ident, + fields: Vec>, + options : EnumVariantPyO3Options, +} + +#[derive(Debug)] struct PyClassEnumVariantNamedField<'a> { ident: &'a syn::Ident, ty: &'a syn::Type, span: Span, } +#[derive(Debug)] +struct PyClassEnumVariantUnnamedField<'a> { + ty : &'a syn::Type, + span: Span, +} + /// `#[pyo3()]` options for pyclass enum variants +#[derive(Debug)] struct EnumVariantPyO3Options { name: Option, } @@ -813,7 +839,7 @@ fn impl_complex_enum( ctx: &Ctx, ) -> Result { let Ctx { pyo3_path } = ctx; - + // Need to rig the enum PyClass options let args = { let mut rigged_args = args.clone(); @@ -823,14 +849,14 @@ fn impl_complex_enum( rigged_args.options.subclass = parse_quote!(subclass); rigged_args }; - + let ctx = &Ctx::new(&args.options.krate); let cls = complex_enum.ident; let variants = complex_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx); - + let default_slots = vec![]; - + let impl_builder = PyClassImplsBuilder::new( cls, &args, @@ -838,8 +864,8 @@ fn impl_complex_enum( complex_enum_default_methods( cls, variants - .iter() - .map(|v| (v.get_ident(), v.get_python_name(&args))), + .iter() + .map(|v| (v.get_ident(), v.get_python_name(&args))), ctx, ), default_slots, @@ -891,25 +917,25 @@ fn impl_complex_enum( let mut variant_cls_impls = vec![]; for variant in &variants { let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident()); - + let variant_cls_zst = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] struct #variant_cls; }; variant_cls_zsts.push(variant_cls_zst); - + let variant_args = PyClassArgs { class_kind: PyClassKind::Struct, // TODO(mkovaxx): propagate variant.options options: parse_quote!(extends = #cls, frozen), }; - + let variant_cls_pytypeinfo = impl_pytypeinfo(&variant_cls, &variant_args, None, ctx); variant_cls_pytypeinfos.push(variant_cls_pytypeinfo); - + let variant_new = complex_enum_variant_new(cls, variant, ctx)?; - + let (variant_cls_impl, field_getters) = impl_complex_enum_variant_cls(cls, variant, ctx)?; variant_cls_impls.push(variant_cls_impl); @@ -921,9 +947,9 @@ fn impl_complex_enum( vec![variant_new], ) .impl_all(ctx)?; - - variant_cls_pyclass_impls.push(pyclass_impl); - } + + variant_cls_pyclass_impls.push(pyclass_impl); +} Ok(quote! { #pytypeinfo @@ -953,6 +979,9 @@ fn impl_complex_enum_variant_cls( PyClassEnumVariant::Struct(struct_variant) => { impl_complex_enum_struct_variant_cls(enum_name, struct_variant, ctx) } + PyClassEnumVariant::Tuple(tuple_variant) => { + impl_complex_enum_tuple_variant_cls(enum_name, tuple_variant, ctx) + } } } @@ -1009,6 +1038,73 @@ fn impl_complex_enum_struct_variant_cls( Ok((cls_impl, field_getters)) } +fn impl_complex_enum_tuple_variant_cls ( + enum_name: &syn::Ident, + variant: &PyClassEnumTupleVariant<'_>, + ctx: &Ctx, +) -> Result<(TokenStream, Vec)> +{ + let Ctx { pyo3_path } = ctx; + let variant_ident = &variant.ident; + let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); + let variant_cls_type = parse_quote!(#variant_cls); + + + // represents the index of the field + let mut field_names : Vec = vec![]; + let mut fields_with_types : Vec = vec![]; + let mut field_getters : Vec = vec![]; + let mut field_getter_impls : Vec = vec![]; + + for (index, field) in variant.fields.iter().enumerate() { + let field_name = format_ident!("_{}", index); + let field_type = field.ty; + let field_with_type = quote! { #field_name : #field_type }; + + let field_getter = + complex_enum_variant_field_getter(&variant_cls_type, &field_name, field.span, ctx)?; + + // Generate the match arms needed to destructure the tuple and access the specific field + let field_access_tokens: Vec<_> = (0..variant.fields.len()) + .map(|i| if i == index { + quote! { val } + } else { + quote! { _ } + }) + .collect(); + + + let field_getter_impl = quote! { + fn #field_name(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult<#field_type> { + match &*slf.into_super() { + #enum_name::#variant_ident ( #(#field_access_tokens), *) => Ok(val.clone()), + _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), + } + } + }; + + field_names.push(field_name); + fields_with_types.push(field_with_type); + field_getters.push(field_getter); + field_getter_impls.push(field_getter_impl); + } + + let cls_impl = quote! { + #[doc(hidden)] + #[allow(non_snake_case)] + impl #variant_cls { + 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::from(base_value).add_subclass(#variant_cls) + } + + #(#field_getter_impls)* + } + }; + + Ok((cls_impl, field_getters)) +} + fn gen_complex_enum_variant_class_ident(enum_: &syn::Ident, variant: &syn::Ident) -> syn::Ident { format_ident!("{}_{}", enum_, variant) } @@ -1126,6 +1222,9 @@ fn complex_enum_variant_new<'a>( match variant { PyClassEnumVariant::Struct(struct_variant) => { complex_enum_struct_variant_new(cls, struct_variant, ctx) + }, + PyClassEnumVariant::Tuple(tuple_variant) => { + complex_enum_tuple_variant_new(cls, tuple_variant, ctx) } } } @@ -1135,7 +1234,7 @@ fn complex_enum_struct_variant_new<'a>( variant: &'a PyClassEnumStructVariant<'a>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path } = ctx; let variant_cls = format_ident!("{}_{}", cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); @@ -1193,6 +1292,73 @@ fn complex_enum_struct_variant_new<'a>( crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) } +fn complex_enum_tuple_variant_new<'a>( + cls : &'a syn::Ident, + variant: &'a PyClassEnumTupleVariant<'a>, + ctx: &Ctx, +) -> Result { + let Ctx { pyo3_path } = ctx; + + let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident); + let variant_cls_type : syn::Type = parse_quote!(#variant_cls); + + let arg_py_ident : syn::Ident = parse_quote!(py); + let arg_py_type : syn::Type = parse_quote!(#pyo3_path::Python<'_>); + + let args = { + let mut no_pyo3_attrs = vec![]; + let attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; + + let mut args = vec![ + // py: Python<'_> + FnArg { + name : &arg_py_ident, + ty: &arg_py_type, + optional : None, + default : None, + py: true, + attrs: attrs.clone(), + is_varargs : false, + is_kwargs : false, + is_cancel_handle : false, + }, + ]; + + for (i, field) in variant.fields.iter().enumerate() { + // ! Warning : This leaks memory. I have no idea how else to do this - is this even bad? + let field_ident = format_ident!("_{}", i); + let boxed_ident = Box::from(field_ident); + let leaky_ident = Box::leak(boxed_ident); + args.push( FnArg { + name : leaky_ident, + ty : field.ty, + optional : None, + default : None, + py : false, + attrs : attrs.clone(), + is_varargs: false, + is_kwargs: false, + is_cancel_handle: false, + }); + } + args + }; + let signature = crate::pyfunction::FunctionSignature::from_arguments(args)?; + let spec = FnSpec { + tp: crate::method::FnType::FnNew, + name : &format_ident!("__pymethod_constructor__"), + python_name: format_ident!("__new__"), + signature, + convention: crate::method::CallingConvention::TpNew, + text_signature: None, + asyncness : None, + unsafety: None, + deprecations: Deprecations::new(ctx), + }; + + crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) +} + fn complex_enum_variant_field_getter<'a>( variant_cls_type: &'a syn::Type, field_name: &'a syn::Ident, diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index 4bb269fbbd2..59cb408037f 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -6,8 +6,10 @@ use pyo3::{ pub fn enums(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_wrapped(wrap_pyfunction_bound!(do_simple_stuff))?; m.add_wrapped(wrap_pyfunction_bound!(do_complex_stuff))?; + m.add_wrapped(wrap_pyfunction_bound!(do_tuple_stuff))?; Ok(()) } @@ -58,3 +60,31 @@ pub fn do_complex_stuff(thing: &ComplexEnum) -> ComplexEnum { }, } } + +#[pyclass] +pub enum TupleEnum { + Full(i32, f64, bool), + EmptyTuple(), +} + +#[pyfunction] +pub fn do_tuple_stuff(thing: &TupleEnum) -> TupleEnum { + match thing { + TupleEnum::Full(a, b, c) => TupleEnum::Full(*a, *b, *c), + TupleEnum::EmptyTuple() => TupleEnum::EmptyTuple(), + } +} + +#[pyclass] +pub enum MixedComplexEnum { + Nothing {}, + Empty(), +} + +#[pyfunction] +pub fn do_mixed_complex_stuff(thing: &MixedComplexEnum) -> MixedComplexEnum { + match thing { + MixedComplexEnum::Nothing {} => MixedComplexEnum::Nothing{}, + MixedComplexEnum::Empty() => MixedComplexEnum::Empty (), + } +} \ No newline at end of file diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 04b0cdca431..1ae2c0e4942 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -114,3 +114,26 @@ def test_complex_enum_pyfunction_in_out_desugared_match(variant: enums.ComplexEn assert z is True else: assert False + +def test_tuple_enum_variant_constructors(): + tuple_variant = enums.TupleEnum.Full(42, 3.14, False) + assert isinstance(tuple_variant, enums.TupleEnum.Full) + + empty_tuple_variant = enums.TupleEnum.EmptyTuple() + assert isinstance(empty_tuple_variant, enums.TupleEnum.EmptyTuple) + +@pytest.mark.parametrize( + "variant", + [ + enums.TupleEnum.Full(42, 3.14, False), + enums.TupleEnum.EmptyTuple(), + ], +) +def test_tuple_enum_variant_subclasses(variant: enums.TupleEnum): + assert isinstance(variant, enums.TupleEnum) + +def test_tuple_enum_field_getters(): + tuple_variant = enums.TupleEnum.Full(42, 3.14, False) + assert tuple_variant._0 == 42 + assert tuple_variant._1 == 3.14 + assert tuple_variant._2 is False \ No newline at end of file diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index 4d55bbbe351..6a37396e35e 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -57,3 +57,22 @@ def test_complex_enum_pyfunction_in_out(variant: enums.ComplexEnum): assert z is True case _: assert False + +@pytest.mark.parametrize( + "variant", + [ + enums.TupleEnum.Full(42, 3.14, True), + enums.TupleEnum.EmptyTuple(), + ], +) +def test_tuple_enum_match_statement(variant: enums.TupleEnum): + match variant: + case enums.TupleEnum.Full(_0=x, _1=y, _2=z): + assert x == 42 + assert y == 3.14 + assert z == True + case enums.TupleEnum.EmptyTuple(): + assert True + case _: + print(variant) + assert False \ No newline at end of file diff --git a/tests/ui/invalid_pyclass_enum.rs b/tests/ui/invalid_pyclass_enum.rs index 95879c2fbd1..6c4bbd0d0cc 100644 --- a/tests/ui/invalid_pyclass_enum.rs +++ b/tests/ui/invalid_pyclass_enum.rs @@ -21,10 +21,4 @@ enum NoUnitVariants { UnitVariant, } -#[pyclass] -enum NoTupleVariants { - StructVariant { field: i32 }, - TupleVariant(i32), -} - fn main() {} diff --git a/tests/ui/invalid_pyclass_enum.stderr b/tests/ui/invalid_pyclass_enum.stderr index a03a0ae2814..1546464a375 100644 --- a/tests/ui/invalid_pyclass_enum.stderr +++ b/tests/ui/invalid_pyclass_enum.stderr @@ -23,11 +23,3 @@ error: Unit variant `UnitVariant` is not yet supported in a complex enum | 21 | UnitVariant, | ^^^^^^^^^^^ - -error: Tuple variant `TupleVariant` is not yet supported in a complex enum - = help: change to a struct variant with named fields: `TupleVariant { /* fields */ }` - = note: the enum is complex because of non-unit variant `StructVariant` - --> tests/ui/invalid_pyclass_enum.rs:27:5 - | -27 | TupleVariant(i32), - | ^^^^^^^^^^^^ diff --git a/tests/ui/invalid_pymethod_enum.rs b/tests/ui/invalid_pymethod_enum.rs index 9b596e087ff..5c41d19d4e7 100644 --- a/tests/ui/invalid_pymethod_enum.rs +++ b/tests/ui/invalid_pymethod_enum.rs @@ -16,4 +16,20 @@ impl ComplexEnum { } } +#[pyclass] +enum TupleEnum { + Int(i32), + Str(String), +} + +#[pymethods] +impl TupleEnum { + fn mutate_in_place(&mut self) { + *self = match self { + TupleEnum::Int(int) => TupleEnum::Str(int.to_string()), + TupleEnum::Str(string) => TupleEnum::Int(string.len() as i32), + } + } +} + fn main() {} diff --git a/tests/ui/invalid_pymethod_enum.stderr b/tests/ui/invalid_pymethod_enum.stderr index 6cf6fe89bdf..bc377d2a055 100644 --- a/tests/ui/invalid_pymethod_enum.stderr +++ b/tests/ui/invalid_pymethod_enum.stderr @@ -22,3 +22,28 @@ note: required by a bound in `PyRefMut` | pub struct PyRefMut<'p, T: PyClass> { | ^^^^^^^^^^^^^^ required by this bound in `PyRefMut` = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0271]: type mismatch resolving `::Frozen == False` + --> tests/ui/invalid_pymethod_enum.rs:27:24 + | +27 | fn mutate_in_place(&mut self) { + | ^ expected `False`, found `True` + | +note: required by a bound in `extract_pyclass_ref_mut` + --> src/impl_/extract_argument.rs + | + | pub fn extract_pyclass_ref_mut<'a, 'py: 'a, T: PyClass>( + | ^^^^^^^^^^^^^^ required by this bound in `extract_pyclass_ref_mut` + +error[E0271]: type mismatch resolving `::Frozen == False` + --> tests/ui/invalid_pymethod_enum.rs:25:1 + | +25 | #[pymethods] + | ^^^^^^^^^^^^ expected `False`, found `True` + | +note: required by a bound in `PyRefMut` + --> src/pycell.rs + | + | pub struct PyRefMut<'p, T: PyClass> { + | ^^^^^^^^^^^^^^ required by this bound in `PyRefMut` + = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) From 278f4b65af2a05b437309dce11539eb5527b76fa Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 12 Apr 2024 22:43:28 +0200 Subject: [PATCH 2/8] cargo fmt --- pyo3-macros-backend/src/pyclass.rs | 164 +++++++++++++++-------------- pytests/src/enums.rs | 6 +- 2 files changed, 86 insertions(+), 84 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 3bd4551acbd..d7993e18f6e 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -487,19 +487,19 @@ struct PyClassComplexEnum<'a> { impl<'a> PyClassComplexEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { let witness = enum_ - .variants - .iter() - .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) - .expect("complex enum has a non-unit variant") - .ident - .to_owned(); - + .variants + .iter() + .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) + .expect("complex enum has a non-unit variant") + .ident + .to_owned(); + let extract_variant_data = |variant: &'a mut syn::Variant| -> syn::Result> { use syn::Fields; let ident = &variant.ident; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; - + let variant = match &variant.fields { Fields::Unit => { bail_spanned!(variant.span() => format!( @@ -507,9 +507,9 @@ impl<'a> PyClassComplexEnum<'a> { = help: change to a struct variant with no fields: `{ident} {{ }}`\n\ = note: the enum is complex because of non-unit variant `{witness}`", ident=ident, witness=witness)) - } - Fields::Named(fields) => { - let fields = fields + } + Fields::Named(fields) => { + let fields = fields .named .iter() .map(|field| PyClassEnumVariantNamedField { @@ -518,7 +518,7 @@ impl<'a> PyClassComplexEnum<'a> { span: field.span(), }) .collect(); - + PyClassEnumVariant::Struct(PyClassEnumStructVariant { ident, fields, @@ -526,13 +526,16 @@ impl<'a> PyClassComplexEnum<'a> { }) } Fields::Unnamed(types) => { - let fields = types.unnamed.iter().enumerate().map(|(_i, field)| { - PyClassEnumVariantUnnamedField { + let fields = types + .unnamed + .iter() + .enumerate() + .map(|(_i, field)| PyClassEnumVariantUnnamedField { ty: &field.ty, span: field.span(), - } - }).collect(); - + }) + .collect(); + PyClassEnumVariant::Tuple(PyClassEnumTupleVariant { ident, fields, @@ -540,18 +543,18 @@ impl<'a> PyClassComplexEnum<'a> { }) } }; - + Ok(variant) }; - - let ident = &enum_.ident; - - let variants: Vec<_> = enum_ + + let ident = &enum_.ident; + + let variants: Vec<_> = enum_ .variants .iter_mut() .map(extract_variant_data) .collect::>()?; - + Ok(Self { ident, variants }) } } @@ -595,7 +598,7 @@ impl<'a> EnumVariant for PyClassEnumVariant<'a> { fn get_options(&self) -> &EnumVariantPyO3Options { match self { PyClassEnumVariant::Struct(struct_variant) => &struct_variant.options, - PyClassEnumVariant::Tuple(tuple_variant) => &tuple_variant.options + PyClassEnumVariant::Tuple(tuple_variant) => &tuple_variant.options, } } } @@ -628,7 +631,7 @@ struct PyClassEnumStructVariant<'a> { struct PyClassEnumTupleVariant<'a> { ident: &'a syn::Ident, fields: Vec>, - options : EnumVariantPyO3Options, + options: EnumVariantPyO3Options, } #[derive(Debug)] @@ -640,7 +643,7 @@ struct PyClassEnumVariantNamedField<'a> { #[derive(Debug)] struct PyClassEnumVariantUnnamedField<'a> { - ty : &'a syn::Type, + ty: &'a syn::Type, span: Span, } @@ -839,7 +842,7 @@ fn impl_complex_enum( ctx: &Ctx, ) -> Result { let Ctx { pyo3_path } = ctx; - + // Need to rig the enum PyClass options let args = { let mut rigged_args = args.clone(); @@ -849,14 +852,14 @@ fn impl_complex_enum( rigged_args.options.subclass = parse_quote!(subclass); rigged_args }; - + let ctx = &Ctx::new(&args.options.krate); let cls = complex_enum.ident; let variants = complex_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx); - + let default_slots = vec![]; - + let impl_builder = PyClassImplsBuilder::new( cls, &args, @@ -864,8 +867,8 @@ fn impl_complex_enum( complex_enum_default_methods( cls, variants - .iter() - .map(|v| (v.get_ident(), v.get_python_name(&args))), + .iter() + .map(|v| (v.get_ident(), v.get_python_name(&args))), ctx, ), default_slots, @@ -917,25 +920,25 @@ fn impl_complex_enum( let mut variant_cls_impls = vec![]; for variant in &variants { let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident()); - + let variant_cls_zst = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] struct #variant_cls; }; variant_cls_zsts.push(variant_cls_zst); - + let variant_args = PyClassArgs { class_kind: PyClassKind::Struct, // TODO(mkovaxx): propagate variant.options options: parse_quote!(extends = #cls, frozen), }; - + let variant_cls_pytypeinfo = impl_pytypeinfo(&variant_cls, &variant_args, None, ctx); variant_cls_pytypeinfos.push(variant_cls_pytypeinfo); - + let variant_new = complex_enum_variant_new(cls, variant, ctx)?; - + let (variant_cls_impl, field_getters) = impl_complex_enum_variant_cls(cls, variant, ctx)?; variant_cls_impls.push(variant_cls_impl); @@ -947,9 +950,9 @@ fn impl_complex_enum( vec![variant_new], ) .impl_all(ctx)?; - - variant_cls_pyclass_impls.push(pyclass_impl); -} + + variant_cls_pyclass_impls.push(pyclass_impl); + } Ok(quote! { #pytypeinfo @@ -1038,23 +1041,21 @@ fn impl_complex_enum_struct_variant_cls( Ok((cls_impl, field_getters)) } -fn impl_complex_enum_tuple_variant_cls ( +fn impl_complex_enum_tuple_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumTupleVariant<'_>, ctx: &Ctx, -) -> Result<(TokenStream, Vec)> -{ +) -> Result<(TokenStream, Vec)> { let Ctx { pyo3_path } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); - // represents the index of the field - let mut field_names : Vec = vec![]; - let mut fields_with_types : Vec = vec![]; - let mut field_getters : Vec = vec![]; - let mut field_getter_impls : Vec = vec![]; + let mut field_names: Vec = vec![]; + let mut fields_with_types: Vec = vec![]; + let mut field_getters: Vec = vec![]; + let mut field_getter_impls: Vec = vec![]; for (index, field) in variant.fields.iter().enumerate() { let field_name = format_ident!("_{}", index); @@ -1062,27 +1063,28 @@ fn impl_complex_enum_tuple_variant_cls ( let field_with_type = quote! { #field_name : #field_type }; let field_getter = - complex_enum_variant_field_getter(&variant_cls_type, &field_name, field.span, ctx)?; + complex_enum_variant_field_getter(&variant_cls_type, &field_name, field.span, ctx)?; // Generate the match arms needed to destructure the tuple and access the specific field let field_access_tokens: Vec<_> = (0..variant.fields.len()) - .map(|i| if i == index { - quote! { val } - } else { - quote! { _ } + .map(|i| { + if i == index { + quote! { val } + } else { + quote! { _ } + } }) .collect(); - let field_getter_impl = quote! { fn #field_name(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult<#field_type> { match &*slf.into_super() { #enum_name::#variant_ident ( #(#field_access_tokens), *) => Ok(val.clone()), _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), } - } + } }; - + field_names.push(field_name); fields_with_types.push(field_with_type); field_getters.push(field_getter); @@ -1222,7 +1224,7 @@ fn complex_enum_variant_new<'a>( match variant { PyClassEnumVariant::Struct(struct_variant) => { complex_enum_struct_variant_new(cls, struct_variant, ctx) - }, + } PyClassEnumVariant::Tuple(tuple_variant) => { complex_enum_tuple_variant_new(cls, tuple_variant, ctx) } @@ -1234,7 +1236,7 @@ fn complex_enum_struct_variant_new<'a>( variant: &'a PyClassEnumStructVariant<'a>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path } = ctx; let variant_cls = format_ident!("{}_{}", cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); @@ -1293,17 +1295,17 @@ fn complex_enum_struct_variant_new<'a>( } fn complex_enum_tuple_variant_new<'a>( - cls : &'a syn::Ident, + cls: &'a syn::Ident, variant: &'a PyClassEnumTupleVariant<'a>, ctx: &Ctx, -) -> Result { +) -> Result { let Ctx { pyo3_path } = ctx; - + let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident); - let variant_cls_type : syn::Type = parse_quote!(#variant_cls); + let variant_cls_type: syn::Type = parse_quote!(#variant_cls); - let arg_py_ident : syn::Ident = parse_quote!(py); - let arg_py_type : syn::Type = parse_quote!(#pyo3_path::Python<'_>); + let arg_py_ident: syn::Ident = parse_quote!(py); + let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); let args = { let mut no_pyo3_attrs = vec![]; @@ -1312,30 +1314,30 @@ fn complex_enum_tuple_variant_new<'a>( let mut args = vec![ // py: Python<'_> FnArg { - name : &arg_py_ident, + name: &arg_py_ident, ty: &arg_py_type, - optional : None, - default : None, + optional: None, + default: None, py: true, attrs: attrs.clone(), - is_varargs : false, - is_kwargs : false, - is_cancel_handle : false, + is_varargs: false, + is_kwargs: false, + is_cancel_handle: false, }, ]; for (i, field) in variant.fields.iter().enumerate() { // ! Warning : This leaks memory. I have no idea how else to do this - is this even bad? - let field_ident = format_ident!("_{}", i); + let field_ident = format_ident!("_{}", i); let boxed_ident = Box::from(field_ident); let leaky_ident = Box::leak(boxed_ident); - args.push( FnArg { - name : leaky_ident, - ty : field.ty, - optional : None, - default : None, - py : false, - attrs : attrs.clone(), + args.push(FnArg { + name: leaky_ident, + ty: field.ty, + optional: None, + default: None, + py: false, + attrs: attrs.clone(), is_varargs: false, is_kwargs: false, is_cancel_handle: false, @@ -1346,16 +1348,16 @@ fn complex_enum_tuple_variant_new<'a>( let signature = crate::pyfunction::FunctionSignature::from_arguments(args)?; let spec = FnSpec { tp: crate::method::FnType::FnNew, - name : &format_ident!("__pymethod_constructor__"), + name: &format_ident!("__pymethod_constructor__"), python_name: format_ident!("__new__"), signature, convention: crate::method::CallingConvention::TpNew, text_signature: None, - asyncness : None, + asyncness: None, unsafety: None, deprecations: Deprecations::new(ctx), }; - + crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) } diff --git a/pytests/src/enums.rs b/pytests/src/enums.rs index 59cb408037f..142980c9620 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -84,7 +84,7 @@ pub enum MixedComplexEnum { #[pyfunction] pub fn do_mixed_complex_stuff(thing: &MixedComplexEnum) -> MixedComplexEnum { match thing { - MixedComplexEnum::Nothing {} => MixedComplexEnum::Nothing{}, - MixedComplexEnum::Empty() => MixedComplexEnum::Empty (), + MixedComplexEnum::Nothing {} => MixedComplexEnum::Nothing {}, + MixedComplexEnum::Empty() => MixedComplexEnum::Empty(), } -} \ No newline at end of file +} From a41b5ba4aaa65f995396415e48b25dfb3cbbdebe Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 12 Apr 2024 22:52:29 +0200 Subject: [PATCH 3/8] changelog --- newsfragments/4072.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/4072.added.md diff --git a/newsfragments/4072.added.md b/newsfragments/4072.added.md new file mode 100644 index 00000000000..23207c849d8 --- /dev/null +++ b/newsfragments/4072.added.md @@ -0,0 +1 @@ +Support `#[pyclass]` on enums that have tuple variants. \ No newline at end of file From 81a25b6f8440b577ecb586b02846ccfce1a34d74 Mon Sep 17 00:00:00 2001 From: Nikolas von Lonski Date: Fri, 12 Apr 2024 22:59:30 +0200 Subject: [PATCH 4/8] ruff format --- pytests/tests/test_enums.py | 5 ++++- pytests/tests/test_enums_match.py | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 1ae2c0e4942..a005b3d85c3 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -115,6 +115,7 @@ def test_complex_enum_pyfunction_in_out_desugared_match(variant: enums.ComplexEn else: assert False + def test_tuple_enum_variant_constructors(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert isinstance(tuple_variant, enums.TupleEnum.Full) @@ -122,6 +123,7 @@ def test_tuple_enum_variant_constructors(): empty_tuple_variant = enums.TupleEnum.EmptyTuple() assert isinstance(empty_tuple_variant, enums.TupleEnum.EmptyTuple) + @pytest.mark.parametrize( "variant", [ @@ -132,8 +134,9 @@ def test_tuple_enum_variant_constructors(): def test_tuple_enum_variant_subclasses(variant: enums.TupleEnum): assert isinstance(variant, enums.TupleEnum) + def test_tuple_enum_field_getters(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert tuple_variant._0 == 42 assert tuple_variant._1 == 3.14 - assert tuple_variant._2 is False \ No newline at end of file + assert tuple_variant._2 is False diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index 6a37396e35e..2b99c39dfa1 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -58,6 +58,7 @@ def test_complex_enum_pyfunction_in_out(variant: enums.ComplexEnum): case _: assert False + @pytest.mark.parametrize( "variant", [ @@ -70,9 +71,9 @@ def test_tuple_enum_match_statement(variant: enums.TupleEnum): case enums.TupleEnum.Full(_0=x, _1=y, _2=z): assert x == 42 assert y == 3.14 - assert z == True + assert z is True case enums.TupleEnum.EmptyTuple(): assert True case _: print(variant) - assert False \ No newline at end of file + assert False From d768bcf8b1a574ae42bc833862e6f18dc2fcae94 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Fri, 19 Apr 2024 11:09:33 +0100 Subject: [PATCH 5/8] fix class.md from pr comments --- guide/src/class.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 4f7d9b28b01..490105a0273 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -52,23 +52,21 @@ enum HttpResponse { // ... } -/// PyO3 also supports enums with Struct and Tuple variants -/// These complex enums have sligtly different behavior from the simple enums above -/// They are meant to work with instance checks and match statement patterns -/// The variants can be mixed and matched -/// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ... -/// Apart from this both types are functionally identical +// PyO3 also supports enums with Struct and Tuple variants +// These complex enums have sligtly different behavior from the simple enums above +// They are meant to work with instance checks and match statement patterns +// The variants can be mixed and matched +// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ... +// Apart from this both types are functionally identical #[pyclass] enum Shape { Circle { radius: f64 }, - Rectangle{ height : f64, width : f64}, - Square(u32), + Rectangle { height: f64, width: f64 }, + RegularPolygon(u32, f64), Nothing(), } ``` - - The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass`, `Number`, `MyEnum`, `HttpResponse`, and `Shape`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. ### Restrictions @@ -1155,14 +1153,14 @@ PyO3 adds a class attribute for each variant, which may be used to construct val enum Shape { Circle { radius: f64 }, Rectangle { width: f64, height: f64 }, - RegularPolygon ( u32, f64 ), + RegularPolygon(u32, f64), Nothing { }, } # #[cfg(Py_3_10)] Python::with_gil(|py| { let circle = Shape::Circle { radius: 10.0 }.into_py(py); - let square = Shape::RegularPolygon ( 4, 10.0 ).into_py(py); + let square = Shape::RegularPolygon(4, 10.0).into_py(py); let cls = py.get_type_bound::(); pyo3::py_run!(py, circle square cls, r#" assert isinstance(circle, cls) From 1d34f7e77daf474fa1d0c30cdfa9cc28eeeed26a Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Fri, 19 Apr 2024 10:50:00 +0100 Subject: [PATCH 6/8] add enum tuple variant getitem implementation --- pyo3-macros-backend/src/pyclass.rs | 82 +++++++++++++++++++++++++++ pyo3-macros-backend/src/pymethod.rs | 88 +++++++++++++++++++++++++++++ pytests/tests/test_enums.py | 4 ++ 3 files changed, 174 insertions(+) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index d7993e18f6e..ebba9103984 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1091,6 +1091,84 @@ fn impl_complex_enum_tuple_variant_cls( field_getter_impls.push(field_getter_impl); } + let num_fields = variant.fields.len(); + let match_arms: Vec<_> = (0..num_fields) + .map(|i| { + let field_access = format!("tup.{}", i); + quote! { + #i => Ok(Box::new(#field_access.clone())) + } + }) + .collect(); + + let matcher = if num_fields > 0 { + quote! { + let tup = &*slf.into_super(); + match key { + #( #match_arms, )* + _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), + } + } + } else { + quote! { + Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")) + } + }; + + let getitem_method = { + let arg_py_ident: syn::Ident = parse_quote!(py); + let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); + let mut no_pyo3_attrs = vec![]; + let attrs = crate::pyfunction::PyFunctionArgPyO3Attributes::from_attrs(&mut no_pyo3_attrs)?; + let key_name = format_ident!("key"); + let arg_key_type: syn::Type = parse_quote!(usize); + let args = vec![ + // py: Python<'_> + FnArg { + name: &arg_py_ident, + ty: &arg_py_type, + optional: None, + default: None, + py: true, + attrs: attrs.clone(), + is_varargs: false, + is_kwargs: false, + is_cancel_handle: false, + }, + FnArg { + name: &key_name, + ty: &arg_key_type, + optional: None, + default: None, + py: false, + attrs: attrs.clone(), + is_varargs: false, + is_kwargs: false, + is_cancel_handle: false, + }, + ]; + let signature = crate::pyfunction::FunctionSignature::from_arguments(args)?; + let func_self_type: syn::Type = parse_quote!(Box); + let self_type = crate::method::SelfType::TryFromBoundRef(func_self_type.span()); + let spec = FnSpec { + tp: crate::method::FnType::Fn(self_type.clone()), + name: &format_ident!("getitem"), + python_name: format_ident!("__getitem__"), + signature, + convention: crate::method::CallingConvention::Varargs, + text_signature: None, + asyncness: None, + unsafety: None, + deprecations: Deprecations::new(ctx), + }; + // This is the function I'm struggling with + // If this is removed, the code compiles fine and all existing tests pass + // but obviously __getitem__ isn't implemented on Python side + crate::pymethod::impl_py_getitem_def(&variant_cls_type, &self_type, spec, ctx)? + }; + + field_getters.push(getitem_method); + let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] @@ -1100,6 +1178,10 @@ fn impl_complex_enum_tuple_variant_cls( #pyo3_path::PyClassInitializer::from(base_value).add_subclass(#variant_cls) } + fn getitem(slf: #pyo3_path::PyRef, key: usize) -> #pyo3_path::PyResult> { + #matcher + } + #(#field_getter_impls)* } }; diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index abe8c7ac8a3..c2833c2a642 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -733,6 +733,94 @@ fn impl_call_getter( Ok(fncall) } +pub fn impl_py_getitem_def( + cls: &syn::Type, + _self_type: &SelfType, + spec: FnSpec<'_>, + ctx: &Ctx, +) -> Result { + + // + // APPROACH 1 + // This is what the #[pymethods] macro uses + // + // Not used... + // let method = { + // let kind = PyMethodKind::from_name("__getitem__"); + // let method_name = spec.python_name.to_string(); + // PyMethod { + // kind, + // method_name, + // spec, + // } + // }; + let doc = crate::get_doc(&[], None); + Ok(impl_py_method_def(cls, &spec, &doc, None, ctx)?) + + // APPROACH 2 + // APPROACH 2-a + // use the `generate_method_body`... + // let Ctx { pyo3_path } = ctx; + // let python_name = spec.null_terminated_python_name(); + // let mut holders = Holders::new(); + // let arguments: Vec = vec![Ty::Int]; + // let extract_error_mode = ExtractErrorMode::Raise; + // let body = generate_method_body( + // cls, + // spec, + // &arguments, + // extract_error_mode, + // &mut holders, + // None, + // ctx, + // )?; + + // APPROACH 2-b + // Modify code lifted from `impl_py_getter_def` + // let body = { + // let slf = self_type.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx); + // let name = &spec.name; + // let fncall = quote!(#cls::#name(#slf, py, key)); + // quote! { + // #pyo3_path::callback::convert(py, #fncall, key) + // } + // }; + // + // let wrapper_ident = format_ident!("__pymethod_getitem_wrapper__"); + // let cfg_attrs = TokenStream::new(); + // let init_holders = holders.init_holders(ctx); + // let check_gil_refs = holders.check_gil_refs(); + // let associated_method = quote! { + // #cfg_attrs + // unsafe fn #wrapper_ident( + // py: #pyo3_path::Python<'_>, + // _slf: *mut #pyo3_path::ffi::PyObject, + // key: #pyo3_path::ffi::PyObject, + // ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { + // #init_holders + // let result = #body; + // #check_gil_refs + // result + // } + // }; + // + // let method_def = quote! { + // #cfg_attrs + // #pyo3_path::class::PyMethodDefType::Getter( + // #pyo3_path::class::PyMethodDef::new( + // #python_name, + // #pyo3_path::impl_::pymethods::PyGetter(#cls::#wrapper_ident), + // #doc + // ) + // ) + // }; + // + // Ok(MethodAndMethodDef { + // associated_method, + // method_def, + // }) +} + // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_getter_def( cls: &syn::Type, diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index a005b3d85c3..558e577305a 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -140,3 +140,7 @@ def test_tuple_enum_field_getters(): assert tuple_variant._0 == 42 assert tuple_variant._1 == 3.14 assert tuple_variant._2 is False + +def test_tuple_enum_index_getter(): + tuple_variant = enums.TupleEnum.Full(42, 3.14, False) + assert tuple_variant[0] == 42 From 611781af708fd2258ccc131164d192af6637f727 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 29 Apr 2024 10:55:14 +0100 Subject: [PATCH 7/8] fmt --- pyo3-macros-backend/src/pymethod.rs | 1 - pytests/tests/test_enums.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 6f61ee4fa49..c8ff1e9ad7f 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -741,7 +741,6 @@ pub fn impl_py_getitem_def( spec: FnSpec<'_>, ctx: &Ctx, ) -> Result { - // // APPROACH 1 // This is what the #[pymethods] macro uses diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 558e577305a..3a49dd820b3 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -141,6 +141,7 @@ def test_tuple_enum_field_getters(): assert tuple_variant._1 == 3.14 assert tuple_variant._2 is False + def test_tuple_enum_index_getter(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert tuple_variant[0] == 42 From c674c6e56408fb9add801d90d94b2b7c3800ce0b Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 29 Apr 2024 10:55:26 +0100 Subject: [PATCH 8/8] rename newsfragment --- newsfragments/{4072.added.md => 4135.added.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename newsfragments/{4072.added.md => 4135.added.md} (100%) diff --git a/newsfragments/4072.added.md b/newsfragments/4135.added.md similarity index 100% rename from newsfragments/4072.added.md rename to newsfragments/4135.added.md