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