Skip to content

Commit cc7e16f

Browse files
authored
Refactoring of FnArg (#4033)
* refactor `FnArg` * add UI tests * use enum variant types * add comment * remove dead code * remove last FIXME * review feedback davidhewitt
1 parent 721100a commit cc7e16f

9 files changed

+348
-238
lines changed

pyo3-macros-backend/src/method.rs

Lines changed: 139 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use syn::{ext::IdentExt, spanned::Spanned, Ident, Result};
66

77
use crate::utils::Ctx;
88
use crate::{
9-
attributes::{TextSignatureAttribute, TextSignatureAttributeValue},
9+
attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue},
1010
deprecations::{Deprecation, Deprecations},
1111
params::{impl_arg_params, Holders},
1212
pyfunction::{
@@ -17,19 +17,109 @@ use crate::{
1717
};
1818

1919
#[derive(Clone, Debug)]
20-
pub struct FnArg<'a> {
20+
pub struct RegularArg<'a> {
2121
pub name: &'a syn::Ident,
2222
pub ty: &'a syn::Type,
23-
pub optional: Option<&'a syn::Type>,
24-
pub default: Option<syn::Expr>,
25-
pub py: bool,
26-
pub attrs: PyFunctionArgPyO3Attributes,
27-
pub is_varargs: bool,
28-
pub is_kwargs: bool,
29-
pub is_cancel_handle: bool,
23+
pub from_py_with: Option<FromPyWithAttribute>,
24+
pub default_value: Option<syn::Expr>,
25+
pub option_wrapped_type: Option<&'a syn::Type>,
26+
}
27+
28+
/// Pythons *args argument
29+
#[derive(Clone, Debug)]
30+
pub struct VarargsArg<'a> {
31+
pub name: &'a syn::Ident,
32+
pub ty: &'a syn::Type,
33+
}
34+
35+
/// Pythons **kwarg argument
36+
#[derive(Clone, Debug)]
37+
pub struct KwargsArg<'a> {
38+
pub name: &'a syn::Ident,
39+
pub ty: &'a syn::Type,
40+
}
41+
42+
#[derive(Clone, Debug)]
43+
pub struct CancelHandleArg<'a> {
44+
pub name: &'a syn::Ident,
45+
pub ty: &'a syn::Type,
46+
}
47+
48+
#[derive(Clone, Debug)]
49+
pub struct PyArg<'a> {
50+
pub name: &'a syn::Ident,
51+
pub ty: &'a syn::Type,
52+
}
53+
54+
#[derive(Clone, Debug)]
55+
pub enum FnArg<'a> {
56+
Regular(RegularArg<'a>),
57+
VarArgs(VarargsArg<'a>),
58+
KwArgs(KwargsArg<'a>),
59+
Py(PyArg<'a>),
60+
CancelHandle(CancelHandleArg<'a>),
3061
}
3162

3263
impl<'a> FnArg<'a> {
64+
pub fn name(&self) -> &'a syn::Ident {
65+
match self {
66+
FnArg::Regular(RegularArg { name, .. }) => name,
67+
FnArg::VarArgs(VarargsArg { name, .. }) => name,
68+
FnArg::KwArgs(KwargsArg { name, .. }) => name,
69+
FnArg::Py(PyArg { name, .. }) => name,
70+
FnArg::CancelHandle(CancelHandleArg { name, .. }) => name,
71+
}
72+
}
73+
74+
pub fn ty(&self) -> &'a syn::Type {
75+
match self {
76+
FnArg::Regular(RegularArg { ty, .. }) => ty,
77+
FnArg::VarArgs(VarargsArg { ty, .. }) => ty,
78+
FnArg::KwArgs(KwargsArg { ty, .. }) => ty,
79+
FnArg::Py(PyArg { ty, .. }) => ty,
80+
FnArg::CancelHandle(CancelHandleArg { ty, .. }) => ty,
81+
}
82+
}
83+
84+
#[allow(clippy::wrong_self_convention)]
85+
pub fn from_py_with(&self) -> Option<&FromPyWithAttribute> {
86+
if let FnArg::Regular(RegularArg { from_py_with, .. }) = self {
87+
from_py_with.as_ref()
88+
} else {
89+
None
90+
}
91+
}
92+
93+
pub fn to_varargs_mut(&mut self) -> Result<&mut Self> {
94+
if let Self::Regular(RegularArg {
95+
name,
96+
ty,
97+
option_wrapped_type: None,
98+
..
99+
}) = self
100+
{
101+
*self = Self::VarArgs(VarargsArg { name, ty });
102+
Ok(self)
103+
} else {
104+
bail_spanned!(self.name().span() => "args cannot be optional")
105+
}
106+
}
107+
108+
pub fn to_kwargs_mut(&mut self) -> Result<&mut Self> {
109+
if let Self::Regular(RegularArg {
110+
name,
111+
ty,
112+
option_wrapped_type: Some(..),
113+
..
114+
}) = self
115+
{
116+
*self = Self::KwArgs(KwargsArg { name, ty });
117+
Ok(self)
118+
} else {
119+
bail_spanned!(self.name().span() => "kwargs must be Option<_>")
120+
}
121+
}
122+
33123
/// Transforms a rust fn arg parsed with syn into a method::FnArg
34124
pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> {
35125
match arg {
@@ -41,32 +131,43 @@ impl<'a> FnArg<'a> {
41131
bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR);
42132
}
43133

44-
let arg_attrs = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
134+
let PyFunctionArgPyO3Attributes {
135+
from_py_with,
136+
cancel_handle,
137+
} = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
45138
let ident = match &*cap.pat {
46139
syn::Pat::Ident(syn::PatIdent { ident, .. }) => ident,
47140
other => return Err(handle_argument_error(other)),
48141
};
49142

50-
let is_cancel_handle = arg_attrs.cancel_handle.is_some();
143+
if utils::is_python(&cap.ty) {
144+
return Ok(Self::Py(PyArg {
145+
name: ident,
146+
ty: &cap.ty,
147+
}));
148+
}
51149

52-
Ok(FnArg {
150+
if cancel_handle.is_some() {
151+
// `PyFunctionArgPyO3Attributes::from_attrs` validates that
152+
// only compatible attributes are specified, either
153+
// `cancel_handle` or `from_py_with`, dublicates and any
154+
// combination of the two are already rejected.
155+
return Ok(Self::CancelHandle(CancelHandleArg {
156+
name: ident,
157+
ty: &cap.ty,
158+
}));
159+
}
160+
161+
Ok(Self::Regular(RegularArg {
53162
name: ident,
54163
ty: &cap.ty,
55-
optional: utils::option_type_argument(&cap.ty),
56-
default: None,
57-
py: utils::is_python(&cap.ty),
58-
attrs: arg_attrs,
59-
is_varargs: false,
60-
is_kwargs: false,
61-
is_cancel_handle,
62-
})
164+
from_py_with,
165+
default_value: None,
166+
option_wrapped_type: utils::option_type_argument(&cap.ty),
167+
}))
63168
}
64169
}
65170
}
66-
67-
pub fn is_regular(&self) -> bool {
68-
!self.py && !self.is_cancel_handle && !self.is_kwargs && !self.is_varargs
69-
}
70171
}
71172

72173
fn handle_argument_error(pat: &syn::Pat) -> syn::Error {
@@ -492,12 +593,14 @@ impl<'a> FnSpec<'a> {
492593
.signature
493594
.arguments
494595
.iter()
495-
.filter(|arg| arg.is_cancel_handle);
596+
.filter(|arg| matches!(arg, FnArg::CancelHandle(..)));
496597
let cancel_handle = cancel_handle_iter.next();
497-
if let Some(arg) = cancel_handle {
498-
ensure_spanned!(self.asyncness.is_some(), arg.name.span() => "`cancel_handle` attribute can only be used with `async fn`");
499-
if let Some(arg2) = cancel_handle_iter.next() {
500-
bail_spanned!(arg2.name.span() => "`cancel_handle` may only be specified once");
598+
if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle {
599+
ensure_spanned!(self.asyncness.is_some(), name.span() => "`cancel_handle` attribute can only be used with `async fn`");
600+
if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) =
601+
cancel_handle_iter.next()
602+
{
603+
bail_spanned!(name.span() => "`cancel_handle` may only be specified once");
501604
}
502605
}
503606

@@ -605,14 +708,10 @@ impl<'a> FnSpec<'a> {
605708
.signature
606709
.arguments
607710
.iter()
608-
.map(|arg| {
609-
if arg.py {
610-
quote!(py)
611-
} else if arg.is_cancel_handle {
612-
quote!(__cancel_handle)
613-
} else {
614-
unreachable!()
615-
}
711+
.map(|arg| match arg {
712+
FnArg::Py(..) => quote!(py),
713+
FnArg::CancelHandle(..) => quote!(__cancel_handle),
714+
_ => unreachable!("`CallingConvention::Noargs` should not contain any arguments (reaching Python) except for `self`, which is handled below."),
616715
})
617716
.collect();
618717
let call = rust_call(args, &mut holders);
@@ -635,7 +734,7 @@ impl<'a> FnSpec<'a> {
635734
}
636735
CallingConvention::Fastcall => {
637736
let mut holders = Holders::new();
638-
let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx)?;
737+
let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx);
639738
let call = rust_call(args, &mut holders);
640739
let init_holders = holders.init_holders(ctx);
641740
let check_gil_refs = holders.check_gil_refs();
@@ -660,7 +759,7 @@ impl<'a> FnSpec<'a> {
660759
}
661760
CallingConvention::Varargs => {
662761
let mut holders = Holders::new();
663-
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx)?;
762+
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx);
664763
let call = rust_call(args, &mut holders);
665764
let init_holders = holders.init_holders(ctx);
666765
let check_gil_refs = holders.check_gil_refs();
@@ -684,7 +783,7 @@ impl<'a> FnSpec<'a> {
684783
}
685784
CallingConvention::TpNew => {
686785
let mut holders = Holders::new();
687-
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx)?;
786+
let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx);
688787
let self_arg = self
689788
.tp
690789
.self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx);

0 commit comments

Comments
 (0)