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    NonEmptyString,
50    NonEmptyLowercaseString,
51    NonEmptyUppercaseString,
52    NonFalsyString,
53    LowercaseString,
54    UppercaseString,
55    TruthyString,
56    Iterable,
57    Null,
58    Mixed,
59    NonEmptyMixed,
60    NumericString,
61    ClassString,
62    InterfaceString,
63    TraitString,
64    EnumString,
65    StringableObject,
66    PureCallable,
67    PureClosure,
68    UnspecifiedLiteralString,
69    UnspecifiedLiteralInt,
70    UnspecifiedLiteralFloat,
71    NonEmptyUnspecifiedLiteralString,
72    Resource,
73    Void,
74    Scalar,
75    Numeric,
76    NoReturn,
77    NeverReturn,
78    NeverReturns,
79    Never,
80    Nothing,
81    ArrayKey,
82    List,
83    NonEmptyList,
84    OpenResource,
85    ClosedResource,
86    AssociativeArray,
87    KeyOf,
88    ValueOf,
89    IntMask,
90    IntMaskOf,
91    Min,
92    Max,
93    PropertiesOf,
94    PublicPropertiesOf,
95    PrivatePropertiesOf,
96    ProtectedPropertiesOf,
97    PositiveInt,
98    NegativeInt,
99    NonPositiveInt,
100    NonNegativeInt,
101    As,
102    Is,
103    Not,
104    Identifier,
105    QualifiedIdentifier,
106    FullyQualifiedIdentifier,
107    Plus,
108    Minus,
109    LessThan,
110    GreaterThan,
111    Pipe,
112    Ampersand,
113    Question,
114    Exclamation,
115    Comma,
116    Colon,
117    ColonColon,
118    LeftBrace,
119    RightBrace,
120    LeftBracket,
121    RightBracket,
122    LeftParenthesis,
123    RightParenthesis,
124    Equals,
125    Ellipsis,
126    PartialLiteralString,
127    LiteralString,
128    LiteralInteger,
129    LiteralFloat,
130    Variable,
131    Whitespace,
132    SingleLineComment,
133    Asterisk,
134}
135
136#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
137pub struct TypeToken<'input> {
138    pub kind: TypeTokenKind,
139    pub start: Position,
140    pub value: &'input str,
141}
142
143impl<'input> TypeToken<'input> {
144    /// Creates a new TypeToken.
145    #[inline]
146    #[must_use]
147    pub const fn new(kind: TypeTokenKind, value: &'input str, start: Position) -> Self {
148        Self { kind, start, value }
149    }
150
151    /// Computes the end position from start + value length.
152    #[inline]
153    #[must_use]
154    pub const fn end(&self) -> Position {
155        Position::new(self.start.offset + self.value.len() as u32)
156    }
157
158    /// Creates a span with the provided file_id.
159    #[inline]
160    #[must_use]
161    pub const fn span_for(&self, file_id: FileId) -> Span {
162        Span::new(file_id, self.start, self.end())
163    }
164}
165
166impl TypeTokenKind {
167    #[inline]
168    #[must_use]
169    pub const fn is_trivia(&self) -> bool {
170        matches!(self, Self::SingleLineComment | Self::Whitespace)
171    }
172
173    #[inline]
174    #[must_use]
175    pub const fn is_simple_identifier(&self) -> bool {
176        matches!(self, Self::Identifier)
177    }
178
179    #[inline]
180    #[must_use]
181    pub const fn is_identifier(&self) -> bool {
182        matches!(self, Self::Identifier | Self::QualifiedIdentifier | Self::FullyQualifiedIdentifier)
183    }
184
185    #[inline]
186    #[must_use]
187    pub const fn is_keyword(&self) -> bool {
188        matches!(
189            self,
190            Self::Int
191                | Self::Integer
192                | Self::Double
193                | Self::String
194                | Self::Float
195                | Self::Real
196                | Self::Bool
197                | Self::Boolean
198                | Self::False
199                | Self::True
200                | Self::Object
201                | Self::Callable
202                | Self::Array
203                | Self::NonEmptyArray
204                | Self::NonEmptyString
205                | Self::NonEmptyLowercaseString
206                | Self::LowercaseString
207                | Self::NonEmptyUppercaseString
208                | Self::UppercaseString
209                | Self::TruthyString
210                | Self::NonFalsyString
211                | Self::Iterable
212                | Self::Null
213                | Self::Mixed
214                | Self::NonEmptyMixed
215                | Self::NumericString
216                | Self::ClassString
217                | Self::InterfaceString
218                | Self::TraitString
219                | Self::EnumString
220                | Self::StringableObject
221                | Self::PureCallable
222                | Self::PureClosure
223                | Self::UnspecifiedLiteralString
224                | Self::UnspecifiedLiteralFloat
225                | Self::NonEmptyUnspecifiedLiteralString
226                | Self::Resource
227                | Self::Void
228                | Self::Scalar
229                | Self::Numeric
230                | Self::NoReturn
231                | Self::NeverReturn
232                | Self::NeverReturns
233                | Self::Never
234                | Self::Nothing
235                | Self::ArrayKey
236                | Self::List
237                | Self::NonEmptyList
238                | Self::OpenResource
239                | Self::ClosedResource
240                | Self::AssociativeArray
241                | Self::Is
242                | Self::As
243                | Self::Not
244                | Self::KeyOf
245                | Self::ValueOf
246                | Self::IntMask
247                | Self::IntMaskOf
248                | Self::Min
249                | Self::Max
250                | Self::UnspecifiedLiteralInt
251                | Self::PropertiesOf
252                | Self::PublicPropertiesOf
253                | Self::PrivatePropertiesOf
254                | Self::ProtectedPropertiesOf
255                | Self::PositiveInt
256                | Self::NegativeInt
257                | Self::NonPositiveInt
258                | Self::NonNegativeInt
259        )
260    }
261
262    #[inline]
263    #[must_use]
264    pub const fn is_array_like(&self) -> bool {
265        matches!(self, Self::Array | Self::NonEmptyArray | Self::AssociativeArray | Self::List | Self::NonEmptyList)
266    }
267}