Skip to content

Commit bc26a06

Browse files
hgmichdavidhewitt
authored andcommitted
Unraw complex struct enum field python names (#5050)
* Unraw complex struct enum field python names Stray case related to #2395 (probably unnoticed because the functionality is newer and a bit of an edge case). The `python_name` of struct-type enum variant fields (and the `__match_args__` of the variant) do not unraw the names of these fields, which effectively makes them unusable as the resulting identifiers contain '#' which the Python lexer interprets as comments. The only reason you'd probably want to do this anyway is to use reserved identifiers (`type` in my case). * Add newsfragment for fix * Fix clippy lints in impl_complex_enum_variant_match_args and callers No need to pass mutable vec ref anymore * don't overeagerly collect field_names iterator * add test case for complex enums containing raw identifiers * only test raw ident pattern matching on Python 3.10+
1 parent 345735b commit bc26a06

File tree

3 files changed

+42
-5
lines changed

3 files changed

+42
-5
lines changed

newsfragments/5050.fixed.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix struct-type complex enum variant fields exposing raw identifiers as `r#ident` in Python bindings.

pyo3-macros-backend/src/pyclass.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -1188,15 +1188,16 @@ fn impl_complex_enum_variant_cls(
11881188
fn impl_complex_enum_variant_match_args(
11891189
ctx @ Ctx { pyo3_path, .. }: &Ctx,
11901190
variant_cls_type: &syn::Type,
1191-
field_names: &mut Vec<Ident>,
1191+
field_names: &[Ident],
11921192
) -> syn::Result<(MethodAndMethodDef, syn::ImplItemFn)> {
11931193
let ident = format_ident!("__match_args__");
1194+
let field_names_unraw = field_names.iter().map(|name| name.unraw());
11941195
let mut match_args_impl: syn::ImplItemFn = {
11951196
parse_quote! {
11961197
#[classattr]
11971198
fn #ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'_, #pyo3_path::types::PyTuple>> {
11981199
#pyo3_path::types::PyTuple::new::<&str, _>(py, [
1199-
#(stringify!(#field_names),)*
1200+
#(stringify!(#field_names_unraw),)*
12001201
])
12011202
}
12021203
}
@@ -1257,7 +1258,7 @@ fn impl_complex_enum_struct_variant_cls(
12571258
}
12581259

12591260
let (variant_match_args, match_args_const_impl) =
1260-
impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names)?;
1261+
impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &field_names)?;
12611262

12621263
field_getters.push(variant_match_args);
12631264

@@ -1432,7 +1433,7 @@ fn impl_complex_enum_tuple_variant_cls(
14321433
slots.push(variant_getitem);
14331434

14341435
let (variant_match_args, match_args_method_impl) =
1435-
impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names)?;
1436+
impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &field_names)?;
14361437

14371438
field_getters.push(variant_match_args);
14381439

@@ -1743,7 +1744,7 @@ fn complex_enum_variant_field_getter<'a>(
17431744
let spec = FnSpec {
17441745
tp: crate::method::FnType::Getter(self_type.clone()),
17451746
name: field_name,
1746-
python_name: field_name.clone(),
1747+
python_name: field_name.unraw(),
17471748
signature,
17481749
convention: crate::method::CallingConvention::Noargs,
17491750
text_signature: None,

tests/test_enum.rs

+35
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,38 @@ fn custom_eq() {
369369
assert!(b.as_any().ne("A").unwrap());
370370
})
371371
}
372+
373+
#[pyclass]
374+
#[derive(Clone, Copy)]
375+
pub enum ComplexEnumWithRaw {
376+
Raw { r#type: i32 },
377+
}
378+
379+
// Cover simple field lookups with raw identifiers
380+
#[test]
381+
fn complex_enum_with_raw() {
382+
Python::with_gil(|py| {
383+
let complex = ComplexEnumWithRaw::Raw { r#type: 314159 };
384+
385+
py_assert!(py, complex, "complex.type == 314159");
386+
});
387+
}
388+
389+
// Cover pattern matching with raw identifiers
390+
#[test]
391+
#[cfg(Py_3_10)]
392+
fn complex_enum_with_raw_pattern_match() {
393+
Python::with_gil(|py| {
394+
let complex = ComplexEnumWithRaw::Raw { r#type: 314159 };
395+
let cls = py.get_type::<ComplexEnumWithRaw>();
396+
397+
// Cover destructuring by pattern matching
398+
py_run!(py, cls complex, r#"
399+
match complex:
400+
case cls.Raw(type=ty):
401+
assert ty == 314159
402+
case _:
403+
assert False, "no matching variant found"
404+
"#);
405+
});
406+
}

0 commit comments

Comments
 (0)