php_ast/ast/names.rs
1use std::borrow::Cow;
2
3#[cfg(feature = "serde")]
4use serde::Serialize;
5
6use crate::Span;
7
8use super::ArenaVec;
9
10/// A bare identifier — the kind that names a function, class, parameter,
11/// enum case, etc. Distinct from [`Name`], which represents possibly-qualified
12/// names.
13///
14/// The "error" state — produced during error recovery when no identifier was
15/// found in the source — is represented by an empty name, which cannot occur
16/// for a real PHP identifier (the lexer rejects empty matches).
17///
18/// Use [`Ident::new`] / [`Ident::error`] to construct, [`Ident::as_str`] to
19/// extract a real name, [`Ident::is_error`] to test for the error state.
20/// Serialises as `{"name": .., "span": ..}` for real names and `null` for the
21/// error state.
22#[derive(Clone, Copy)]
23pub struct Ident<'src> {
24 name: &'src str,
25 /// Source range of the identifier token; includes the `$` of a variable name.
26 pub span: Span,
27}
28
29impl<'src> Ident<'src> {
30 /// Construct an identifier from a non-empty name.
31 /// Empty input is rejected in debug builds — use [`Ident::error`] instead.
32 #[inline]
33 pub fn new(name: &'src str, span: Span) -> Self {
34 debug_assert!(!name.is_empty(), "Ident::new() called with empty name");
35 Self { name, span }
36 }
37
38 /// Placeholder for "no identifier was parsed" at `span`.
39 #[inline]
40 pub const fn error(span: Span) -> Self {
41 Self { name: "", span }
42 }
43
44 /// Returns `Some(s)` for a real identifier, `None` for the error state.
45 #[inline]
46 pub fn as_str(&self) -> Option<&'src str> {
47 if self.name.is_empty() {
48 None
49 } else {
50 Some(self.name)
51 }
52 }
53
54 /// Returns `true` if this identifier was synthesised during error recovery.
55 #[inline]
56 pub fn is_error(&self) -> bool {
57 self.name.is_empty()
58 }
59
60 /// Returns the inner string, or `"<error>"` for the error state.
61 /// Useful when constructing diagnostic messages.
62 #[inline]
63 pub fn or_error(&self) -> &'src str {
64 if self.name.is_empty() {
65 "<error>"
66 } else {
67 self.name
68 }
69 }
70}
71
72impl<'src> std::fmt::Debug for Ident<'src> {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 if self.name.is_empty() {
75 write!(f, "Ident::error({:?})", self.span)
76 } else {
77 write!(f, "Ident({:?}, {:?})", self.name, self.span)
78 }
79 }
80}
81
82impl<'src> std::fmt::Display for Ident<'src> {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.write_str(self.or_error())
85 }
86}
87
88#[cfg(feature = "serde")]
89impl<'src> serde::Serialize for Ident<'src> {
90 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
91 use serde::ser::SerializeStruct;
92 if self.name.is_empty() {
93 return s.serialize_none();
94 }
95 let mut st = s.serialize_struct("Ident", 2)?;
96 st.serialize_field("name", self.name)?;
97 st.serialize_field("span", &self.span)?;
98 st.end()
99 }
100}
101
102impl<'src> PartialEq<&str> for Ident<'src> {
103 fn eq(&self, other: &&str) -> bool {
104 !self.name.is_empty() && self.name == *other
105 }
106}
107
108/// A PHP name (identifier, qualified name, fully-qualified name, or relative name).
109///
110/// The `Simple` variant is the fast path for the common case (~95%) of single
111/// unqualified identifiers like `strlen`, `Foo`, `MyClass`. It avoids allocating
112/// an `ArenaVec` entirely.
113///
114/// The `Complex` variant handles qualified (`Foo\Bar`), fully-qualified (`\Foo\Bar`),
115/// and relative (`namespace\Foo`) names.
116///
117/// The `Error` variant is synthesised during error recovery when the parser
118/// expected a name but found none. It carries only a span so consumers can
119/// distinguish it from any user-written name.
120#[non_exhaustive]
121pub enum Name<'arena, 'src> {
122 /// Single unqualified identifier — no `ArenaVec` allocation.
123 /// `&'src str` instead of `Cow` since this is always a borrowed slice of the source.
124 Simple {
125 /// The identifier text.
126 value: &'src str,
127 /// Source range of the name.
128 span: Span,
129 },
130 /// Multi-part or prefixed name (`Foo\Bar`, `\Foo`, `namespace\Foo`).
131 Complex {
132 /// Name segments split on `\`.
133 parts: ArenaVec<'arena, &'src str>,
134 /// Qualification form.
135 kind: NameKind,
136 /// Source range of the name.
137 span: Span,
138 },
139 /// Synthesised during error recovery when no real name could be parsed.
140 /// Distinguishable from any user-written name; visitors and tools can
141 /// explicitly skip or flag these.
142 Error {
143 /// Source range where a name was expected.
144 span: Span,
145 },
146}
147
148impl<'arena, 'src> Name<'arena, 'src> {
149 /// Source range of the name.
150 #[inline]
151 pub fn span(&self) -> Span {
152 match self {
153 Self::Simple { span, .. } | Self::Complex { span, .. } | Self::Error { span } => *span,
154 }
155 }
156
157 /// Qualification form of the name.
158 #[inline]
159 pub fn kind(&self) -> NameKind {
160 match self {
161 Self::Simple { .. } => NameKind::Unqualified,
162 Self::Complex { kind, .. } => *kind,
163 Self::Error { .. } => NameKind::Error,
164 }
165 }
166
167 /// Returns the name as a borrowed slice of the source string.
168 ///
169 /// Unlike `to_string_repr`, this never allocates: it uses the stored
170 /// span to slice directly into `src`. The slice includes any leading `\`
171 /// for fully-qualified names, exactly as it appears in the source.
172 ///
173 /// Use this when you need a zero-copy `&'src str` and already have the
174 /// source buffer available (e.g. inside [`crate::visitor::ScopeWalker`]).
175 #[inline]
176 pub fn src_repr(&self, src: &'src str) -> &'src str {
177 match self {
178 Self::Simple { value, .. } => value,
179 Self::Complex { span, .. } => &src[span.start as usize..span.end as usize],
180 Self::Error { .. } => "",
181 }
182 }
183
184 /// Joins all parts with `\` and prepends `\` if fully qualified.
185 /// Returns `Cow::Borrowed` for simple names (zero allocation).
186 /// Returns an empty `Cow::Borrowed("")` for `Name::Error`.
187 #[inline]
188 pub fn to_string_repr(&self) -> Cow<'src, str> {
189 match self {
190 Self::Simple { value, .. } => Cow::Borrowed(value),
191 Self::Complex { parts, kind, .. } => {
192 let joined = parts.join("\\");
193 if *kind == NameKind::FullyQualified {
194 Cow::Owned(format!("\\{}", joined))
195 } else {
196 Cow::Owned(joined)
197 }
198 }
199 Self::Error { .. } => Cow::Borrowed(""),
200 }
201 }
202
203 /// Joins all parts with `\` without any leading backslash.
204 /// Returns `Cow::Borrowed` for simple names (zero allocation).
205 /// Returns an empty `Cow::Borrowed("")` for `Name::Error`.
206 #[inline]
207 pub fn join_parts(&self) -> Cow<'src, str> {
208 match self {
209 Self::Simple { value, .. } => Cow::Borrowed(value),
210 Self::Complex { parts, .. } => Cow::Owned(parts.join("\\")),
211 Self::Error { .. } => Cow::Borrowed(""),
212 }
213 }
214
215 /// Returns the parts as a slice.
216 /// For `Simple`, returns a single-element slice of the value.
217 #[inline]
218 pub fn parts_slice(&self) -> &[&'src str] {
219 match self {
220 Self::Simple { value, .. } => std::slice::from_ref(value),
221 Self::Complex { parts, .. } => parts,
222 Self::Error { .. } => &[],
223 }
224 }
225}
226
227impl<'arena, 'src> std::fmt::Debug for Name<'arena, 'src> {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 match self {
230 Self::Simple { value, span } => f
231 .debug_struct("Name")
232 .field("parts", &std::slice::from_ref(value))
233 .field("kind", &NameKind::Unqualified)
234 .field("span", span)
235 .finish(),
236 Self::Complex { parts, kind, span } => f
237 .debug_struct("Name")
238 .field("parts", parts)
239 .field("kind", kind)
240 .field("span", span)
241 .finish(),
242 Self::Error { span } => {
243 let empty: [&str; 0] = [];
244 f.debug_struct("Name")
245 .field("parts", &empty)
246 .field("kind", &NameKind::Error)
247 .field("span", span)
248 .finish()
249 }
250 }
251 }
252}
253
254#[cfg(feature = "serde")]
255impl<'arena, 'src> serde::Serialize for Name<'arena, 'src> {
256 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
257 use serde::ser::SerializeStruct;
258 let mut st = s.serialize_struct("Name", 3)?;
259 match self {
260 Self::Simple { value, span } => {
261 st.serialize_field("parts", std::slice::from_ref(value))?;
262 st.serialize_field("kind", &NameKind::Unqualified)?;
263 st.serialize_field("span", span)?;
264 }
265 Self::Complex { parts, kind, span } => {
266 st.serialize_field("parts", parts)?;
267 st.serialize_field("kind", kind)?;
268 st.serialize_field("span", span)?;
269 }
270 Self::Error { span } => {
271 let empty: [&str; 0] = [];
272 st.serialize_field("parts", &empty[..])?;
273 st.serialize_field("kind", &NameKind::Error)?;
274 st.serialize_field("span", span)?;
275 }
276 }
277 st.end()
278 }
279}
280
281/// How a name is qualified.
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283#[cfg_attr(feature = "serde", derive(Serialize))]
284#[non_exhaustive]
285pub enum NameKind {
286 /// A bare identifier with no namespace separator: `Foo`, `strlen`.
287 Unqualified,
288 /// A name with at least one internal `\` but no leading backslash: `Foo\Bar`.
289 Qualified,
290 /// A name with a leading `\`: `\Foo\Bar`.
291 FullyQualified,
292 /// A name starting with the `namespace` keyword: `namespace\Foo`.
293 Relative,
294 /// Synthesised during error recovery — no real name was present in the source.
295 Error,
296}
297
298/// PHP built-in type keyword — compact alternative to `Name::Simple` for the
299/// 17 reserved type names.
300#[repr(u8)]
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302#[cfg_attr(feature = "serde", derive(Serialize))]
303#[non_exhaustive]
304pub enum BuiltinType {
305 /// `int` — integer scalar type.
306 Int,
307 /// `float` — floating-point scalar type.
308 Float,
309 /// `string` — string scalar type.
310 String,
311 /// `bool` — boolean scalar type.
312 Bool,
313 /// `void` — return-only type indicating no value is returned.
314 Void,
315 /// `never` — return-only type for functions that never return normally (PHP 8.1+).
316 Never,
317 /// `mixed` — top type; accepts any value.
318 Mixed,
319 /// `object` — any object instance.
320 Object,
321 /// `iterable` — `array` or `Traversable` (deprecated in PHP 8.2; use `array|Traversable`).
322 Iterable,
323 /// `callable` — any callable value.
324 Callable,
325 /// `array` — any PHP array.
326 Array,
327 /// `self` — refers to the class in which the type hint appears.
328 Self_,
329 /// `parent` — refers to the parent class of the class in which the type hint appears.
330 Parent_,
331 /// `static` — late-static-bound type; the class on which the method was called.
332 Static,
333 /// `null` — the null type; only valid in union types.
334 Null,
335 /// `true` — the literal boolean `true` (PHP 8.2+).
336 True,
337 /// `false` — the literal boolean `false`.
338 False,
339}
340
341impl BuiltinType {
342 /// Returns the canonical lowercase spelling used in PHP and in serialized output.
343 #[inline]
344 pub fn as_str(self) -> &'static str {
345 match self {
346 Self::Int => "int",
347 Self::Float => "float",
348 Self::String => "string",
349 Self::Bool => "bool",
350 Self::Void => "void",
351 Self::Never => "never",
352 Self::Mixed => "mixed",
353 Self::Object => "object",
354 Self::Iterable => "iterable",
355 Self::Callable => "callable",
356 Self::Array => "array",
357 Self::Self_ => "self",
358 Self::Parent_ => "parent",
359 Self::Static => "static",
360 Self::Null => "null",
361 Self::True => "true",
362 Self::False => "false",
363 }
364 }
365}
366
367/// A type declaration with its source range.
368#[derive(Debug)]
369#[cfg_attr(feature = "serde", derive(Serialize))]
370pub struct TypeHint<'arena, 'src> {
371 /// Type form.
372 pub kind: TypeHintKind<'arena, 'src>,
373 /// Source range of this node.
374 pub span: Span,
375}
376
377/// A PHP type hint.
378///
379/// `Keyword` is the fast path for the 17 built-in type names (`int`, `string`,
380/// `bool`, `self`, `array`, etc.). It stores only a 1-byte discriminant and a
381/// `Span` instead of a `Name`.
382///
383/// Serialises identically to `Named` so all existing snapshots remain unchanged.
384#[derive(Debug)]
385#[non_exhaustive]
386pub enum TypeHintKind<'arena, 'src> {
387 /// A user-defined or qualified class name: `Foo`, `\Ns\Bar`.
388 Named(Name<'arena, 'src>),
389 /// Built-in type keyword (`int`, `string`, `bool`, `self`, …) — serialises as `Named` for snapshot compatibility.
390 Keyword(BuiltinType, Span),
391 /// Nullable type: `?T` — equivalent to `T|null`.
392 Nullable(&'arena TypeHint<'arena, 'src>),
393 /// Union type: `A|B|C` (PHP 8.0+).
394 Union(ArenaVec<'arena, TypeHint<'arena, 'src>>),
395 /// Intersection type: `A&B` (PHP 8.1+).
396 Intersection(ArenaVec<'arena, TypeHint<'arena, 'src>>),
397}
398
399#[cfg(feature = "serde")]
400impl<'arena, 'src> serde::Serialize for TypeHintKind<'arena, 'src> {
401 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
402 match self {
403 // Standard variants — match what #[derive(Serialize)] would produce.
404 Self::Named(name) => s.serialize_newtype_variant("TypeHintKind", 0, "Named", name),
405 Self::Nullable(inner) => {
406 s.serialize_newtype_variant("TypeHintKind", 2, "Nullable", inner)
407 }
408 Self::Union(types) => s.serialize_newtype_variant("TypeHintKind", 3, "Union", types),
409 Self::Intersection(types) => {
410 s.serialize_newtype_variant("TypeHintKind", 4, "Intersection", types)
411 }
412 // Keyword — serialise as if it were Named(Name::Simple { value: kw.as_str(), span }).
413 // This preserves all existing snapshot output.
414 Self::Keyword(builtin, span) => {
415 struct BuiltinNameRepr<'a>(&'a BuiltinType, &'a Span);
416 impl serde::Serialize for BuiltinNameRepr<'_> {
417 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
418 use serde::ser::SerializeStruct;
419 let mut st = s.serialize_struct("Name", 3)?;
420 st.serialize_field("parts", &[self.0.as_str()])?;
421 st.serialize_field("kind", &NameKind::Unqualified)?;
422 st.serialize_field("span", self.1)?;
423 st.end()
424 }
425 }
426 s.serialize_newtype_variant(
427 "TypeHintKind",
428 0,
429 "Named",
430 &BuiltinNameRepr(builtin, span),
431 )
432 }
433 }
434 }
435}