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