Skip to main content

mago_type_syntax/
token.rs

1use serde::Deserialize;
2use serde::Serialize;
3use strum::Display;
4
5use mago_database::file::FileId;
6use mago_span::Position;
7use mago_span::Span;
8
9/// Type parsing precedence levels.
10///
11/// Lower ordinal values = lower precedence = binds more loosely.
12/// Higher ordinal values = higher precedence = binds more tightly.
13///
14/// For example, in `Closure(): int|string`:
15/// - With `Lowest` precedence: parses as `Union(Closure(): int, string)` (correct PHPStan/Psalm behavior)
16/// - Callable return types use `Callable` precedence, which stops before `|`, `&`, and `is`
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
18pub enum TypePrecedence {
19    /// Lowest precedence - parses everything including unions, intersections, conditionals
20    Lowest,
21    /// Conditional types: `T is U ? V : W`
22    Conditional,
23    /// Union types: `T|U`
24    Union,
25    /// Intersection types: `T&U`
26    Intersection,
27    /// Postfix operations: `T[]`, `T[K]`
28    Postfix,
29    /// Callable return type context - stops before `|`, `&`, `is`
30    Callable,
31}
32
33#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord, Display)]
34pub enum TypeTokenKind {
35    Int,
36    Integer,
37    String,
38    Float,
39    Real,
40    Double,
41    Bool,
42    Boolean,
43    False,
44    True,
45    Object,
46    Callable,
47    Array,
48    NonEmptyArray,
49    CallableString,
50    LowercaseCallableString,
51    UppercaseCallableString,
52    NonEmptyString,
53    NonEmptyLowercaseString,
54    NonEmptyUppercaseString,
55    NonFalsyString,
56    LowercaseString,
57    UppercaseString,
58    TruthyString,
59    Iterable,
60    Null,
61    Mixed,
62    NonEmptyMixed,
63    NumericString,
64    ClassString,
65    InterfaceString,
66    TraitString,
67    EnumString,
68    StringableObject,
69    PureCallable,
70    PureClosure,
71    UnspecifiedLiteralString,
72    UnspecifiedLiteralInt,
73    UnspecifiedLiteralFloat,
74    NonEmptyUnspecifiedLiteralString,
75    Resource,
76    Void,
77    Scalar,
78    Numeric,
79    NoReturn,
80    NeverReturn,
81    NeverReturns,
82    Never,
83    Nothing,
84    ArrayKey,
85    List,
86    NonEmptyList,
87    OpenResource,
88    ClosedResource,
89    AssociativeArray,
90    KeyOf,
91    ValueOf,
92    IntMask,
93    IntMaskOf,
94    New,
95    TemplateType,
96    Min,
97    Max,
98    PropertiesOf,
99    PublicPropertiesOf,
100    PrivatePropertiesOf,
101    ProtectedPropertiesOf,
102    PositiveInt,
103    NegativeInt,
104    NonPositiveInt,
105    NonNegativeInt,
106    NonZeroInt,
107    As,
108    Is,
109    Not,
110    Identifier,
111    QualifiedIdentifier,
112    FullyQualifiedIdentifier,
113    Plus,
114    Minus,
115    LessThan,
116    GreaterThan,
117    Pipe,
118    Ampersand,
119    Question,
120    Exclamation,
121    Comma,
122    Colon,
123    ColonColon,
124    LeftBrace,
125    RightBrace,
126    LeftBracket,
127    RightBracket,
128    LeftParenthesis,
129    RightParenthesis,
130    Equals,
131    Ellipsis,
132    PartialLiteralString,
133    LiteralString,
134    LiteralInteger,
135    LiteralFloat,
136    Variable,
137    Whitespace,
138    SingleLineComment,
139    Asterisk,
140}
141
142#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
143pub struct TypeToken<'input> {
144    pub kind: TypeTokenKind,
145    pub start: Position,
146    pub value: &'input str,
147}
148
149impl<'input> TypeToken<'input> {
150    /// Creates a new TypeToken.
151    #[inline]
152    #[must_use]
153    pub const fn new(kind: TypeTokenKind, value: &'input str, start: Position) -> Self {
154        Self { kind, start, value }
155    }
156
157    /// Computes the end position from start + value length.
158    #[inline]
159    #[must_use]
160    pub const fn end(&self) -> Position {
161        Position::new(self.start.offset + self.value.len() as u32)
162    }
163
164    /// Creates a span with the provided file_id.
165    #[inline]
166    #[must_use]
167    pub const fn span_for(&self, file_id: FileId) -> Span {
168        Span::new(file_id, self.start, self.end())
169    }
170}
171
172impl TypeTokenKind {
173    #[inline]
174    #[must_use]
175    pub const fn is_trivia(&self) -> bool {
176        matches!(self, Self::SingleLineComment | Self::Whitespace)
177    }
178
179    #[inline]
180    #[must_use]
181    pub const fn is_simple_identifier(&self) -> bool {
182        matches!(self, Self::Identifier)
183    }
184
185    #[inline]
186    #[must_use]
187    pub const fn is_identifier(&self) -> bool {
188        matches!(self, Self::Identifier | Self::QualifiedIdentifier | Self::FullyQualifiedIdentifier)
189    }
190
191    #[inline]
192    #[must_use]
193    pub const fn is_keyword(&self) -> bool {
194        matches!(
195            self,
196            Self::Int
197                | Self::Integer
198                | Self::Double
199                | Self::String
200                | Self::Float
201                | Self::Real
202                | Self::Bool
203                | Self::Boolean
204                | Self::False
205                | Self::True
206                | Self::Object
207                | Self::Callable
208                | Self::Array
209                | Self::NonEmptyArray
210                | Self::CallableString
211                | Self::LowercaseCallableString
212                | Self::UppercaseCallableString
213                | Self::NonEmptyString
214                | Self::NonEmptyLowercaseString
215                | Self::LowercaseString
216                | Self::NonEmptyUppercaseString
217                | Self::UppercaseString
218                | Self::TruthyString
219                | Self::NonFalsyString
220                | Self::Iterable
221                | Self::Null
222                | Self::Mixed
223                | Self::NonEmptyMixed
224                | Self::NumericString
225                | Self::ClassString
226                | Self::InterfaceString
227                | Self::TraitString
228                | Self::EnumString
229                | Self::StringableObject
230                | Self::PureCallable
231                | Self::PureClosure
232                | Self::UnspecifiedLiteralString
233                | Self::UnspecifiedLiteralFloat
234                | Self::NonEmptyUnspecifiedLiteralString
235                | Self::Resource
236                | Self::Void
237                | Self::Scalar
238                | Self::Numeric
239                | Self::NoReturn
240                | Self::NeverReturn
241                | Self::NeverReturns
242                | Self::Never
243                | Self::Nothing
244                | Self::ArrayKey
245                | Self::List
246                | Self::NonEmptyList
247                | Self::OpenResource
248                | Self::ClosedResource
249                | Self::AssociativeArray
250                | Self::Is
251                | Self::As
252                | Self::Not
253                | Self::KeyOf
254                | Self::ValueOf
255                | Self::IntMask
256                | Self::IntMaskOf
257                | Self::New
258                | Self::TemplateType
259                | Self::Min
260                | Self::Max
261                | Self::UnspecifiedLiteralInt
262                | Self::PropertiesOf
263                | Self::PublicPropertiesOf
264                | Self::PrivatePropertiesOf
265                | Self::ProtectedPropertiesOf
266                | Self::PositiveInt
267                | Self::NegativeInt
268                | Self::NonPositiveInt
269                | Self::NonNegativeInt
270                | Self::NonZeroInt
271        )
272    }
273
274    #[inline]
275    #[must_use]
276    pub const fn is_array_like(&self) -> bool {
277        matches!(self, Self::Array | Self::NonEmptyArray | Self::AssociativeArray | Self::List | Self::NonEmptyList)
278    }
279}