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