Skip to main content

mago_type_syntax/ast/
shape.rs

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