Merge pull request #2234 from davidhewitt/pyclass-args-refactor
pyclass: unify pyclass with its pyo3 arguments
This commit is contained in:
commit
87c79c0319
|
@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [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
|
||||
|
||||
- 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)
|
||||
|
|
|
@ -195,22 +195,16 @@ Python::with_gil(|py|{
|
|||
|
||||
## 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.
|
||||
* `freelist=XXX` - The `freelist` parameter adds support of free allocation list to custom class.
|
||||
The performance improvement applies to types that are often created and deleted in a row,
|
||||
so that they can benefit from a freelist. `XXX` is a number of items for the free list.
|
||||
* `gc` - Classes with the `gc` parameter participate in Python garbage collection.
|
||||
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.
|
||||
* `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.
|
||||
* `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.
|
||||
[params-1]: {{#PYO3_DOCS_URL}}/pyo3/prelude/struct.PyAny.html
|
||||
[params-2]: https://en.wikipedia.org/wiki/Free_list
|
||||
[params-3]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html
|
||||
[params-4]: https://doc.rust-lang.org/stable/std/rc/struct.Rc.html
|
||||
[params-5]: https://doc.rust-lang.org/stable/std/sync/struct.Rc.html
|
||||
[params-6]: https://docs.python.org/3/library/weakref.html
|
||||
|
||||
These parameters are covered in various sections of this guide.
|
||||
|
||||
### Return type
|
||||
|
||||
|
@ -716,7 +710,7 @@ num=-1
|
|||
|
||||
## 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
|
||||
# #![allow(dead_code)]
|
||||
|
@ -724,8 +718,7 @@ use pyo3::prelude::*;
|
|||
use pyo3::types::PyType;
|
||||
|
||||
// it works even if the item is not documented:
|
||||
#[pyclass]
|
||||
#[pyo3(text_signature = "(c, d, /)")]
|
||||
#[pyclass(text_signature = "(c, d, /)")]
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
|
|
|
@ -1,77 +1,107 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token::Comma,
|
||||
Attribute, ExprPath, Ident, LitStr, Path, Result, Token,
|
||||
Attribute, Expr, ExprPath, Ident, LitStr, Path, Result, Token,
|
||||
};
|
||||
|
||||
pub mod kw {
|
||||
syn::custom_keyword!(annotation);
|
||||
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!(gc);
|
||||
syn::custom_keyword!(get);
|
||||
syn::custom_keyword!(item);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(module);
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(set);
|
||||
syn::custom_keyword!(signature);
|
||||
syn::custom_keyword!(subclass);
|
||||
syn::custom_keyword!(text_signature);
|
||||
syn::custom_keyword!(transparent);
|
||||
syn::custom_keyword!(unsendable);
|
||||
syn::custom_keyword!(weakref);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FromPyWithAttribute(pub ExprPath);
|
||||
#[derive(Clone, Debug)]
|
||||
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> {
|
||||
let _: kw::from_py_with = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
string_literal.parse().map(FromPyWithAttribute)
|
||||
let lit_str: LitStr = input.parse()?;
|
||||
lit_str.parse().map(LitStrValue)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NameAttribute(pub Ident);
|
||||
|
||||
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)
|
||||
impl<T: ToTokens> ToTokens for LitStrValue<T> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.0.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
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 type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>;
|
||||
|
||||
pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
|
||||
if is_attribute_ident(attr, "pyo3") {
|
||||
|
|
|
@ -252,7 +252,9 @@ impl<'a> Container<'a> {
|
|||
None => quote!(
|
||||
obj.get_item(#index)?.extract()
|
||||
),
|
||||
Some(FromPyWithAttribute(expr_path)) => quote! (
|
||||
Some(FromPyWithAttribute {
|
||||
value: expr_path, ..
|
||||
}) => quote! (
|
||||
#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
|
||||
})?),
|
||||
Some(FromPyWithAttribute(expr_path)) => quote! (
|
||||
Some(FromPyWithAttribute {
|
||||
value: expr_path, ..
|
||||
}) => quote! (
|
||||
#expr_path(#get_field).map_err(|inner| {
|
||||
let py = _pyo3::PyNativeType::py(obj);
|
||||
let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
|
||||
|
@ -388,7 +392,7 @@ impl ContainerOptions {
|
|||
ContainerPyO3Attribute::Crate(path) => {
|
||||
ensure_spanned!(
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ pub struct ConstSpec {
|
|||
impl ConstSpec {
|
||||
pub fn python_name(&self) -> Cow<Ident> {
|
||||
if let Some(name) = &self.attributes.name {
|
||||
Cow::Borrowed(&name.0)
|
||||
Cow::Borrowed(&name.value.0)
|
||||
} else {
|
||||
Cow::Owned(self.rust_ident.unraw())
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ impl ConstAttributes {
|
|||
fn set_name(&mut self, name: NameAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
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);
|
||||
Ok(())
|
||||
|
|
|
@ -14,7 +14,7 @@ use syn::ext::IdentExt;
|
|||
use syn::spanned::Spanned;
|
||||
use syn::Result;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FnArg<'a> {
|
||||
pub name: &'a syn::Ident,
|
||||
pub by_ref: &'a Option<syn::token::Ref>,
|
||||
|
@ -273,7 +273,7 @@ impl<'a> FnSpec<'a> {
|
|||
ty: fn_type_attr,
|
||||
args: fn_attrs,
|
||||
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) =
|
||||
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
|
||||
|
|
|
@ -31,7 +31,7 @@ impl PyModuleOptions {
|
|||
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
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)?,
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ impl PyModuleOptions {
|
|||
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
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);
|
||||
|
|
|
@ -231,7 +231,9 @@ fn impl_arg_param(
|
|||
let arg_value = quote_arg_span!(#args_array[#option_pos]);
|
||||
*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()) {
|
||||
(Some(default), true) if default.to_string() != "None" => {
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
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::konst::{ConstAttributes, ConstSpec};
|
||||
use crate::pyimpl::{gen_default_items, gen_py_const, PyClassMethodsType};
|
||||
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 quote::quote;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
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`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -24,27 +25,18 @@ pub enum PyClassKind {
|
|||
|
||||
/// The parsed arguments of the pyclass macro
|
||||
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 options: PyClassPyO3Options,
|
||||
pub deprecations: Deprecations,
|
||||
}
|
||||
|
||||
impl PyClassArgs {
|
||||
fn parse(input: ParseStream, kind: PyClassKind) -> Result<Self> {
|
||||
let mut slf = PyClassArgs::new(kind);
|
||||
let vars = Punctuated::<Expr, Token![,]>::parse_terminated(input)?;
|
||||
for expr in vars {
|
||||
slf.add_expr(&expr)?;
|
||||
}
|
||||
Ok(slf)
|
||||
Ok(PyClassArgs {
|
||||
class_kind: kind,
|
||||
options: PyClassPyO3Options::parse(input)?,
|
||||
deprecations: Deprecations::new(),
|
||||
})
|
||||
}
|
||||
|
||||
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> {
|
||||
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)]
|
||||
pub struct PyClassPyO3Options {
|
||||
pub text_signature: Option<TextSignatureAttribute>,
|
||||
pub deprecations: Deprecations,
|
||||
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 {
|
||||
TextSignature(TextSignatureAttribute),
|
||||
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 {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::text_signature) {
|
||||
input.parse().map(PyClassPyO3Option::TextSignature)
|
||||
} else if lookahead.peek(Token![crate]) {
|
||||
if lookahead.peek(Token![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 {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
|
@ -210,57 +111,69 @@ impl Parse for PyClassPyO3Option {
|
|||
}
|
||||
|
||||
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();
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
PyClassPyO3Option::TextSignature(text_signature) => {
|
||||
options.set_text_signature(text_signature)?;
|
||||
}
|
||||
PyClassPyO3Option::Crate(path) => {
|
||||
options.set_crate(path)?;
|
||||
}
|
||||
}
|
||||
|
||||
for option in Punctuated::<PyClassPyO3Option, syn::Token![,]>::parse_terminated(input)? {
|
||||
options.set_option(option)?;
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
pub fn set_text_signature(
|
||||
&mut self,
|
||||
text_signature: TextSignatureAttribute,
|
||||
) -> syn::Result<()> {
|
||||
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 take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
|
||||
take_pyo3_options(attrs)?
|
||||
.into_iter()
|
||||
.try_for_each(|option| self.set_option(option))
|
||||
}
|
||||
|
||||
pub fn set_crate(&mut self, path: CrateAttribute) -> syn::Result<()> {
|
||||
ensure_spanned!(
|
||||
self.krate.is_none(),
|
||||
path.0.span() => "`text_signature` may only be specified once"
|
||||
);
|
||||
self.krate = Some(path);
|
||||
fn set_option(&mut self, option: PyClassPyO3Option) -> syn::Result<()> {
|
||||
macro_rules! set_option {
|
||||
($key:ident) => {
|
||||
{
|
||||
ensure_spanned!(
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_py_class(
|
||||
class: &mut syn::ItemStruct,
|
||||
args: &PyClassArgs,
|
||||
mut args: PyClassArgs,
|
||||
methods_type: PyClassMethodsType,
|
||||
) -> 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(
|
||||
&class.attrs,
|
||||
options
|
||||
args.options
|
||||
.text_signature
|
||||
.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!(
|
||||
class.generics.params.is_empty(),
|
||||
|
@ -290,15 +203,7 @@ pub fn build_py_class(
|
|||
}
|
||||
};
|
||||
|
||||
impl_class(
|
||||
&class.ident,
|
||||
args,
|
||||
doc,
|
||||
field_options,
|
||||
methods_type,
|
||||
options.deprecations,
|
||||
krate,
|
||||
)
|
||||
impl_class(&class.ident, &args, doc, field_options, methods_type, krate)
|
||||
}
|
||||
|
||||
/// `#[pyo3()]` options for pyclass fields
|
||||
|
@ -356,7 +261,7 @@ impl FieldPyO3Options {
|
|||
FieldPyO3Option::Name(name) => {
|
||||
ensure_spanned!(
|
||||
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);
|
||||
}
|
||||
|
@ -367,24 +272,27 @@ impl FieldPyO3Options {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_class_python_name<'a>(cls: &'a syn::Ident, attr: &'a PyClassArgs) -> &'a syn::Ident {
|
||||
attr.name.as_ref().unwrap_or(cls)
|
||||
fn get_class_python_name<'a>(cls: &'a syn::Ident, args: &'a PyClassArgs) -> &'a syn::Ident {
|
||||
args.options
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name_attr| &name_attr.value.0)
|
||||
.unwrap_or(cls)
|
||||
}
|
||||
|
||||
fn impl_class(
|
||||
cls: &syn::Ident,
|
||||
attr: &PyClassArgs,
|
||||
args: &PyClassArgs,
|
||||
doc: PythonDoc,
|
||||
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
|
||||
methods_type: PyClassMethodsType,
|
||||
deprecations: Deprecations,
|
||||
krate: syn::Path,
|
||||
) -> 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(
|
||||
cls,
|
||||
attr,
|
||||
args,
|
||||
methods_type,
|
||||
descriptors_to_items(cls, field_options)?,
|
||||
vec![],
|
||||
|
@ -458,23 +366,28 @@ impl<'a> PyClassEnum<'a> {
|
|||
|
||||
pub fn build_py_enum(
|
||||
enum_: &mut syn::ItemEnum,
|
||||
args: &PyClassArgs,
|
||||
mut args: PyClassArgs,
|
||||
method_type: PyClassMethodsType,
|
||||
) -> 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() {
|
||||
bail_spanned!(enum_.brace_token.span => "Empty enums can't be #[pyclass].");
|
||||
if let Some(extends) = &args.options.extends {
|
||||
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(
|
||||
&enum_.attrs,
|
||||
options
|
||||
args.options
|
||||
.text_signature
|
||||
.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_)?;
|
||||
Ok(impl_enum(enum_, args, doc, method_type, options))
|
||||
Ok(impl_enum(enum_, &args, doc, method_type))
|
||||
}
|
||||
|
||||
fn impl_enum(
|
||||
|
@ -482,9 +395,8 @@ fn impl_enum(
|
|||
args: &PyClassArgs,
|
||||
doc: PythonDoc,
|
||||
methods_type: PyClassMethodsType,
|
||||
options: PyClassPyO3Options,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
|
@ -613,7 +525,10 @@ fn enum_default_methods<'a>(
|
|||
rust_ident: ident.clone(),
|
||||
attributes: ConstAttributes {
|
||||
is_class_attr: true,
|
||||
name: Some(NameAttribute(ident.clone())),
|
||||
name: Some(NameAttribute {
|
||||
kw: syn::parse_quote! { name },
|
||||
value: NameLitStr(ident.clone()),
|
||||
}),
|
||||
deprecations: Default::default(),
|
||||
},
|
||||
};
|
||||
|
@ -649,7 +564,7 @@ fn descriptors_to_items(
|
|||
.enumerate()
|
||||
.flat_map(|(field_index, (field, options))| {
|
||||
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 {
|
||||
None
|
||||
};
|
||||
|
@ -686,8 +601,8 @@ fn impl_pytypeinfo(
|
|||
) -> TokenStream {
|
||||
let cls_name = get_class_python_name(cls, attr).to_string();
|
||||
|
||||
let module = if let Some(m) = &attr.module {
|
||||
quote! { ::core::option::Option::Some(#m) }
|
||||
let module = if let Some(ModuleAttribute { value, .. }) = &attr.options.module {
|
||||
quote! { ::core::option::Option::Some(#value) }
|
||||
} else {
|
||||
quote! { ::core::option::Option::None }
|
||||
};
|
||||
|
@ -765,20 +680,20 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
fn impl_pyclass(&self) -> TokenStream {
|
||||
let cls = self.cls;
|
||||
let attr = self.attr;
|
||||
let dict = if attr.has_dict {
|
||||
let dict = if attr.options.dict.is_some() {
|
||||
quote! { _pyo3::impl_::pyclass::PyClassDictSlot }
|
||||
} else {
|
||||
quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
|
||||
};
|
||||
|
||||
// insert space for weak ref
|
||||
let weakref = if attr.has_weaklist {
|
||||
let weakref = if attr.options.weakref.is_some() {
|
||||
quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot }
|
||||
} else {
|
||||
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 }
|
||||
} else {
|
||||
quote! { _pyo3::PyAny }
|
||||
|
@ -810,7 +725,7 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
let cls = self.cls;
|
||||
let attr = self.attr;
|
||||
// If #cls is not extended type, we allow Self->PyObject conversion
|
||||
if !attr.has_extends {
|
||||
if attr.options.extends.is_none() {
|
||||
quote! {
|
||||
impl _pyo3::IntoPy<_pyo3::PyObject> for #cls {
|
||||
fn into_py(self, py: _pyo3::Python) -> _pyo3::PyObject {
|
||||
|
@ -825,11 +740,17 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
fn impl_pyclassimpl(&self) -> TokenStream {
|
||||
let cls = self.cls;
|
||||
let doc = self.doc.as_ref().map_or(quote! {"\0"}, |doc| quote! {#doc});
|
||||
let is_basetype = self.attr.is_basetype;
|
||||
let base = &self.attr.base;
|
||||
let is_subclass = self.attr.has_extends;
|
||||
let is_basetype = self.attr.options.subclass.is_some();
|
||||
let base = self
|
||||
.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! {
|
||||
fn dict_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
|
||||
::std::option::Option::Some(_pyo3::impl_::pyclass::dict_offset::<Self>())
|
||||
|
@ -840,7 +761,7 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
};
|
||||
|
||||
// insert space for weak ref
|
||||
let weaklist_offset = if self.attr.has_weaklist {
|
||||
let weaklist_offset = if self.attr.options.weakref.is_some() {
|
||||
quote! {
|
||||
fn weaklist_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
|
||||
::std::option::Option::Some(_pyo3::impl_::pyclass::weaklist_offset::<Self>())
|
||||
|
@ -850,9 +771,9 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
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> }
|
||||
} else if self.attr.has_extends {
|
||||
} else if self.attr.options.extends.is_some() {
|
||||
quote! {
|
||||
_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 {
|
||||
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! {
|
||||
impl _pyo3::impl_::pyclass::PyClassWithFreeList for #cls {
|
||||
#[inline]
|
||||
|
@ -962,7 +884,7 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
fn freelist_slots(&self) -> Vec<TokenStream> {
|
||||
let cls = self.cls;
|
||||
|
||||
if self.attr.freelist.is_some() {
|
||||
if self.attr.options.freelist.is_some() {
|
||||
vec![
|
||||
quote! {
|
||||
_pyo3::ffi::PyType_Slot {
|
||||
|
|
|
@ -40,7 +40,7 @@ pub struct PyFunctionSignature {
|
|||
has_kwargs: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PyFunctionArgPyO3Attributes {
|
||||
pub from_py_with: Option<FromPyWithAttribute>,
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ impl PyFunctionArgPyO3Attributes {
|
|||
PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => {
|
||||
ensure_spanned!(
|
||||
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);
|
||||
}
|
||||
|
@ -339,7 +339,7 @@ impl PyFunctionOptions {
|
|||
PyFunctionOption::Crate(path) => {
|
||||
ensure_spanned!(
|
||||
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);
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ impl PyFunctionOptions {
|
|||
pub fn set_name(&mut self, name: NameAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
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);
|
||||
Ok(())
|
||||
|
@ -377,7 +377,7 @@ pub fn impl_wrap_pyfunction(
|
|||
|
||||
let python_name = options
|
||||
.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();
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ impl PyImplOptions {
|
|||
fn set_crate(&mut self, path: CrateAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
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);
|
||||
|
|
|
@ -544,7 +544,7 @@ impl PropertyType<'_> {
|
|||
field, python_name, ..
|
||||
} => {
|
||||
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, None) => {
|
||||
bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`");
|
||||
|
|
|
@ -77,7 +77,8 @@ pub fn get_doc(
|
|||
syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| {
|
||||
if let Some((python_name, text_signature)) = 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);
|
||||
comma.to_tokens(tokens);
|
||||
}
|
||||
|
@ -154,13 +155,6 @@ pub fn ensure_not_async_fn(sig: &syn::Signature) -> syn::Result<()> {
|
|||
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 {
|
||||
while let syn::Type::Group(g) = ty {
|
||||
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`).
|
||||
pub(crate) fn get_pyo3_crate(attr: &Option<CrateAttribute>) -> syn::Path {
|
||||
attr.as_ref()
|
||||
.map(|p| p.0.clone())
|
||||
.map(|p| p.value.0.clone())
|
||||
.unwrap_or_else(|| syn::parse_str("::pyo3").unwrap())
|
||||
}
|
||||
|
|
28
pyo3-macros/docs/pyclass_parameters.md
Normal file
28
pyo3-macros/docs/pyclass_parameters.md
Normal 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 { }
|
||||
```
|
|
@ -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.
|
||||
///
|
||||
/// `#[pyclass]` accepts the following [parameters][2]:
|
||||
///
|
||||
/// | 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`. |
|
||||
#[cfg_attr(docsrs, cfg_attr(docsrs, doc = include_str!("../docs/pyclass_parameters.md")))]
|
||||
///
|
||||
/// For more on creating Python classes,
|
||||
/// see the [class section of the guide][1].
|
||||
///
|
||||
/// [1]: https://pyo3.rs/latest/class.html
|
||||
/// [2]: https://pyo3.rs/latest/class.html#customizing-the-class
|
||||
/// [3]: std::marker::Send
|
||||
/// [4]: ../prelude/struct.PyAny.html
|
||||
/// [5]: https://pyo3.rs/latest/class/protocols.html#garbage-collector-integration
|
||||
/// [6]: https://docs.python.org/3/library/weakref.html
|
||||
/// [7]: std::rc::Rc
|
||||
/// [8]: std::sync::Arc
|
||||
/// [9]: https://en.wikipedia.org/wiki/Free_list
|
||||
/// [params-1]: ../prelude/struct.PyAny.html
|
||||
/// [params-2]: https://en.wikipedia.org/wiki/Free_list
|
||||
/// [params-3]: std::marker::Send
|
||||
/// [params-4]: std::rc::Rc
|
||||
/// [params-5]: std::sync::Arc
|
||||
/// [params-6]: https://docs.python.org/3/library/weakref.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
use syn::Item;
|
||||
|
@ -230,7 +218,7 @@ fn pyclass_impl(
|
|||
methods_type: PyClassMethodsType,
|
||||
) -> TokenStream {
|
||||
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!(
|
||||
#ast
|
||||
|
@ -245,7 +233,7 @@ fn pyclass_enum_impl(
|
|||
methods_type: PyClassMethodsType,
|
||||
) -> TokenStream {
|
||||
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!(
|
||||
#ast
|
||||
|
|
|
@ -35,13 +35,13 @@ error: `set` 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);
|
||||
| ^^^^^
|
||||
| ^^^^
|
||||
|
||||
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);
|
||||
| ^^^^^^^
|
||||
| ^^^^
|
||||
|
|
|
@ -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
|
||||
|
|
||||
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
|
||||
|
|
||||
6 | #[pyclass(extends = "PyDict")]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: expected type name (e.g. "Name")
|
||||
error: expected string literal
|
||||
--> tests/ui/invalid_pyclass_args.rs:9:18
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
15 | #[pyclass(name = CustomName)]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: expected string literal (e.g., "my_mod")
|
||||
error: expected string literal
|
||||
--> tests/ui/invalid_pyclass_args.rs:18:20
|
||||
|
|
||||
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
|
||||
|
|
||||
21 | #[pyclass(weakrev)]
|
||||
|
|
|
@ -2,14 +2,14 @@ use pyo3::prelude::*;
|
|||
|
||||
#[pyclass(subclass)]
|
||||
enum NotBaseClass {
|
||||
x,
|
||||
y,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[pyclass(extends = PyList)]
|
||||
enum NotDrivedClass {
|
||||
x,
|
||||
y,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
|
|
|
@ -4,13 +4,13 @@ error: enums can't be inherited by other classes
|
|||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
16 | enum NoEmptyEnum {}
|
||||
|
|
|
@ -5,10 +5,10 @@ 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")]
|
||||
| ^^^^^
|
||||
| ^^^^
|
||||
|
||||
error: `name` not allowed with `#[new]`
|
||||
--> tests/ui/invalid_pymethod_names.rs:24:19
|
||||
|
|
Loading…
Reference in a new issue