Skip to content

Commit 13efe6e

Browse files
split up FnSpec::parse
1 parent 5d0e217 commit 13efe6e

File tree

1 file changed

+112
-85
lines changed

1 file changed

+112
-85
lines changed

pyo3-macros-backend/src/method.rs

+112-85
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use quote::ToTokens;
88
use quote::{quote, quote_spanned};
99
use std::ops::Deref;
1010
use syn::ext::IdentExt;
11+
use syn::punctuated::Punctuated;
1112
use syn::spanned::Spanned;
1213

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

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

118119
impl<'a> FnSpec<'a> {
119120
/// Parser function signature and function attributes
120-
#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
121121
pub fn parse(
122122
sig: &'a mut syn::Signature,
123123
meth_attrs: &mut Vec<syn::Attribute>,
124124
allow_custom_name: bool,
125125
) -> syn::Result<FnSpec<'a>> {
126-
let name = &sig.ident;
127126
let MethodAttributes {
128127
ty: fn_type_attr,
129128
args: fn_attrs,
130129
mut python_name,
131130
} = parse_method_attributes(meth_attrs, allow_custom_name)?;
132131

133-
let mut arguments = Vec::new();
132+
let (fn_type, skip_args) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
133+
134+
let name = &sig.ident;
135+
let ty = get_return_info(&sig.output);
136+
let python_name = python_name.as_ref().unwrap_or(name).unraw();
137+
138+
let text_signature = Self::parse_text_signature(meth_attrs, &fn_type, &python_name)?;
139+
let doc = utils::get_doc(&meth_attrs, text_signature, true)?;
140+
141+
let arguments = Self::parse_arguments(&mut sig.inputs, skip_args)?;
142+
143+
Ok(FnSpec {
144+
tp: fn_type,
145+
name,
146+
python_name,
147+
attrs: fn_attrs,
148+
args: arguments,
149+
output: ty,
150+
doc,
151+
})
152+
}
153+
154+
fn parse_text_signature(
155+
meth_attrs: &mut Vec<syn::Attribute>,
156+
fn_type: &FnType,
157+
python_name: &syn::Ident,
158+
) -> syn::Result<Option<syn::LitStr>> {
159+
let mut parse_erroneous_text_signature = |error_msg: &str| {
160+
// try to parse anyway to give better error messages
161+
if let Some(text_signature) =
162+
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
163+
{
164+
bail_spanned!(text_signature.span() => error_msg)
165+
} else {
166+
Ok(None)
167+
}
168+
};
169+
170+
let text_signature = match &fn_type {
171+
FnType::Fn(_) | FnType::FnClass | FnType::FnStatic => {
172+
utils::parse_text_signature_attrs(&mut *meth_attrs, &python_name)?
173+
}
174+
FnType::FnNew => parse_erroneous_text_signature(
175+
"text_signature not allowed on __new__; if you want to add a signature on \
176+
__new__, put it on the struct definition instead",
177+
)?,
178+
FnType::FnCall(_) | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
179+
parse_erroneous_text_signature("text_signature not allowed with this method type")?
180+
}
181+
};
182+
183+
Ok(text_signature)
184+
}
185+
186+
fn parse_arguments(
187+
// inputs: &'a mut [syn::FnArg],
188+
inputs: &'a mut Punctuated<syn::FnArg, syn::Token![,]>,
189+
skip_args: usize,
190+
) -> syn::Result<Vec<FnArg<'a>>> {
191+
let mut arguments = vec![];
192+
for input in inputs.iter_mut().skip(skip_args) {
193+
match input {
194+
syn::FnArg::Receiver(recv) => {
195+
bail_spanned!(recv.span() => "unexpected receiver for method")
196+
} // checked in parse_fn_type
197+
syn::FnArg::Typed(cap) => {
198+
let arg_attrs = PyFunctionArgAttrs::from_attrs(&mut cap.attrs)?;
199+
let (ident, by_ref, mutability) = match *cap.pat {
200+
syn::Pat::Ident(syn::PatIdent {
201+
ref ident,
202+
ref by_ref,
203+
ref mutability,
204+
..
205+
}) => (ident, by_ref, mutability),
206+
_ => bail_spanned!(cap.pat.span() => "unsupported argument"),
207+
};
208+
209+
arguments.push(FnArg {
210+
name: ident,
211+
by_ref,
212+
mutability,
213+
ty: cap.ty.deref(),
214+
optional: utils::option_type_argument(cap.ty.deref()),
215+
py: utils::is_python(cap.ty.deref()),
216+
attrs: arg_attrs,
217+
});
218+
}
219+
}
220+
}
134221

135-
// TODO: maybe there's a cleaner solution
136-
let inputs_empty = sig.inputs.is_empty();
137-
let sig_span = sig.span();
138-
let inputs_span = sig.inputs.span();
222+
Ok(arguments)
223+
}
139224

140-
let mut inputs_iter = sig.inputs.iter_mut();
225+
fn parse_fn_type(
226+
sig: &syn::Signature,
227+
fn_type_attr: Option<MethodTypeAttribute>,
228+
python_name: &mut Option<syn::Ident>,
229+
) -> syn::Result<(FnType, usize)> {
230+
let name = &sig.ident;
231+
let mut inputs_iter = sig.inputs.iter().enumerate();
232+
let inputs_len = sig.inputs.len();
141233
let mut parse_receiver = |msg: &'static str| {
142234
inputs_iter
143235
.next()
144-
.ok_or_else(|| err_spanned!(sig_span => msg))
236+
.ok_or_else(|| err_spanned!(sig.span() => msg))
237+
.map(|(_, arg)| arg)
145238
.and_then(parse_method_receiver)
146239
};
147240

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

158-
// Parse receiver & function type for various method types
159252
let fn_type = match fn_type_attr {
160253
Some(MethodTypeAttribute::StaticMethod) => FnType::FnStatic,
161254
Some(MethodTypeAttribute::ClassAttribute) => {
162255
ensure_spanned!(
163-
inputs_empty,
164-
inputs_span => "class attribute methods cannot take arguments"
256+
sig.inputs.is_empty(),
257+
sig.inputs.span() => "class attribute methods cannot take arguments"
165258
);
166259
FnType::ClassAttribute
167260
}
@@ -177,15 +270,15 @@ impl<'a> FnSpec<'a> {
177270
Some(MethodTypeAttribute::Getter) => {
178271
// Strip off "get_" prefix if needed
179272
if python_name.is_none() {
180-
python_name = strip_fn_name("get_");
273+
*python_name = strip_fn_name("get_");
181274
}
182275

183276
FnType::Getter(parse_receiver("expected receiver for #[getter]")?)
184277
}
185278
Some(MethodTypeAttribute::Setter) => {
186279
// Strip off "set_" prefix if needed
187280
if python_name.is_none() {
188-
python_name = strip_fn_name("set_");
281+
*python_name = strip_fn_name("set_");
189282
}
190283

191284
FnType::Setter(parse_receiver("expected receiver for #[setter]")?)
@@ -194,76 +287,10 @@ impl<'a> FnSpec<'a> {
194287
"static method needs #[staticmethod] attribute",
195288
)?),
196289
};
197-
198-
// parse rest of arguments
199-
for input in inputs_iter {
200-
match input {
201-
syn::FnArg::Receiver(recv) => {
202-
bail_spanned!(recv.span() => "unexpected receiver for method")
203-
}
204-
syn::FnArg::Typed(cap) => {
205-
let arg_attrs = PyFunctionArgAttrs::from_attrs(&mut cap.attrs)?;
206-
let (ident, by_ref, mutability) = match *cap.pat {
207-
syn::Pat::Ident(syn::PatIdent {
208-
ref ident,
209-
ref by_ref,
210-
ref mutability,
211-
..
212-
}) => (ident, by_ref, mutability),
213-
_ => bail_spanned!(cap.pat.span() => "unsupported argument"),
214-
};
215-
216-
arguments.push(FnArg {
217-
name: ident,
218-
by_ref,
219-
mutability,
220-
ty: cap.ty.deref(),
221-
optional: utils::option_type_argument(cap.ty.deref()),
222-
py: utils::is_python(cap.ty.deref()),
223-
attrs: arg_attrs,
224-
});
225-
}
226-
}
227-
}
228-
229-
let ty = get_return_info(&sig.output);
230-
let python_name = python_name.as_ref().unwrap_or(name).unraw();
231-
232-
let mut parse_erroneous_text_signature = |error_msg: &str| {
233-
// try to parse anyway to give better error messages
234-
if let Some(text_signature) =
235-
utils::parse_text_signature_attrs(meth_attrs, &python_name)?
236-
{
237-
bail_spanned!(text_signature.span() => error_msg)
238-
} else {
239-
Ok(None)
240-
}
241-
};
242-
243-
let text_signature = match &fn_type {
244-
FnType::Fn(_) | FnType::FnClass | FnType::FnStatic => {
245-
utils::parse_text_signature_attrs(&mut *meth_attrs, &python_name)?
246-
}
247-
FnType::FnNew => parse_erroneous_text_signature(
248-
"text_signature not allowed on __new__; if you want to add a signature on \
249-
__new__, put it on the struct definition instead",
250-
)?,
251-
FnType::FnCall(_) | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
252-
parse_erroneous_text_signature("text_signature not allowed with this method type")?
253-
}
254-
};
255-
256-
let doc = utils::get_doc(&meth_attrs, text_signature, true)?;
257-
258-
Ok(FnSpec {
259-
tp: fn_type,
260-
name,
261-
python_name,
262-
attrs: fn_attrs,
263-
args: arguments,
264-
output: ty,
265-
doc,
266-
})
290+
Ok((
291+
fn_type,
292+
inputs_iter.next().map_or(inputs_len, |(count, _)| count),
293+
))
267294
}
268295

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

0 commit comments

Comments
 (0)