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 => 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 => write!(f, "interface-string"),
154 Atomic::TEnumString => write!(f, "enum-string"),
155 Atomic::TTraitString => write!(f, "trait-string"),
156 Atomic::TLiteralEnumCase {
157 enum_fqcn,
158 case_name,
159 } => {
160 write!(f, "{enum_fqcn}::{case_name}")
161 }
162
163 Atomic::TIntersection { parts } => {
164 let mut iter = parts.iter();
165 if let Some(first) = iter.next() {
166 write!(f, "{first}")?;
167 for part in iter {
168 write!(f, "&{part}")?;
169 }
170 }
171 Ok(())
172 }
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn int_range_unbounded_displays_as_int() {
183 assert_eq!(
184 format!(
185 "{}",
186 Atomic::TIntRange {
187 min: None,
188 max: None
189 }
190 ),
191 "int"
192 );
193 }
194
195 #[test]
196 fn int_range_bounded_min_displays_range() {
197 assert_eq!(
198 format!(
199 "{}",
200 Atomic::TIntRange {
201 min: Some(0),
202 max: None
203 }
204 ),
205 "int<0, max>"
206 );
207 }
208
209 #[test]
210 fn int_range_bounded_max_displays_range() {
211 assert_eq!(
212 format!(
213 "{}",
214 Atomic::TIntRange {
215 min: None,
216 max: Some(100)
217 }
218 ),
219 "int<min, 100>"
220 );
221 }
222
223 #[test]
224 fn int_range_fully_bounded_displays_range() {
225 assert_eq!(
226 format!(
227 "{}",
228 Atomic::TIntRange {
229 min: Some(1),
230 max: Some(10)
231 }
232 ),
233 "int<1, 10>"
234 );
235 }
236
237 #[test]
238 fn unbounded_int_range_in_union_displays_as_int() {
239 let mut u = Type::empty();
240 u.add_type(Atomic::TIntRange {
241 min: None,
242 max: None,
243 });
244 u.add_type(Atomic::TFalse);
245 assert_eq!(format!("{u}"), "int|false");
246 }
247}