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