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 PHP name (identifier, qualified name, fully-qualified name, or relative name).
10///
11/// The `Simple` variant is the fast path for the common case (~95%) of single
12/// unqualified identifiers like `strlen`, `Foo`, `MyClass`. It avoids allocating
13/// an `ArenaVec` entirely.
14///
15/// The `Complex` variant handles qualified (`Foo\Bar`), fully-qualified (`\Foo\Bar`),
16/// and relative (`namespace\Foo`) names.
17pub enum Name<'arena, 'src> {
18    /// Single unqualified identifier — no `ArenaVec` allocation.
19    /// `&'src str` instead of `Cow` since this is always a borrowed slice of the source.
20    Simple { value: &'src str, span: Span },
21    /// Multi-part or prefixed name (`Foo\Bar`, `\Foo`, `namespace\Foo`).
22    Complex {
23        parts: ArenaVec<'arena, &'src str>,
24        kind: NameKind,
25        span: Span,
26    },
27}
28
29impl<'arena, 'src> Name<'arena, 'src> {
30    #[inline]
31    pub fn span(&self) -> Span {
32        match self {
33            Self::Simple { span, .. } | Self::Complex { span, .. } => *span,
34        }
35    }
36
37    #[inline]
38    pub fn kind(&self) -> NameKind {
39        match self {
40            Self::Simple { .. } => NameKind::Unqualified,
41            Self::Complex { kind, .. } => *kind,
42        }
43    }
44
45    /// Returns the name as a borrowed slice of the source string.
46    ///
47    /// Unlike [`to_string_repr`], this never allocates: it uses the stored
48    /// span to slice directly into `src`.  The slice includes any leading `\`
49    /// for fully-qualified names, exactly as it appears in the source.
50    ///
51    /// Use this when you need a zero-copy `&'src str` and already have the
52    /// source buffer available (e.g. inside [`crate::visitor::ScopeWalker`]).
53    #[inline]
54    pub fn src_repr(&self, src: &'src str) -> &'src str {
55        match self {
56            Self::Simple { value, .. } => value,
57            Self::Complex { span, .. } => &src[span.start as usize..span.end as usize],
58        }
59    }
60
61    /// Joins all parts with `\` and prepends `\` if fully qualified.
62    /// Returns `Cow::Borrowed` for simple names (zero allocation).
63    #[inline]
64    pub fn to_string_repr(&self) -> Cow<'src, str> {
65        match self {
66            Self::Simple { value, .. } => Cow::Borrowed(value),
67            Self::Complex { parts, kind, .. } => {
68                let joined = parts.join("\\");
69                if *kind == NameKind::FullyQualified {
70                    Cow::Owned(format!("\\{}", joined))
71                } else {
72                    Cow::Owned(joined)
73                }
74            }
75        }
76    }
77
78    /// Joins all parts with `\` without any leading backslash.
79    /// Returns `Cow::Borrowed` for simple names (zero allocation).
80    #[inline]
81    pub fn join_parts(&self) -> Cow<'src, str> {
82        match self {
83            Self::Simple { value, .. } => Cow::Borrowed(value),
84            Self::Complex { parts, .. } => Cow::Owned(parts.join("\\")),
85        }
86    }
87
88    /// Returns the parts as a slice.
89    /// For `Simple`, returns a single-element slice of the value.
90    #[inline]
91    pub fn parts_slice(&self) -> &[&'src str] {
92        match self {
93            Self::Simple { value, .. } => std::slice::from_ref(value),
94            Self::Complex { parts, .. } => parts,
95        }
96    }
97}
98
99impl<'arena, 'src> std::fmt::Debug for Name<'arena, 'src> {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            Self::Simple { value, span } => f
103                .debug_struct("Name")
104                .field("parts", &std::slice::from_ref(value))
105                .field("kind", &NameKind::Unqualified)
106                .field("span", span)
107                .finish(),
108            Self::Complex { parts, kind, span } => f
109                .debug_struct("Name")
110                .field("parts", parts)
111                .field("kind", kind)
112                .field("span", span)
113                .finish(),
114        }
115    }
116}
117
118impl<'arena, 'src> serde::Serialize for Name<'arena, 'src> {
119    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
120        use serde::ser::SerializeStruct;
121        let mut st = s.serialize_struct("Name", 3)?;
122        match self {
123            Self::Simple { value, span } => {
124                st.serialize_field("parts", std::slice::from_ref(value))?;
125                st.serialize_field("kind", &NameKind::Unqualified)?;
126                st.serialize_field("span", span)?;
127            }
128            Self::Complex { parts, kind, span } => {
129                st.serialize_field("parts", parts)?;
130                st.serialize_field("kind", kind)?;
131                st.serialize_field("span", span)?;
132            }
133        }
134        st.end()
135    }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
139pub enum NameKind {
140    /// A bare identifier with no namespace separator: `Foo`, `strlen`.
141    Unqualified,
142    /// A name with at least one internal `\` but no leading backslash: `Foo\Bar`.
143    Qualified,
144    /// A name with a leading `\`: `\Foo\Bar`.
145    FullyQualified,
146    /// A name starting with the `namespace` keyword: `namespace\Foo`.
147    Relative,
148}
149
150/// PHP built-in type keyword — zero-cost alternative to `Name::Simple` for the
151/// 20 reserved type names. One byte instead of a `Cow<str>` + `Span` in the AST.
152#[repr(u8)]
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
154pub enum BuiltinType {
155    /// `int` — integer scalar type.
156    Int,
157    /// `integer` — alias for `int`, accepted in type casts.
158    Integer,
159    /// `float` — floating-point scalar type.
160    Float,
161    /// `double` — alias for `float`, accepted in type casts.
162    Double,
163    /// `string` — string scalar type.
164    String,
165    /// `bool` — boolean scalar type.
166    Bool,
167    /// `boolean` — alias for `bool`, accepted in type casts.
168    Boolean,
169    /// `void` — return-only type indicating no value is returned.
170    Void,
171    /// `never` — return-only type for functions that never return normally (PHP 8.1+).
172    Never,
173    /// `mixed` — top type; accepts any value.
174    Mixed,
175    /// `object` — any object instance.
176    Object,
177    /// `iterable` — `array` or `Traversable` (deprecated in PHP 8.2; use `array|Traversable`).
178    Iterable,
179    /// `callable` — any callable value.
180    Callable,
181    /// `array` — any PHP array.
182    Array,
183    /// `self` — refers to the class in which the type hint appears.
184    Self_,
185    /// `parent` — refers to the parent class of the class in which the type hint appears.
186    Parent_,
187    /// `static` — late-static-bound type; the class on which the method was called.
188    Static,
189    /// `null` — the null type; only valid in union types.
190    Null,
191    /// `true` — the literal boolean `true` (PHP 8.2+).
192    True,
193    /// `false` — the literal boolean `false`.
194    False,
195}
196
197impl BuiltinType {
198    /// Returns the canonical lowercase spelling used in PHP and in serialized output.
199    #[inline]
200    pub fn as_str(self) -> &'static str {
201        match self {
202            Self::Int => "int",
203            Self::Integer => "integer",
204            Self::Float => "float",
205            Self::Double => "double",
206            Self::String => "string",
207            Self::Bool => "bool",
208            Self::Boolean => "boolean",
209            Self::Void => "void",
210            Self::Never => "never",
211            Self::Mixed => "mixed",
212            Self::Object => "object",
213            Self::Iterable => "iterable",
214            Self::Callable => "callable",
215            Self::Array => "array",
216            Self::Self_ => "self",
217            Self::Parent_ => "parent",
218            Self::Static => "static",
219            Self::Null => "null",
220            Self::True => "true",
221            Self::False => "false",
222        }
223    }
224}
225
226#[derive(Debug, Serialize)]
227pub struct TypeHint<'arena, 'src> {
228    pub kind: TypeHintKind<'arena, 'src>,
229    pub span: Span,
230}
231
232/// A PHP type hint.
233///
234/// `Keyword` is the fast path for the 20 built-in type names (`int`, `string`,
235/// `bool`, `self`, `array`, etc.). It stores only a 1-byte discriminant and a
236/// `Span`, avoiding the `Cow<str>` that `Named(Name::Simple)` would require.
237///
238/// Serialises identically to `Named` so all existing snapshots remain unchanged.
239#[derive(Debug)]
240pub enum TypeHintKind<'arena, 'src> {
241    /// A user-defined or qualified class name: `Foo`, `\Ns\Bar`.
242    Named(Name<'arena, 'src>),
243    /// Built-in type keyword (`int`, `string`, `bool`, `self`, …) — serialises as `Named` for snapshot compatibility.
244    Keyword(BuiltinType, Span),
245    /// Nullable type: `?T` — equivalent to `T|null`.
246    Nullable(&'arena TypeHint<'arena, 'src>),
247    /// Union type: `A|B|C` (PHP 8.0+).
248    Union(ArenaVec<'arena, TypeHint<'arena, 'src>>),
249    /// Intersection type: `A&B` (PHP 8.1+).
250    Intersection(ArenaVec<'arena, TypeHint<'arena, 'src>>),
251}
252
253impl<'arena, 'src> serde::Serialize for TypeHintKind<'arena, 'src> {
254    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
255        match self {
256            // Standard variants — match what #[derive(Serialize)] would produce.
257            Self::Named(name) => s.serialize_newtype_variant("TypeHintKind", 0, "Named", name),
258            Self::Nullable(inner) => {
259                s.serialize_newtype_variant("TypeHintKind", 2, "Nullable", inner)
260            }
261            Self::Union(types) => s.serialize_newtype_variant("TypeHintKind", 3, "Union", types),
262            Self::Intersection(types) => {
263                s.serialize_newtype_variant("TypeHintKind", 4, "Intersection", types)
264            }
265            // Keyword — serialise as if it were Named(Name::Simple { value: kw.as_str(), span }).
266            // This preserves all existing snapshot output.
267            Self::Keyword(builtin, span) => {
268                struct BuiltinNameRepr<'a>(&'a BuiltinType, &'a Span);
269                impl serde::Serialize for BuiltinNameRepr<'_> {
270                    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
271                        use serde::ser::SerializeStruct;
272                        let mut st = s.serialize_struct("Name", 3)?;
273                        st.serialize_field("parts", &[self.0.as_str()])?;
274                        st.serialize_field("kind", &NameKind::Unqualified)?;
275                        st.serialize_field("span", self.1)?;
276                        st.end()
277                    }
278                }
279                s.serialize_newtype_variant(
280                    "TypeHintKind",
281                    0,
282                    "Named",
283                    &BuiltinNameRepr(builtin, span),
284                )
285            }
286        }
287    }
288}