Skip to main content

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.
127#[non_exhaustive]
128pub enum Name<'arena, 'src> {
129    /// Single unqualified identifier — no `ArenaVec` allocation.
130    /// `&'src str` instead of `Cow` since this is always a borrowed slice of the source.
131    Simple {
132        /// The identifier text.
133        value: &'src str,
134        /// Source range of the name.
135        span: Span,
136    },
137    /// Multi-part or prefixed name (`Foo\Bar`, `\Foo`, `namespace\Foo`).
138    Complex {
139        /// Name segments split on `\`.
140        parts: ArenaVec<'arena, &'src str>,
141        /// Qualification form.
142        kind: NameKind,
143        /// Source range of the name.
144        span: Span,
145    },
146    /// Synthesised during error recovery when no real name could be parsed.
147    /// Distinguishable from any user-written name; visitors and tools can
148    /// explicitly skip or flag these.
149    Error {
150        /// Source range where a name was expected.
151        span: Span,
152    },
153}
154
155impl<'arena, 'src> Name<'arena, 'src> {
156    /// Source range of the name.
157    #[inline]
158    pub fn span(&self) -> Span {
159        match self {
160            Self::Simple { span, .. } | Self::Complex { span, .. } | Self::Error { span } => *span,
161        }
162    }
163
164    /// Qualification form of the name.
165    #[inline]
166    pub fn kind(&self) -> NameKind {
167        match self {
168            Self::Simple { .. } => NameKind::Unqualified,
169            Self::Complex { kind, .. } => *kind,
170            Self::Error { .. } => NameKind::Error,
171        }
172    }
173
174    /// Returns the name as a borrowed slice of the source string.
175    ///
176    /// Unlike `to_string_repr`, this never allocates: it uses the stored
177    /// span to slice directly into `src`.  The slice includes any leading `\`
178    /// for fully-qualified names, exactly as it appears in the source.
179    ///
180    /// Use this when you need a zero-copy `&'src str` and already have the
181    /// source buffer available (e.g. inside [`crate::visitor::ScopeWalker`]).
182    #[inline]
183    pub fn src_repr(&self, src: &'src str) -> &'src str {
184        match self {
185            Self::Simple { value, .. } => value,
186            Self::Complex { span, .. } => &src[span.start as usize..span.end as usize],
187            Self::Error { .. } => "",
188        }
189    }
190
191    /// Joins all parts with `\` and prepends `\` if fully qualified.
192    /// Returns `Cow::Borrowed` for simple names (zero allocation).
193    /// Returns an empty `Cow::Borrowed("")` for `Name::Error`.
194    #[inline]
195    pub fn to_string_repr(&self) -> Cow<'src, str> {
196        match self {
197            Self::Simple { value, .. } => Cow::Borrowed(value),
198            Self::Complex { parts, kind, .. } => {
199                let joined = parts.join("\\");
200                if *kind == NameKind::FullyQualified {
201                    Cow::Owned(format!("\\{}", joined))
202                } else {
203                    Cow::Owned(joined)
204                }
205            }
206            Self::Error { .. } => Cow::Borrowed(""),
207        }
208    }
209
210    /// Joins all parts with `\` without any leading backslash.
211    /// Returns `Cow::Borrowed` for simple names (zero allocation).
212    /// Returns an empty `Cow::Borrowed("")` for `Name::Error`.
213    #[inline]
214    pub fn join_parts(&self) -> Cow<'src, str> {
215        match self {
216            Self::Simple { value, .. } => Cow::Borrowed(value),
217            Self::Complex { parts, .. } => Cow::Owned(parts.join("\\")),
218            Self::Error { .. } => Cow::Borrowed(""),
219        }
220    }
221
222    /// Returns the parts as a slice.
223    /// For `Simple`, returns a single-element slice of the value.
224    #[inline]
225    pub fn parts_slice(&self) -> &[&'src str] {
226        match self {
227            Self::Simple { value, .. } => std::slice::from_ref(value),
228            Self::Complex { parts, .. } => parts,
229            Self::Error { .. } => &[],
230        }
231    }
232}
233
234impl<'arena, 'src> std::fmt::Debug for Name<'arena, 'src> {
235    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236        match self {
237            Self::Simple { value, span } => f
238                .debug_struct("Name")
239                .field("parts", &std::slice::from_ref(value))
240                .field("kind", &NameKind::Unqualified)
241                .field("span", span)
242                .finish(),
243            Self::Complex { parts, kind, span } => f
244                .debug_struct("Name")
245                .field("parts", parts)
246                .field("kind", kind)
247                .field("span", span)
248                .finish(),
249            Self::Error { span } => {
250                let empty: [&str; 0] = [];
251                f.debug_struct("Name")
252                    .field("parts", &empty)
253                    .field("kind", &NameKind::Error)
254                    .field("span", span)
255                    .finish()
256            }
257        }
258    }
259}
260
261impl<'arena, 'src> serde::Serialize for Name<'arena, 'src> {
262    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
263        use serde::ser::SerializeStruct;
264        let mut st = s.serialize_struct("Name", 3)?;
265        match self {
266            Self::Simple { value, span } => {
267                st.serialize_field("parts", std::slice::from_ref(value))?;
268                st.serialize_field("kind", &NameKind::Unqualified)?;
269                st.serialize_field("span", span)?;
270            }
271            Self::Complex { parts, kind, span } => {
272                st.serialize_field("parts", parts)?;
273                st.serialize_field("kind", kind)?;
274                st.serialize_field("span", span)?;
275            }
276            Self::Error { span } => {
277                let empty: [&str; 0] = [];
278                st.serialize_field("parts", &empty[..])?;
279                st.serialize_field("kind", &NameKind::Error)?;
280                st.serialize_field("span", span)?;
281            }
282        }
283        st.end()
284    }
285}
286
287/// How a name is qualified.
288#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
289#[non_exhaustive]
290pub enum NameKind {
291    /// A bare identifier with no namespace separator: `Foo`, `strlen`.
292    Unqualified,
293    /// A name with at least one internal `\` but no leading backslash: `Foo\Bar`.
294    Qualified,
295    /// A name with a leading `\`: `\Foo\Bar`.
296    FullyQualified,
297    /// A name starting with the `namespace` keyword: `namespace\Foo`.
298    Relative,
299    /// Synthesised during error recovery — no real name was present in the source.
300    Error,
301}
302
303/// PHP built-in type keyword — zero-cost alternative to `Name::Simple` for the
304/// 20 reserved type names. One byte instead of a `Cow<str>` + `Span` in the AST.
305#[repr(u8)]
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
307#[non_exhaustive]
308pub enum BuiltinType {
309    /// `int` — integer scalar type.
310    Int,
311    /// `integer` — alias for `int`, accepted in type casts.
312    Integer,
313    /// `float` — floating-point scalar type.
314    Float,
315    /// `double` — alias for `float`, accepted in type casts.
316    Double,
317    /// `string` — string scalar type.
318    String,
319    /// `bool` — boolean scalar type.
320    Bool,
321    /// `boolean` — alias for `bool`, accepted in type casts.
322    Boolean,
323    /// `void` — return-only type indicating no value is returned.
324    Void,
325    /// `never` — return-only type for functions that never return normally (PHP 8.1+).
326    Never,
327    /// `mixed` — top type; accepts any value.
328    Mixed,
329    /// `object` — any object instance.
330    Object,
331    /// `iterable` — `array` or `Traversable` (deprecated in PHP 8.2; use `array|Traversable`).
332    Iterable,
333    /// `callable` — any callable value.
334    Callable,
335    /// `array` — any PHP array.
336    Array,
337    /// `self` — refers to the class in which the type hint appears.
338    Self_,
339    /// `parent` — refers to the parent class of the class in which the type hint appears.
340    Parent_,
341    /// `static` — late-static-bound type; the class on which the method was called.
342    Static,
343    /// `null` — the null type; only valid in union types.
344    Null,
345    /// `true` — the literal boolean `true` (PHP 8.2+).
346    True,
347    /// `false` — the literal boolean `false`.
348    False,
349}
350
351impl BuiltinType {
352    /// Returns the canonical lowercase spelling used in PHP and in serialized output.
353    #[inline]
354    pub fn as_str(self) -> &'static str {
355        match self {
356            Self::Int => "int",
357            Self::Integer => "integer",
358            Self::Float => "float",
359            Self::Double => "double",
360            Self::String => "string",
361            Self::Bool => "bool",
362            Self::Boolean => "boolean",
363            Self::Void => "void",
364            Self::Never => "never",
365            Self::Mixed => "mixed",
366            Self::Object => "object",
367            Self::Iterable => "iterable",
368            Self::Callable => "callable",
369            Self::Array => "array",
370            Self::Self_ => "self",
371            Self::Parent_ => "parent",
372            Self::Static => "static",
373            Self::Null => "null",
374            Self::True => "true",
375            Self::False => "false",
376        }
377    }
378}
379
380/// A type declaration with its source range.
381#[derive(Debug, Serialize)]
382pub struct TypeHint<'arena, 'src> {
383    /// Type form.
384    pub kind: TypeHintKind<'arena, 'src>,
385    /// Source range of this node.
386    pub span: Span,
387}
388
389/// A PHP type hint.
390///
391/// `Keyword` is the fast path for the 20 built-in type names (`int`, `string`,
392/// `bool`, `self`, `array`, etc.). It stores only a 1-byte discriminant and a
393/// `Span`, avoiding the `Cow<str>` that `Named(Name::Simple)` would require.
394///
395/// Serialises identically to `Named` so all existing snapshots remain unchanged.
396#[derive(Debug)]
397#[non_exhaustive]
398pub enum TypeHintKind<'arena, 'src> {
399    /// A user-defined or qualified class name: `Foo`, `\Ns\Bar`.
400    Named(Name<'arena, 'src>),
401    /// Built-in type keyword (`int`, `string`, `bool`, `self`, …) — serialises as `Named` for snapshot compatibility.
402    Keyword(BuiltinType, Span),
403    /// Nullable type: `?T` — equivalent to `T|null`.
404    Nullable(&'arena TypeHint<'arena, 'src>),
405    /// Union type: `A|B|C` (PHP 8.0+).
406    Union(ArenaVec<'arena, TypeHint<'arena, 'src>>),
407    /// Intersection type: `A&B` (PHP 8.1+).
408    Intersection(ArenaVec<'arena, TypeHint<'arena, 'src>>),
409}
410
411impl<'arena, 'src> serde::Serialize for TypeHintKind<'arena, 'src> {
412    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
413        match self {
414            // Standard variants — match what #[derive(Serialize)] would produce.
415            Self::Named(name) => s.serialize_newtype_variant("TypeHintKind", 0, "Named", name),
416            Self::Nullable(inner) => {
417                s.serialize_newtype_variant("TypeHintKind", 2, "Nullable", inner)
418            }
419            Self::Union(types) => s.serialize_newtype_variant("TypeHintKind", 3, "Union", types),
420            Self::Intersection(types) => {
421                s.serialize_newtype_variant("TypeHintKind", 4, "Intersection", types)
422            }
423            // Keyword — serialise as if it were Named(Name::Simple { value: kw.as_str(), span }).
424            // This preserves all existing snapshot output.
425            Self::Keyword(builtin, span) => {
426                struct BuiltinNameRepr<'a>(&'a BuiltinType, &'a Span);
427                impl serde::Serialize for BuiltinNameRepr<'_> {
428                    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
429                        use serde::ser::SerializeStruct;
430                        let mut st = s.serialize_struct("Name", 3)?;
431                        st.serialize_field("parts", &[self.0.as_str()])?;
432                        st.serialize_field("kind", &NameKind::Unqualified)?;
433                        st.serialize_field("span", self.1)?;
434                        st.end()
435                    }
436                }
437                s.serialize_newtype_variant(
438                    "TypeHintKind",
439                    0,
440                    "Named",
441                    &BuiltinNameRepr(builtin, span),
442                )
443            }
444        }
445    }
446}