Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't use rust keywords as python function names #4225

Open
Databean opened this issue Jun 2, 2024 · 7 comments
Open

Can't use rust keywords as python function names #4225

Databean opened this issue Jun 2, 2024 · 7 comments
Labels

Comments

@Databean
Copy link
Contributor

Databean commented Jun 2, 2024

Bug Description

Using a rust keyword as the name of a function, e.g. #[pyo3(name = "<struct>")] results in a compilation error.

Steps to Reproduce

Sample program:

use pyo3::prelude::*;

#[pyclass(frozen, subclass)]
struct MyClass {
}

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

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

#[pyfunction]
#[pyo3(name = "struct")]
fn struct_function() -> usize {
    42
}

/// A Python module implemented in Rust.
#[pymodule]
fn pyo3_example(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
    m.add_class::<MyClass>()?;
    m.add_function(wrap_pyfunction!(struct_function, m)?)?;
    Ok(())
}

Backtrace

$ RUSTFLAGS="-Zmacro-backtrace" cargo build
   Compiling pyo3_example v0.1.0 (/home/data/programming/pyo3_example)
error: expected a single identifier in double quotes
  --> src/lib.rs:14:19
   |
14 |     #[pyo3(name = "struct")]
   |                   ^^^^^^^^

error: expected a single identifier in double quotes
  --> src/lib.rs:21:15
   |
21 | #[pyo3(name = "struct")]
   |               ^^^^^^^^

error[E0433]: failed to resolve: function `wrapped_pyfunction` is not a crate or module
   --> /home/data/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pyo3-0.21.2/src/macros.rs:153:14
    |
135 | macro_rules! wrap_pyfunction {
    | ---------------------------- in this expansion of `wrap_pyfunction!`
...
153 |             &wrapped_pyfunction::_PYO3_DEF,
    |              ^^^^^^^^^^^^^^^^^^ function `wrapped_pyfunction` is not a crate or module
    |
   ::: src/lib.rs:30:20
    |
30  |     m.add_function(wrap_pyfunction!(struct_function, m)?)?;
    |                    ------------------------------------ in this macro invocation

For more information about this error, try `rustc --explain E0433`.
error: could not compile `pyo3_example` (lib) due to 3 previous errors

Your operating system and version

$ uname -a
Linux data-desktop 6.9.1-arch1-2 #1 SMP PREEMPT_DYNAMIC Wed, 22 May 2024 13:47:07 +0000 x86_64 GNU/Linux

Your Python version (python --version)

Python 3.12.3

Your Rust version (rustc --version)

rustc 1.80.0-nightly (867900499 2024-05-23)

Your PyO3 version

0.21.2

How did you install python? Did you use a virtualenv?

$ pacman -S pyton
$ python3 -m venv .venv
$ source .venv/bin/activate
$ cargo build

Additional Info

struct is not a reserved word in Python, so it works normally as Python code:

class MyClass:
    def struct(self):
        return 42

def struct():
    return 42

m = MyClass()
print(m.struct())
print(struct())
@Databean Databean added the bug label Jun 2, 2024
Databean added a commit to Databean/pyo3 that referenced this issue Jun 2, 2024
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/latest/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/latest/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 PyO3#4225
@Databean
Copy link
Contributor Author

Databean commented Jun 2, 2024

Created #4226 to fix this issue.

@Icxolu
Copy link
Contributor

Icxolu commented Jun 2, 2024

This is also already possibly by using a raw identifier:

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

Databean added a commit to Databean/pyo3 that referenced this issue Jun 2, 2024
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 PyO3#4225
Databean added a commit to Databean/pyo3 that referenced this issue Jun 2, 2024
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 PyO3#4225
Databean added a commit to Databean/pyo3 that referenced this issue Jun 2, 2024
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 PyO3#4225
Databean added a commit to Databean/pyo3 that referenced this issue Jun 2, 2024
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 PyO3#4225
@Databean
Copy link
Contributor Author

Databean commented Jun 2, 2024

This is also already possibly by using a raw identifier:

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

This does work for the method, thanks:

>>> import pyo3_example
>>> m = pyo3_example.MyClass()
>>> m.struct()
42

But it does not work for the standalone function:

>>> import pyo3_example
>>> pyo3_example.struct()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'pyo3_example' has no attribute 'struct'. Did you mean: 'r#struct'?
>>> pyo3_example.r#struct
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'pyo3_example' has no attribute 'r'
>>> pyo3_example["r#struct"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'module' object is not subscriptable

#[pyo3(name = r#struct)] doesn't work either, still giving

error: expected string literal
  --> src/lib.rs:21:15
   |
21 | #[pyo3(name = r#struct)]
   |               ^^^^^^^^

@Icxolu
Copy link
Contributor

Icxolu commented Jun 2, 2024

It does work for standalone functions, if you use the raw identifier for the function itself:

#[pyfunction]
fn r#struct() -> usize {
    42
}

The syntax for the pyo3 attribute is (notice the quotes):

#[pyfunction]
#[pyo3(name = "r#struct")]
fn some_function -> usize {
    42
}

But this currently does also not work, but I would consider that a bug (I did track that down already, so fixing that should be easy).

@davidhewitt
Copy link
Member

davidhewitt commented Jun 3, 2024

I'd hope that name = "r#struct" shouldn't be necessary, as I mostly view the r# bit as a syntax ugly that's necessary to escape the parser handling keywords when not wanted. So my personal take is that I'd probably prefer

#[pyfunction]
#[pyo3(name = "r#struct")]
fn some_function -> usize {
    42
}

to continue to be invalid.

@Icxolu
Copy link
Contributor

Icxolu commented Jun 3, 2024

Just for reference, your example is not invalid (in the sense that is does compile just fine), it just produces no usable function on the Python side.

@davidhewitt
Copy link
Member

😬

github-merge-queue bot pushed a commit that referenced this issue Jun 3, 2024
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants