Skip to content

Commit

Permalink
split up FnSpec::parse
Browse files Browse the repository at this point in the history
  • Loading branch information
daniil-konovalenko committed Feb 15, 2021
1 parent 5d0e217 commit 13efe6e
Showing 1 changed file with 112 additions and 85 deletions.
197 changes: 112 additions & 85 deletions pyo3-macros-backend/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use quote::ToTokens;
use quote::{quote, quote_spanned};
use std::ops::Deref;
use syn::ext::IdentExt;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;

#[derive(Clone, PartialEq, Debug)]
Expand Down Expand Up @@ -106,7 +107,7 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
}
}

pub fn parse_method_receiver(arg: &mut syn::FnArg) -> syn::Result<SelfType> {
pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result<SelfType> {
match arg {
syn::FnArg::Receiver(recv) => Ok(SelfType::Receiver {
mutable: recv.mutability.is_some(),
Expand All @@ -117,34 +118,127 @@ pub fn parse_method_receiver(arg: &mut syn::FnArg) -> syn::Result<SelfType> {

impl<'a> FnSpec<'a> {
/// Parser function signature and function attributes
#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
pub fn parse(
sig: &'a mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
allow_custom_name: bool,
) -> syn::Result<FnSpec<'a>> {
let name = &sig.ident;
let MethodAttributes {
ty: fn_type_attr,
args: fn_attrs,
mut python_name,
} = parse_method_attributes(meth_attrs, allow_custom_name)?;

let mut arguments = Vec::new();
let (fn_type, skip_args) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;

let name = &sig.ident;
let ty = get_return_info(&sig.output);
let python_name = python_name.as_ref().unwrap_or(name).unraw();

let text_signature = Self::parse_text_signature(meth_attrs, &fn_type, &python_name)?;
let doc = utils::get_doc(&meth_attrs, text_signature, true)?;

let arguments = Self::parse_arguments(&mut sig.inputs, skip_args)?;

Ok(FnSpec {
tp: fn_type,
name,
python_name,
attrs: fn_attrs,
args: arguments,
output: ty,
doc,
})
}

fn parse_text_signature(
meth_attrs: &mut Vec<syn::Attribute>,
fn_type: &FnType,
python_name: &syn::Ident,
) -> syn::Result<Option<syn::LitStr>> {
let mut parse_erroneous_text_signature = |error_msg: &str| {
// try to parse anyway to give better error messages
if let Some(text_signature) =
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
{
bail_spanned!(text_signature.span() => error_msg)
} else {
Ok(None)
}
};

let text_signature = match &fn_type {
FnType::Fn(_) | FnType::FnClass | FnType::FnStatic => {
utils::parse_text_signature_attrs(&mut *meth_attrs, &python_name)?
}
FnType::FnNew => parse_erroneous_text_signature(
"text_signature not allowed on __new__; if you want to add a signature on \
__new__, put it on the struct definition instead",
)?,
FnType::FnCall(_) | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
parse_erroneous_text_signature("text_signature not allowed with this method type")?
}
};

Ok(text_signature)
}

fn parse_arguments(
// inputs: &'a mut [syn::FnArg],
inputs: &'a mut Punctuated<syn::FnArg, syn::Token![,]>,
skip_args: usize,
) -> syn::Result<Vec<FnArg<'a>>> {
let mut arguments = vec![];
for input in inputs.iter_mut().skip(skip_args) {
match input {
syn::FnArg::Receiver(recv) => {
bail_spanned!(recv.span() => "unexpected receiver for method")
} // checked in parse_fn_type
syn::FnArg::Typed(cap) => {
let arg_attrs = PyFunctionArgAttrs::from_attrs(&mut cap.attrs)?;
let (ident, by_ref, mutability) = match *cap.pat {
syn::Pat::Ident(syn::PatIdent {
ref ident,
ref by_ref,
ref mutability,
..
}) => (ident, by_ref, mutability),
_ => bail_spanned!(cap.pat.span() => "unsupported argument"),
};

arguments.push(FnArg {
name: ident,
by_ref,
mutability,
ty: cap.ty.deref(),
optional: utils::option_type_argument(cap.ty.deref()),
py: utils::is_python(cap.ty.deref()),
attrs: arg_attrs,
});
}
}
}

// TODO: maybe there's a cleaner solution
let inputs_empty = sig.inputs.is_empty();
let sig_span = sig.span();
let inputs_span = sig.inputs.span();
Ok(arguments)
}

let mut inputs_iter = sig.inputs.iter_mut();
fn parse_fn_type(
sig: &syn::Signature,
fn_type_attr: Option<MethodTypeAttribute>,
python_name: &mut Option<syn::Ident>,
) -> syn::Result<(FnType, usize)> {
let name = &sig.ident;
let mut inputs_iter = sig.inputs.iter().enumerate();
let inputs_len = sig.inputs.len();
let mut parse_receiver = |msg: &'static str| {
inputs_iter
.next()
.ok_or_else(|| err_spanned!(sig_span => msg))
.ok_or_else(|| err_spanned!(sig.span() => msg))
.map(|(_, arg)| arg)
.and_then(parse_method_receiver)
};

#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
// strip get_ or set_
let strip_fn_name = |prefix: &'static str| {
let ident = name.unraw().to_string();
Expand All @@ -155,13 +249,12 @@ impl<'a> FnSpec<'a> {
}
};

// Parse receiver & function type for various method types
let fn_type = match fn_type_attr {
Some(MethodTypeAttribute::StaticMethod) => FnType::FnStatic,
Some(MethodTypeAttribute::ClassAttribute) => {
ensure_spanned!(
inputs_empty,
inputs_span => "class attribute methods cannot take arguments"
sig.inputs.is_empty(),
sig.inputs.span() => "class attribute methods cannot take arguments"
);
FnType::ClassAttribute
}
Expand All @@ -177,15 +270,15 @@ impl<'a> FnSpec<'a> {
Some(MethodTypeAttribute::Getter) => {
// Strip off "get_" prefix if needed
if python_name.is_none() {
python_name = strip_fn_name("get_");
*python_name = strip_fn_name("get_");
}

FnType::Getter(parse_receiver("expected receiver for #[getter]")?)
}
Some(MethodTypeAttribute::Setter) => {
// Strip off "set_" prefix if needed
if python_name.is_none() {
python_name = strip_fn_name("set_");
*python_name = strip_fn_name("set_");
}

FnType::Setter(parse_receiver("expected receiver for #[setter]")?)
Expand All @@ -194,76 +287,10 @@ impl<'a> FnSpec<'a> {
"static method needs #[staticmethod] attribute",
)?),
};

// parse rest of arguments
for input in inputs_iter {
match input {
syn::FnArg::Receiver(recv) => {
bail_spanned!(recv.span() => "unexpected receiver for method")
}
syn::FnArg::Typed(cap) => {
let arg_attrs = PyFunctionArgAttrs::from_attrs(&mut cap.attrs)?;
let (ident, by_ref, mutability) = match *cap.pat {
syn::Pat::Ident(syn::PatIdent {
ref ident,
ref by_ref,
ref mutability,
..
}) => (ident, by_ref, mutability),
_ => bail_spanned!(cap.pat.span() => "unsupported argument"),
};

arguments.push(FnArg {
name: ident,
by_ref,
mutability,
ty: cap.ty.deref(),
optional: utils::option_type_argument(cap.ty.deref()),
py: utils::is_python(cap.ty.deref()),
attrs: arg_attrs,
});
}
}
}

let ty = get_return_info(&sig.output);
let python_name = python_name.as_ref().unwrap_or(name).unraw();

let mut parse_erroneous_text_signature = |error_msg: &str| {
// try to parse anyway to give better error messages
if let Some(text_signature) =
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
{
bail_spanned!(text_signature.span() => error_msg)
} else {
Ok(None)
}
};

let text_signature = match &fn_type {
FnType::Fn(_) | FnType::FnClass | FnType::FnStatic => {
utils::parse_text_signature_attrs(&mut *meth_attrs, &python_name)?
}
FnType::FnNew => parse_erroneous_text_signature(
"text_signature not allowed on __new__; if you want to add a signature on \
__new__, put it on the struct definition instead",
)?,
FnType::FnCall(_) | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
parse_erroneous_text_signature("text_signature not allowed with this method type")?
}
};

let doc = utils::get_doc(&meth_attrs, text_signature, true)?;

Ok(FnSpec {
tp: fn_type,
name,
python_name,
attrs: fn_attrs,
args: arguments,
output: ty,
doc,
})
Ok((
fn_type,
inputs_iter.next().map_or(inputs_len, |(count, _)| count),
))
}

pub fn is_args(&self, name: &syn::Ident) -> bool {
Expand Down

0 comments on commit 13efe6e

Please sign in to comment.