diff --git a/guide/src/class.md b/guide/src/class.md index b5ef95cb2f7..5d0acd14675 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -52,15 +52,18 @@ enum HttpResponse { // ... } -// PyO3 also supports enums with non-unit variants +// 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 }, + RegularPolygon(u32, f64), + Nothing(), } ``` @@ -1178,7 +1181,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. @@ -1188,14 +1191,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) @@ -1204,8 +1207,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: @@ -1213,7 +1216,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/newsfragments/4135.added.md b/newsfragments/4135.added.md new file mode 100644 index 00000000000..23207c849d8 --- /dev/null +++ b/newsfragments/4135.added.md @@ -0,0 +1 @@ +Support `#[pyclass]` on enums that have tuple variants. \ No newline at end of file diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index d9c84655b42..19c0d4393ce 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -503,10 +503,10 @@ impl<'a> PyClassComplexEnum<'a> { 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)) + "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 @@ -525,12 +525,22 @@ impl<'a> PyClassComplexEnum<'a> { 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, + }) } }; @@ -549,10 +559,11 @@ impl<'a> PyClassComplexEnum<'a> { } } +#[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 +591,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 +620,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, } @@ -953,6 +982,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 +1041,154 @@ 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 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)] + 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) + } + + fn getitem(slf: #pyo3_path::PyRef, key: usize) -> #pyo3_path::PyResult> { + #matcher + } + + #(#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) } @@ -1127,6 +1307,9 @@ fn complex_enum_variant_new<'a>( 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) + } } } @@ -1179,6 +1362,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/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index aac804316f8..c8ff1e9ad7f 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -735,6 +735,93 @@ 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/src/enums.rs b/pytests/src/enums.rs index 0a1bc49bb63..209300d1b82 100644 --- a/pytests/src/enums.rs +++ b/pytests/src/enums.rs @@ -8,8 +8,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(()) } @@ -60,3 +62,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(), + } +} diff --git a/pytests/tests/test_enums.py b/pytests/tests/test_enums.py index 04b0cdca431..3a49dd820b3 100644 --- a/pytests/tests/test_enums.py +++ b/pytests/tests/test_enums.py @@ -114,3 +114,34 @@ 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 + + +def test_tuple_enum_index_getter(): + tuple_variant = enums.TupleEnum.Full(42, 3.14, False) + assert tuple_variant[0] == 42 diff --git a/pytests/tests/test_enums_match.py b/pytests/tests/test_enums_match.py index 4d55bbbe351..2b99c39dfa1 100644 --- a/pytests/tests/test_enums_match.py +++ b/pytests/tests/test_enums_match.py @@ -57,3 +57,23 @@ 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 is True + case enums.TupleEnum.EmptyTuple(): + assert True + case _: + print(variant) + assert False 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)