Skip to main content

eure_schema/synth/
types.rs

1//! Synthesized type definitions
2//!
3//! These types represent inferred types from Eure document values,
4//! without the constraint information found in schema types.
5
6use eure_document::identifier::Identifier;
7use std::collections::HashMap;
8use std::fmt;
9
10/// A synthesized type inferred from document values.
11///
12/// Unlike `SchemaNodeContent`, this type does not include validation
13/// constraints (min/max length, patterns, etc.) - it represents only
14/// the structural type of the data.
15#[derive(Debug, Clone, PartialEq)]
16pub enum SynthType {
17    // === Primitives ===
18    /// Null value
19    Null,
20
21    /// Boolean value
22    Boolean,
23
24    /// Integer value (arbitrary precision)
25    Integer,
26
27    /// Floating-point value
28    Float,
29
30    /// Text value with optional language tag
31    ///
32    /// - `None` - implicit/unknown language (from `` `...` ``)
33    /// - `Some("plaintext")` - plaintext (from `"..."`)
34    /// - `Some("rust")` - language-tagged (from `` rust`...` ``)
35    Text(Option<String>),
36
37    // === Compounds ===
38    /// Homogeneous array type
39    Array(Box<SynthType>),
40
41    /// Tuple type (fixed-length, heterogeneous)
42    Tuple(Vec<SynthType>),
43
44    /// Record type (fixed named fields)
45    Record(SynthRecord),
46
47    // === Special ===
48    /// Union of types (structural, not tagged)
49    ///
50    /// Invariants:
51    /// - At least 2 variants
52    /// - No nested unions (flattened)
53    /// - No duplicate variants
54    Union(SynthUnion),
55
56    /// Top type - accepts any value
57    ///
58    /// Used for:
59    /// - Empty arrays: `[]` has type `Array<Any>`
60    /// - Unknown types
61    Any,
62
63    /// Bottom type - no values
64    ///
65    /// Used for contradictions (rare in practice)
66    Never,
67
68    /// Unfilled placeholder
69    ///
70    /// Holes are absorbed during unification:
71    /// `unify(Integer, Hole) = Integer`
72    Hole(Option<Identifier>),
73}
74
75/// A record type with named fields
76#[derive(Debug, Clone, PartialEq)]
77pub struct SynthRecord {
78    /// Field name to field definition
79    pub fields: HashMap<String, SynthField>,
80}
81
82/// A field in a record type
83#[derive(Debug, Clone, PartialEq)]
84pub struct SynthField {
85    /// The type of the field
86    pub ty: SynthType,
87
88    /// Whether the field is optional
89    ///
90    /// Note: In synthesized types, optionality comes from unifying
91    /// records where some have the field and others don't.
92    pub optional: bool,
93}
94
95/// A structural union of types
96///
97/// Unlike schema unions, these are anonymous/structural and don't
98/// have named variants.
99#[derive(Debug, Clone, PartialEq)]
100pub struct SynthUnion {
101    /// The variant types in this union
102    ///
103    /// Invariants:
104    /// - At least 2 elements
105    /// - No `SynthType::Union` elements (flattened)
106    /// - No duplicates
107    pub variants: Vec<SynthType>,
108}
109
110// === Constructors ===
111
112impl SynthRecord {
113    /// Create an empty record
114    pub fn empty() -> Self {
115        Self {
116            fields: HashMap::new(),
117        }
118    }
119
120    /// Create a record from field definitions
121    pub fn new(fields: impl IntoIterator<Item = (String, SynthField)>) -> Self {
122        Self {
123            fields: fields.into_iter().collect(),
124        }
125    }
126}
127
128impl SynthField {
129    /// Create a required field
130    pub fn required(ty: SynthType) -> Self {
131        Self {
132            ty,
133            optional: false,
134        }
135    }
136
137    /// Create an optional field
138    pub fn optional(ty: SynthType) -> Self {
139        Self { ty, optional: true }
140    }
141}
142
143impl SynthUnion {
144    /// Create a union from variants, normalizing as needed
145    ///
146    /// This function:
147    /// - Flattens nested unions
148    /// - Removes duplicates
149    /// - Returns single type if only one variant remains
150    /// - Returns `Never` for empty input
151    pub fn from_variants(variants: impl IntoIterator<Item = SynthType>) -> SynthType {
152        let mut flat: Vec<SynthType> = Vec::new();
153
154        for variant in variants {
155            match variant {
156                // Flatten nested unions
157                SynthType::Union(inner) => {
158                    for v in inner.variants {
159                        if !flat.contains(&v) {
160                            flat.push(v);
161                        }
162                    }
163                }
164                // Skip Never (identity for union)
165                SynthType::Never => {}
166                // Skip Holes (absorbed)
167                SynthType::Hole(_) => {}
168                // Add if not duplicate
169                other => {
170                    if !flat.contains(&other) {
171                        flat.push(other);
172                    }
173                }
174            }
175        }
176
177        match flat.len() {
178            0 => SynthType::Never,
179            1 => flat.pop().unwrap(),
180            _ => SynthType::Union(SynthUnion { variants: flat }),
181        }
182    }
183}
184
185// === Display implementations ===
186
187impl fmt::Display for SynthType {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        match self {
190            SynthType::Null => write!(f, "null"),
191            SynthType::Boolean => write!(f, "boolean"),
192            SynthType::Integer => write!(f, "integer"),
193            SynthType::Float => write!(f, "float"),
194            SynthType::Text(None) => write!(f, "text"),
195            SynthType::Text(Some(lang)) => write!(f, "text.{}", lang),
196            SynthType::Array(inner) => write!(f, "[{}]", inner),
197            SynthType::Tuple(elems) => {
198                write!(f, "(")?;
199                for (i, elem) in elems.iter().enumerate() {
200                    if i > 0 {
201                        write!(f, ", ")?;
202                    }
203                    write!(f, "{}", elem)?;
204                }
205                write!(f, ")")
206            }
207            SynthType::Record(rec) => write!(f, "{}", rec),
208            SynthType::Union(union) => write!(f, "{}", union),
209            SynthType::Any => write!(f, "any"),
210            SynthType::Never => write!(f, "never"),
211            SynthType::Hole(None) => write!(f, "!"),
212            SynthType::Hole(Some(id)) => write!(f, "!{}", id),
213        }
214    }
215}
216
217impl fmt::Display for SynthRecord {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(f, "{{")?;
220        let mut first = true;
221        for (name, field) in &self.fields {
222            if !first {
223                write!(f, ", ")?;
224            }
225            first = false;
226            write!(f, "{}", name)?;
227            if field.optional {
228                write!(f, "?")?;
229            }
230            write!(f, ": {}", field.ty)?;
231        }
232        write!(f, "}}")
233    }
234}
235
236impl fmt::Display for SynthUnion {
237    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        for (i, variant) in self.variants.iter().enumerate() {
239            if i > 0 {
240                write!(f, " | ")?;
241            }
242            write!(f, "{}", variant)?;
243        }
244        Ok(())
245    }
246}
247
248// === Type predicates ===
249
250impl SynthType {
251    /// Check if this is a primitive type
252    pub fn is_primitive(&self) -> bool {
253        matches!(
254            self,
255            SynthType::Null
256                | SynthType::Boolean
257                | SynthType::Integer
258                | SynthType::Float
259                | SynthType::Text(_)
260        )
261    }
262
263    /// Check if this is a compound type
264    pub fn is_compound(&self) -> bool {
265        matches!(
266            self,
267            SynthType::Array(_) | SynthType::Tuple(_) | SynthType::Record(_)
268        )
269    }
270
271    /// Check if this type contains any holes
272    pub fn has_holes(&self) -> bool {
273        match self {
274            SynthType::Hole(_) => true,
275            SynthType::Array(inner) => inner.has_holes(),
276            SynthType::Tuple(elems) => elems.iter().any(|e| e.has_holes()),
277            SynthType::Record(rec) => rec.fields.values().any(|f| f.ty.has_holes()),
278            SynthType::Union(union) => union.variants.iter().any(|v| v.has_holes()),
279            _ => false,
280        }
281    }
282
283    /// Check if this type is complete (no holes, no Any, no Never)
284    pub fn is_complete(&self) -> bool {
285        match self {
286            SynthType::Hole(_) | SynthType::Any | SynthType::Never => false,
287            SynthType::Array(inner) => inner.is_complete(),
288            SynthType::Tuple(elems) => elems.iter().all(|e| e.is_complete()),
289            SynthType::Record(rec) => rec.fields.values().all(|f| f.ty.is_complete()),
290            SynthType::Union(union) => union.variants.iter().all(|v| v.is_complete()),
291            _ => true,
292        }
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn test_union_flattening() {
302        // Union of unions should flatten
303        let inner = SynthUnion::from_variants([SynthType::Integer, SynthType::Boolean]);
304        let outer = SynthUnion::from_variants([inner, SynthType::Text(None)]);
305
306        assert_eq!(
307            outer,
308            SynthType::Union(SynthUnion {
309                variants: vec![
310                    SynthType::Integer,
311                    SynthType::Boolean,
312                    SynthType::Text(None)
313                ]
314            })
315        );
316    }
317
318    #[test]
319    fn test_union_dedup() {
320        let union =
321            SynthUnion::from_variants([SynthType::Integer, SynthType::Integer, SynthType::Boolean]);
322
323        assert_eq!(
324            union,
325            SynthType::Union(SynthUnion {
326                variants: vec![SynthType::Integer, SynthType::Boolean]
327            })
328        );
329    }
330
331    #[test]
332    fn test_union_single_collapses() {
333        let union = SynthUnion::from_variants([SynthType::Integer]);
334        assert_eq!(union, SynthType::Integer);
335    }
336
337    #[test]
338    fn test_union_absorbs_holes() {
339        let union = SynthUnion::from_variants([SynthType::Integer, SynthType::Hole(None)]);
340        assert_eq!(union, SynthType::Integer);
341    }
342
343    #[test]
344    fn test_union_absorbs_never() {
345        let union = SynthUnion::from_variants([SynthType::Integer, SynthType::Never]);
346        assert_eq!(union, SynthType::Integer);
347    }
348
349    #[test]
350    fn test_display() {
351        assert_eq!(SynthType::Integer.to_string(), "integer");
352        assert_eq!(
353            SynthType::Text(Some("rust".to_string())).to_string(),
354            "text.rust"
355        );
356        assert_eq!(
357            SynthType::Array(Box::new(SynthType::Integer)).to_string(),
358            "[integer]"
359        );
360    }
361
362    #[test]
363    fn test_has_holes() {
364        assert!(!SynthType::Integer.has_holes());
365        assert!(SynthType::Hole(None).has_holes());
366        assert!(SynthType::Array(Box::new(SynthType::Hole(None))).has_holes());
367    }
368}