Skip to main content

mir_types/
display.rs

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