Skip to main content

calimero_wasm_abi/
normalize.rs

1use syn::{GenericArgument, Type, TypePath};
2
3use crate::schema::{CollectionType, CrdtCollectionType, ScalarType, TypeRef};
4
5/// Error types for type normalization
6#[derive(Debug, thiserror::Error)]
7pub enum NormalizeError {
8    #[error("type path error: {0}")]
9    TypePathError(String),
10    #[error("unsupported map key type: {0}")]
11    UnsupportedMapKey(String),
12    #[error("unsupported array element type: {0}")]
13    UnsupportedArrayElement(String),
14}
15
16/// Resolved local type information
17#[derive(Debug, Clone, Copy)]
18pub enum ResolvedLocal {
19    /// Newtype bytes wrapper (e.g., struct UserId([u8;32]))
20    NewtypeBytes { size: usize },
21    /// Record struct (shape filled elsewhere)
22    Record,
23    /// Variant enum (shape filled elsewhere)
24    Variant,
25}
26
27/// Trait for resolving local type names
28pub trait TypeResolver {
29    fn resolve_local(&self, name: &str) -> Option<ResolvedLocal>;
30}
31
32/// Normalize a Rust type to an ABI TypeRef
33pub fn normalize_type(
34    ty: &Type,
35    wasm32: bool,
36    resolver: &dyn TypeResolver,
37) -> Result<TypeRef, NormalizeError> {
38    match ty {
39        Type::Path(type_path) => normalize_path_type(type_path, wasm32, resolver),
40        Type::Reference(type_ref) => {
41            // Strip references and lifetimes
42            normalize_type(&type_ref.elem, wasm32, resolver)
43        }
44        Type::Slice(type_slice) => {
45            // [T] -> list<T>
46            let item_type = normalize_type(&type_slice.elem, wasm32, resolver)?;
47            Ok(TypeRef::list(item_type))
48        }
49        Type::Array(type_array) => {
50            // [T; N] -> list<T> or bytes{size:N} for [u8; N]
51            let elem_type = &*type_array.elem;
52            let len = &type_array.len;
53
54            // Check if it's [u8; N]
55            if let Type::Path(TypePath { path, .. }) = elem_type {
56                eprintln!("Checking if array element is u8");
57                if is_u8_type(path) {
58                    eprintln!("Array element is u8, extracting length");
59                    let size = extract_array_len(len)?;
60                    eprintln!("Array size: {size}");
61                    return Ok(TypeRef::bytes_with_size(size, None));
62                }
63            }
64
65            // Otherwise, treat as list
66            let item_type = normalize_type(elem_type, wasm32, resolver)?;
67            Ok(TypeRef::list(item_type))
68        }
69        Type::Tuple(type_tuple) => {
70            // () -> unit
71            if type_tuple.elems.is_empty() {
72                Ok(TypeRef::unit())
73            } else {
74                Err(NormalizeError::TypePathError(
75                    "unsupported tuple".to_owned(),
76                ))
77            }
78        }
79        _ => Err(NormalizeError::TypePathError("unsupported type".to_owned())),
80    }
81}
82
83/// Normalize a path type (e.g., Option<T>, Vec<T>, etc.)
84fn normalize_path_type(
85    type_path: &TypePath,
86    wasm32: bool,
87    resolver: &dyn TypeResolver,
88) -> Result<TypeRef, NormalizeError> {
89    let path = &type_path.path;
90
91    eprintln!("Path segments: {}", path.segments.len());
92    for (i, seg) in path.segments.iter().enumerate() {
93        eprintln!("  Segment {}: {}", i, seg.ident);
94    }
95
96    if path.segments.len() == 1 {
97        let segment = &path.segments[0];
98        let ident = &segment.ident;
99
100        eprintln!(
101            "Processing path type: {} with {} segments",
102            ident,
103            path.segments.len()
104        );
105
106        // Handle generic types
107        if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
108            eprintln!("Found angle-bracketed arguments");
109            return normalize_generic_type(ident, args, wasm32, resolver);
110        }
111
112        // Handle scalar types
113        eprintln!("No angle-bracketed arguments, treating as scalar");
114        return normalize_scalar_type(path, wasm32, resolver);
115    } else if path.segments.len() == 2 {
116        // Handle qualified paths like app::Result
117        let first_segment = &path.segments[0];
118        let second_segment = &path.segments[1];
119
120        eprintln!(
121            "Processing qualified path: {}::{}",
122            first_segment.ident, second_segment.ident
123        );
124
125        // Handle app::Result -> Result
126        if first_segment.ident == "app" && second_segment.ident == "Result" {
127            if let syn::PathArguments::AngleBracketed(args) = &second_segment.arguments {
128                return normalize_generic_type(&second_segment.ident, args, wasm32, resolver);
129            }
130        }
131
132        // Handle serde_json::Value - arbitrary JSON, treat as string for ABI
133        if first_segment.ident == "serde_json" && second_segment.ident == "Value" {
134            return Ok(TypeRef::string());
135        }
136    }
137
138    Err(NormalizeError::TypePathError(
139        "invalid type path".to_owned(),
140    ))
141}
142
143/// Normalize generic types like Option<T>, Vec<T>, etc.
144fn normalize_generic_type(
145    ident: &syn::Ident,
146    args: &syn::AngleBracketedGenericArguments,
147    wasm32: bool,
148    resolver: &dyn TypeResolver,
149) -> Result<TypeRef, NormalizeError> {
150    let ident_str = ident.to_string();
151    eprintln!(
152        "Processing generic type: '{}' (len: {}) with {} args",
153        ident_str,
154        ident_str.len(),
155        args.args.len()
156    );
157    match ident_str.as_str() {
158        "Option" => {
159            // Option<T> -> T (nullable handled at field level)
160            if args.args.len() != 1 {
161                return Err(NormalizeError::TypePathError(
162                    "invalid Option type".to_owned(),
163                ));
164            }
165            let arg = &args.args[0];
166            let GenericArgument::Type(ty) = arg else {
167                return Err(NormalizeError::TypePathError(
168                    "invalid Option argument".to_owned(),
169                ));
170            };
171            normalize_type(ty, wasm32, resolver)
172        }
173        // List/Vector types - normalize to semantic list type
174        "Vec" | "VecDeque" | "LinkedList" => {
175            // All list types -> list<T> or bytes for Vec<u8>
176            if args.args.len() != 1 {
177                return Err(NormalizeError::TypePathError(format!(
178                    "invalid {ident_str} type - expected 1 type argument"
179                )));
180            }
181            let item_arg = &args.args[0];
182            let GenericArgument::Type(item_ty) = item_arg else {
183                return Err(NormalizeError::TypePathError(format!(
184                    "invalid {ident_str} item type"
185                )));
186            };
187
188            // Special case: Vec<u8> -> bytes (only for Vec, not other list types)
189            if ident_str == "Vec" {
190                if let Type::Path(TypePath { path, .. }) = item_ty {
191                    if is_u8_type(path) {
192                        return Ok(TypeRef::Scalar(ScalarType::Bytes {
193                            size: None,
194                            encoding: None,
195                        }));
196                    }
197                }
198            }
199
200            let item_type = normalize_type(item_ty, wasm32, resolver)?;
201            Ok(TypeRef::list(item_type))
202        }
203        // Collection types - normalize to semantic ABI types
204        "BTreeMap" | "HashMap" | "UnorderedMap" | "IndexMap" => {
205            // All map types -> map<K, V> (normalize to semantic type)
206            // UnorderedMap preserves CRDT type metadata
207            if args.args.len() != 2 {
208                return Err(NormalizeError::TypePathError(format!(
209                    "invalid {ident_str} type - expected 2 type arguments"
210                )));
211            }
212
213            let key_arg = &args.args[0];
214            let value_arg = &args.args[1];
215
216            let GenericArgument::Type(key_ty) = key_arg else {
217                return Err(NormalizeError::TypePathError(format!(
218                    "invalid {ident_str} key type"
219                )));
220            };
221
222            let GenericArgument::Type(value_ty) = value_arg else {
223                return Err(NormalizeError::TypePathError(format!(
224                    "invalid {ident_str} value type"
225                )));
226            };
227
228            // Normalize key and value types
229            let _key_type = normalize_type(key_ty, wasm32, resolver)?;
230            let value_type = normalize_type(value_ty, wasm32, resolver)?;
231
232            // Preserve CRDT type for UnorderedMap
233            let crdt_type = if ident_str == "UnorderedMap" {
234                Some(CrdtCollectionType::UnorderedMap)
235            } else {
236                None
237            };
238
239            Ok(TypeRef::Collection {
240                collection: CollectionType::Map {
241                    key: Box::new(TypeRef::Scalar(ScalarType::String)),
242                    value: Box::new(value_type),
243                },
244                crdt_type,
245                inner_type: None, // Inner types are in Map.key and Map.value
246            })
247        }
248        // Set types - normalize to semantic list type (sets are just lists without duplicates)
249        "HashSet" | "BTreeSet" | "IndexSet" => {
250            // All set types -> list<T> (normalize to semantic type)
251            if args.args.len() != 1 {
252                return Err(NormalizeError::TypePathError(format!(
253                    "invalid {ident_str} type - expected 1 type argument"
254                )));
255            }
256
257            let arg = &args.args[0];
258            let GenericArgument::Type(item_ty) = arg else {
259                return Err(NormalizeError::TypePathError(format!(
260                    "invalid {ident_str} argument"
261                )));
262            };
263
264            let item_type = normalize_type(item_ty, wasm32, resolver)?;
265            Ok(TypeRef::list(item_type))
266        }
267        "Result" => {
268            // Result<T, E> -> T (error handling separate)
269            // Handle both Result<T, E> and Result<T> (where E has a default)
270            if args.args.len() == 1 {
271                // Result<T> - single argument, error type has default
272                let arg = &args.args[0];
273                let GenericArgument::Type(ty) = arg else {
274                    return Err(NormalizeError::TypePathError(
275                        "invalid Result argument".to_owned(),
276                    ));
277                };
278                normalize_type(ty, wasm32, resolver)
279            } else if args.args.len() == 2 {
280                // Result<T, E> - two arguments
281                let arg = &args.args[0];
282                let GenericArgument::Type(ty) = arg else {
283                    return Err(NormalizeError::TypePathError(
284                        "invalid Result argument".to_owned(),
285                    ));
286                };
287                normalize_type(ty, wasm32, resolver)
288            } else {
289                return Err(NormalizeError::TypePathError(
290                    "invalid Result type".to_owned(),
291                ));
292            }
293        }
294        // CRDT types - unwrap to inner type for ABI but preserve CRDT type metadata
295        "LwwRegister"
296        | "Counter"
297        | "ReplicatedGrowableArray"
298        | "Vector"
299        | "UnorderedSet"
300        | "FrozenValue" => {
301            // These CRDT wrappers unwrap to their inner type for ABI purposes
302            // but we preserve the CRDT type so deserializers know the format
303
304            if ident_str == "Counter" || ident_str == "ReplicatedGrowableArray" {
305                // Counter and RGA don't have generic args (or are opaque)
306                // Counter -> bytes (but preserve Counter type), RGA -> string (but preserve RGA type)
307                if ident_str == "Counter" {
308                    // Counter serializes as (positive: UnorderedMap<String, u64>, negative?: UnorderedMap<String, u64>)
309                    // We represent it as bytes with CRDT type metadata
310                    return Ok(TypeRef::Collection {
311                        collection: CollectionType::Record {
312                            fields: vec![], // Placeholder - Counter has complex internal structure
313                        },
314                        crdt_type: Some(CrdtCollectionType::Counter),
315                        inner_type: None, // Counter doesn't wrap another type
316                    });
317                } else {
318                    // RGA serializes as a string with CRDT metadata
319                    return Ok(TypeRef::Collection {
320                        collection: CollectionType::Record {
321                            fields: vec![], // Placeholder
322                        },
323                        crdt_type: Some(CrdtCollectionType::ReplicatedGrowableArray),
324                        inner_type: None,
325                    });
326                }
327            }
328
329            // LwwRegister<T>, Vector<T>, UnorderedSet<T> -> unwrap T but preserve CRDT type
330            if args.args.is_empty() {
331                return Err(NormalizeError::TypePathError(format!(
332                    "invalid {ident_str} type - expected 1 type argument"
333                )));
334            }
335            let arg = &args.args[0];
336            let GenericArgument::Type(ty) = arg else {
337                return Err(NormalizeError::TypePathError(
338                    "invalid CRDT argument".to_owned(),
339                ));
340            };
341            let inner_type = normalize_type(ty, wasm32, resolver)?;
342
343            // Wrap the inner type in a Collection with CRDT metadata
344            match ident_str.as_str() {
345                "LwwRegister" => {
346                    // LwwRegister<T> wraps a single value T with CRDT metadata
347                    // We preserve the inner type so deserializer knows how to deserialize the value
348                    // The deserializer will handle the (value: T, timestamp, node_id) format
349                    Ok(TypeRef::Collection {
350                        collection: CollectionType::Record {
351                            fields: vec![], // Placeholder - inner_type stores the actual type
352                        },
353                        crdt_type: Some(CrdtCollectionType::LwwRegister),
354                        inner_type: Some(Box::new(inner_type)),
355                    })
356                }
357                "Vector" => {
358                    // Vector<T> -> List<T> with CRDT type
359                    // The inner_type is already in the List's items field
360                    Ok(TypeRef::Collection {
361                        collection: CollectionType::List {
362                            items: Box::new(inner_type),
363                        },
364                        crdt_type: Some(CrdtCollectionType::Vector),
365                        inner_type: None, // Inner type is in List.items
366                    })
367                }
368                "UnorderedSet" => {
369                    // UnorderedSet<T> -> List<T> with CRDT type
370                    // The inner_type is already in the List's items field
371                    Ok(TypeRef::Collection {
372                        collection: CollectionType::List {
373                            items: Box::new(inner_type),
374                        },
375                        crdt_type: Some(CrdtCollectionType::UnorderedSet),
376                        inner_type: None, // Inner type is in List.items
377                    })
378                }
379                "FrozenValue" => {
380                    // FrozenValue is not a CRDT, just a wrapper - no CRDT metadata
381                    Ok(inner_type)
382                }
383                _ => Ok(inner_type),
384            }
385        }
386        // Handle UserStorage and FrozenStorage
387        "UserStorage" | "FrozenStorage" => {
388            // These normalize to map<string, T>
389            // Key (PublicKey or Hash) is treated as a string
390            if args.args.is_empty() {
391                return Err(NormalizeError::TypePathError(format!(
392                    "invalid {ident_str} type - expected 1 type argument"
393                )));
394            }
395            let arg = &args.args[0];
396            let GenericArgument::Type(ty) = arg else {
397                return Err(NormalizeError::TypePathError(
398                    "invalid storage argument".to_owned(),
399                ));
400            };
401            let value_type = normalize_type(ty, wasm32, resolver)?;
402
403            // UserStorage and FrozenStorage are not CRDTs, just storage wrappers
404            Ok(TypeRef::Collection {
405                collection: CollectionType::Map {
406                    key: Box::new(TypeRef::Scalar(ScalarType::String)),
407                    value: Box::new(value_type),
408                },
409                crdt_type: None,
410                inner_type: None,
411            })
412        }
413        _ => Err(NormalizeError::TypePathError(format!(
414            "unknown generic type: {ident}"
415        ))),
416    }
417}
418
419/// Check if a path represents a u8 type
420fn is_u8_type(path: &syn::Path) -> bool {
421    path.segments.len() == 1 && path.segments[0].ident == "u8"
422}
423
424/// Extract array length from [T; N]
425fn extract_array_len(len: &syn::Expr) -> Result<usize, NormalizeError> {
426    if let syn::Expr::Lit(syn::ExprLit {
427        lit: syn::Lit::Int(lit),
428        ..
429    }) = len
430    {
431        lit.base10_parse()
432            .map_err(|_| NormalizeError::TypePathError("failed to parse array length".to_owned()))
433    } else {
434        Err(NormalizeError::TypePathError(
435            "array length must be a literal integer".to_owned(),
436        ))
437    }
438}
439
440/// Normalize a scalar type
441fn normalize_scalar_type(
442    path: &syn::Path,
443    wasm32: bool,
444    resolver: &dyn TypeResolver,
445) -> Result<TypeRef, NormalizeError> {
446    if path.segments.len() != 1 {
447        return Err(NormalizeError::TypePathError(
448            "invalid scalar type path".to_owned(),
449        ));
450    }
451
452    let ident = &path.segments[0].ident;
453    match ident.to_string().as_str() {
454        "bool" => Ok(TypeRef::bool()),
455        "i8" | "i16" | "i32" => Ok(TypeRef::i32()),
456        "i64" => Ok(TypeRef::i64()),
457        "u8" | "u16" | "u32" => Ok(TypeRef::u32()),
458        "u64" => Ok(TypeRef::u64()),
459        "f32" => Ok(TypeRef::f32()),
460        "f64" => Ok(TypeRef::f64()),
461        "String" | "str" => Ok(TypeRef::string()),
462        "usize" => {
463            if wasm32 {
464                Ok(TypeRef::u32())
465            } else {
466                Ok(TypeRef::u64())
467            }
468        }
469        "isize" => {
470            if wasm32 {
471                Ok(TypeRef::i32())
472            } else {
473                Ok(TypeRef::i64())
474            }
475        }
476        // Handle PublicKey
477        "PublicKey" => {
478            // PublicKey is [u8; 32], so it's bytes with a fixed size
479            Ok(TypeRef::bytes_with_size(32, None))
480        }
481        // Handle ProposalId (calimero_sdk::env::ext)
482        "ProposalId" => {
483            // ProposalId is [u8; 32]
484            Ok(TypeRef::bytes_with_size(32, None))
485        }
486        // Storage CRDT wrappers – treat as opaque blobs until ABI definitions exist.
487        "Counter" => Ok(TypeRef::Collection {
488            collection: CollectionType::Record {
489                fields: vec![], // Placeholder - Counter has complex internal structure
490            },
491            crdt_type: Some(CrdtCollectionType::Counter),
492            inner_type: None, // Counter doesn't wrap another type
493        }),
494        "ReplicatedGrowableArray" => Ok(TypeRef::Collection {
495            collection: CollectionType::Record {
496                fields: vec![], // Placeholder
497            },
498            crdt_type: Some(CrdtCollectionType::ReplicatedGrowableArray),
499            inner_type: None,
500        }),
501        _ => {
502            // Check if it's a local type
503            resolver.resolve_local(&ident.to_string()).map_or_else(
504                || {
505                    Err(NormalizeError::TypePathError(format!(
506                        "unknown type: {ident}"
507                    )))
508                },
509                |resolved| match resolved {
510                    ResolvedLocal::NewtypeBytes { size } => {
511                        Ok(TypeRef::bytes_with_size(size, None))
512                    }
513                    ResolvedLocal::Record | ResolvedLocal::Variant => {
514                        Ok(TypeRef::reference(&ident.to_string()))
515                    }
516                },
517            )
518        }
519    }
520}
521
522/// Extension trait for TypeRef to set nullable flag
523pub trait TypeRefExt {
524    fn set_nullable(&mut self, nullable: bool);
525}
526
527impl TypeRefExt for TypeRef {
528    fn set_nullable(&mut self, _nullable: bool) {
529        // Nullable is now handled at the Parameter/Field/Method.returns_nullable level
530    }
531}