pyclass: unify pyclass with its pyo3 arguments

This commit is contained in:
David Hewitt 2022-03-18 14:58:44 +00:00
parent 3eb654c81c
commit 5cc3ce99f1
17 changed files with 292 additions and 323 deletions

View File

@ -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)]
pub struct KeywordAttribute<K, V> {
pub kw: K,
pub value: V,
}
/// 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 FromPyWithAttribute(pub ExprPath);
pub struct LitStrValue<T>(pub T);
impl Parse for FromPyWithAttribute {
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)
}
}
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 NameAttribute(pub Ident);
pub struct NameLitStr(pub Ident);
impl Parse for NameAttribute {
impl Parse for NameLitStr {
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)
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") {

View File

@ -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);
}

View File

@ -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(())

View File

@ -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)?;

View File

@ -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);

View File

@ -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" => {

View File

@ -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<()> {
fn set_option(&mut self, option: PyClassPyO3Option) -> syn::Result<()> {
macro_rules! set_option {
($key:ident) => {
{
ensure_spanned!(
self.krate.is_none(),
path.0.span() => "`text_signature` may only be specified once"
self.$key.is_none(),
$key.span() => concat!("`", stringify!($key), "` may only be specified once")
);
self.krate = Some(path);
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 {

View File

@ -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();

View File

@ -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);

View File

@ -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`");

View File

@ -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())
}

View File

@ -81,17 +81,34 @@ 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]:
/// `#[pyclass]` can be used 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">`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`][4] |
/// | <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. |
/// | <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`][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`. |
/// | `weakref` | Allows this class to be [weakly referenceable][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 { }
/// ```
///
/// For more on creating Python classes,
/// see the [class section of the guide][1].
@ -230,7 +247,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 +262,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

View File

@ -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);
| ^^^^^^^
| ^^^^

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
|
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)]

View File

@ -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]

View File

@ -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 {}

View File

@ -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