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}