Skip to content

Commit 87c79c0

Browse files
authored
Merge pull request #2234 from davidhewitt/pyclass-args-refactor
pyclass: unify pyclass with its pyo3 arguments
2 parents 4339214 + 49c1d22 commit 87c79c0

20 files changed

+309
-344
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11+
### Changed
12+
13+
- Allow `#[pyo3(crate = "...", text_signature = "...")]` options to be used directly in `#[pyclass(crate = "...", text_signature = "...")]`. [#2234](https://github.com/PyO3/pyo3/pull/2234)
14+
1115
### Fixed
1216

1317
- Considered `PYTHONFRAMEWORK` when cross compiling in order that on macos cross compiling against a [Framework bundle](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html) is considered shared. [#2233](https://github.com/PyO3/pyo3/pull/2233)

guide/src/class.md

+12-19
Original file line numberDiff line numberDiff line change
@@ -195,22 +195,16 @@ Python::with_gil(|py|{
195195

196196
## Customizing the class
197197

198-
The `#[pyclass]` macro accepts the following parameters:
199-
200-
* `name="XXX"` - Set the class name shown in Python code. By default, the struct name is used as the class name.
201-
* `freelist=XXX` - The `freelist` parameter adds support of free allocation list to custom class.
202-
The performance improvement applies to types that are often created and deleted in a row,
203-
so that they can benefit from a freelist. `XXX` is a number of items for the free list.
204-
* `gc` - Classes with the `gc` parameter participate in Python garbage collection.
205-
If a custom class contains references to other Python objects that can be collected, the [`PyGCProtocol`]({{#PYO3_DOCS_URL}}/pyo3/class/gc/trait.PyGCProtocol.html) trait has to be implemented.
206-
* `weakref` - Adds support for Python weak references.
207-
* `extends=BaseType` - Use a custom base class. The base `BaseType` must implement `PyTypeInfo`. `enum` pyclasses can't use a custom base class.
208-
* `subclass` - Allows Python classes to inherit from this class. `enum` pyclasses can't be inherited from.
209-
* `dict` - Adds `__dict__` support, so that the instances of this type have a dictionary containing arbitrary instance variables.
210-
* `unsendable` - Making it safe to expose `!Send` structs to Python, where all object can be accessed
211-
by multiple threads. A class marked with `unsendable` panics when accessed by another thread.
212-
* `module="XXX"` - Set the name of the module the class will be shown as defined in. If not given, the class
213-
will be a virtual member of the `builtins` module.
198+
{{#include ../../pyo3-macros/docs/pyclass_parameters.md}}
199+
200+
[params-1]: {{#PYO3_DOCS_URL}}/pyo3/prelude/struct.PyAny.html
201+
[params-2]: https://en.wikipedia.org/wiki/Free_list
202+
[params-3]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html
203+
[params-4]: https://doc.rust-lang.org/stable/std/rc/struct.Rc.html
204+
[params-5]: https://doc.rust-lang.org/stable/std/sync/struct.Rc.html
205+
[params-6]: https://docs.python.org/3/library/weakref.html
206+
207+
These parameters are covered in various sections of this guide.
214208

215209
### Return type
216210

@@ -716,16 +710,15 @@ num=-1
716710

717711
## Making class method signatures available to Python
718712

719-
The [`#[pyo3(text_signature = "...")]`](./function.md#text_signature) option for `#[pyfunction]` also works for classes and methods:
713+
The [`text_signature = "..."`](./function.md#text_signature) option for `#[pyfunction]` also works for classes and methods:
720714

721715
```rust
722716
# #![allow(dead_code)]
723717
use pyo3::prelude::*;
724718
use pyo3::types::PyType;
725719

726720
// it works even if the item is not documented:
727-
#[pyclass]
728-
#[pyo3(text_signature = "(c, d, /)")]
721+
#[pyclass(text_signature = "(c, d, /)")]
729722
struct MyClass {}
730723

731724
#[pymethods]

pyo3-macros-backend/src/attributes.rs

+64-34
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,108 @@
1+
use proc_macro2::TokenStream;
2+
use quote::ToTokens;
13
use syn::{
24
parse::{Parse, ParseStream},
35
punctuated::Punctuated,
6+
spanned::Spanned,
47
token::Comma,
5-
Attribute, ExprPath, Ident, LitStr, Path, Result, Token,
8+
Attribute, Expr, ExprPath, Ident, LitStr, Path, Result, Token,
69
};
710

811
pub mod kw {
912
syn::custom_keyword!(annotation);
1013
syn::custom_keyword!(attribute);
14+
syn::custom_keyword!(dict);
15+
syn::custom_keyword!(extends);
16+
syn::custom_keyword!(freelist);
1117
syn::custom_keyword!(from_py_with);
18+
syn::custom_keyword!(gc);
1219
syn::custom_keyword!(get);
1320
syn::custom_keyword!(item);
14-
syn::custom_keyword!(pass_module);
21+
syn::custom_keyword!(module);
1522
syn::custom_keyword!(name);
23+
syn::custom_keyword!(pass_module);
1624
syn::custom_keyword!(set);
1725
syn::custom_keyword!(signature);
26+
syn::custom_keyword!(subclass);
1827
syn::custom_keyword!(text_signature);
1928
syn::custom_keyword!(transparent);
29+
syn::custom_keyword!(unsendable);
30+
syn::custom_keyword!(weakref);
31+
}
32+
33+
#[derive(Clone, Debug)]
34+
pub struct KeywordAttribute<K, V> {
35+
pub kw: K,
36+
pub value: V,
2037
}
2138

39+
/// A helper type which parses the inner type via a literal string
40+
/// e.g. LitStrValue<Path> -> parses "some::path" in quotes.
2241
#[derive(Clone, Debug, PartialEq)]
23-
pub struct FromPyWithAttribute(pub ExprPath);
42+
pub struct LitStrValue<T>(pub T);
2443

25-
impl Parse for FromPyWithAttribute {
44+
impl<T: Parse> Parse for LitStrValue<T> {
2645
fn parse(input: ParseStream) -> Result<Self> {
27-
let _: kw::from_py_with = input.parse()?;
28-
let _: Token![=] = input.parse()?;
29-
let string_literal: LitStr = input.parse()?;
30-
string_literal.parse().map(FromPyWithAttribute)
46+
let lit_str: LitStr = input.parse()?;
47+
lit_str.parse().map(LitStrValue)
3148
}
3249
}
3350

34-
#[derive(Clone, Debug, PartialEq)]
35-
pub struct NameAttribute(pub Ident);
36-
37-
impl Parse for NameAttribute {
38-
fn parse(input: ParseStream) -> Result<Self> {
39-
let _: kw::name = input.parse()?;
40-
let _: Token![=] = input.parse()?;
41-
let string_literal: LitStr = input.parse()?;
42-
string_literal.parse().map(NameAttribute)
51+
impl<T: ToTokens> ToTokens for LitStrValue<T> {
52+
fn to_tokens(&self, tokens: &mut TokenStream) {
53+
self.0.to_tokens(tokens)
4354
}
4455
}
4556

46-
/// For specifying the path to the pyo3 crate.
57+
/// A helper type which parses a name via a literal string
4758
#[derive(Clone, Debug, PartialEq)]
48-
pub struct CrateAttribute(pub Path);
59+
pub struct NameLitStr(pub Ident);
4960

50-
impl Parse for CrateAttribute {
61+
impl Parse for NameLitStr {
5162
fn parse(input: ParseStream) -> Result<Self> {
52-
let _: Token![crate] = input.parse()?;
53-
let _: Token![=] = input.parse()?;
5463
let string_literal: LitStr = input.parse()?;
55-
string_literal.parse().map(CrateAttribute)
64+
if let Ok(ident) = string_literal.parse() {
65+
Ok(NameLitStr(ident))
66+
} else {
67+
bail_spanned!(string_literal.span() => "expected a single identifier in double quotes")
68+
}
5669
}
5770
}
5871

59-
#[derive(Clone, Debug, PartialEq)]
60-
pub struct TextSignatureAttribute {
61-
pub kw: kw::text_signature,
62-
pub eq_token: Token![=],
63-
pub lit: LitStr,
72+
impl ToTokens for NameLitStr {
73+
fn to_tokens(&self, tokens: &mut TokenStream) {
74+
self.0.to_tokens(tokens)
75+
}
6476
}
6577

66-
impl Parse for TextSignatureAttribute {
78+
pub type ExtendsAttribute = KeywordAttribute<kw::extends, Path>;
79+
pub type FreelistAttribute = KeywordAttribute<kw::freelist, Box<Expr>>;
80+
pub type ModuleAttribute = KeywordAttribute<kw::module, LitStr>;
81+
pub type NameAttribute = KeywordAttribute<kw::name, NameLitStr>;
82+
pub type TextSignatureAttribute = KeywordAttribute<kw::text_signature, LitStr>;
83+
84+
impl<K: Parse + std::fmt::Debug, V: Parse> Parse for KeywordAttribute<K, V> {
6785
fn parse(input: ParseStream) -> Result<Self> {
68-
Ok(TextSignatureAttribute {
69-
kw: input.parse()?,
70-
eq_token: input.parse()?,
71-
lit: input.parse()?,
72-
})
86+
let kw: K = input.parse()?;
87+
let _: Token![=] = input.parse()?;
88+
let value = input.parse()?;
89+
Ok(KeywordAttribute { kw, value })
7390
}
7491
}
7592

93+
impl<K: ToTokens, V: ToTokens> ToTokens for KeywordAttribute<K, V> {
94+
fn to_tokens(&self, tokens: &mut TokenStream) {
95+
self.kw.to_tokens(tokens);
96+
Token![=](self.kw.span()).to_tokens(tokens);
97+
self.value.to_tokens(tokens);
98+
}
99+
}
100+
101+
pub type FromPyWithAttribute = KeywordAttribute<kw::from_py_with, LitStrValue<ExprPath>>;
102+
103+
/// For specifying the path to the pyo3 crate.
104+
pub type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>;
105+
76106
pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
77107
if is_attribute_ident(attr, "pyo3") {
78108
attr.parse_args_with(Punctuated::parse_terminated).map(Some)

pyo3-macros-backend/src/frompyobject.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,9 @@ impl<'a> Container<'a> {
252252
None => quote!(
253253
obj.get_item(#index)?.extract()
254254
),
255-
Some(FromPyWithAttribute(expr_path)) => quote! (
255+
Some(FromPyWithAttribute {
256+
value: expr_path, ..
257+
}) => quote! (
256258
#expr_path(obj.get_item(#index)?)
257259
),
258260
};
@@ -308,7 +310,9 @@ impl<'a> Container<'a> {
308310
new_err.set_cause(py, ::std::option::Option::Some(inner));
309311
new_err
310312
})?),
311-
Some(FromPyWithAttribute(expr_path)) => quote! (
313+
Some(FromPyWithAttribute {
314+
value: expr_path, ..
315+
}) => quote! (
312316
#expr_path(#get_field).map_err(|inner| {
313317
let py = _pyo3::PyNativeType::py(obj);
314318
let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
@@ -388,7 +392,7 @@ impl ContainerOptions {
388392
ContainerPyO3Attribute::Crate(path) => {
389393
ensure_spanned!(
390394
options.krate.is_none(),
391-
path.0.span() => "`crate` may only be provided once"
395+
path.span() => "`crate` may only be provided once"
392396
);
393397
options.krate = Some(path);
394398
}

pyo3-macros-backend/src/konst.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct ConstSpec {
2121
impl ConstSpec {
2222
pub fn python_name(&self) -> Cow<Ident> {
2323
if let Some(name) = &self.attributes.name {
24-
Cow::Borrowed(&name.0)
24+
Cow::Borrowed(&name.value.0)
2525
} else {
2626
Cow::Owned(self.rust_ident.unraw())
2727
}
@@ -89,7 +89,7 @@ impl ConstAttributes {
8989
fn set_name(&mut self, name: NameAttribute) -> Result<()> {
9090
ensure_spanned!(
9191
self.name.is_none(),
92-
name.0.span() => "`name` may only be specified once"
92+
name.span() => "`name` may only be specified once"
9393
);
9494
self.name = Some(name);
9595
Ok(())

pyo3-macros-backend/src/method.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use syn::ext::IdentExt;
1414
use syn::spanned::Spanned;
1515
use syn::Result;
1616

17-
#[derive(Clone, PartialEq, Debug)]
17+
#[derive(Clone, Debug)]
1818
pub struct FnArg<'a> {
1919
pub name: &'a syn::Ident,
2020
pub by_ref: &'a Option<syn::token::Ref>,
@@ -273,7 +273,7 @@ impl<'a> FnSpec<'a> {
273273
ty: fn_type_attr,
274274
args: fn_attrs,
275275
mut python_name,
276-
} = parse_method_attributes(meth_attrs, name.map(|name| name.0), &mut deprecations)?;
276+
} = parse_method_attributes(meth_attrs, name.map(|name| name.value.0), &mut deprecations)?;
277277

278278
let (fn_type, skip_first_arg, fixed_convention) =
279279
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;

pyo3-macros-backend/src/module.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl PyModuleOptions {
3131

3232
for option in take_pyo3_options(attrs)? {
3333
match option {
34-
PyModulePyO3Option::Name(name) => options.set_name(name.0)?,
34+
PyModulePyO3Option::Name(name) => options.set_name(name.value.0)?,
3535
PyModulePyO3Option::Crate(path) => options.set_crate(path)?,
3636
}
3737
}
@@ -52,7 +52,7 @@ impl PyModuleOptions {
5252
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
5353
ensure_spanned!(
5454
self.krate.is_none(),
55-
path.0.span() => "`crate` may only be specified once"
55+
path.span() => "`crate` may only be specified once"
5656
);
5757

5858
self.krate = Some(path);

pyo3-macros-backend/src/params.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@ fn impl_arg_param(
231231
let arg_value = quote_arg_span!(#args_array[#option_pos]);
232232
*option_pos += 1;
233233

234-
let arg_value_or_default = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with
234+
let arg_value_or_default = if let Some(FromPyWithAttribute {
235+
value: expr_path, ..
236+
}) = &arg.attrs.from_py_with
235237
{
236238
match (spec.default_value(name), arg.optional.is_some()) {
237239
(Some(default), true) if default.to_string() != "None" => {

0 commit comments

Comments
 (0)