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