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