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