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