1use strum::Display;
2
3use mago_span::HasSpan;
4use mago_span::Span;
5use mago_syntax_core::cst::Sequence;
6
7use crate::cst::Type;
8use crate::cst::generics::GenericParameters;
9use crate::cst::identifier::Identifier;
10use crate::cst::keyword::Keyword;
11
12#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Display)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14#[cfg_attr(feature = "serde", serde(tag = "type", content = "value"))]
15pub enum ShapeTypeKind {
16 Array,
17 NonEmptyArray,
18 AssociativeArray,
19 List,
20 NonEmptyList,
21}
22
23#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize))]
25pub struct ShapeType<'arena> {
26 pub kind: ShapeTypeKind,
27 pub keyword: Keyword<'arena>,
28 pub left_brace: Span,
29 pub fields: Sequence<'arena, ShapeField<'arena>>,
30 pub additional_fields: Option<ShapeAdditionalFields<'arena>>,
31 pub right_brace: Span,
32}
33
34#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36pub enum ShapeKey<'arena> {
37 String {
38 value: &'arena [u8],
39 span: Span,
40 },
41 Integer {
42 value: i64,
43 span: Span,
44 },
45 ClassLikeConstant {
46 class_name: Identifier<'arena>,
47 double_colon: Span,
48 constant_name: Identifier<'arena>,
49 span: Span,
50 },
51}
52
53#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize))]
55pub struct ShapeFieldKey<'arena> {
56 pub key: ShapeKey<'arena>,
57 pub question_mark: Option<Span>,
58 pub colon: Span,
59}
60
61#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63pub struct ShapeField<'arena> {
64 pub key: Option<ShapeFieldKey<'arena>>,
65 pub value: &'arena Type<'arena>,
66 pub comma: Option<Span>,
67}
68
69#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize))]
71pub struct ShapeAdditionalFields<'arena> {
72 pub ellipsis: Span,
73 pub parameters: Option<GenericParameters<'arena>>,
74 pub comma: Option<Span>,
75}
76
77impl ShapeTypeKind {
78 #[inline]
79 #[must_use]
80 pub const fn is_array(&self) -> bool {
81 matches!(self, ShapeTypeKind::Array | ShapeTypeKind::NonEmptyArray | ShapeTypeKind::AssociativeArray)
82 }
83
84 #[inline]
85 #[must_use]
86 pub const fn is_list(&self) -> bool {
87 matches!(self, ShapeTypeKind::List | ShapeTypeKind::NonEmptyList)
88 }
89
90 #[inline]
91 #[must_use]
92 pub const fn is_non_empty(&self) -> bool {
93 matches!(self, ShapeTypeKind::NonEmptyArray | ShapeTypeKind::NonEmptyList)
94 }
95}
96
97impl ShapeField<'_> {
98 #[inline]
99 #[must_use]
100 pub fn is_optional(&self) -> bool {
101 if let Some(key) = self.key.as_ref() { key.question_mark.is_some() } else { false }
102 }
103}
104
105impl ShapeType<'_> {
106 #[inline]
107 #[must_use]
108 pub fn has_fields(&self) -> bool {
109 !self.fields.is_empty()
110 }
111
112 #[inline]
113 #[must_use]
114 pub fn has_non_optional_fields(&self) -> bool {
115 self.fields.iter().any(|field| !field.is_optional())
116 }
117}
118
119impl HasSpan for ShapeType<'_> {
120 fn span(&self) -> Span {
121 self.keyword.span().join(self.right_brace)
122 }
123}
124
125impl HasSpan for ShapeKey<'_> {
126 fn span(&self) -> Span {
127 match self {
128 ShapeKey::String { span, .. } => *span,
129 ShapeKey::Integer { span, .. } => *span,
130 ShapeKey::ClassLikeConstant { span, .. } => *span,
131 }
132 }
133}
134
135impl HasSpan for ShapeFieldKey<'_> {
136 fn span(&self) -> Span {
137 self.key.span().join(self.colon)
138 }
139}
140
141impl HasSpan for ShapeField<'_> {
142 fn span(&self) -> Span {
143 if let Some(key) = &self.key {
144 if let Some(comma) = self.comma { key.span().join(comma) } else { key.span().join(self.value.span()) }
145 } else if let Some(comma) = self.comma {
146 self.value.span().join(comma)
147 } else {
148 self.value.span()
149 }
150 }
151}
152
153impl HasSpan for ShapeAdditionalFields<'_> {
154 fn span(&self) -> Span {
155 let span = match &self.parameters {
156 Some(generics) => self.ellipsis.join(generics.span()),
157 None => self.ellipsis,
158 };
159
160 if let Some(comma) = self.comma { span.join(comma) } else { span }
161 }
162}
163
164impl std::fmt::Display for ShapeKey<'_> {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 match self {
167 ShapeKey::String { value, .. } => f.write_str(&String::from_utf8_lossy(value)),
168 ShapeKey::Integer { value, .. } => write!(f, "{value}"),
169 ShapeKey::ClassLikeConstant { class_name, constant_name, .. } => {
170 write!(f, "{}::{}", class_name, constant_name)
171 }
172 }
173 }
174}
175
176impl std::fmt::Display for ShapeFieldKey<'_> {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 write!(f, "{}{}:", self.key, self.question_mark.as_ref().map_or("", |_| "?"))
179 }
180}
181
182impl std::fmt::Display for ShapeField<'_> {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 if let Some(key) = self.key.as_ref() {
185 write!(f, "{} {}", key, self.value)
186 } else {
187 write!(f, "{}", self.value)
188 }
189 }
190}
191
192impl std::fmt::Display for ShapeAdditionalFields<'_> {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 write!(f, "...")?;
195
196 if let Some(generics) = &self.parameters { write!(f, "{generics}") } else { Ok(()) }
197 }
198}
199
200impl std::fmt::Display for ShapeType<'_> {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 write!(f, "{}{{", self.keyword)?;
203
204 for (i, field) in self.fields.iter().enumerate() {
205 if i > 0 {
206 write!(f, ", ")?;
207 }
208
209 write!(f, "{field}")?;
210 }
211
212 if let Some(additional_fields) = &self.additional_fields {
213 if !self.fields.is_empty() {
214 write!(f, ", ")?;
215 }
216
217 write!(f, "{additional_fields}")?;
218 }
219
220 write!(f, "}}")
221 }
222}