Merge pull request #2234 from davidhewitt/pyclass-args-refactor

pyclass: unify pyclass with its pyo3 arguments
This commit is contained in:
David Hewitt 2022-03-22 11:38:05 +00:00 committed by GitHub
commit 87c79c0319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 320 additions and 355 deletions

View File

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
- Allow `#[pyo3(crate = "...", text_signature = "...")]` options to be used directly in `#[pyclass(crate = "...", text_signature = "...")]`. [#2234](https://github.com/PyO3/pyo3/pull/2234)
### Fixed ### Fixed
- 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) - 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)

View File

@ -195,22 +195,16 @@ Python::with_gil(|py|{
## Customizing the class ## Customizing the class
The `#[pyclass]` macro accepts the following parameters: {{#include ../../pyo3-macros/docs/pyclass_parameters.md}}
* `name="XXX"` - Set the class name shown in Python code. By default, the struct name is used as the class name. [params-1]: {{#PYO3_DOCS_URL}}/pyo3/prelude/struct.PyAny.html
* `freelist=XXX` - The `freelist` parameter adds support of free allocation list to custom class. [params-2]: https://en.wikipedia.org/wiki/Free_list
The performance improvement applies to types that are often created and deleted in a row, [params-3]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html
so that they can benefit from a freelist. `XXX` is a number of items for the free list. [params-4]: https://doc.rust-lang.org/stable/std/rc/struct.Rc.html
* `gc` - Classes with the `gc` parameter participate in Python garbage collection. [params-5]: https://doc.rust-lang.org/stable/std/sync/struct.Rc.html
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. [params-6]: https://docs.python.org/3/library/weakref.html
* `weakref` - Adds support for Python weak references.
* `extends=BaseType` - Use a custom base class. The base `BaseType` must implement `PyTypeInfo`. `enum` pyclasses can't use a custom base class. These parameters are covered in various sections of this guide.
* `subclass` - Allows Python classes to inherit from this class. `enum` pyclasses can't be inherited from.
* `dict` - Adds `__dict__` support, so that the instances of this type have a dictionary containing arbitrary instance variables.
* `unsendable` - Making it safe to expose `!Send` structs to Python, where all object can be accessed
by multiple threads. A class marked with `unsendable` panics when accessed by another thread.
* `module="XXX"` - Set the name of the module the class will be shown as defined in. If not given, the class
will be a virtual member of the `builtins` module.
### Return type ### Return type
@ -716,7 +710,7 @@ num=-1
## Making class method signatures available to Python ## Making class method signatures available to Python
The [`#[pyo3(text_signature = "...")]`](./function.md#text_signature) option for `#[pyfunction]` also works for classes and methods: The [`text_signature = "..."`](./function.md#text_signature) option for `#[pyfunction]` also works for classes and methods:
```rust ```rust
# #![allow(dead_code)] # #![allow(dead_code)]
@ -724,8 +718,7 @@ use pyo3::prelude::*;
use pyo3::types::PyType; use pyo3::types::PyType;
// it works even if the item is not documented: // it works even if the item is not documented:
#[pyclass] #[pyclass(text_signature = "(c, d, /)")]
#[pyo3(text_signature = "(c, d, /)")]
struct MyClass {} struct MyClass {}
#[pymethods] #[pymethods]

View File

@ -1,77 +1,107 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
punctuated::Punctuated, punctuated::Punctuated,
spanned::Spanned,
token::Comma, token::Comma,
Attribute, ExprPath, Ident, LitStr, Path, Result, Token, Attribute, Expr, ExprPath, Ident, LitStr, Path, Result, Token,
}; };
pub mod kw { pub mod kw {
syn::custom_keyword!(annotation); syn::custom_keyword!(annotation);
syn::custom_keyword!(attribute); syn::custom_keyword!(attribute);
syn::custom_keyword!(dict);
syn::custom_keyword!(extends);
syn::custom_keyword!(freelist);
syn::custom_keyword!(from_py_with); syn::custom_keyword!(from_py_with);
syn::custom_keyword!(gc);
syn::custom_keyword!(get); syn::custom_keyword!(get);
syn::custom_keyword!(item); syn::custom_keyword!(item);
syn::custom_keyword!(pass_module); syn::custom_keyword!(module);
syn::custom_keyword!(name); syn::custom_keyword!(name);
syn::custom_keyword!(pass_module);
syn::custom_keyword!(set); syn::custom_keyword!(set);
syn::custom_keyword!(signature); syn::custom_keyword!(signature);
syn::custom_keyword!(subclass);
syn::custom_keyword!(text_signature); syn::custom_keyword!(text_signature);
syn::custom_keyword!(transparent); syn::custom_keyword!(transparent);
syn::custom_keyword!(unsendable);
syn::custom_keyword!(weakref);
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug)]
pub struct FromPyWithAttribute(pub ExprPath); pub struct KeywordAttribute<K, V> {
pub kw: K,
pub value: V,
}
impl Parse for FromPyWithAttribute { /// A helper type which parses the inner type via a literal string
/// e.g. LitStrValue<Path> -> parses "some::path" in quotes.
#[derive(Clone, Debug, PartialEq)]
pub struct LitStrValue<T>(pub T);
impl<T: Parse> Parse for LitStrValue<T> {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream) -> Result<Self> {
let _: kw::from_py_with = input.parse()?; let lit_str: LitStr = input.parse()?;
let _: Token![=] = input.parse()?; lit_str.parse().map(LitStrValue)
let string_literal: LitStr = input.parse()?;
string_literal.parse().map(FromPyWithAttribute)
} }
} }
#[derive(Clone, Debug, PartialEq)] impl<T: ToTokens> ToTokens for LitStrValue<T> {
pub struct NameAttribute(pub Ident); fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
impl Parse for NameAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let _: kw::name = input.parse()?;
let _: Token![=] = input.parse()?;
let string_literal: LitStr = input.parse()?;
string_literal.parse().map(NameAttribute)
} }
} }
/// A helper type which parses a name via a literal string
#[derive(Clone, Debug, PartialEq)]
pub struct NameLitStr(pub Ident);
impl Parse for NameLitStr {
fn parse(input: ParseStream) -> Result<Self> {
let string_literal: LitStr = input.parse()?;
if let Ok(ident) = string_literal.parse() {
Ok(NameLitStr(ident))
} else {
bail_spanned!(string_literal.span() => "expected a single identifier in double quotes")
}
}
}
impl ToTokens for NameLitStr {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
}
}
pub type ExtendsAttribute = KeywordAttribute<kw::extends, Path>;
pub type FreelistAttribute = KeywordAttribute<kw::freelist, Box<Expr>>;
pub type ModuleAttribute = KeywordAttribute<kw::module, LitStr>;
pub type NameAttribute = KeywordAttribute<kw::name, NameLitStr>;
pub type TextSignatureAttribute = KeywordAttribute<kw::text_signature, LitStr>;
impl<K: Parse + std::fmt::Debug, V: Parse> Parse for KeywordAttribute<K, V> {
fn parse(input: ParseStream) -> Result<Self> {
let kw: K = input.parse()?;
let _: Token![=] = input.parse()?;
let value = input.parse()?;
Ok(KeywordAttribute { kw, value })
}
}
impl<K: ToTokens, V: ToTokens> ToTokens for KeywordAttribute<K, V> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.kw.to_tokens(tokens);
Token![=](self.kw.span()).to_tokens(tokens);
self.value.to_tokens(tokens);
}
}
pub type FromPyWithAttribute = KeywordAttribute<kw::from_py_with, LitStrValue<ExprPath>>;
/// For specifying the path to the pyo3 crate. /// For specifying the path to the pyo3 crate.
#[derive(Clone, Debug, PartialEq)] pub type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>;
pub struct CrateAttribute(pub Path);
impl Parse for CrateAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let _: Token![crate] = input.parse()?;
let _: Token![=] = input.parse()?;
let string_literal: LitStr = input.parse()?;
string_literal.parse().map(CrateAttribute)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextSignatureAttribute {
pub kw: kw::text_signature,
pub eq_token: Token![=],
pub lit: LitStr,
}
impl Parse for TextSignatureAttribute {
fn parse(input: ParseStream) -> Result<Self> {
Ok(TextSignatureAttribute {
kw: input.parse()?,
eq_token: input.parse()?,
lit: input.parse()?,
})
}
}
pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> { pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
if is_attribute_ident(attr, "pyo3") { if is_attribute_ident(attr, "pyo3") {

View File

@ -252,7 +252,9 @@ impl<'a> Container<'a> {
None => quote!( None => quote!(
obj.get_item(#index)?.extract() obj.get_item(#index)?.extract()
), ),
Some(FromPyWithAttribute(expr_path)) => quote! ( Some(FromPyWithAttribute {
value: expr_path, ..
}) => quote! (
#expr_path(obj.get_item(#index)?) #expr_path(obj.get_item(#index)?)
), ),
}; };
@ -308,7 +310,9 @@ impl<'a> Container<'a> {
new_err.set_cause(py, ::std::option::Option::Some(inner)); new_err.set_cause(py, ::std::option::Option::Some(inner));
new_err new_err
})?), })?),
Some(FromPyWithAttribute(expr_path)) => quote! ( Some(FromPyWithAttribute {
value: expr_path, ..
}) => quote! (
#expr_path(#get_field).map_err(|inner| { #expr_path(#get_field).map_err(|inner| {
let py = _pyo3::PyNativeType::py(obj); let py = _pyo3::PyNativeType::py(obj);
let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg); let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
@ -388,7 +392,7 @@ impl ContainerOptions {
ContainerPyO3Attribute::Crate(path) => { ContainerPyO3Attribute::Crate(path) => {
ensure_spanned!( ensure_spanned!(
options.krate.is_none(), options.krate.is_none(),
path.0.span() => "`crate` may only be provided once" path.span() => "`crate` may only be provided once"
); );
options.krate = Some(path); options.krate = Some(path);
} }

View File

@ -21,7 +21,7 @@ pub struct ConstSpec {
impl ConstSpec { impl ConstSpec {
pub fn python_name(&self) -> Cow<Ident> { pub fn python_name(&self) -> Cow<Ident> {
if let Some(name) = &self.attributes.name { if let Some(name) = &self.attributes.name {
Cow::Borrowed(&name.0) Cow::Borrowed(&name.value.0)
} else { } else {
Cow::Owned(self.rust_ident.unraw()) Cow::Owned(self.rust_ident.unraw())
} }
@ -89,7 +89,7 @@ impl ConstAttributes {
fn set_name(&mut self, name: NameAttribute) -> Result<()> { fn set_name(&mut self, name: NameAttribute) -> Result<()> {
ensure_spanned!( ensure_spanned!(
self.name.is_none(), self.name.is_none(),
name.0.span() => "`name` may only be specified once" name.span() => "`name` may only be specified once"
); );
self.name = Some(name); self.name = Some(name);
Ok(()) Ok(())

View File

@ -14,7 +14,7 @@ use syn::ext::IdentExt;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::Result; use syn::Result;
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, Debug)]
pub struct FnArg<'a> { pub struct FnArg<'a> {
pub name: &'a syn::Ident, pub name: &'a syn::Ident,
pub by_ref: &'a Option<syn::token::Ref>, pub by_ref: &'a Option<syn::token::Ref>,
@ -273,7 +273,7 @@ impl<'a> FnSpec<'a> {
ty: fn_type_attr, ty: fn_type_attr,
args: fn_attrs, args: fn_attrs,
mut python_name, mut python_name,
} = parse_method_attributes(meth_attrs, name.map(|name| name.0), &mut deprecations)?; } = parse_method_attributes(meth_attrs, name.map(|name| name.value.0), &mut deprecations)?;
let (fn_type, skip_first_arg, fixed_convention) = let (fn_type, skip_first_arg, fixed_convention) =
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?; Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;

View File

@ -31,7 +31,7 @@ impl PyModuleOptions {
for option in take_pyo3_options(attrs)? { for option in take_pyo3_options(attrs)? {
match option { match option {
PyModulePyO3Option::Name(name) => options.set_name(name.0)?, PyModulePyO3Option::Name(name) => options.set_name(name.value.0)?,
PyModulePyO3Option::Crate(path) => options.set_crate(path)?, PyModulePyO3Option::Crate(path) => options.set_crate(path)?,
} }
} }
@ -52,7 +52,7 @@ impl PyModuleOptions {
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> { fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
ensure_spanned!( ensure_spanned!(
self.krate.is_none(), self.krate.is_none(),
path.0.span() => "`crate` may only be specified once" path.span() => "`crate` may only be specified once"
); );
self.krate = Some(path); self.krate = Some(path);

View File

@ -231,7 +231,9 @@ fn impl_arg_param(
let arg_value = quote_arg_span!(#args_array[#option_pos]); let arg_value = quote_arg_span!(#args_array[#option_pos]);
*option_pos += 1; *option_pos += 1;
let arg_value_or_default = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with let arg_value_or_default = if let Some(FromPyWithAttribute {
value: expr_path, ..
}) = &arg.attrs.from_py_with
{ {
match (spec.default_value(name), arg.optional.is_some()) { match (spec.default_value(name), arg.optional.is_some()) {
(Some(default), true) if default.to_string() != "None" => { (Some(default), true) if default.to_string() != "None" => {

View File

@ -1,19 +1,20 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attributes::{ use crate::attributes::{
self, take_pyo3_options, CrateAttribute, NameAttribute, TextSignatureAttribute, self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
ModuleAttribute, NameAttribute, NameLitStr, TextSignatureAttribute,
}; };
use crate::deprecations::{Deprecation, Deprecations}; use crate::deprecations::{Deprecation, Deprecations};
use crate::konst::{ConstAttributes, ConstSpec}; use crate::konst::{ConstAttributes, ConstSpec};
use crate::pyimpl::{gen_default_items, gen_py_const, PyClassMethodsType}; use crate::pyimpl::{gen_default_items, gen_py_const, PyClassMethodsType};
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType}; use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
use crate::utils::{self, get_pyo3_crate, unwrap_group, PythonDoc}; use crate::utils::{self, get_pyo3_crate, PythonDoc};
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::ext::IdentExt; use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::{parse_quote, spanned::Spanned, Expr, Result, Token}; //unraw use syn::{parse_quote, spanned::Spanned, Result, Token};
/// If the class is derived from a Rust `struct` or `enum`. /// If the class is derived from a Rust `struct` or `enum`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -24,27 +25,18 @@ pub enum PyClassKind {
/// The parsed arguments of the pyclass macro /// The parsed arguments of the pyclass macro
pub struct PyClassArgs { pub struct PyClassArgs {
pub freelist: Option<syn::Expr>,
pub name: Option<syn::Ident>,
pub base: syn::TypePath,
pub has_dict: bool,
pub has_weaklist: bool,
pub is_basetype: bool,
pub has_extends: bool,
pub has_unsendable: bool,
pub module: Option<syn::LitStr>,
pub class_kind: PyClassKind, pub class_kind: PyClassKind,
pub options: PyClassPyO3Options,
pub deprecations: Deprecations, pub deprecations: Deprecations,
} }
impl PyClassArgs { impl PyClassArgs {
fn parse(input: ParseStream, kind: PyClassKind) -> Result<Self> { fn parse(input: ParseStream, kind: PyClassKind) -> Result<Self> {
let mut slf = PyClassArgs::new(kind); Ok(PyClassArgs {
let vars = Punctuated::<Expr, Token![,]>::parse_terminated(input)?; class_kind: kind,
for expr in vars { options: PyClassPyO3Options::parse(input)?,
slf.add_expr(&expr)?; deprecations: Deprecations::new(),
} })
Ok(slf)
} }
pub fn parse_stuct_args(input: ParseStream) -> syn::Result<Self> { pub fn parse_stuct_args(input: ParseStream) -> syn::Result<Self> {
@ -54,155 +46,64 @@ impl PyClassArgs {
pub fn parse_enum_args(input: ParseStream) -> syn::Result<Self> { pub fn parse_enum_args(input: ParseStream) -> syn::Result<Self> {
Self::parse(input, PyClassKind::Enum) Self::parse(input, PyClassKind::Enum)
} }
fn new(class_kind: PyClassKind) -> Self {
PyClassArgs {
freelist: None,
name: None,
module: None,
base: parse_quote! { _pyo3::PyAny },
has_dict: false,
has_weaklist: false,
is_basetype: false,
has_extends: false,
has_unsendable: false,
class_kind,
deprecations: Deprecations::new(),
}
}
/// Add a single expression from the comma separated list in the attribute, which is
/// either a single word or an assignment expression
fn add_expr(&mut self, expr: &Expr) -> Result<()> {
match expr {
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => self.add_path(exp),
syn::Expr::Assign(assign) => self.add_assign(assign),
_ => bail_spanned!(expr.span() => "failed to parse arguments"),
}
}
/// Match a key/value flag
fn add_assign(&mut self, assign: &syn::ExprAssign) -> syn::Result<()> {
let syn::ExprAssign { left, right, .. } = assign;
let key = match &**left {
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => {
exp.path.segments.first().unwrap().ident.to_string()
}
_ => bail_spanned!(assign.span() => "failed to parse arguments"),
};
macro_rules! expected {
($expected: literal) => {
expected!($expected, right.span())
};
($expected: literal, $span: expr) => {
bail_spanned!($span => concat!("expected ", $expected))
};
}
match key.as_str() {
"freelist" => {
// We allow arbitrary expressions here so you can e.g. use `8*64`
self.freelist = Some(syn::Expr::clone(right));
}
"name" => match unwrap_group(&**right) {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit),
..
}) => {
self.name = Some(lit.parse().map_err(|_| {
err_spanned!(
lit.span() => "expected a single identifier in double-quotes")
})?);
}
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => {
bail_spanned!(
exp.span() => format!(
"since PyO3 0.13 a pyclass name should be in double-quotes, \
e.g. \"{}\"",
exp.path.get_ident().expect("path has 1 segment")
)
);
}
_ => expected!("type name (e.g. \"Name\")"),
},
"extends" => match unwrap_group(&**right) {
syn::Expr::Path(exp) => {
if self.class_kind == PyClassKind::Enum {
bail_spanned!( assign.span() => "enums cannot extend from other classes" );
}
self.base = syn::TypePath {
path: exp.path.clone(),
qself: None,
};
self.has_extends = true;
}
_ => expected!("type path (e.g., my_mod::BaseClass)"),
},
"module" => match unwrap_group(&**right) {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit),
..
}) => {
self.module = Some(lit.clone());
}
_ => expected!(r#"string literal (e.g., "my_mod")"#),
},
_ => expected!("one of freelist/name/extends/module", left.span()),
};
Ok(())
}
/// Match a single flag
fn add_path(&mut self, exp: &syn::ExprPath) -> syn::Result<()> {
let flag = exp.path.segments.first().unwrap().ident.to_string();
match flag.as_str() {
"gc" => self
.deprecations
.push(Deprecation::PyClassGcOption, exp.span()),
"weakref" => {
self.has_weaklist = true;
}
"subclass" => {
if self.class_kind == PyClassKind::Enum {
bail_spanned!(exp.span() => "enums can't be inherited by other classes");
}
self.is_basetype = true;
}
"dict" => {
self.has_dict = true;
}
"unsendable" => {
self.has_unsendable = true;
}
_ => bail_spanned!(
exp.path.span() => "expected one of gc/weakref/subclass/dict/unsendable"
),
};
Ok(())
}
} }
#[derive(Default)] #[derive(Default)]
pub struct PyClassPyO3Options { pub struct PyClassPyO3Options {
pub text_signature: Option<TextSignatureAttribute>,
pub deprecations: Deprecations,
pub krate: Option<CrateAttribute>, pub krate: Option<CrateAttribute>,
pub dict: Option<kw::dict>,
pub extends: Option<ExtendsAttribute>,
pub freelist: Option<FreelistAttribute>,
pub module: Option<ModuleAttribute>,
pub name: Option<NameAttribute>,
pub subclass: Option<kw::subclass>,
pub text_signature: Option<TextSignatureAttribute>,
pub unsendable: Option<kw::unsendable>,
pub weakref: Option<kw::weakref>,
pub deprecations: Deprecations,
} }
enum PyClassPyO3Option { enum PyClassPyO3Option {
TextSignature(TextSignatureAttribute),
Crate(CrateAttribute), Crate(CrateAttribute),
Dict(kw::dict),
Extends(ExtendsAttribute),
Freelist(FreelistAttribute),
Module(ModuleAttribute),
Name(NameAttribute),
Subclass(kw::subclass),
TextSignature(TextSignatureAttribute),
Unsendable(kw::unsendable),
Weakref(kw::weakref),
DeprecatedGC(kw::gc),
} }
impl Parse for PyClassPyO3Option { impl Parse for PyClassPyO3Option {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1(); let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::text_signature) { if lookahead.peek(Token![crate]) {
input.parse().map(PyClassPyO3Option::TextSignature)
} else if lookahead.peek(Token![crate]) {
input.parse().map(PyClassPyO3Option::Crate) input.parse().map(PyClassPyO3Option::Crate)
} else if lookahead.peek(kw::dict) {
input.parse().map(PyClassPyO3Option::Dict)
} else if lookahead.peek(kw::extends) {
input.parse().map(PyClassPyO3Option::Extends)
} else if lookahead.peek(attributes::kw::freelist) {
input.parse().map(PyClassPyO3Option::Freelist)
} else if lookahead.peek(attributes::kw::module) {
input.parse().map(PyClassPyO3Option::Module)
} else if lookahead.peek(kw::name) {
input.parse().map(PyClassPyO3Option::Name)
} else if lookahead.peek(attributes::kw::subclass) {
input.parse().map(PyClassPyO3Option::Subclass)
} else if lookahead.peek(attributes::kw::text_signature) {
input.parse().map(PyClassPyO3Option::TextSignature)
} else if lookahead.peek(attributes::kw::unsendable) {
input.parse().map(PyClassPyO3Option::Unsendable)
} else if lookahead.peek(attributes::kw::weakref) {
input.parse().map(PyClassPyO3Option::Weakref)
} else if lookahead.peek(attributes::kw::gc) {
input.parse().map(PyClassPyO3Option::DeprecatedGC)
} else { } else {
Err(lookahead.error()) Err(lookahead.error())
} }
@ -210,57 +111,69 @@ impl Parse for PyClassPyO3Option {
} }
impl PyClassPyO3Options { impl PyClassPyO3Options {
pub fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let mut options: PyClassPyO3Options = Default::default(); let mut options: PyClassPyO3Options = Default::default();
for option in take_pyo3_options(attrs)? {
match option { for option in Punctuated::<PyClassPyO3Option, syn::Token![,]>::parse_terminated(input)? {
PyClassPyO3Option::TextSignature(text_signature) => { options.set_option(option)?;
options.set_text_signature(text_signature)?;
}
PyClassPyO3Option::Crate(path) => {
options.set_crate(path)?;
}
}
} }
Ok(options) Ok(options)
} }
pub fn set_text_signature( pub fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
&mut self, take_pyo3_options(attrs)?
text_signature: TextSignatureAttribute, .into_iter()
) -> syn::Result<()> { .try_for_each(|option| self.set_option(option))
ensure_spanned!(
self.text_signature.is_none(),
text_signature.kw.span() => "`text_signature` may only be specified once"
);
self.text_signature = Some(text_signature);
Ok(())
} }
pub fn set_crate(&mut self, path: CrateAttribute) -> syn::Result<()> { fn set_option(&mut self, option: PyClassPyO3Option) -> syn::Result<()> {
ensure_spanned!( macro_rules! set_option {
self.krate.is_none(), ($key:ident) => {
path.0.span() => "`text_signature` may only be specified once" {
); ensure_spanned!(
self.krate = Some(path); self.$key.is_none(),
$key.span() => concat!("`", stringify!($key), "` may only be specified once")
);
self.$key = Some($key);
}
};
}
match option {
PyClassPyO3Option::Crate(krate) => set_option!(krate),
PyClassPyO3Option::Dict(dict) => set_option!(dict),
PyClassPyO3Option::Extends(extends) => set_option!(extends),
PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
PyClassPyO3Option::Module(module) => set_option!(module),
PyClassPyO3Option::Name(name) => set_option!(name),
PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
PyClassPyO3Option::TextSignature(text_signature) => set_option!(text_signature),
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
PyClassPyO3Option::Weakref(weakref) => set_option!(weakref),
PyClassPyO3Option::DeprecatedGC(gc) => self
.deprecations
.push(Deprecation::PyClassGcOption, gc.span()),
}
Ok(()) Ok(())
} }
} }
pub fn build_py_class( pub fn build_py_class(
class: &mut syn::ItemStruct, class: &mut syn::ItemStruct,
args: &PyClassArgs, mut args: PyClassArgs,
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let options = PyClassPyO3Options::take_pyo3_options(&mut class.attrs)?; args.options.take_pyo3_options(&mut class.attrs)?;
let doc = utils::get_doc( let doc = utils::get_doc(
&class.attrs, &class.attrs,
options args.options
.text_signature .text_signature
.as_ref() .as_ref()
.map(|attr| (get_class_python_name(&class.ident, args), attr)), .map(|attr| (get_class_python_name(&class.ident, &args), attr)),
); );
let krate = get_pyo3_crate(&options.krate); let krate = get_pyo3_crate(&args.options.krate);
ensure_spanned!( ensure_spanned!(
class.generics.params.is_empty(), class.generics.params.is_empty(),
@ -290,15 +203,7 @@ pub fn build_py_class(
} }
}; };
impl_class( impl_class(&class.ident, &args, doc, field_options, methods_type, krate)
&class.ident,
args,
doc,
field_options,
methods_type,
options.deprecations,
krate,
)
} }
/// `#[pyo3()]` options for pyclass fields /// `#[pyo3()]` options for pyclass fields
@ -356,7 +261,7 @@ impl FieldPyO3Options {
FieldPyO3Option::Name(name) => { FieldPyO3Option::Name(name) => {
ensure_spanned!( ensure_spanned!(
options.name.is_none(), options.name.is_none(),
name.0.span() => "`name` may only be specified once" name.span() => "`name` may only be specified once"
); );
options.name = Some(name); options.name = Some(name);
} }
@ -367,24 +272,27 @@ impl FieldPyO3Options {
} }
} }
fn get_class_python_name<'a>(cls: &'a syn::Ident, attr: &'a PyClassArgs) -> &'a syn::Ident { fn get_class_python_name<'a>(cls: &'a syn::Ident, args: &'a PyClassArgs) -> &'a syn::Ident {
attr.name.as_ref().unwrap_or(cls) args.options
.name
.as_ref()
.map(|name_attr| &name_attr.value.0)
.unwrap_or(cls)
} }
fn impl_class( fn impl_class(
cls: &syn::Ident, cls: &syn::Ident,
attr: &PyClassArgs, args: &PyClassArgs,
doc: PythonDoc, doc: PythonDoc,
field_options: Vec<(&syn::Field, FieldPyO3Options)>, field_options: Vec<(&syn::Field, FieldPyO3Options)>,
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
deprecations: Deprecations,
krate: syn::Path, krate: syn::Path,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let pytypeinfo_impl = impl_pytypeinfo(cls, attr, Some(&deprecations)); let pytypeinfo_impl = impl_pytypeinfo(cls, args, Some(&args.options.deprecations));
let py_class_impl = PyClassImplsBuilder::new( let py_class_impl = PyClassImplsBuilder::new(
cls, cls,
attr, args,
methods_type, methods_type,
descriptors_to_items(cls, field_options)?, descriptors_to_items(cls, field_options)?,
vec![], vec![],
@ -458,23 +366,28 @@ impl<'a> PyClassEnum<'a> {
pub fn build_py_enum( pub fn build_py_enum(
enum_: &mut syn::ItemEnum, enum_: &mut syn::ItemEnum,
args: &PyClassArgs, mut args: PyClassArgs,
method_type: PyClassMethodsType, method_type: PyClassMethodsType,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let options = PyClassPyO3Options::take_pyo3_options(&mut enum_.attrs)?; args.options.take_pyo3_options(&mut enum_.attrs)?;
if enum_.variants.is_empty() { if let Some(extends) = &args.options.extends {
bail_spanned!(enum_.brace_token.span => "Empty enums can't be #[pyclass]."); bail_spanned!(extends.span() => "enums can't extend from other classes");
} else if let Some(subclass) = &args.options.subclass {
bail_spanned!(subclass.span() => "enums can't be inherited by other classes");
} else if enum_.variants.is_empty() {
bail_spanned!(enum_.brace_token.span => "#[pyclass] can't be used on enums without any variants");
} }
let doc = utils::get_doc( let doc = utils::get_doc(
&enum_.attrs, &enum_.attrs,
options args.options
.text_signature .text_signature
.as_ref() .as_ref()
.map(|attr| (get_class_python_name(&enum_.ident, args), attr)), .map(|attr| (get_class_python_name(&enum_.ident, &args), attr)),
); );
let enum_ = PyClassEnum::new(enum_)?; let enum_ = PyClassEnum::new(enum_)?;
Ok(impl_enum(enum_, args, doc, method_type, options)) Ok(impl_enum(enum_, &args, doc, method_type))
} }
fn impl_enum( fn impl_enum(
@ -482,9 +395,8 @@ fn impl_enum(
args: &PyClassArgs, args: &PyClassArgs,
doc: PythonDoc, doc: PythonDoc,
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
options: PyClassPyO3Options,
) -> TokenStream { ) -> TokenStream {
let krate = get_pyo3_crate(&options.krate); let krate = get_pyo3_crate(&args.options.krate);
impl_enum_class(enum_, args, doc, methods_type, krate) impl_enum_class(enum_, args, doc, methods_type, krate)
} }
@ -613,7 +525,10 @@ fn enum_default_methods<'a>(
rust_ident: ident.clone(), rust_ident: ident.clone(),
attributes: ConstAttributes { attributes: ConstAttributes {
is_class_attr: true, is_class_attr: true,
name: Some(NameAttribute(ident.clone())), name: Some(NameAttribute {
kw: syn::parse_quote! { name },
value: NameLitStr(ident.clone()),
}),
deprecations: Default::default(), deprecations: Default::default(),
}, },
}; };
@ -649,7 +564,7 @@ fn descriptors_to_items(
.enumerate() .enumerate()
.flat_map(|(field_index, (field, options))| { .flat_map(|(field_index, (field, options))| {
let name_err = if options.name.is_some() && !options.get && !options.set { let name_err = if options.name.is_some() && !options.get && !options.set {
Some(Err(err_spanned!(options.name.as_ref().unwrap().0.span() => "`name` is useless without `get` or `set`"))) Some(Err(err_spanned!(options.name.as_ref().unwrap().span() => "`name` is useless without `get` or `set`")))
} else { } else {
None None
}; };
@ -686,8 +601,8 @@ fn impl_pytypeinfo(
) -> TokenStream { ) -> TokenStream {
let cls_name = get_class_python_name(cls, attr).to_string(); let cls_name = get_class_python_name(cls, attr).to_string();
let module = if let Some(m) = &attr.module { let module = if let Some(ModuleAttribute { value, .. }) = &attr.options.module {
quote! { ::core::option::Option::Some(#m) } quote! { ::core::option::Option::Some(#value) }
} else { } else {
quote! { ::core::option::Option::None } quote! { ::core::option::Option::None }
}; };
@ -765,20 +680,20 @@ impl<'a> PyClassImplsBuilder<'a> {
fn impl_pyclass(&self) -> TokenStream { fn impl_pyclass(&self) -> TokenStream {
let cls = self.cls; let cls = self.cls;
let attr = self.attr; let attr = self.attr;
let dict = if attr.has_dict { let dict = if attr.options.dict.is_some() {
quote! { _pyo3::impl_::pyclass::PyClassDictSlot } quote! { _pyo3::impl_::pyclass::PyClassDictSlot }
} else { } else {
quote! { _pyo3::impl_::pyclass::PyClassDummySlot } quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
}; };
// insert space for weak ref // insert space for weak ref
let weakref = if attr.has_weaklist { let weakref = if attr.options.weakref.is_some() {
quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot } quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot }
} else { } else {
quote! { _pyo3::impl_::pyclass::PyClassDummySlot } quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
}; };
let base_nativetype = if attr.has_extends { let base_nativetype = if attr.options.extends.is_some() {
quote! { <Self::BaseType as _pyo3::impl_::pyclass::PyClassBaseType>::BaseNativeType } quote! { <Self::BaseType as _pyo3::impl_::pyclass::PyClassBaseType>::BaseNativeType }
} else { } else {
quote! { _pyo3::PyAny } quote! { _pyo3::PyAny }
@ -810,7 +725,7 @@ impl<'a> PyClassImplsBuilder<'a> {
let cls = self.cls; let cls = self.cls;
let attr = self.attr; let attr = self.attr;
// If #cls is not extended type, we allow Self->PyObject conversion // If #cls is not extended type, we allow Self->PyObject conversion
if !attr.has_extends { if attr.options.extends.is_none() {
quote! { quote! {
impl _pyo3::IntoPy<_pyo3::PyObject> for #cls { impl _pyo3::IntoPy<_pyo3::PyObject> for #cls {
fn into_py(self, py: _pyo3::Python) -> _pyo3::PyObject { fn into_py(self, py: _pyo3::Python) -> _pyo3::PyObject {
@ -825,11 +740,17 @@ impl<'a> PyClassImplsBuilder<'a> {
fn impl_pyclassimpl(&self) -> TokenStream { fn impl_pyclassimpl(&self) -> TokenStream {
let cls = self.cls; let cls = self.cls;
let doc = self.doc.as_ref().map_or(quote! {"\0"}, |doc| quote! {#doc}); let doc = self.doc.as_ref().map_or(quote! {"\0"}, |doc| quote! {#doc});
let is_basetype = self.attr.is_basetype; let is_basetype = self.attr.options.subclass.is_some();
let base = &self.attr.base; let base = self
let is_subclass = self.attr.has_extends; .attr
.options
.extends
.as_ref()
.map(|extends_attr| extends_attr.value.clone())
.unwrap_or_else(|| parse_quote! { _pyo3::PyAny });
let is_subclass = self.attr.options.extends.is_some();
let dict_offset = if self.attr.has_dict { let dict_offset = if self.attr.options.dict.is_some() {
quote! { quote! {
fn dict_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> { fn dict_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
::std::option::Option::Some(_pyo3::impl_::pyclass::dict_offset::<Self>()) ::std::option::Option::Some(_pyo3::impl_::pyclass::dict_offset::<Self>())
@ -840,7 +761,7 @@ impl<'a> PyClassImplsBuilder<'a> {
}; };
// insert space for weak ref // insert space for weak ref
let weaklist_offset = if self.attr.has_weaklist { let weaklist_offset = if self.attr.options.weakref.is_some() {
quote! { quote! {
fn weaklist_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> { fn weaklist_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
::std::option::Option::Some(_pyo3::impl_::pyclass::weaklist_offset::<Self>()) ::std::option::Option::Some(_pyo3::impl_::pyclass::weaklist_offset::<Self>())
@ -850,9 +771,9 @@ impl<'a> PyClassImplsBuilder<'a> {
TokenStream::new() TokenStream::new()
}; };
let thread_checker = if self.attr.has_unsendable { let thread_checker = if self.attr.options.unsendable.is_some() {
quote! { _pyo3::impl_::pyclass::ThreadCheckerImpl<#cls> } quote! { _pyo3::impl_::pyclass::ThreadCheckerImpl<#cls> }
} else if self.attr.has_extends { } else if self.attr.options.extends.is_some() {
quote! { quote! {
_pyo3::impl_::pyclass::ThreadCheckerInherited<#cls, <#cls as _pyo3::impl_::pyclass::PyClassImpl>::BaseType> _pyo3::impl_::pyclass::ThreadCheckerInherited<#cls, <#cls as _pyo3::impl_::pyclass::PyClassImpl>::BaseType>
} }
@ -940,7 +861,8 @@ impl<'a> PyClassImplsBuilder<'a> {
fn impl_freelist(&self) -> TokenStream { fn impl_freelist(&self) -> TokenStream {
let cls = self.cls; let cls = self.cls;
self.attr.freelist.as_ref().map_or(quote!{}, |freelist| { self.attr.options.freelist.as_ref().map_or(quote!{}, |freelist| {
let freelist = &freelist.value;
quote! { quote! {
impl _pyo3::impl_::pyclass::PyClassWithFreeList for #cls { impl _pyo3::impl_::pyclass::PyClassWithFreeList for #cls {
#[inline] #[inline]
@ -962,7 +884,7 @@ impl<'a> PyClassImplsBuilder<'a> {
fn freelist_slots(&self) -> Vec<TokenStream> { fn freelist_slots(&self) -> Vec<TokenStream> {
let cls = self.cls; let cls = self.cls;
if self.attr.freelist.is_some() { if self.attr.options.freelist.is_some() {
vec![ vec![
quote! { quote! {
_pyo3::ffi::PyType_Slot { _pyo3::ffi::PyType_Slot {

View File

@ -40,7 +40,7 @@ pub struct PyFunctionSignature {
has_kwargs: bool, has_kwargs: bool,
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, Debug)]
pub struct PyFunctionArgPyO3Attributes { pub struct PyFunctionArgPyO3Attributes {
pub from_py_with: Option<FromPyWithAttribute>, pub from_py_with: Option<FromPyWithAttribute>,
} }
@ -71,7 +71,7 @@ impl PyFunctionArgPyO3Attributes {
PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => { PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => {
ensure_spanned!( ensure_spanned!(
attributes.from_py_with.is_none(), attributes.from_py_with.is_none(),
from_py_with.0.span() => "`from_py_with` may only be specified once per argument" from_py_with.span() => "`from_py_with` may only be specified once per argument"
); );
attributes.from_py_with = Some(from_py_with); attributes.from_py_with = Some(from_py_with);
} }
@ -339,7 +339,7 @@ impl PyFunctionOptions {
PyFunctionOption::Crate(path) => { PyFunctionOption::Crate(path) => {
ensure_spanned!( ensure_spanned!(
self.krate.is_none(), self.krate.is_none(),
path.0.span() => "`crate` may only be specified once" path.span() => "`crate` may only be specified once"
); );
self.krate = Some(path); self.krate = Some(path);
} }
@ -351,7 +351,7 @@ impl PyFunctionOptions {
pub fn set_name(&mut self, name: NameAttribute) -> Result<()> { pub fn set_name(&mut self, name: NameAttribute) -> Result<()> {
ensure_spanned!( ensure_spanned!(
self.name.is_none(), self.name.is_none(),
name.0.span() => "`name` may only be specified once" name.span() => "`name` may only be specified once"
); );
self.name = Some(name); self.name = Some(name);
Ok(()) Ok(())
@ -377,7 +377,7 @@ pub fn impl_wrap_pyfunction(
let python_name = options let python_name = options
.name .name
.map_or_else(|| func.sig.ident.unraw(), |name| name.0); .map_or_else(|| func.sig.ident.unraw(), |name| name.value.0);
let signature = options.signature.unwrap_or_default(); let signature = options.signature.unwrap_or_default();

View File

@ -61,7 +61,7 @@ impl PyImplOptions {
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> { fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
ensure_spanned!( ensure_spanned!(
self.krate.is_none(), self.krate.is_none(),
path.0.span() => "`crate` may only be specified once" path.span() => "`crate` may only be specified once"
); );
self.krate = Some(path); self.krate = Some(path);

View File

@ -544,7 +544,7 @@ impl PropertyType<'_> {
field, python_name, .. field, python_name, ..
} => { } => {
let name = match (python_name, &field.ident) { let name = match (python_name, &field.ident) {
(Some(name), _) => name.0.to_string(), (Some(name), _) => name.value.0.to_string(),
(None, Some(field_name)) => format!("{}\0", field_name.unraw()), (None, Some(field_name)) => format!("{}\0", field_name.unraw()),
(None, None) => { (None, None) => {
bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`"); bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`");

View File

@ -77,7 +77,8 @@ pub fn get_doc(
syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| { syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| {
if let Some((python_name, text_signature)) = text_signature { if let Some((python_name, text_signature)) = text_signature {
// create special doc string lines to set `__text_signature__` // create special doc string lines to set `__text_signature__`
let signature_lines = format!("{}{}\n--\n\n", python_name, text_signature.lit.value()); let signature_lines =
format!("{}{}\n--\n\n", python_name, text_signature.value.value());
signature_lines.to_tokens(tokens); signature_lines.to_tokens(tokens);
comma.to_tokens(tokens); comma.to_tokens(tokens);
} }
@ -154,13 +155,6 @@ pub fn ensure_not_async_fn(sig: &syn::Signature) -> syn::Result<()> {
Ok(()) Ok(())
} }
pub fn unwrap_group(mut expr: &syn::Expr) -> &syn::Expr {
while let syn::Expr::Group(g) = expr {
expr = &*g.expr;
}
expr
}
pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type { pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type {
while let syn::Type::Group(g) = ty { while let syn::Type::Group(g) = ty {
ty = &*g.elem; ty = &*g.elem;
@ -193,6 +187,6 @@ pub(crate) fn replace_self(ty: &mut syn::Type, cls: &syn::Type) {
/// Extract the path to the pyo3 crate, or use the default (`::pyo3`). /// Extract the path to the pyo3 crate, or use the default (`::pyo3`).
pub(crate) fn get_pyo3_crate(attr: &Option<CrateAttribute>) -> syn::Path { pub(crate) fn get_pyo3_crate(attr: &Option<CrateAttribute>) -> syn::Path {
attr.as_ref() attr.as_ref()
.map(|p| p.0.clone()) .map(|p| p.value.0.clone())
.unwrap_or_else(|| syn::parse_str("::pyo3").unwrap()) .unwrap_or_else(|| syn::parse_str("::pyo3").unwrap())
} }

View File

@ -0,0 +1,28 @@
`#[pyclass]` can be used with the following parameters:
| Parameter | Description |
| :- | :- |
| <span style="white-space: pre">`crate = "some::path"`</span> | Path to import the `pyo3` crate, if it's not accessible at `::pyo3`. |
| `dict` | Gives instances of this class an empty `__dict__` to store custom attributes. |
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
| <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
| <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
| <span style="white-space: pre">`text_signature = "(arg1, arg2, ...)"`</span> | Sets the text signature for the Python class' `__new__` method. |
| `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread.|
| `weakref` | Allows this class to be [weakly referenceable][params-6]. |
All of these parameters can either be passed directly on the `#[pyclass(...)]` annotation, or as one or
more accompanying `#[pyo3(...)]` annotations, e.g.:
```rust,ignore
// Argument supplied directly to the `#[pyclass]` annotation.
#[pyclass(name = "SomeName", subclass)]
struct MyClass { }
// Argument supplied as a separate annotation.
#[pyclass]
#[pyo3(name = "SomeName", subclass)]
struct MyClass { }
```

View File

@ -81,30 +81,18 @@ pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
/// A proc macro used to expose Rust structs and fieldless enums as Python objects. /// A proc macro used to expose Rust structs and fieldless enums as Python objects.
/// ///
/// `#[pyclass]` accepts the following [parameters][2]: #[cfg_attr(docsrs, cfg_attr(docsrs, doc = include_str!("../docs/pyclass_parameters.md")))]
///
/// | Parameter | Description |
/// | :- | :- |
/// | <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
/// | <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][9] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
/// | `weakref` | Allows this class to be [weakly referenceable][6]. |
/// | <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][4] |
/// | `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
/// | `unsendable` | Required if your struct is not [`Send`][3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][7] with [`Arc`][8]. By using `unsendable`, your class will panic when accessed by another thread.|
/// | <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
/// ///
/// For more on creating Python classes, /// For more on creating Python classes,
/// see the [class section of the guide][1]. /// see the [class section of the guide][1].
/// ///
/// [1]: https://pyo3.rs/latest/class.html /// [1]: https://pyo3.rs/latest/class.html
/// [2]: https://pyo3.rs/latest/class.html#customizing-the-class /// [params-1]: ../prelude/struct.PyAny.html
/// [3]: std::marker::Send /// [params-2]: https://en.wikipedia.org/wiki/Free_list
/// [4]: ../prelude/struct.PyAny.html /// [params-3]: std::marker::Send
/// [5]: https://pyo3.rs/latest/class/protocols.html#garbage-collector-integration /// [params-4]: std::rc::Rc
/// [6]: https://docs.python.org/3/library/weakref.html /// [params-5]: std::sync::Arc
/// [7]: std::rc::Rc /// [params-6]: https://docs.python.org/3/library/weakref.html
/// [8]: std::sync::Arc
/// [9]: https://en.wikipedia.org/wiki/Free_list
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream { pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
use syn::Item; use syn::Item;
@ -230,7 +218,7 @@ fn pyclass_impl(
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
) -> TokenStream { ) -> TokenStream {
let args = parse_macro_input!(attrs with PyClassArgs::parse_stuct_args); let args = parse_macro_input!(attrs with PyClassArgs::parse_stuct_args);
let expanded = build_py_class(&mut ast, &args, methods_type).unwrap_or_compile_error(); let expanded = build_py_class(&mut ast, args, methods_type).unwrap_or_compile_error();
quote!( quote!(
#ast #ast
@ -245,7 +233,7 @@ fn pyclass_enum_impl(
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
) -> TokenStream { ) -> TokenStream {
let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args); let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args);
let expanded = build_py_enum(&mut ast, &args, methods_type).unwrap_or_compile_error(); let expanded = build_py_enum(&mut ast, args, methods_type).unwrap_or_compile_error();
quote!( quote!(
#ast #ast

View File

@ -35,13 +35,13 @@ error: `set` may only be specified once
| ^^^ | ^^^
error: `name` may only be specified once error: `name` may only be specified once
--> tests/ui/invalid_property_args.rs:37:49 --> tests/ui/invalid_property_args.rs:37:42
| |
37 | struct MultipleName(#[pyo3(name = "foo", name = "bar")] i32); 37 | struct MultipleName(#[pyo3(name = "foo", name = "bar")] i32);
| ^^^^^ | ^^^^
error: `name` is useless without `get` or `set` error: `name` is useless without `get` or `set`
--> tests/ui/invalid_property_args.rs:40:40 --> tests/ui/invalid_property_args.rs:40:33
| |
40 | struct NameWithoutGetSet(#[pyo3(name = "value")] i32); 40 | struct NameWithoutGetSet(#[pyo3(name = "value")] i32);
| ^^^^^^^ | ^^^^

View File

@ -1,40 +1,40 @@
error: expected one of freelist/name/extends/module error: expected one of: `crate`, `dict`, `extends`, `freelist`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
--> tests/ui/invalid_pyclass_args.rs:3:11 --> tests/ui/invalid_pyclass_args.rs:3:11
| |
3 | #[pyclass(extend=pyo3::types::PyDict)] 3 | #[pyclass(extend=pyo3::types::PyDict)]
| ^^^^^^ | ^^^^^^
error: expected type path (e.g., my_mod::BaseClass) error: expected identifier
--> tests/ui/invalid_pyclass_args.rs:6:21 --> tests/ui/invalid_pyclass_args.rs:6:21
| |
6 | #[pyclass(extends = "PyDict")] 6 | #[pyclass(extends = "PyDict")]
| ^^^^^^^^ | ^^^^^^^^
error: expected type name (e.g. "Name") error: expected string literal
--> tests/ui/invalid_pyclass_args.rs:9:18 --> tests/ui/invalid_pyclass_args.rs:9:18
| |
9 | #[pyclass(name = m::MyClass)] 9 | #[pyclass(name = m::MyClass)]
| ^ | ^
error: expected a single identifier in double-quotes error: expected a single identifier in double quotes
--> tests/ui/invalid_pyclass_args.rs:12:18 --> tests/ui/invalid_pyclass_args.rs:12:18
| |
12 | #[pyclass(name = "Custom Name")] 12 | #[pyclass(name = "Custom Name")]
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
error: since PyO3 0.13 a pyclass name should be in double-quotes, e.g. "CustomName" error: expected string literal
--> tests/ui/invalid_pyclass_args.rs:15:18 --> tests/ui/invalid_pyclass_args.rs:15:18
| |
15 | #[pyclass(name = CustomName)] 15 | #[pyclass(name = CustomName)]
| ^^^^^^^^^^ | ^^^^^^^^^^
error: expected string literal (e.g., "my_mod") error: expected string literal
--> tests/ui/invalid_pyclass_args.rs:18:20 --> tests/ui/invalid_pyclass_args.rs:18:20
| |
18 | #[pyclass(module = my_module)] 18 | #[pyclass(module = my_module)]
| ^^^^^^^^^ | ^^^^^^^^^
error: expected one of gc/weakref/subclass/dict/unsendable error: expected one of: `crate`, `dict`, `extends`, `freelist`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
--> tests/ui/invalid_pyclass_args.rs:21:11 --> tests/ui/invalid_pyclass_args.rs:21:11
| |
21 | #[pyclass(weakrev)] 21 | #[pyclass(weakrev)]

View File

@ -2,14 +2,14 @@ use pyo3::prelude::*;
#[pyclass(subclass)] #[pyclass(subclass)]
enum NotBaseClass { enum NotBaseClass {
x, X,
y, Y,
} }
#[pyclass(extends = PyList)] #[pyclass(extends = PyList)]
enum NotDrivedClass { enum NotDrivedClass {
x, X,
y, Y,
} }
#[pyclass] #[pyclass]

View File

@ -4,13 +4,13 @@ error: enums can't be inherited by other classes
3 | #[pyclass(subclass)] 3 | #[pyclass(subclass)]
| ^^^^^^^^ | ^^^^^^^^
error: enums cannot extend from other classes error: enums can't extend from other classes
--> tests/ui/invalid_pyclass_enum.rs:9:11 --> tests/ui/invalid_pyclass_enum.rs:9:11
| |
9 | #[pyclass(extends = PyList)] 9 | #[pyclass(extends = PyList)]
| ^^^^^^^ | ^^^^^^^
error: Empty enums can't be #[pyclass]. error: #[pyclass] can't be used on enums without any variants
--> tests/ui/invalid_pyclass_enum.rs:16:18 --> tests/ui/invalid_pyclass_enum.rs:16:18
| |
16 | enum NoEmptyEnum {} 16 | enum NoEmptyEnum {}

View File

@ -5,10 +5,10 @@ error: `name` may only be specified once
| ^^^^^ | ^^^^^
error: `name` may only be specified once error: `name` may only be specified once
--> tests/ui/invalid_pymethod_names.rs:18:19 --> tests/ui/invalid_pymethod_names.rs:18:12
| |
18 | #[pyo3(name = "bar")] 18 | #[pyo3(name = "bar")]
| ^^^^^ | ^^^^
error: `name` not allowed with `#[new]` error: `name` not allowed with `#[new]`
--> tests/ui/invalid_pymethod_names.rs:24:19 --> tests/ui/invalid_pymethod_names.rs:24:19