Skip to content

Commit 1fcb656

Browse files
authored
Set {min,max}_arity (RustPython#5994)
* General cleanup * Compute {min,max}_arity
1 parent 80a9e0e commit 1fcb656

1 file changed

Lines changed: 78 additions & 48 deletions

File tree

derive-impl/src/from_args.rs

Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,53 @@ use syn::{Attribute, Data, DeriveInput, Expr, Field, Ident, Result, Token, parse
66

77
/// The kind of the python parameter, this corresponds to the value of Parameter.kind
88
/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
9+
#[derive(Default)]
910
enum ParameterKind {
1011
PositionalOnly,
12+
#[default]
1113
PositionalOrKeyword,
1214
KeywordOnly,
1315
Flatten,
1416
}
1517

16-
impl ParameterKind {
17-
fn from_ident(ident: &Ident) -> Option<Self> {
18-
match ident.to_string().as_str() {
19-
"positional" => Some(Self::PositionalOnly),
20-
"any" => Some(Self::PositionalOrKeyword),
21-
"named" => Some(Self::KeywordOnly),
22-
"flatten" => Some(Self::Flatten),
23-
_ => None,
24-
}
18+
impl TryFrom<&Ident> for ParameterKind {
19+
type Error = ();
20+
21+
fn try_from(ident: &Ident) -> std::result::Result<Self, Self::Error> {
22+
Ok(match ident.to_string().as_str() {
23+
"positional" => Self::PositionalOnly,
24+
"any" => Self::PositionalOrKeyword,
25+
"named" => Self::KeywordOnly,
26+
"flatten" => Self::Flatten,
27+
_ => return Err(()),
28+
})
2529
}
2630
}
2731

32+
// None == quote!(Default::default())
33+
type DefaultValue = Option<Expr>;
34+
35+
#[derive(Default)]
2836
struct ArgAttribute {
2937
name: Option<String>,
3038
kind: ParameterKind,
3139
default: Option<DefaultValue>,
3240
}
33-
// None == quote!(Default::default())
34-
type DefaultValue = Option<Expr>;
3541

3642
impl ArgAttribute {
3743
fn from_attribute(attr: &Attribute) -> Option<Result<Self>> {
3844
if !attr.path().is_ident("pyarg") {
3945
return None;
4046
}
47+
4148
let inner = move || {
4249
let mut arg_attr = None;
4350
attr.parse_nested_meta(|meta| {
4451
let Some(arg_attr) = &mut arg_attr else {
4552
let kind = meta
4653
.path
4754
.get_ident()
48-
.and_then(ParameterKind::from_ident)
55+
.and_then(|ident| ParameterKind::try_from(ident).ok())
4956
.ok_or_else(|| {
5057
meta.error(
5158
"The first argument to #[pyarg()] must be the parameter type, \
@@ -95,47 +102,54 @@ impl ArgAttribute {
95102
}
96103
}
97104

98-
fn generate_field((i, field): (usize, &Field)) -> Result<TokenStream> {
99-
let mut pyarg_attrs = field
100-
.attrs
101-
.iter()
102-
.filter_map(ArgAttribute::from_attribute)
103-
.collect::<std::result::Result<Vec<_>, _>>()?;
104-
let attr = if pyarg_attrs.is_empty() {
105-
ArgAttribute {
106-
name: None,
107-
kind: ParameterKind::PositionalOrKeyword,
108-
default: None,
109-
}
110-
} else if pyarg_attrs.len() == 1 {
111-
pyarg_attrs.remove(0)
112-
} else {
113-
bail_span!(field, "Multiple pyarg attributes on field");
114-
};
105+
impl TryFrom<&Field> for ArgAttribute {
106+
type Error = syn::Error;
107+
108+
fn try_from(field: &Field) -> std::result::Result<Self, Self::Error> {
109+
let mut pyarg_attrs = field
110+
.attrs
111+
.iter()
112+
.filter_map(Self::from_attribute)
113+
.collect::<std::result::Result<Vec<_>, _>>()?;
114+
115+
if pyarg_attrs.len() >= 2 {
116+
bail_span!(field, "Multiple pyarg attributes on field")
117+
};
115118

119+
Ok(pyarg_attrs.pop().unwrap_or_default())
120+
}
121+
}
122+
123+
fn generate_field((i, field): (usize, &Field)) -> Result<TokenStream> {
124+
let attr = ArgAttribute::try_from(field)?;
116125
let name = field.ident.as_ref();
117126
let name_string = name.map(|ident| ident.unraw().to_string());
118127
if matches!(&name_string, Some(s) if s.starts_with("_phantom")) {
119128
return Ok(quote! {
120129
#name: ::std::marker::PhantomData,
121130
});
122131
}
132+
123133
let field_name = match name {
124134
Some(id) => id.to_token_stream(),
125135
None => syn::Index::from(i).into_token_stream(),
126136
};
137+
127138
if let ParameterKind::Flatten = attr.kind {
128139
return Ok(quote! {
129140
#field_name: ::rustpython_vm::function::FromArgs::from_args(vm, args)?,
130141
});
131142
}
143+
132144
let pyname = attr
133145
.name
134146
.or(name_string)
135147
.ok_or_else(|| err_span!(field, "field in tuple struct must have name attribute"))?;
148+
136149
let middle = quote! {
137150
.map(|x| ::rustpython_vm::convert::TryFromObject::try_from_object(vm, x)).transpose()?
138151
};
152+
139153
let ending = if let Some(default) = attr.default {
140154
let ty = &field.ty;
141155
let default = default.unwrap_or_else(|| parse_quote!(::std::default::Default::default()));
@@ -159,46 +173,62 @@ fn generate_field((i, field): (usize, &Field)) -> Result<TokenStream> {
159173
};
160174

161175
let file_output = match attr.kind {
162-
ParameterKind::PositionalOnly => {
163-
quote! {
164-
#field_name: args.take_positional()#middle #ending,
165-
}
166-
}
167-
ParameterKind::PositionalOrKeyword => {
168-
quote! {
169-
#field_name: args.take_positional_keyword(#pyname)#middle #ending,
170-
}
171-
}
172-
ParameterKind::KeywordOnly => {
173-
quote! {
174-
#field_name: args.take_keyword(#pyname)#middle #ending,
175-
}
176-
}
176+
ParameterKind::PositionalOnly => quote! {
177+
#field_name: args.take_positional()#middle #ending,
178+
},
179+
ParameterKind::PositionalOrKeyword => quote! {
180+
#field_name: args.take_positional_keyword(#pyname)#middle #ending,
181+
},
182+
ParameterKind::KeywordOnly => quote! {
183+
#field_name: args.take_keyword(#pyname)#middle #ending,
184+
},
177185
ParameterKind::Flatten => unreachable!(),
178186
};
187+
179188
Ok(file_output)
180189
}
181190

191+
fn compute_arity_bounds(field_attrs: &[ArgAttribute]) -> (usize, usize) {
192+
let positional_fields = field_attrs.iter().filter(|attr| {
193+
matches!(
194+
attr.kind,
195+
ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword
196+
)
197+
});
198+
199+
let min_arity = positional_fields
200+
.clone()
201+
.filter(|attr| attr.default.is_none())
202+
.count();
203+
let max_arity = positional_fields.count();
204+
205+
(min_arity, max_arity)
206+
}
207+
182208
pub fn impl_from_args(input: DeriveInput) -> Result<TokenStream> {
183-
// TODO: Set lower arity bound dynamicly
184-
let (fields, arity) = match input.data {
209+
let (fields, field_attrs) = match input.data {
185210
Data::Struct(syn::DataStruct { fields, .. }) => (
186211
fields
187212
.iter()
188213
.enumerate()
189214
.map(generate_field)
190215
.collect::<Result<TokenStream>>()?,
191-
fields.len(),
216+
fields
217+
.iter()
218+
.filter_map(|field| field.try_into().ok())
219+
.collect::<Vec<ArgAttribute>>(),
192220
),
193221
_ => bail_span!(input, "FromArgs input must be a struct"),
194222
};
195223

224+
let (min_arity, max_arity) = compute_arity_bounds(&field_attrs);
225+
196226
let name = input.ident;
197227
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
198228
let output = quote! {
199229
impl #impl_generics ::rustpython_vm::function::FromArgs for #name #ty_generics #where_clause {
200230
fn arity() -> ::std::ops::RangeInclusive<usize> {
201-
0..=#arity
231+
#min_arity..=#max_arity
202232
}
203233

204234
fn from_args(

0 commit comments

Comments
 (0)