Use `Ident::parse_any` for `name` attributes (#4226)

This makes it possible to use rust keywords as the name of python class
methods and standalone functions. For example:

```
struct MyClass {
}

impl MyClass {
    #[new]
    fn new() -> Self {
        MyClass {}
    }

    #[pyo3(name = "struct")]
    fn struct_method(&self) -> usize {
        42
    }
}

fn struct_function() -> usize {
    42
}
```

From the [`syn::Ident`
documentation](https://docs.rs/syn/2.0.66/syn/struct.Ident.html):

> An identifier constructed with `Ident::new` is permitted to be a Rust
keyword, though parsing one through its
[`Parse`](https://docs.rs/syn/2.0.66/syn/parse/trait.Parse.html)
implementation rejects Rust keywords. Use `input.call(Ident::parse_any)`
when parsing to match the behaviour of `Ident::new`.

Fixes issue #4225
This commit is contained in:
A. Cody Schuffelen 2024-06-03 12:45:36 -07:00 committed by GitHub
parent 7e5884c40b
commit 93ef056711
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 1 deletions

View File

@ -0,0 +1 @@
Fixes a compile error when declaring a standalone function or class method with a Python name that is a Rust keyword.

View File

@ -1,6 +1,7 @@
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::ToTokens; use quote::ToTokens;
use syn::{ use syn::{
ext::IdentExt,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
punctuated::Punctuated, punctuated::Punctuated,
spanned::Spanned, spanned::Spanned,
@ -72,7 +73,7 @@ pub struct NameLitStr(pub Ident);
impl Parse for NameLitStr { impl Parse for NameLitStr {
fn parse(input: ParseStream<'_>) -> Result<Self> { fn parse(input: ParseStream<'_>) -> Result<Self> {
let string_literal: LitStr = input.parse()?; 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)) Ok(NameLitStr(ident))
} else { } else {
bail_spanned!(string_literal.span() => "expected a single identifier in double quotes") bail_spanned!(string_literal.span() => "expected a single identifier in double quotes")

View File

@ -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::<ClassRustKeywords>();
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] #[pyclass]
struct RawIdents { struct RawIdents {
#[pyo3(get, set)] #[pyo3(get, set)]

View File

@ -14,6 +14,18 @@ use pyo3::types::{self, PyCFunction};
#[path = "../src/tests/common.rs"] #[path = "../src/tests/common.rs"]
mod common; 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))] #[pyfunction(signature = (arg = true))]
fn optional_bool(arg: Option<bool>) -> String { fn optional_bool(arg: Option<bool>) -> String {
format!("{:?}", arg) format!("{:?}", arg)