1+ use quote:: ToTokens ;
12use stellar_xdr:: curr as stellar_xdr;
23use stellar_xdr:: {
34 ScSpecTypeBytesN , ScSpecTypeDef , ScSpecTypeMap , ScSpecTypeOption , ScSpecTypeResult ,
45 ScSpecTypeTuple , ScSpecTypeUdt , ScSpecTypeVec ,
56} ;
6- use syn:: TypeReference ;
77use syn:: {
8- spanned:: Spanned , Error , Expr , ExprLit , GenericArgument , Lit , Path , PathArguments , PathSegment ,
9- Type , TypePath , TypeTuple ,
8+ spanned:: Spanned , Error , Expr , ExprLit , GenericArgument , Ident , Lit , Path , PathArguments ,
9+ PathSegment , Type , TypePath , TypeTuple ,
1010} ;
11+ use syn:: { Generics , TypeReference } ;
1112
1213// These constants' values must match the definitions of the constants with the
1314// same names in soroban_sdk::crypto::bls12_381.
@@ -21,6 +22,56 @@ pub const BN254_FP_SERIALIZED_SIZE: u32 = 32;
2122pub const BN254_G1_SERIALIZED_SIZE : u32 = BN254_FP_SERIALIZED_SIZE * 2 ; // 64
2223pub const BN254_G2_SERIALIZED_SIZE : u32 = BN254_G1_SERIALIZED_SIZE * 2 ; // 128
2324
25+ /// Checks if an `ident` and `generics` input type maps to a user-defined type (UDT).
26+ ///
27+ /// Returns Ok if the input will be parsed as a UDT, and returns an Err with a message if not.
28+ ///
29+ /// When users defined types like with `#[contracttype]`, the type name must map to a UDT.
30+ /// Otherwise, the type might get mapped to a built-in soroban_sdk type instead.
31+ ///
32+ /// ### Errors
33+ /// - If `ident` cannot be parsed as a Rust type
34+ /// - If `ident` cannot be mapped to a type with [map_type]
35+ /// - If the type mapped from `ident` is not a UDT
36+ /// - If `generics` has any parameters, as UDTs don't support generics
37+ pub fn is_mapped_type_udt ( ident : & Ident , generics : & Generics ) -> Result < ( ) , Error > {
38+ let name = ident. to_string ( ) ;
39+ let ty: Type = syn:: parse_str ( & name) . map_err ( |e| {
40+ Error :: new (
41+ ident. span ( ) ,
42+ format ! ( "type `{}` cannot be used in XDR spec: {}" , ident, e) ,
43+ )
44+ } ) ?;
45+ match map_type ( & ty, false , false ) {
46+ Ok ( ScSpecTypeDef :: Udt ( _) ) => {
47+ // `ty` does not contain the generics, so check manually here
48+ if generics. params . len ( ) > 0 {
49+ Err ( Error :: new (
50+ ident. span ( ) ,
51+ format ! ( "type `{}` contains generics `{}`, which are not supported for user-defined types" , ident, generics. params. to_token_stream( ) ) ,
52+ ) )
53+ } else {
54+ Ok ( ( ) )
55+ }
56+ }
57+ _ => {
58+ // Check if the error originated from the UDT-arm of `map_type`
59+ let _ = ScSpecTypeDef :: Udt ( ScSpecTypeUdt {
60+ name : name. try_into ( ) . map_err ( |e| {
61+ Error :: new (
62+ ident. span ( ) ,
63+ format ! ( "type `{}` cannot be used in XDR spec: {}" , ident, e) ,
64+ )
65+ } ) ?,
66+ } ) ;
67+ Err ( Error :: new (
68+ ident. span ( ) ,
69+ format ! ( "type `{}` conflicts with a soroban_sdk type and cannot be used as a user-defined type" , ident) ,
70+ ) )
71+ }
72+ }
73+ }
74+
2475#[ allow( clippy:: too_many_lines) ]
2576pub fn map_type ( t : & Type , allow_ref : bool , allow_hash : bool ) -> Result < ScSpecTypeDef , Error > {
2677 match t {
@@ -58,6 +109,11 @@ pub fn map_type(t: &Type, allow_ref: bool, allow_hash: bool) -> Result<ScSpecTyp
58109 "MuxedAddress" => Ok ( ScSpecTypeDef :: MuxedAddress ) ,
59110 "Timepoint" => Ok ( ScSpecTypeDef :: Timepoint ) ,
60111 "Duration" => Ok ( ScSpecTypeDef :: Duration ) ,
112+ // Check if types that require generics are being used without any path arguments
113+ "Result" | "Option" | "Vec" | "Map" | "BytesN" | "Hash" => Err ( Error :: new (
114+ ident. span ( ) ,
115+ format ! ( "type {} requires generic arguments" , ident) ,
116+ ) ) ,
61117 // The BLS and BN types defined below are represented in the contract's
62118 // interface by their underlying data types, i.e.
63119 // Fp/Fp2/G1Affine/G2Affine => BytesN<N>, Fr => U256. This approach
@@ -119,7 +175,7 @@ pub fn map_type(t: &Type, allow_ref: bool, allow_hash: bool) -> Result<ScSpecTyp
119175 name : s. try_into ( ) . map_err ( |e| {
120176 Error :: new (
121177 t. span ( ) ,
122- format ! ( "Udt name {:?} cannot be used in XDR spec: {}" , s, e) ,
178+ format ! ( "type `{}` cannot be used in XDR spec: {}" , s, e) ,
123179 )
124180 } ) ?,
125181 } ) ) ,
@@ -244,7 +300,7 @@ pub fn map_type(t: &Type, allow_ref: bool, allow_hash: bool) -> Result<ScSpecTyp
244300#[ cfg( test) ]
245301mod test {
246302 use super :: * ;
247- use syn:: parse_quote;
303+ use syn:: { parse_quote, DeriveInput } ;
248304
249305 #[ test]
250306 fn test_path ( ) {
@@ -299,4 +355,118 @@ mod test {
299355 } ) )
300356 ) ;
301357 }
358+
359+ #[ test]
360+ fn test_generic_type ( ) {
361+ let ty: Type = parse_quote ! ( Vec <u32 >) ;
362+ let res = map_type ( & ty, false , false ) ;
363+ assert_eq ! (
364+ res. unwrap( ) ,
365+ ScSpecTypeDef :: Vec ( Box :: new( ScSpecTypeVec {
366+ element_type: Box :: new( ScSpecTypeDef :: U32 ) ,
367+ } ) )
368+ ) ;
369+ }
370+
371+ #[ test]
372+ fn test_generic_type_multiple_params ( ) {
373+ let ty: Type = parse_quote ! ( Result <u32 , i64 >) ;
374+ let res = map_type ( & ty, false , false ) ;
375+ assert_eq ! (
376+ res. unwrap( ) ,
377+ ScSpecTypeDef :: Result ( Box :: new( ScSpecTypeResult {
378+ ok_type: Box :: new( ScSpecTypeDef :: U32 ) ,
379+ error_type: Box :: new( ScSpecTypeDef :: I64 ) ,
380+ } ) )
381+ ) ;
382+ }
383+
384+ #[ test]
385+ fn test_generic_type_without_params_errors ( ) {
386+ let ty: Type = parse_quote ! ( Vec ) ;
387+ assert ! ( map_type( & ty, false , false ) . is_err( ) ) ;
388+ }
389+
390+ #[ test]
391+ fn test_generic_type_incorrect_params_errors ( ) {
392+ let ty: Type = parse_quote ! ( Result <u32 >) ;
393+ assert ! ( map_type( & ty, false , false ) . is_err( ) ) ;
394+ }
395+
396+ #[ test]
397+ fn test_is_mapped_type_udt_sdk_type_errors ( ) {
398+ let input: DeriveInput = parse_quote ! (
399+ struct Address {
400+ pub key: [ u8 ; 32 ] ,
401+ }
402+ ) ;
403+ let err = is_mapped_type_udt ( & input. ident , & input. generics ) . unwrap_err ( ) ;
404+ assert_eq ! (
405+ err. to_string( ) ,
406+ "type `Address` conflicts with a soroban_sdk type and cannot be used as a user-defined type"
407+ ) ;
408+ }
409+
410+ #[ test]
411+ fn test_is_mapped_type_udt_unique_generic_type_errors ( ) {
412+ let input: DeriveInput = parse_quote ! (
413+ struct GenericType <A , B > {
414+ pub key: T ,
415+ }
416+ ) ;
417+ let err = is_mapped_type_udt ( & input. ident , & input. generics ) . unwrap_err ( ) ;
418+ assert_eq ! ( err. to_string( ) , "type `GenericType` contains generics `A , B`, which are not supported for user-defined types" ) ;
419+ }
420+
421+ #[ test]
422+ fn test_is_mapped_type_udt_sdk_generic_type_errors ( ) {
423+ let input: DeriveInput = parse_quote ! (
424+ struct BytesN <T > {
425+ pub key: T ,
426+ }
427+ ) ;
428+ let err = is_mapped_type_udt ( & input. ident , & input. generics ) . unwrap_err ( ) ;
429+ assert_eq ! (
430+ err. to_string( ) ,
431+ "type `BytesN` conflicts with a soroban_sdk type and cannot be used as a user-defined type"
432+ ) ;
433+ }
434+
435+ #[ test]
436+ fn test_is_mapped_type_udt_sdk_generic_no_params_errors ( ) {
437+ let input: DeriveInput = parse_quote ! (
438+ struct BytesN {
439+ pub key: [ u8 ; 32 ] ,
440+ }
441+ ) ;
442+ let err = is_mapped_type_udt ( & input. ident , & input. generics ) . unwrap_err ( ) ;
443+ assert_eq ! (
444+ err. to_string( ) ,
445+ "type `BytesN` conflicts with a soroban_sdk type and cannot be used as a user-defined type"
446+ ) ;
447+ }
448+
449+ #[ test]
450+ fn test_is_mapped_type_udt_unique_xdr_error ( ) {
451+ let input: DeriveInput = parse_quote ! (
452+ struct MyTypeIsOverSixtyCharactersLongAndShouldFailToCompileDueToThat {
453+ pub key: [ u8 ; 32 ] ,
454+ }
455+ ) ;
456+ let err = is_mapped_type_udt ( & input. ident , & input. generics ) . unwrap_err ( ) ;
457+ assert_eq ! (
458+ err. to_string( ) ,
459+ "type `MyTypeIsOverSixtyCharactersLongAndShouldFailToCompileDueToThat` cannot be used in XDR spec: xdr value max length exceeded"
460+ ) ;
461+ }
462+
463+ #[ test]
464+ fn test_is_mapped_type_udt_unique_ok ( ) {
465+ let input: DeriveInput = parse_quote ! (
466+ struct MyType {
467+ pub key: [ u8 ; 32 ] ,
468+ }
469+ ) ;
470+ assert ! ( is_mapped_type_udt( & input. ident, & input. generics) . is_ok( ) ) ;
471+ }
302472}
0 commit comments