11use crate :: attributes:: { DefaultAttribute , FromPyWithAttribute , RenamingRule } ;
22use crate :: derive_attributes:: { ContainerAttributes , FieldAttributes , FieldGetter } ;
3+ #[ cfg( feature = "experimental-inspect" ) ]
4+ use crate :: introspection:: ConcatenationBuilder ;
5+ #[ cfg( feature = "experimental-inspect" ) ]
6+ use crate :: utils:: TypeExt ;
37use crate :: utils:: { self , Ctx } ;
48use proc_macro2:: TokenStream ;
59use quote:: { format_ident, quote, quote_spanned, ToTokens } ;
@@ -96,17 +100,29 @@ impl<'a> Enum<'a> {
96100 )
97101 )
98102 }
103+
104+ #[ cfg( feature = "experimental-inspect" ) ]
105+ fn write_input_type ( & self , builder : & mut ConcatenationBuilder , ctx : & Ctx ) {
106+ for ( i, var) in self . variants . iter ( ) . enumerate ( ) {
107+ if i > 0 {
108+ builder. push_str ( " | " ) ;
109+ }
110+ var. write_input_type ( builder, ctx) ;
111+ }
112+ }
99113}
100114
101115struct NamedStructField < ' a > {
102116 ident : & ' a syn:: Ident ,
103117 getter : Option < FieldGetter > ,
104118 from_py_with : Option < FromPyWithAttribute > ,
105119 default : Option < DefaultAttribute > ,
120+ ty : & ' a syn:: Type ,
106121}
107122
108123struct TupleStructField {
109124 from_py_with : Option < FromPyWithAttribute > ,
125+ ty : syn:: Type ,
110126}
111127
112128/// Container Style
@@ -120,7 +136,8 @@ enum ContainerType<'a> {
120136 /// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }`
121137 ///
122138 /// The field specified by the identifier is extracted directly from the object.
123- StructNewtype ( & ' a syn:: Ident , Option < FromPyWithAttribute > ) ,
139+ #[ cfg_attr( not( feature = "experimental-inspect" ) , allow( unused) ) ]
140+ StructNewtype ( & ' a syn:: Ident , Option < FromPyWithAttribute > , & ' a syn:: Type ) ,
124141 /// Tuple struct, e.g. `struct Foo(String)`.
125142 ///
126143 /// Variant contains a list of conversion methods for each of the fields that are directly
@@ -129,7 +146,8 @@ enum ContainerType<'a> {
129146 /// Tuple newtype, e.g. `#[transparent] struct Foo(String)`
130147 ///
131148 /// The wrapped field is directly extracted from the object.
132- TupleNewtype ( Option < FromPyWithAttribute > ) ,
149+ #[ cfg_attr( not( feature = "experimental-inspect" ) , allow( unused) ) ]
150+ TupleNewtype ( Option < FromPyWithAttribute > , Box < syn:: Type > ) ,
133151}
134152
135153/// Data container
@@ -168,6 +186,7 @@ impl<'a> Container<'a> {
168186 ) ;
169187 Ok ( TupleStructField {
170188 from_py_with : attrs. from_py_with ,
189+ ty : field. ty . clone ( ) ,
171190 } )
172191 } )
173192 . collect :: < Result < Vec < _ > > > ( ) ?;
@@ -176,7 +195,7 @@ impl<'a> Container<'a> {
176195 // Always treat a 1-length tuple struct as "transparent", even without the
177196 // explicit annotation.
178197 let field = tuple_fields. pop ( ) . unwrap ( ) ;
179- ContainerType :: TupleNewtype ( field. from_py_with )
198+ ContainerType :: TupleNewtype ( field. from_py_with , Box :: new ( field . ty ) )
180199 } else if options. transparent . is_some ( ) {
181200 bail_spanned ! (
182201 fields. span( ) => "transparent structs and variants can only have 1 field"
@@ -216,6 +235,7 @@ impl<'a> Container<'a> {
216235 getter : attrs. getter ,
217236 from_py_with : attrs. from_py_with ,
218237 default : attrs. default ,
238+ ty : & field. ty ,
219239 } )
220240 } )
221241 . collect :: < Result < Vec < _ > > > ( ) ?;
@@ -237,7 +257,7 @@ impl<'a> Container<'a> {
237257 field. getter. is_none( ) ,
238258 field. ident. span( ) => "`transparent` structs may not have a `getter` for the inner field"
239259 ) ;
240- ContainerType :: StructNewtype ( field. ident , field. from_py_with )
260+ ContainerType :: StructNewtype ( field. ident , field. from_py_with , field . ty )
241261 } else {
242262 ContainerType :: Struct ( struct_fields)
243263 }
@@ -274,10 +294,10 @@ impl<'a> Container<'a> {
274294 /// Build derivation body for a struct.
275295 fn build ( & self , ctx : & Ctx ) -> TokenStream {
276296 match & self . ty {
277- ContainerType :: StructNewtype ( ident, from_py_with) => {
297+ ContainerType :: StructNewtype ( ident, from_py_with, _ ) => {
278298 self . build_newtype_struct ( Some ( ident) , from_py_with, ctx)
279299 }
280- ContainerType :: TupleNewtype ( from_py_with) => {
300+ ContainerType :: TupleNewtype ( from_py_with, _ ) => {
281301 self . build_newtype_struct ( None , from_py_with, ctx)
282302 }
283303 ContainerType :: Tuple ( tups) => self . build_tuple_struct ( tups, ctx) ,
@@ -438,6 +458,51 @@ impl<'a> Container<'a> {
438458
439459 quote ! ( :: std:: result:: Result :: Ok ( #self_ty{ #fields} ) )
440460 }
461+
462+ #[ cfg( feature = "experimental-inspect" ) ]
463+ fn write_input_type ( & self , builder : & mut ConcatenationBuilder , ctx : & Ctx ) {
464+ match & self . ty {
465+ ContainerType :: StructNewtype ( _, from_py_with, ty) => {
466+ Self :: write_field_input_type ( from_py_with, ty, builder, ctx) ;
467+ }
468+ ContainerType :: TupleNewtype ( from_py_with, ty) => {
469+ Self :: write_field_input_type ( from_py_with, ty, builder, ctx) ;
470+ }
471+ ContainerType :: Tuple ( tups) => {
472+ builder. push_str ( "tuple[" ) ;
473+ for ( i, TupleStructField { from_py_with, ty } ) in tups. iter ( ) . enumerate ( ) {
474+ if i > 0 {
475+ builder. push_str ( ", " ) ;
476+ }
477+ Self :: write_field_input_type ( from_py_with, ty, builder, ctx) ;
478+ }
479+ builder. push_str ( "]" ) ;
480+ }
481+ ContainerType :: Struct ( _) => {
482+ // TODO: implement using a Protocol?
483+ builder. push_str ( "_typeshed.Incomplete" )
484+ }
485+ }
486+ }
487+
488+ #[ cfg( feature = "experimental-inspect" ) ]
489+ fn write_field_input_type (
490+ from_py_with : & Option < FromPyWithAttribute > ,
491+ ty : & syn:: Type ,
492+ builder : & mut ConcatenationBuilder ,
493+ ctx : & Ctx ,
494+ ) {
495+ if from_py_with. is_some ( ) {
496+ // We don't know what from_py_with is doing
497+ builder. push_str ( "_typeshed.Incomplete" )
498+ } else {
499+ let ty = ty. clone ( ) . elide_lifetimes ( ) ;
500+ let pyo3_crate_path = & ctx. pyo3_path ;
501+ builder. push_tokens (
502+ quote ! { <#ty as #pyo3_crate_path:: FromPyObject <' _>>:: INPUT_TYPE . as_bytes( ) } ,
503+ )
504+ }
505+ }
441506}
442507
443508fn verify_and_get_lifetime ( generics : & syn:: Generics ) -> Result < Option < & syn:: LifetimeParam > > {
@@ -487,29 +552,64 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
487552 bail_spanned ! ( tokens. span( ) => "`transparent` or `annotation` is not supported \
488553 at top level for enums") ;
489554 }
490- let en = Enum :: new ( en, & tokens. ident , options) ?;
555+ let en = Enum :: new ( en, & tokens. ident , options. clone ( ) ) ?;
491556 en. build ( ctx)
492557 }
493558 syn:: Data :: Struct ( st) => {
494559 if let Some ( lit_str) = & options. annotation {
495560 bail_spanned ! ( lit_str. span( ) => "`annotation` is unsupported for structs" ) ;
496561 }
497562 let ident = & tokens. ident ;
498- let st = Container :: new ( & st. fields , parse_quote ! ( #ident) , options) ?;
563+ let st = Container :: new ( & st. fields , parse_quote ! ( #ident) , options. clone ( ) ) ?;
499564 st. build ( ctx)
500565 }
501566 syn:: Data :: Union ( _) => bail_spanned ! (
502567 tokens. span( ) => "#[derive(FromPyObject)] is not supported for unions"
503568 ) ,
504569 } ;
505570
571+ #[ cfg( feature = "experimental-inspect" ) ]
572+ let input_type = {
573+ let mut builder = ConcatenationBuilder :: default ( ) ;
574+ if tokens
575+ . generics
576+ . params
577+ . iter ( )
578+ . all ( |p| matches ! ( p, syn:: GenericParam :: Lifetime ( _) ) )
579+ {
580+ match & tokens. data {
581+ syn:: Data :: Enum ( en) => {
582+ Enum :: new ( en, & tokens. ident , options) ?. write_input_type ( & mut builder, ctx)
583+ }
584+ syn:: Data :: Struct ( st) => {
585+ let ident = & tokens. ident ;
586+ Container :: new ( & st. fields , parse_quote ! ( #ident) , options. clone ( ) ) ?
587+ . write_input_type ( & mut builder, ctx)
588+ }
589+ syn:: Data :: Union ( _) => {
590+ // Not supported at this point
591+ builder. push_str ( "_typeshed.Incomplete" )
592+ }
593+ }
594+ } else {
595+ // We don't know how to deal with generic parameters
596+ // Blocked by https://github.com/rust-lang/rust/issues/76560
597+ builder. push_str ( "_typeshed.Incomplete" )
598+ } ;
599+ let input_type = builder. into_token_stream ( & ctx. pyo3_path ) ;
600+ quote ! { const INPUT_TYPE : & ' static str = unsafe { :: std:: str :: from_utf8_unchecked( #input_type) } ; }
601+ } ;
602+ #[ cfg( not( feature = "experimental-inspect" ) ) ]
603+ let input_type = quote ! { } ;
604+
506605 let ident = & tokens. ident ;
507606 Ok ( quote ! (
508607 #[ automatically_derived]
509608 impl #impl_generics #pyo3_path:: FromPyObject <#lt_param> for #ident #ty_generics #where_clause {
510609 fn extract_bound( obj: & #pyo3_path:: Bound <#lt_param, #pyo3_path:: PyAny >) -> #pyo3_path:: PyResult <Self > {
511610 #derives
512611 }
612+ #input_type
513613 }
514614 ) )
515615}
0 commit comments