Skip to content

Commit 3bf283d

Browse files
authored
Merge pull request #832 from gilescope/macro
Need to be able to create structs via macro_rules
2 parents 455cc95 + 67948a2 commit 3bf283d

File tree

3 files changed

+102
-4
lines changed

3 files changed

+102
-4
lines changed

pyo3-macros-backend/src/pyclass.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::attributes::{
77
use crate::deprecations::Deprecations;
88
use crate::pyimpl::PyClassMethodsType;
99
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
10-
use crate::utils;
10+
use crate::utils::{self, unwrap_group};
1111
use proc_macro2::{Span, TokenStream};
1212
use quote::quote;
1313
use syn::ext::IdentExt;
@@ -93,7 +93,7 @@ impl PyClassArgs {
9393
// We allow arbitrary expressions here so you can e.g. use `8*64`
9494
self.freelist = Some(syn::Expr::clone(right));
9595
}
96-
"name" => match &**right {
96+
"name" => match unwrap_group(&**right) {
9797
syn::Expr::Lit(syn::ExprLit {
9898
lit: syn::Lit::Str(lit),
9999
..
@@ -114,7 +114,7 @@ impl PyClassArgs {
114114
}
115115
_ => expected!("type name (e.g. \"Name\")"),
116116
},
117-
"extends" => match &**right {
117+
"extends" => match unwrap_group(&**right) {
118118
syn::Expr::Path(exp) => {
119119
self.base = syn::TypePath {
120120
path: exp.path.clone(),
@@ -124,7 +124,7 @@ impl PyClassArgs {
124124
}
125125
_ => expected!("type path (e.g., my_mod::BaseClass)"),
126126
},
127-
"module" => match &**right {
127+
"module" => match unwrap_group(&**right) {
128128
syn::Expr::Lit(syn::ExprLit {
129129
lit: syn::Lit::Str(lit),
130130
..

pyo3-macros-backend/src/utils.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,10 @@ pub fn ensure_not_async_fn(sig: &syn::Signature) -> syn::Result<()> {
117117
};
118118
Ok(())
119119
}
120+
121+
pub fn unwrap_group(mut expr: &syn::Expr) -> &syn::Expr {
122+
while let syn::Expr::Group(g) = expr {
123+
expr = &*g.expr;
124+
}
125+
expr
126+
}

tests/test_macros.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//! Ensure that pyo3 macros can be used inside macro_rules!
2+
3+
use pyo3::prelude::*;
4+
use pyo3::wrap_pyfunction;
5+
6+
#[macro_use]
7+
mod common;
8+
9+
macro_rules! make_struct_using_macro {
10+
// Ensure that one doesn't need to fall back on the escape type: tt
11+
// in order to macro create pyclass.
12+
($class_name:ident, $py_name:literal) => {
13+
#[pyclass(name=$py_name)]
14+
struct $class_name {}
15+
};
16+
}
17+
18+
make_struct_using_macro!(MyBaseClass, "MyClass");
19+
20+
macro_rules! set_extends_via_macro {
21+
($class_name:ident, $base_class:path) => {
22+
// Try and pass a variable into the extends parameter
23+
#[pyclass(extends=$base_class)]
24+
struct $class_name {}
25+
};
26+
}
27+
28+
set_extends_via_macro!(MyClass2, MyBaseClass);
29+
30+
//
31+
// Check that pyfunctiona nd text_signature can be called with macro arguments.
32+
//
33+
34+
macro_rules! fn_macro {
35+
($sig:literal, $a_exp:expr, $b_exp:expr, $c_exp: expr) => {
36+
// Try and pass a variable into the extends parameter
37+
#[pyfunction($a_exp, $b_exp, "*", $c_exp)]
38+
#[pyo3(text_signature = $sig)]
39+
fn my_function_in_macro(a: i32, b: Option<i32>, c: i32) {
40+
let _ = (a, b, c);
41+
}
42+
};
43+
}
44+
45+
fn_macro!("(a, b=None, *, c=42)", a, b = "None", c = 42);
46+
47+
macro_rules! property_rename_via_macro {
48+
($prop_name:ident) => {
49+
#[pyclass]
50+
struct ClassWithProperty {
51+
member: u64,
52+
}
53+
54+
#[pymethods]
55+
impl ClassWithProperty {
56+
#[getter($prop_name)]
57+
fn get_member(&self) -> u64 {
58+
self.member
59+
}
60+
61+
#[setter($prop_name)]
62+
fn set_member(&mut self, member: u64) {
63+
self.member = member;
64+
}
65+
}
66+
};
67+
}
68+
69+
property_rename_via_macro!(my_new_property_name);
70+
71+
#[test]
72+
fn test_macro_rules_interactions() {
73+
Python::with_gil(|py| {
74+
let my_base = py.get_type::<MyBaseClass>();
75+
py_assert!(py, my_base, "my_base.__name__ == 'MyClass'");
76+
77+
let my_func = wrap_pyfunction!(my_function_in_macro, py).unwrap();
78+
py_assert!(
79+
py,
80+
my_func,
81+
"my_func.__text_signature__ == '(a, b=None, *, c=42)'"
82+
);
83+
84+
let renamed_prop = py.get_type::<ClassWithProperty>();
85+
py_assert!(
86+
py,
87+
renamed_prop,
88+
"hasattr(renamed_prop, 'my_new_property_name')"
89+
);
90+
});
91+
}

0 commit comments

Comments
 (0)