1use std::fmt;
2
3use crate::atomic::Atomic;
4use crate::union::Type;
5
6fn 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
22fn 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}