@@ -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 ) ]
910enum 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 ) ]
2836struct ArgAttribute {
2937 name : Option < String > ,
3038 kind : ParameterKind ,
3139 default : Option < DefaultValue > ,
3240}
33- // None == quote!(Default::default())
34- type DefaultValue = Option < Expr > ;
3541
3642impl 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+
182208pub 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