Skip to main content

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::Complex {
191                parts,
192                kind: NameKind::FullyQualified,
193                ..
194            } => Cow::Owned(fully_qualified(parts)),
195            _ => self.join_parts(),
196        }
197    }
198
199    /// Joins all parts with `\` without any leading backslash.
200    /// Returns `Cow::Borrowed` for single-part names (zero allocation).
201    /// Returns an empty `Cow::Borrowed("")` for `Name::Error`.
202    ///
203    /// Prefer [`Name::join_parts_in`] when the source is at hand: it also
204    /// borrows multi-part names.
205    #[inline]
206    pub fn join_parts(&self) -> Cow<'src, str> {
207        match self {
208            Self::Simple { value, .. } => Cow::Borrowed(value),
209            Self::Complex { parts, .. } if parts.len() == 1 => Cow::Borrowed(parts[0]),
210            Self::Complex { parts, .. } => Cow::Owned(parts.join("\\")),
211            Self::Error { .. } => Cow::Borrowed(""),
212        }
213    }
214
215    /// Same result as [`Name::join_parts`], but borrows from `src` (the source
216    /// the name was parsed from) whenever the name is written there without
217    /// whitespace or comments, which avoids allocating for multi-part names.
218    ///
219    /// Falls back to joining for names that are not contiguous in the source,
220    /// such as `Foo \ Bar` (valid before PHP 8.0) or `A\B` built from the
221    /// group use `use A\{B}`.
222    pub fn join_parts_in(&self, src: &'src str) -> Cow<'src, str> {
223        let Self::Complex { parts, .. } = self else {
224            return self.join_parts();
225        };
226        if parts.len() == 1 {
227            return Cow::Borrowed(parts[0]);
228        }
229        match contiguous_in(parts, src) {
230            Some(joined) => Cow::Borrowed(joined),
231            None => Cow::Owned(parts.join("\\")),
232        }
233    }
234
235    /// Returns the parts as a slice.
236    /// For `Simple`, returns a single-element slice of the value.
237    #[inline]
238    pub fn parts_slice(&self) -> &[&'src str] {
239        match self {
240            Self::Simple { value, .. } => std::slice::from_ref(value),
241            Self::Complex { parts, .. } => parts,
242            Self::Error { .. } => &[],
243        }
244    }
245}
246
247/// `parts` joined with `\` behind a leading `\`, in one allocation.
248pub(crate) fn fully_qualified<S: AsRef<str>>(parts: &[S]) -> String {
249    let mut s =
250        String::with_capacity(parts.iter().map(|p| p.as_ref().len() + 1).sum::<usize>() + 1);
251    s.push('\\');
252    for (i, part) in parts.iter().enumerate() {
253        if i > 0 {
254            s.push('\\');
255        }
256        s.push_str(part.as_ref());
257    }
258    s
259}
260
261/// The slice of `src` that reads `parts` joined with `\`, if the parts sit there back to back.
262fn contiguous_in<'src>(parts: &[&'src str], src: &'src str) -> Option<&'src str> {
263    let base = src.as_ptr() as usize;
264    let start = (parts.first()?.as_ptr() as usize).checked_sub(base)?;
265    let len = parts.iter().map(|p| p.len()).sum::<usize>() + parts.len() - 1;
266    let joined = src.get(start..start.checked_add(len)?)?;
267    let bytes = joined.as_bytes();
268    let mut offset = 0;
269    for (i, part) in parts.iter().enumerate() {
270        // Pointer identity, not text equality: a part at this exact offset is these bytes.
271        if part.as_ptr() as usize != base + start + offset {
272            return None;
273        }
274        offset += part.len();
275        if i + 1 < parts.len() {
276            if bytes[offset] != b'\\' {
277                return None;
278            }
279            offset += 1;
280        }
281    }
282    Some(joined)
283}
284
285impl<'arena, 'src> std::fmt::Debug for Name<'arena, 'src> {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        match self {
288            Self::Simple { value, span } => f
289                .debug_struct("Name")
290                .field("parts", &std::slice::from_ref(value))
291                .field("kind", &NameKind::Unqualified)
292                .field("span", span)
293                .finish(),
294            Self::Complex { parts, kind, span } => f
295                .debug_struct("Name")
296                .field("parts", parts)
297                .field("kind", kind)
298                .field("span", span)
299                .finish(),
300            Self::Error { span } => {
301                let empty: [&str; 0] = [];
302                f.debug_struct("Name")
303                    .field("parts", &empty)
304                    .field("kind", &NameKind::Error)
305                    .field("span", span)
306                    .finish()
307            }
308        }
309    }
310}
311
312#[cfg(feature = "serde")]
313impl<'arena, 'src> serde::Serialize for Name<'arena, 'src> {
314    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
315        use serde::ser::SerializeStruct;
316        let mut st = s.serialize_struct("Name", 3)?;
317        match self {
318            Self::Simple { value, span } => {
319                st.serialize_field("parts", std::slice::from_ref(value))?;
320                st.serialize_field("kind", &NameKind::Unqualified)?;
321                st.serialize_field("span", span)?;
322            }
323            Self::Complex { parts, kind, span } => {
324                st.serialize_field("parts", parts)?;
325                st.serialize_field("kind", kind)?;
326                st.serialize_field("span", span)?;
327            }
328            Self::Error { span } => {
329                let empty: [&str; 0] = [];
330                st.serialize_field("parts", &empty[..])?;
331                st.serialize_field("kind", &NameKind::Error)?;
332                st.serialize_field("span", span)?;
333            }
334        }
335        st.end()
336    }
337}
338
339/// How a name is qualified.
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341#[cfg_attr(feature = "serde", derive(Serialize))]
342#[non_exhaustive]
343pub enum NameKind {
344    /// A bare identifier with no namespace separator: `Foo`, `strlen`.
345    Unqualified,
346    /// A name with at least one internal `\` but no leading backslash: `Foo\Bar`.
347    Qualified,
348    /// A name with a leading `\`: `\Foo\Bar`.
349    FullyQualified,
350    /// A name starting with the `namespace` keyword: `namespace\Foo`.
351    Relative,
352    /// Synthesised during error recovery — no real name was present in the source.
353    Error,
354}
355
356/// PHP built-in type keyword — compact alternative to `Name::Simple` for the
357/// 17 reserved type names.
358#[repr(u8)]
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360#[cfg_attr(feature = "serde", derive(Serialize))]
361#[non_exhaustive]
362pub enum BuiltinType {
363    /// `int` — integer scalar type.
364    Int,
365    /// `float` — floating-point scalar type.
366    Float,
367    /// `string` — string scalar type.
368    String,
369    /// `bool` — boolean scalar type.
370    Bool,
371    /// `void` — return-only type indicating no value is returned.
372    Void,
373    /// `never` — return-only type for functions that never return normally (PHP 8.1+).
374    Never,
375    /// `mixed` — top type; accepts any value.
376    Mixed,
377    /// `object` — any object instance.
378    Object,
379    /// `iterable` — `array` or `Traversable` (deprecated in PHP 8.2; use `array|Traversable`).
380    Iterable,
381    /// `callable` — any callable value.
382    Callable,
383    /// `array` — any PHP array.
384    Array,
385    /// `self` — refers to the class in which the type hint appears.
386    Self_,
387    /// `parent` — refers to the parent class of the class in which the type hint appears.
388    Parent_,
389    /// `static` — late-static-bound type; the class on which the method was called.
390    Static,
391    /// `null` — the null type; only valid in union types.
392    Null,
393    /// `true` — the literal boolean `true` (PHP 8.2+).
394    True,
395    /// `false` — the literal boolean `false`.
396    False,
397}
398
399impl BuiltinType {
400    /// Returns the canonical lowercase spelling used in PHP and in serialized output.
401    #[inline]
402    pub fn as_str(self) -> &'static str {
403        match self {
404            Self::Int => "int",
405            Self::Float => "float",
406            Self::String => "string",
407            Self::Bool => "bool",
408            Self::Void => "void",
409            Self::Never => "never",
410            Self::Mixed => "mixed",
411            Self::Object => "object",
412            Self::Iterable => "iterable",
413            Self::Callable => "callable",
414            Self::Array => "array",
415            Self::Self_ => "self",
416            Self::Parent_ => "parent",
417            Self::Static => "static",
418            Self::Null => "null",
419            Self::True => "true",
420            Self::False => "false",
421        }
422    }
423}
424
425/// A type declaration with its source range.
426#[derive(Debug)]
427#[cfg_attr(feature = "serde", derive(Serialize))]
428pub struct TypeHint<'arena, 'src> {
429    /// Type form.
430    pub kind: TypeHintKind<'arena, 'src>,
431    /// Source range of this node.
432    pub span: Span,
433}
434
435/// A PHP type hint.
436///
437/// `Keyword` is the fast path for the 17 built-in type names (`int`, `string`,
438/// `bool`, `self`, `array`, etc.). It stores only a 1-byte discriminant and a
439/// `Span` instead of a `Name`.
440///
441/// Serialises identically to `Named` so all existing snapshots remain unchanged.
442#[derive(Debug)]
443#[non_exhaustive]
444pub enum TypeHintKind<'arena, 'src> {
445    /// A user-defined or qualified class name: `Foo`, `\Ns\Bar`.
446    Named(Name<'arena, 'src>),
447    /// Built-in type keyword (`int`, `string`, `bool`, `self`, …) — serialises as `Named` for snapshot compatibility.
448    Keyword(BuiltinType, Span),
449    /// Nullable type: `?T` — equivalent to `T|null`.
450    Nullable(&'arena TypeHint<'arena, 'src>),
451    /// Union type: `A|B|C` (PHP 8.0+).
452    Union(ArenaVec<'arena, TypeHint<'arena, 'src>>),
453    /// Intersection type: `A&B` (PHP 8.1+).
454    Intersection(ArenaVec<'arena, TypeHint<'arena, 'src>>),
455}
456
457#[cfg(feature = "serde")]
458impl<'arena, 'src> serde::Serialize for TypeHintKind<'arena, 'src> {
459    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
460        match self {
461            // Standard variants — match what #[derive(Serialize)] would produce.
462            Self::Named(name) => s.serialize_newtype_variant("TypeHintKind", 0, "Named", name),
463            Self::Nullable(inner) => {
464                s.serialize_newtype_variant("TypeHintKind", 2, "Nullable", inner)
465            }
466            Self::Union(types) => s.serialize_newtype_variant("TypeHintKind", 3, "Union", types),
467            Self::Intersection(types) => {
468                s.serialize_newtype_variant("TypeHintKind", 4, "Intersection", types)
469            }
470            // Keyword — serialise as if it were Named(Name::Simple { value: kw.as_str(), span }).
471            // This preserves all existing snapshot output.
472            Self::Keyword(builtin, span) => {
473                struct BuiltinNameRepr<'a>(&'a BuiltinType, &'a Span);
474                impl serde::Serialize for BuiltinNameRepr<'_> {
475                    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
476                        use serde::ser::SerializeStruct;
477                        let mut st = s.serialize_struct("Name", 3)?;
478                        st.serialize_field("parts", &[self.0.as_str()])?;
479                        st.serialize_field("kind", &NameKind::Unqualified)?;
480                        st.serialize_field("span", self.1)?;
481                        st.end()
482                    }
483                }
484                s.serialize_newtype_variant(
485                    "TypeHintKind",
486                    0,
487                    "Named",
488                    &BuiltinNameRepr(builtin, span),
489                )
490            }
491        }
492    }
493}