Skip to main content

assura_codegen/
type_map.rs

1//! Reverse type mapping: Rust types to Assura types.
2//!
3//! The forward mapping (Assura -> Rust) lives in `map_type_token()` in `lib.rs`.
4//! This module provides the inverse: given a Rust type string, produce the
5//! canonical Assura type. Used by AI contract generation templates and the
6//! `assura infer` command.
7
8/// Map a Rust type string to its Assura equivalent.
9///
10/// Handles primitives, standard library generics, references, and nested types.
11/// Unknown types are passed through unchanged (they become user-defined types
12/// in Assura).
13///
14/// # Examples
15/// ```
16/// use assura_codegen::type_map::rust_type_to_assura;
17/// assert_eq!(rust_type_to_assura("i64"), "Int");
18/// assert_eq!(rust_type_to_assura("Vec<u8>"), "Bytes");
19/// assert_eq!(rust_type_to_assura("Vec<i64>"), "List<Int>");
20/// assert_eq!(rust_type_to_assura("Option<i64>"), "Int?");
21/// ```
22pub fn rust_type_to_assura(rust_type: &str) -> String {
23    let trimmed = rust_type.trim();
24
25    // Handle references: &str, &[u8], &T, &mut T
26    if let Some(inner) = trimmed.strip_prefix('&') {
27        let inner = inner.trim_start();
28        if let Some(inner) = inner.strip_prefix("mut ") {
29            return rust_type_to_assura(inner.trim());
30        }
31        if inner == "str" {
32            return "String".to_string();
33        }
34        if inner == "[u8]" {
35            return "Bytes".to_string();
36        }
37        // &[T] -> List<T>
38        if let Some(slice_inner) = inner.strip_prefix('[')
39            && let Some(slice_inner) = slice_inner.strip_suffix(']')
40        {
41            let mapped = rust_type_to_assura(slice_inner.trim());
42            return format!("List<{mapped}>");
43        }
44        return rust_type_to_assura(inner);
45    }
46
47    // Handle tuple types: (T, U, ...) -> (T, U, ...)
48    if trimmed.starts_with('(') && trimmed.ends_with(')') {
49        let inner = &trimmed[1..trimmed.len() - 1];
50        if inner.is_empty() {
51            return "Unit".to_string();
52        }
53        let parts = split_type_args(inner);
54        let mapped: Vec<String> = parts.iter().map(|p| rust_type_to_assura(p)).collect();
55        return format!("({})", mapped.join(", "));
56    }
57
58    // Handle simple primitives first (no generics)
59    if !trimmed.contains('<') {
60        return map_simple_rust_type(trimmed).to_string();
61    }
62
63    // Handle generic types: Name<Args>
64    if let Some((base, args)) = split_generic(trimmed) {
65        let base_trimmed = base.trim();
66        let type_args = split_type_args(args);
67
68        match base_trimmed {
69            // Vec<u8> -> Bytes, Vec<T> -> List<T>
70            "Vec" => {
71                if type_args.len() == 1 && type_args[0].trim() == "u8" {
72                    "Bytes".to_string()
73                } else if type_args.len() == 1 {
74                    let inner = rust_type_to_assura(type_args[0].trim());
75                    format!("List<{inner}>")
76                } else {
77                    format!("Vec<{}>", map_type_arg_list(&type_args))
78                }
79            }
80
81            // Option<T> -> T?
82            "Option" => {
83                if type_args.len() == 1 {
84                    let inner = rust_type_to_assura(type_args[0].trim());
85                    format!("{inner}?")
86                } else {
87                    format!("Option<{}>", map_type_arg_list(&type_args))
88                }
89            }
90
91            // Result<T, E> -> Result<T, E> (context-dependent, pass through)
92            "Result" => {
93                format!("Result<{}>", map_type_arg_list(&type_args))
94            }
95
96            // Map types
97            "HashMap" | "BTreeMap" | "std::collections::HashMap" | "std::collections::BTreeMap" => {
98                format!("Map<{}>", map_type_arg_list(&type_args))
99            }
100
101            // Set types
102            "HashSet" | "BTreeSet" | "std::collections::HashSet" | "std::collections::BTreeSet" => {
103                if type_args.len() == 1 {
104                    let inner = rust_type_to_assura(type_args[0].trim());
105                    format!("Set<{inner}>")
106                } else {
107                    format!("Set<{}>", map_type_arg_list(&type_args))
108                }
109            }
110
111            // Box<T>, Arc<T>, Rc<T>, Cow<T> -> just T (wrapper erasure)
112            "Box" | "Arc" | "Rc" | "Cow" | "std::sync::Arc" | "std::rc::Rc"
113            | "std::borrow::Cow" => {
114                if type_args.len() == 1 {
115                    rust_type_to_assura(type_args[0].trim())
116                } else {
117                    format!("{base_trimmed}<{}>", map_type_arg_list(&type_args))
118                }
119            }
120
121            // Unknown generic: pass through with mapped args
122            _ => {
123                let mapped_base = map_simple_rust_type(base_trimmed);
124                format!("{mapped_base}<{}>", map_type_arg_list(&type_args))
125            }
126        }
127    } else {
128        map_simple_rust_type(trimmed).to_string()
129    }
130}
131
132/// Map a simple (non-generic) Rust type to Assura.
133fn map_simple_rust_type(ty: &str) -> &str {
134    match ty {
135        // Signed integers -> Int
136        "i8" | "i16" | "i32" | "i64" | "i128" | "isize" => "Int",
137        // Unsigned integers -> Nat
138        "u8" | "u16" | "u32" | "u64" | "u128" | "usize" => "Nat",
139        // Floats
140        "f32" | "f64" => "Float",
141        // Bool
142        "bool" => "Bool",
143        // String types
144        "String" | "str" => "String",
145        // Unit
146        "()" => "Unit",
147        // Never
148        "!" | "Infallible" | "std::convert::Infallible" => "Never",
149        // Bytes as a standalone type name
150        "Bytes" | "bytes::Bytes" => "Bytes",
151        // Pass through anything else
152        _ => ty,
153    }
154}
155
156/// Split `Name<A, B, C>` into `("Name", "A, B, C")`.
157fn split_generic(ty: &str) -> Option<(&str, &str)> {
158    let open = ty.find('<')?;
159    let close = ty.rfind('>')?;
160    if close <= open {
161        return None;
162    }
163    Some((&ty[..open], &ty[open + 1..close]))
164}
165
166/// Split a comma-separated type argument list, respecting nested `<>`.
167fn split_type_args(args: &str) -> Vec<&str> {
168    let mut result = Vec::new();
169    let mut depth = 0i32;
170    let mut paren_depth = 0i32;
171    let mut start = 0;
172
173    for (i, ch) in args.char_indices() {
174        match ch {
175            '<' => depth += 1,
176            '>' if depth > 0 => depth -= 1,
177            '(' => paren_depth += 1,
178            ')' if paren_depth > 0 => paren_depth -= 1,
179            ',' if depth == 0 && paren_depth == 0 => {
180                result.push(&args[start..i]);
181                start = i + 1;
182            }
183            _ => {}
184        }
185    }
186    if start < args.len() {
187        result.push(&args[start..]);
188    }
189    result
190}
191
192/// Map a list of type arguments recursively.
193fn map_type_arg_list(args: &[&str]) -> String {
194    args.iter()
195        .map(|a| rust_type_to_assura(a.trim()))
196        .collect::<Vec<_>>()
197        .join(", ")
198}
199
200/// Like [`rust_type_to_assura`], but maps unrecognized Rust types to `Unknown`
201/// instead of passing them through. Also handles `impl Trait`, `dyn Trait`,
202/// `Self`, and lifetime annotations that the standard mapper does not.
203///
204/// Used by `assura audit` where unknown Rust types would cause A02001
205/// resolution errors that prevent verification from running.
206pub fn rust_type_to_assura_lenient(rust_type: &str) -> String {
207    let ty = rust_type.trim();
208    // Unmappable Rust type syntax: return Unknown directly
209    if ty.starts_with("impl ") || ty.starts_with("dyn ") || ty == "Self" {
210        return "Unknown".to_string();
211    }
212    if ty.starts_with("Pin<") || ty.starts_with("PhantomData") {
213        return "Unknown".to_string();
214    }
215    // Strip lifetime from references: &'a T -> &T, &'_ T -> &T
216    let cleaned = if ty.starts_with("&'") {
217        if let Some(space_idx) = ty[1..].find(' ') {
218            format!("&{}", &ty[2 + space_idx..])
219        } else {
220            ty.to_string()
221        }
222    } else {
223        ty.to_string()
224    };
225    let mapped = rust_type_to_assura(&cleaned);
226    sanitize_mapped_type(&mapped)
227}
228
229/// Recursively replace type names that are not valid Assura types with `Unknown`.
230fn sanitize_mapped_type(ty: &str) -> String {
231    let ty = ty.trim();
232    if ty.is_empty() {
233        return "Unknown".to_string();
234    }
235    // Optional: T?
236    if let Some(inner) = ty.strip_suffix('?') {
237        let s = sanitize_mapped_type(inner);
238        return format!("{s}?");
239    }
240    // Tuple: (T, U, ...)
241    if ty.starts_with('(') && ty.ends_with(')') {
242        let inner = &ty[1..ty.len() - 1];
243        if inner.is_empty() {
244            return "Unit".to_string();
245        }
246        let parts = split_type_args(inner);
247        let sanitized: Vec<String> = parts
248            .iter()
249            .map(|p| sanitize_mapped_type(p.trim()))
250            .collect();
251        return format!("({})", sanitized.join(", "));
252    }
253    // Known generic bases: List<...>, Map<...>, Set<...>, Result<...>
254    if let Some((base, args)) = split_generic(ty) {
255        let base = base.trim();
256        match base {
257            "List" | "Map" | "Set" | "Result" => {
258                let parts = split_type_args(args);
259                let sanitized: Vec<String> = parts
260                    .iter()
261                    .map(|p| sanitize_mapped_type(p.trim()))
262                    .collect();
263                return format!("{base}<{}>", sanitized.join(", "));
264            }
265            _ => return "Unknown".to_string(),
266        }
267    }
268    // Known Assura primitive types
269    match ty {
270        "Int" | "Nat" | "Float" | "Bool" | "String" | "Bytes" | "Unit" | "Never" | "Unknown" => {
271            ty.to_string()
272        }
273        _ => "Unknown".to_string(),
274    }
275}
276
277// ---------------------------------------------------------------------------
278// Tests
279// ---------------------------------------------------------------------------
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    // --- Primitive mapping ---
286
287    #[test]
288    fn signed_integers_map_to_int() {
289        for ty in &["i8", "i16", "i32", "i64", "i128", "isize"] {
290            assert_eq!(rust_type_to_assura(ty), "Int", "failed for {ty}");
291        }
292    }
293
294    #[test]
295    fn unsigned_integers_map_to_nat() {
296        for ty in &["u8", "u16", "u32", "u64", "u128", "usize"] {
297            assert_eq!(rust_type_to_assura(ty), "Nat", "failed for {ty}");
298        }
299    }
300
301    #[test]
302    fn floats_map_to_float() {
303        assert_eq!(rust_type_to_assura("f32"), "Float");
304        assert_eq!(rust_type_to_assura("f64"), "Float");
305    }
306
307    #[test]
308    fn bool_maps_to_bool() {
309        assert_eq!(rust_type_to_assura("bool"), "Bool");
310    }
311
312    #[test]
313    fn string_types() {
314        assert_eq!(rust_type_to_assura("String"), "String");
315        assert_eq!(rust_type_to_assura("&str"), "String");
316    }
317
318    #[test]
319    fn unit_and_never() {
320        assert_eq!(rust_type_to_assura("()"), "Unit");
321        assert_eq!(rust_type_to_assura("!"), "Never");
322        assert_eq!(rust_type_to_assura("Infallible"), "Never");
323    }
324
325    // --- Collection mapping ---
326
327    #[test]
328    fn vec_u8_maps_to_bytes() {
329        assert_eq!(rust_type_to_assura("Vec<u8>"), "Bytes");
330    }
331
332    #[test]
333    fn vec_maps_to_list() {
334        assert_eq!(rust_type_to_assura("Vec<i64>"), "List<Int>");
335        assert_eq!(rust_type_to_assura("Vec<String>"), "List<String>");
336    }
337
338    #[test]
339    fn map_types() {
340        assert_eq!(
341            rust_type_to_assura("HashMap<String, i64>"),
342            "Map<String, Int>"
343        );
344        assert_eq!(
345            rust_type_to_assura("BTreeMap<String, u64>"),
346            "Map<String, Nat>"
347        );
348    }
349
350    #[test]
351    fn set_types() {
352        assert_eq!(rust_type_to_assura("HashSet<i64>"), "Set<Int>");
353        assert_eq!(rust_type_to_assura("BTreeSet<String>"), "Set<String>");
354    }
355
356    // --- Option mapping ---
357
358    #[test]
359    fn option_maps_to_nullable() {
360        assert_eq!(rust_type_to_assura("Option<i64>"), "Int?");
361        assert_eq!(rust_type_to_assura("Option<String>"), "String?");
362    }
363
364    // --- Reference erasure ---
365
366    #[test]
367    fn references_are_erased() {
368        assert_eq!(rust_type_to_assura("&i64"), "Int");
369        assert_eq!(rust_type_to_assura("&mut i64"), "Int");
370        assert_eq!(rust_type_to_assura("&[u8]"), "Bytes");
371        assert_eq!(rust_type_to_assura("&[i64]"), "List<Int>");
372    }
373
374    // --- Wrapper erasure ---
375
376    #[test]
377    fn smart_pointers_are_erased() {
378        assert_eq!(rust_type_to_assura("Box<i64>"), "Int");
379        assert_eq!(rust_type_to_assura("Arc<String>"), "String");
380        assert_eq!(rust_type_to_assura("Rc<Vec<i64>>"), "List<Int>");
381    }
382
383    // --- Nested generics ---
384
385    #[test]
386    fn nested_generics() {
387        assert_eq!(rust_type_to_assura("Vec<Option<i64>>"), "List<Int?>");
388        assert_eq!(
389            rust_type_to_assura("Vec<Option<BTreeMap<String, i64>>>"),
390            "List<Map<String, Int>?>"
391        );
392    }
393
394    // --- Tuples ---
395
396    #[test]
397    fn tuple_types() {
398        assert_eq!(rust_type_to_assura("(i64, u64)"), "(Int, Nat)");
399        assert_eq!(
400            rust_type_to_assura("(String, bool, f64)"),
401            "(String, Bool, Float)"
402        );
403    }
404
405    // --- Unknown passthrough ---
406
407    #[test]
408    fn unknown_types_pass_through() {
409        assert_eq!(rust_type_to_assura("MyCustomStruct"), "MyCustomStruct");
410        assert_eq!(rust_type_to_assura("MyGeneric<i64>"), "MyGeneric<Int>");
411    }
412
413    // --- Result passthrough ---
414
415    #[test]
416    fn result_type() {
417        assert_eq!(
418            rust_type_to_assura("Result<i64, String>"),
419            "Result<Int, String>"
420        );
421    }
422
423    // --- Lenient mapping (for audit) ---
424
425    #[test]
426    fn lenient_primitives_pass_through() {
427        assert_eq!(rust_type_to_assura_lenient("i64"), "Int");
428        assert_eq!(rust_type_to_assura_lenient("u32"), "Nat");
429        assert_eq!(rust_type_to_assura_lenient("bool"), "Bool");
430        assert_eq!(rust_type_to_assura_lenient("String"), "String");
431        assert_eq!(rust_type_to_assura_lenient("f64"), "Float");
432    }
433
434    #[test]
435    fn lenient_unknown_types_become_unknown() {
436        assert_eq!(rust_type_to_assura_lenient("Config"), "Unknown");
437        assert_eq!(rust_type_to_assura_lenient("PathBuf"), "Unknown");
438        assert_eq!(rust_type_to_assura_lenient("MyStruct"), "Unknown");
439    }
440
441    #[test]
442    fn lenient_unknown_generics_become_unknown() {
443        assert_eq!(rust_type_to_assura_lenient("MyGeneric<i64>"), "Unknown");
444        assert_eq!(rust_type_to_assura_lenient("Foo<Bar, Baz>"), "Unknown");
445    }
446
447    #[test]
448    fn lenient_known_generics_preserve_structure() {
449        assert_eq!(rust_type_to_assura_lenient("Vec<i64>"), "List<Int>");
450        assert_eq!(
451            rust_type_to_assura_lenient("HashMap<String, i64>"),
452            "Map<String, Int>"
453        );
454        assert_eq!(rust_type_to_assura_lenient("Option<bool>"), "Bool?");
455    }
456
457    #[test]
458    fn lenient_nested_unknown_in_known_generic() {
459        assert_eq!(rust_type_to_assura_lenient("Vec<Config>"), "List<Unknown>");
460        assert_eq!(
461            rust_type_to_assura_lenient("Result<i64, MyError>"),
462            "Result<Int, Unknown>"
463        );
464    }
465
466    #[test]
467    fn lenient_impl_dyn_self() {
468        assert_eq!(rust_type_to_assura_lenient("impl Iterator"), "Unknown");
469        assert_eq!(rust_type_to_assura_lenient("dyn Trait"), "Unknown");
470        assert_eq!(rust_type_to_assura_lenient("Self"), "Unknown");
471    }
472
473    #[test]
474    fn lenient_lifetime_references() {
475        assert_eq!(rust_type_to_assura_lenient("&'a str"), "String");
476        assert_eq!(rust_type_to_assura_lenient("&'_ i64"), "Int");
477    }
478
479    #[test]
480    fn lenient_pin_phantomdata() {
481        assert_eq!(
482            rust_type_to_assura_lenient("Pin<Box<dyn Future>>"),
483            "Unknown"
484        );
485        assert_eq!(rust_type_to_assura_lenient("PhantomData<T>"), "Unknown");
486    }
487
488    #[test]
489    fn lenient_wrapper_erasure_then_sanitize() {
490        assert_eq!(rust_type_to_assura_lenient("Box<i64>"), "Int");
491        assert_eq!(rust_type_to_assura_lenient("Arc<String>"), "String");
492        assert_eq!(rust_type_to_assura_lenient("Arc<Config>"), "Unknown");
493    }
494}