Skip to content

Commit 86a46d8

Browse files
committed
chore: improve compiler error messages and docs for contracttype udt check
1 parent b68f537 commit 86a46d8

File tree

2 files changed

+79
-38
lines changed

2 files changed

+79
-38
lines changed

soroban-sdk-macros/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use derive_trait::derive_trait;
4040

4141
use darling::{ast::NestedMeta, FromMeta};
4242
use macro_string::MacroString;
43-
use map_type::is_input_type_spec_safe;
43+
use map_type::is_mapped_type_udt;
4444
use proc_macro::TokenStream;
4545
use proc_macro2::{Span, TokenStream as TokenStream2};
4646
use quote::{format_ident, quote, ToTokens};
@@ -422,7 +422,7 @@ pub fn contracttype(metadata: TokenStream, input: TokenStream) -> TokenStream {
422422
let vis = &input.vis;
423423
let ident = &input.ident;
424424
let attrs = &input.attrs;
425-
match is_input_type_spec_safe(ident, &input.generics) {
425+
match is_mapped_type_udt(ident, &input.generics) {
426426
Ok(()) => {}
427427
Err(e) => return e.to_compile_error().into(),
428428
}

soroban-sdk-macros/src/map_type.rs

Lines changed: 77 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use quote::ToTokens;
12
use stellar_xdr::curr as stellar_xdr;
23
use stellar_xdr::{
34
ScSpecTypeBytesN, ScSpecTypeDef, ScSpecTypeMap, ScSpecTypeOption, ScSpecTypeResult,
@@ -21,36 +22,53 @@ pub const BN254_FP_SERIALIZED_SIZE: u32 = 32;
2122
pub const BN254_G1_SERIALIZED_SIZE: u32 = BN254_FP_SERIALIZED_SIZE * 2; // 64
2223
pub const BN254_G2_SERIALIZED_SIZE: u32 = BN254_G1_SERIALIZED_SIZE * 2; // 128
2324

24-
/// Checks if a type corresponds to a user-defined type (UDT) that can be used in the XDR spec safely.
25-
/// Returns without error if the type is OK to use as a UDT spec name.
25+
/// Checks if an `ident` and `generics` input type maps to a user-defined type (UDT).
2626
///
27-
/// This function is used to check if a user-defined type input maps to a UDT. Otherwise,
28-
/// the identifier risks collision with a built-in spec type of the same name.
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.
2931
///
3032
/// ### Errors
31-
/// - If `generics` has any parameters, as UDTs don't support generics
3233
/// - If `ident` cannot be parsed as a Rust type
33-
/// - If `ident` cannot be mapped to a type with `map_type`
34+
/// - If `ident` cannot be mapped to a type with [map_type]
3435
/// - If the type mapped from `ident` is not a UDT
35-
pub fn is_input_type_spec_safe(ident: &Ident, generics: &Generics) -> Result<(), Error> {
36-
if generics.params.len() > 0 {
37-
return Err(Error::new(
38-
ident.span(),
39-
"generics unsupported on user-defined types",
40-
));
41-
}
42-
let ty: Type = syn::parse_str(&ident.to_string()).map_err(|e| {
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| {
4340
Error::new(
4441
ident.span(),
45-
format!("type name {} cannot be used in XDR spec: {}", ident, e),
42+
format!("type `{}` cannot be used in XDR spec: {}", ident, e),
4643
)
4744
})?;
4845
match map_type(&ty, false, false) {
49-
Ok(ScSpecTypeDef::Udt(_)) => Ok(()),
50-
_ => Err(Error::new(
51-
ident.span(),
52-
format!("type name `{}` conflicts with a soroban_sdk type", ident),
53-
)),
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+
}
5472
}
5573
}
5674

@@ -157,7 +175,7 @@ pub fn map_type(t: &Type, allow_ref: bool, allow_hash: bool) -> Result<ScSpecTyp
157175
name: s.try_into().map_err(|e| {
158176
Error::new(
159177
t.span(),
160-
format!("Udt name {:?} cannot be used in XDR spec: {}", s, e),
178+
format!("type `{}` cannot be used in XDR spec: {}", s, e),
161179
)
162180
})?,
163181
})),
@@ -376,56 +394,79 @@ mod test {
376394
}
377395

378396
#[test]
379-
fn test_is_input_type_spec_safe_non_udt_errors() {
397+
fn test_is_mapped_type_udt_sdk_type_errors() {
380398
let input: DeriveInput = parse_quote!(
381399
struct Address {
382400
pub key: [u8; 32],
383401
}
384402
);
385-
is_input_type_spec_safe(&input.ident, &input.generics)
386-
.expect_err("type name `Address` conflicts with a soroban_sdk type");
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+
);
387408
}
388409

389410
#[test]
390-
fn test_is_input_type_spec_safe_unique_generic_type_errors() {
411+
fn test_is_mapped_type_udt_unique_generic_type_errors() {
391412
let input: DeriveInput = parse_quote!(
392-
struct GenericType<T> {
413+
struct GenericType<A, B> {
393414
pub key: T,
394415
}
395416
);
396-
is_input_type_spec_safe(&input.ident, &input.generics)
397-
.expect_err("generics unsupported on user-defined types");
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");
398419
}
399420

400421
#[test]
401-
fn test_is_input_type_spec_safe_generic_type_errors() {
422+
fn test_is_mapped_type_udt_sdk_generic_type_errors() {
402423
let input: DeriveInput = parse_quote!(
403424
struct BytesN<T> {
404425
pub key: T,
405426
}
406427
);
407-
is_input_type_spec_safe(&input.ident, &input.generics)
408-
.expect_err("generics unsupported on user-defined types");
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+
);
409433
}
410434

411435
#[test]
412-
fn test_is_input_type_spec_safe_generic_no_params_errors() {
436+
fn test_is_mapped_type_udt_sdk_generic_no_params_errors() {
413437
let input: DeriveInput = parse_quote!(
414438
struct BytesN {
415439
pub key: [u8; 32],
416440
}
417441
);
418-
is_input_type_spec_safe(&input.ident, &input.generics)
419-
.expect_err("type name `BytesN` conflicts with a soroban_sdk type");
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+
);
420461
}
421462

422463
#[test]
423-
fn test_is_input_type_spec_safe_unique_ok() {
464+
fn test_is_mapped_type_udt_unique_ok() {
424465
let input: DeriveInput = parse_quote!(
425466
struct MyType {
426467
pub key: [u8; 32],
427468
}
428469
);
429-
assert!(is_input_type_spec_safe(&input.ident, &input.generics).is_ok());
470+
assert!(is_mapped_type_udt(&input.ident, &input.generics).is_ok());
430471
}
431472
}

0 commit comments

Comments
 (0)