diff --git a/newsfragments/4226.fixed.md b/newsfragments/4226.fixed.md new file mode 100644 index 00000000..b2b7d7d1 --- /dev/null +++ b/newsfragments/4226.fixed.md @@ -0,0 +1 @@ +Fixes a compile error when declaring a standalone function or class method with a Python name that is a Rust keyword. diff --git a/pyo3-macros-backend/src/attributes.rs b/pyo3-macros-backend/src/attributes.rs index b7ef2ae6..52479552 100644 --- a/pyo3-macros-backend/src/attributes.rs +++ b/pyo3-macros-backend/src/attributes.rs @@ -1,6 +1,7 @@ use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ + ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, @@ -72,7 +73,7 @@ pub struct NameLitStr(pub Ident); impl Parse for NameLitStr { fn parse(input: ParseStream<'_>) -> Result { let string_literal: LitStr = input.parse()?; - if let Ok(ident) = string_literal.parse() { + if let Ok(ident) = string_literal.parse_with(Ident::parse_any) { Ok(NameLitStr(ident)) } else { bail_spanned!(string_literal.span() => "expected a single identifier in double quotes") diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index 5b14e8a6..325b3d52 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -123,6 +123,36 @@ fn custom_names() { }); } +#[pyclass(name = "loop")] +struct ClassRustKeywords { + #[pyo3(name = "unsafe", get, set)] + unsafe_variable: usize, +} + +#[pymethods] +impl ClassRustKeywords { + #[pyo3(name = "struct")] + fn struct_method(&self) {} + + #[staticmethod] + #[pyo3(name = "type")] + fn type_method() {} +} + +#[test] +fn keyword_names() { + Python::with_gil(|py| { + let typeobj = py.get_type_bound::(); + py_assert!(py, typeobj, "typeobj.__name__ == 'loop'"); + py_assert!(py, typeobj, "typeobj.struct.__name__ == 'struct'"); + py_assert!(py, typeobj, "typeobj.type.__name__ == 'type'"); + py_assert!(py, typeobj, "typeobj.unsafe.__name__ == 'unsafe'"); + py_assert!(py, typeobj, "not hasattr(typeobj, 'unsafe_variable')"); + py_assert!(py, typeobj, "not hasattr(typeobj, 'struct_method')"); + py_assert!(py, typeobj, "not hasattr(typeobj, 'type_method')"); + }); +} + #[pyclass] struct RawIdents { #[pyo3(get, set)] diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index ce4dff7b..9028f71a 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -14,6 +14,18 @@ use pyo3::types::{self, PyCFunction}; #[path = "../src/tests/common.rs"] mod common; +#[pyfunction(name = "struct")] +fn struct_function() {} + +#[test] +fn test_rust_keyword_name() { + Python::with_gil(|py| { + let f = wrap_pyfunction_bound!(struct_function)(py).unwrap(); + + py_assert!(py, f, "f.__name__ == 'struct'"); + }); +} + #[pyfunction(signature = (arg = true))] fn optional_bool(arg: Option) -> String { format!("{:?}", arg)