From 13efe6eb96f19d0325b4605edf0c3c8d4e69f8bc Mon Sep 17 00:00:00 2001 From: Daniil Konovalenko Date: Sun, 14 Feb 2021 20:23:48 +0300 Subject: [PATCH] split up FnSpec::parse --- pyo3-macros-backend/src/method.rs | 197 +++++++++++++++++------------- 1 file changed, 112 insertions(+), 85 deletions(-) diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index 4357309914c..84f8e796a8e 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -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)] @@ -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 { +pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result { match arg { syn::FnArg::Receiver(recv) => Ok(SelfType::Receiver { mutable: recv.mutability.is_some(), @@ -117,34 +118,127 @@ pub fn parse_method_receiver(arg: &mut syn::FnArg) -> syn::Result { 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, allow_custom_name: bool, ) -> syn::Result> { - 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, + fn_type: &FnType, + python_name: &syn::Ident, + ) -> syn::Result> { + 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, + skip_args: usize, + ) -> syn::Result>> { + 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, + python_name: &mut Option, + ) -> 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(); @@ -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 } @@ -177,7 +270,7 @@ 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]")?) @@ -185,7 +278,7 @@ impl<'a> FnSpec<'a> { 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]")?) @@ -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 {