Skip to main content

mir_types/
display.rs

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