Skip to main content

aleph_syntax_tree/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// Static type attached to a subtree via `AlephTree::Typed`, or to a
4/// `TypeDef`'s variant fields. Deliberately small: enough to type-check
5/// records, sum types, and function signatures (see the Aleph-Next spec)
6/// without committing yet to full parametric polymorphism.
7#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(tag = "type")]
9pub enum Type {
10    /// 64-bit signed integer.
11    Int,
12    /// 64-bit floating point number.
13    Float,
14    /// Boolean.
15    Bool,
16    /// UTF-8 text.
17    String,
18    /// Raw byte sequence.
19    Bytes,
20    /// The single-valued "nothing" type.
21    ///
22    /// `#[default]` here exists only so `AlephTree`'s `strum::EnumString`
23    /// derive can build a placeholder `Type` value for its `FromStr` path
24    /// (which requires every struct-like variant's fields to implement
25    /// `Default`). It is not a sentinel for "no type annotation" — use
26    /// `Option<Type>` for that. Don't let `Type::default()` leak into
27    /// real type-checking logic.
28    #[default]
29    Unit,
30    /// Homogeneous list of `elem`.
31    List { elem: Box<Type> },
32    /// Fixed-size heterogeneous tuple.
33    Tuple { elems: Vec<Type> },
34    /// Named-field record (struct).
35    Record { fields: Vec<RecordField> },
36    /// Tagged union of named variants (sum/enum type).
37    Sum { variants: Vec<Variant> },
38    /// Function signature: positional parameter types and a return type.
39    Fun { params: Vec<Type>, ret: Box<Type> },
40    /// A type variable, referenced by name (for as-yet-unresolved/generic types).
41    Var { name: String },
42}
43
44/// One named field of a `Type::Record`.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct RecordField {
47    pub name: String,
48    pub ty: Type,
49}
50
51/// One named variant of a `Type::Sum` (e.g. `Circle` in `Circle(radius: Float)`).
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct Variant {
54    pub name: String,
55    pub fields: Vec<Type>,
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn primitive_round_trips_through_json() {
64        let ty = Type::Int;
65        let json = serde_json::to_string(&ty).unwrap();
66        let back: Type = serde_json::from_str(&json).unwrap();
67        assert_eq!(ty, back);
68    }
69
70    #[test]
71    fn function_type_round_trips_through_json() {
72        let ty = Type::Fun {
73            params: vec![Type::Int, Type::String],
74            ret: Box::new(Type::Bool),
75        };
76        let json = serde_json::to_string(&ty).unwrap();
77        let back: Type = serde_json::from_str(&json).unwrap();
78        assert_eq!(ty, back);
79    }
80
81    #[test]
82    fn sum_type_round_trips_through_json() {
83        let ty = Type::Sum {
84            variants: vec![
85                Variant { name: "Circle".to_string(), fields: vec![Type::Float] },
86                Variant { name: "Rect".to_string(), fields: vec![Type::Float, Type::Float] },
87            ],
88        };
89        let json = serde_json::to_string(&ty).unwrap();
90        let back: Type = serde_json::from_str(&json).unwrap();
91        assert_eq!(ty, back);
92    }
93
94    #[test]
95    fn struct_like_variant_wire_format_is_locked() {
96        let ty = Type::List { elem: Box::new(Type::Int) };
97        assert_eq!(
98            serde_json::to_string(&ty).unwrap(),
99            r#"{"type":"List","elem":{"type":"Int"}}"#
100        );
101    }
102
103    #[test]
104    fn record_wire_format_is_locked() {
105        let ty = Type::Record {
106            fields: vec![RecordField { name: "x".to_string(), ty: Type::Int }],
107        };
108        assert_eq!(
109            serde_json::to_string(&ty).unwrap(),
110            r#"{"type":"Record","fields":[{"name":"x","ty":{"type":"Int"}}]}"#
111        );
112    }
113
114    #[test]
115    fn var_round_trips_through_json() {
116        let ty = Type::Var { name: "a".to_string() };
117        let json = serde_json::to_string(&ty).unwrap();
118        let back: Type = serde_json::from_str(&json).unwrap();
119        assert_eq!(ty, back);
120    }
121
122    #[test]
123    fn nested_composite_type_round_trips_through_json() {
124        let ty = Type::List {
125            elem: Box::new(Type::Record {
126                fields: vec![RecordField { name: "x".to_string(), ty: Type::Float }],
127            }),
128        };
129        let json = serde_json::to_string(&ty).unwrap();
130        let back: Type = serde_json::from_str(&json).unwrap();
131        assert_eq!(ty, back);
132    }
133}