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/// 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}