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}