Skip to main content

mir_types/
display.rs

1use std::fmt;
2
3use crate::atomic::Atomic;
4use crate::union::Union;
5
6impl fmt::Display for Union {
7    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8        if self.types.is_empty() {
9            return write!(f, "never");
10        }
11        let strs: Vec<String> = self.types.iter().map(|a| format!("{a}")).collect();
12        write!(f, "{}", strs.join("|"))
13    }
14}
15
16impl fmt::Display for Atomic {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            Atomic::TString => write!(f, "string"),
20            Atomic::TLiteralString(s) => write!(f, "\"{s}\""),
21            Atomic::TClassString(None) => write!(f, "class-string"),
22            Atomic::TClassString(Some(cls)) => write!(f, "class-string<{cls}>"),
23            Atomic::TNonEmptyString => write!(f, "non-empty-string"),
24            Atomic::TNumericString => write!(f, "numeric-string"),
25
26            Atomic::TInt => write!(f, "int"),
27            Atomic::TLiteralInt(n) => write!(f, "{n}"),
28            Atomic::TIntRange { min, max } => match (min, max) {
29                (None, None) => write!(f, "int"),
30                (lo, hi) => {
31                    let lo = lo.map_or_else(|| "min".to_string(), |n| n.to_string());
32                    let hi = hi.map_or_else(|| "max".to_string(), |n| n.to_string());
33                    write!(f, "int<{lo}, {hi}>")
34                }
35            },
36            Atomic::TPositiveInt => write!(f, "positive-int"),
37            Atomic::TNegativeInt => write!(f, "negative-int"),
38            Atomic::TNonNegativeInt => write!(f, "non-negative-int"),
39
40            Atomic::TFloat => write!(f, "float"),
41            Atomic::TLiteralFloat(high, low) => {
42                let bits = ((*high as u64) << 32) | (*low as u32 as u64);
43                let value = f64::from_bits(bits);
44                write!(f, "{value}")
45            }
46
47            Atomic::TBool => write!(f, "bool"),
48            Atomic::TTrue => write!(f, "true"),
49            Atomic::TFalse => write!(f, "false"),
50
51            Atomic::TNull => write!(f, "null"),
52            Atomic::TVoid => write!(f, "void"),
53            Atomic::TNever => write!(f, "never"),
54            Atomic::TMixed => write!(f, "mixed"),
55            Atomic::TScalar => write!(f, "scalar"),
56            Atomic::TNumeric => write!(f, "numeric"),
57
58            Atomic::TObject => write!(f, "object"),
59            Atomic::TNamedObject { fqcn, type_params } => {
60                if type_params.is_empty() {
61                    write!(f, "{fqcn}")
62                } else {
63                    let params: Vec<String> = type_params.iter().map(|p| format!("{p}")).collect();
64                    write!(f, "{}<{}>", fqcn, params.join(", "))
65                }
66            }
67            Atomic::TStaticObject { fqcn } => write!(f, "static({fqcn})"),
68            Atomic::TSelf { fqcn } => write!(f, "self({fqcn})"),
69            Atomic::TParent { fqcn } => write!(f, "parent({fqcn})"),
70
71            Atomic::TCallable {
72                params: None,
73                return_type: None,
74            } => write!(f, "callable"),
75            Atomic::TCallable {
76                params: Some(params),
77                return_type,
78            } => {
79                let ps: Vec<String> = params
80                    .iter()
81                    .map(|p| {
82                        if let Some(ty) = &p.ty {
83                            format!("{ty}")
84                        } else {
85                            "mixed".to_string()
86                        }
87                    })
88                    .collect();
89                let ret = return_type
90                    .as_ref()
91                    .map_or_else(|| "mixed".to_string(), |r| format!("{r}"));
92                write!(f, "callable({}): {}", ps.join(", "), ret)
93            }
94            Atomic::TCallable {
95                params: None,
96                return_type: Some(ret),
97            } => {
98                write!(f, "callable(): {ret}")
99            }
100            Atomic::TClosure {
101                params,
102                return_type,
103                ..
104            } => {
105                let ps: Vec<String> = params
106                    .iter()
107                    .map(|p| {
108                        if let Some(ty) = &p.ty {
109                            format!("{ty}")
110                        } else {
111                            "mixed".to_string()
112                        }
113                    })
114                    .collect();
115                write!(f, "Closure({}): {}", ps.join(", "), return_type)
116            }
117
118            Atomic::TArray { key, value } => {
119                write!(f, "array<{key}, {value}>")
120            }
121            Atomic::TList { value } => write!(f, "list<{value}>"),
122            Atomic::TNonEmptyArray { key, value } => {
123                write!(f, "non-empty-array<{key}, {value}>")
124            }
125            Atomic::TNonEmptyList { value } => write!(f, "non-empty-list<{value}>"),
126            Atomic::TKeyedArray { properties, .. } => {
127                let entries: Vec<String> = properties
128                    .iter()
129                    .map(|(k, v)| {
130                        let key_str = match k {
131                            crate::atomic::ArrayKey::String(s) => format!("'{s}'"),
132                            crate::atomic::ArrayKey::Int(n) => n.to_string(),
133                        };
134                        let opt = if v.optional { "?" } else { "" };
135                        format!("{}{}: {}", key_str, opt, v.ty)
136                    })
137                    .collect();
138                write!(f, "array{{{}}}", entries.join(", "))
139            }
140
141            Atomic::TTemplateParam { name, .. } => write!(f, "{name}"),
142            Atomic::TConditional {
143                subject,
144                if_true,
145                if_false,
146            } => {
147                write!(f, "({subject} is ? {if_true} : {if_false})")
148            }
149
150            Atomic::TInterfaceString => write!(f, "interface-string"),
151            Atomic::TEnumString => write!(f, "enum-string"),
152            Atomic::TTraitString => write!(f, "trait-string"),
153            Atomic::TLiteralEnumCase {
154                enum_fqcn,
155                case_name,
156            } => {
157                write!(f, "{enum_fqcn}::{case_name}")
158            }
159
160            Atomic::TIntersection { parts } => {
161                let mut iter = parts.iter();
162                if let Some(first) = iter.next() {
163                    write!(f, "{first}")?;
164                    for part in iter {
165                        write!(f, "&{part}")?;
166                    }
167                }
168                Ok(())
169            }
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn int_range_unbounded_displays_as_int() {
180        assert_eq!(
181            format!(
182                "{}",
183                Atomic::TIntRange {
184                    min: None,
185                    max: None
186                }
187            ),
188            "int"
189        );
190    }
191
192    #[test]
193    fn int_range_bounded_min_displays_range() {
194        assert_eq!(
195            format!(
196                "{}",
197                Atomic::TIntRange {
198                    min: Some(0),
199                    max: None
200                }
201            ),
202            "int<0, max>"
203        );
204    }
205
206    #[test]
207    fn int_range_bounded_max_displays_range() {
208        assert_eq!(
209            format!(
210                "{}",
211                Atomic::TIntRange {
212                    min: None,
213                    max: Some(100)
214                }
215            ),
216            "int<min, 100>"
217        );
218    }
219
220    #[test]
221    fn int_range_fully_bounded_displays_range() {
222        assert_eq!(
223            format!(
224                "{}",
225                Atomic::TIntRange {
226                    min: Some(1),
227                    max: Some(10)
228                }
229            ),
230            "int<1, 10>"
231        );
232    }
233
234    #[test]
235    fn unbounded_int_range_in_union_displays_as_int() {
236        let mut u = Union::empty();
237        u.add_type(Atomic::TIntRange {
238            min: None,
239            max: None,
240        });
241        u.add_type(Atomic::TFalse);
242        assert_eq!(format!("{u}"), "int|false");
243    }
244}