Skip to content

Commit

Permalink
pyclass: unify pyclass with its pyo3 arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Mar 18, 2022
1 parent 3eb654c commit 2cb814b
Show file tree
Hide file tree
Showing 17 changed files with 280 additions and 311 deletions.
98 changes: 64 additions & 34 deletions pyo3-macros-backend/src/attributes.rs
@@ -1,78 +1,108 @@
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)
}
}

#[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)
}
}

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

impl Parse for CrateAttribute {
impl Parse for NameLitStr {
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)
if let Ok(ident) = string_literal.parse() {
Ok(NameLitStr(ident))
} else {
bail_spanned!(string_literal.span() => "expected a single identifier in double quotes")
}
}
}

#[derive(Clone, Debug, PartialEq)]
pub struct TextSignatureAttribute {
pub kw: kw::text_signature,
pub eq_token: Token![=],
pub lit: LitStr,
impl ToTokens for NameLitStr {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
}
}

impl Parse for TextSignatureAttribute {
pub type ExtendsAttribute = KeywordAttribute<kw::extends, Path>;
pub type FreelistAttribute = KeywordAttribute<kw::freelist, 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> {
Ok(TextSignatureAttribute {
kw: input.parse()?,
eq_token: input.parse()?,
lit: input.parse()?,
})
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.
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") {
attr.parse_args_with(Punctuated::parse_terminated).map(Some)
Expand Down
10 changes: 7 additions & 3 deletions pyo3-macros-backend/src/frompyobject.rs
Expand Up @@ -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)?)
),
};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions pyo3-macros-backend/src/konst.rs
Expand Up @@ -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())
}
Expand Down Expand Up @@ -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(())
Expand Down
4 changes: 2 additions & 2 deletions pyo3-macros-backend/src/method.rs
Expand Up @@ -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>,
Expand Down Expand Up @@ -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)?;
Expand Down
4 changes: 2 additions & 2 deletions pyo3-macros-backend/src/module.rs
Expand Up @@ -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)?,
}
}
Expand All @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion pyo3-macros-backend/src/params.rs
Expand Up @@ -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" => {
Expand Down

0 comments on commit 2cb814b

Please sign in to comment.