Skip to main content

mago_type_syntax/
token.rs

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