Skip to main content

php_ast/ast/
decls.rs

1#[cfg(feature = "serde")]
2use serde::Serialize;
3
4use crate::Span;
5
6use super::{ArenaVec, Attribute, Block, Comment, Expr, Ident, Name, TypeHint};
7
8/// A named function declaration: `function foo() {}`.
9#[derive(Debug)]
10#[cfg_attr(feature = "serde", derive(Serialize))]
11#[non_exhaustive]
12pub struct FunctionDecl<'arena, 'src> {
13    /// Function name.
14    pub name: Ident<'src>,
15    /// Declared parameters.
16    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
17    /// `true` when the parameter list is missing its `(`, which PHP rejects.
18    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
19    pub missing_param_list: bool,
20    /// Function body.
21    pub body: &'arena Block<'arena, 'src>,
22    /// Declared return type, if any.
23    pub return_type: Option<TypeHint<'arena, 'src>>,
24    /// `true` when by reference (`&`).
25    pub by_ref: bool,
26    /// `#[...]` attributes applied to this node.
27    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
28    /// The immediately preceding `/** */` doc-block, if any.
29    ///
30    /// When present, this comment is **removed** from
31    /// `ParseResult::comments` — the
32    /// two collections are disjoint. All other comment forms (line, hash,
33    /// block) remain in `ParseResult::comments` regardless of position.
34    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
35    pub doc_comment: Option<Comment<'src>>,
36}
37
38impl<'arena, 'src> FunctionDecl<'arena, 'src> {
39    /// Builds a `FunctionDecl` from its identifying fields with every other field empty. Set
40    /// the rest by assignment, as with `Param::new`.
41    pub fn new(
42        arena: &'arena bumpalo::Bump,
43        name: Ident<'src>,
44        body: &'arena Block<'arena, 'src>,
45    ) -> Self {
46        Self {
47            name,
48            params: ArenaVec::new_in(arena),
49            missing_param_list: false,
50            body,
51            return_type: None,
52            by_ref: false,
53            attributes: ArenaVec::new_in(arena),
54            doc_comment: None,
55        }
56    }
57}
58
59/// A function, method, closure or hook parameter.
60#[derive(Debug)]
61#[cfg_attr(feature = "serde", derive(Serialize))]
62#[non_exhaustive]
63pub struct Param<'arena, 'src> {
64    /// Parameter name without `$`.
65    pub name: Ident<'src>,
66    /// Declared parameter type.
67    pub type_hint: Option<TypeHint<'arena, 'src>>,
68    /// Default value expression.
69    pub default: Option<Expr<'arena, 'src>>,
70    /// `true` when by reference (`&`).
71    pub by_ref: bool,
72    /// `true` for a variadic parameter (`...$x`).
73    pub variadic: bool,
74    /// Visibility, `static`, `final`, `abstract` and `readonly` modifiers.
75    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Modifiers::is_empty"))]
76    pub modifiers: Modifiers,
77    /// `#[...]` attributes applied to this node.
78    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
79    /// Property hooks on a promoted property (PHP 8.4+).
80    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "ArenaVec::is_empty"))]
81    pub hooks: ArenaVec<'arena, PropertyHook<'arena, 'src>>,
82    /// `true` for an empty hook list, `{}`, which PHP rejects.
83    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
84    pub empty_hook_list: bool,
85    /// A doc-block attached directly to this parameter (PHP 8.6 recognizes
86    /// this for reflection purposes), either immediately before it or
87    /// trailing it up to the comma. Parsed at every version since a bare
88    /// comment is legal PHP regardless of target version.
89    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
90    pub doc_comment: Option<Comment<'src>>,
91    /// Source range of this node.
92    pub span: Span,
93}
94
95impl<'arena, 'src> Param<'arena, 'src> {
96    /// A parameter with `name` and `span` set and every other field empty
97    /// (`None`, `false`, no attributes or hooks). The fields are public, so set
98    /// the rest by assignment; `Param` is `#[non_exhaustive]` and cannot be
99    /// built with a struct literal outside this crate.
100    pub fn new(arena: &'arena bumpalo::Bump, name: Ident<'src>, span: Span) -> Self {
101        Self {
102            name,
103            type_hint: None,
104            default: None,
105            by_ref: false,
106            variadic: false,
107            modifiers: Modifiers::new(),
108            attributes: ArenaVec::new_in(arena),
109            hooks: ArenaVec::new_in(arena),
110            empty_hook_list: false,
111            doc_comment: None,
112            span,
113        }
114    }
115}
116
117/// The modifiers of a member or promoted parameter. Which flags are valid depends on the node
118/// that carries them.
119#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
120#[cfg_attr(feature = "serde", derive(Serialize))]
121#[non_exhaustive]
122pub struct Modifiers {
123    /// Declared visibility, if any.
124    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
125    pub visibility: Option<Visibility>,
126    /// Asymmetric write visibility, e.g. `private(set)` (PHP 8.4+).
127    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
128    pub set_visibility: Option<Visibility>,
129    /// `true` for `static`.
130    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
131    pub is_static: bool,
132    /// `true` for `abstract`.
133    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
134    pub is_abstract: bool,
135    /// `true` for `final`.
136    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
137    pub is_final: bool,
138    /// `true` for `readonly`.
139    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
140    pub is_readonly: bool,
141    /// `true` for a property declared with `var`, whose `visibility` is `Public`.
142    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
143    pub is_var: bool,
144}
145
146impl Modifiers {
147    /// No modifiers. The fields are public, so set the ones that apply by assignment;
148    /// `Modifiers` is `#[non_exhaustive]` and cannot be built with a struct literal outside
149    /// this crate.
150    pub const fn new() -> Self {
151        Self {
152            visibility: None,
153            set_visibility: None,
154            is_static: false,
155            is_abstract: false,
156            is_final: false,
157            is_readonly: false,
158            is_var: false,
159        }
160    }
161
162    /// Whether no modifier is set.
163    pub fn is_empty(&self) -> bool {
164        *self == Self::new()
165    }
166}
167
168/// Member visibility modifier.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170#[cfg_attr(feature = "serde", derive(Serialize))]
171#[non_exhaustive]
172pub enum Visibility {
173    /// `public` — accessible from anywhere.
174    Public,
175    /// `protected` — accessible within the class and its subclasses.
176    Protected,
177    /// `private` — accessible only within the declaring class.
178    Private,
179}
180
181/// The braced member list of a class-like declaration.
182#[derive(Debug)]
183#[cfg_attr(feature = "serde", derive(Serialize))]
184pub struct ClassBody<'arena, 'src> {
185    /// Class members in source order.
186    pub members: ArenaVec<'arena, ClassMember<'arena, 'src>>,
187    /// Span covering `{` to `}` of the body.
188    #[cfg_attr(feature = "serde", serde(skip))]
189    pub span: Span,
190}
191
192/// A class declaration, or the body of an anonymous class.
193#[derive(Debug)]
194#[cfg_attr(feature = "serde", derive(Serialize))]
195#[non_exhaustive]
196pub struct ClassDecl<'arena, 'src> {
197    /// Class name; `None` for anonymous classes.
198    pub name: Option<Ident<'src>>,
199    /// `abstract`, `final` and `readonly` modifiers.
200    pub modifiers: ClassModifiers,
201    /// Parent class.
202    pub extends: Option<Name<'arena, 'src>>,
203    /// Implemented interfaces.
204    pub implements: ArenaVec<'arena, Name<'arena, 'src>>,
205    /// Class body, flattened on serialization.
206    #[cfg_attr(feature = "serde", serde(flatten))]
207    pub body: ClassBody<'arena, 'src>,
208    /// `#[...]` attributes applied to this node.
209    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
210    /// Preceding `/** */` doc-block, if any.
211    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
212    pub doc_comment: Option<Comment<'src>>,
213}
214
215impl<'arena, 'src> ClassDecl<'arena, 'src> {
216    /// Builds a `ClassDecl` from its identifying fields with every other field empty. Set
217    /// the rest by assignment, as with `Param::new`.
218    pub fn new(
219        arena: &'arena bumpalo::Bump,
220        name: Option<Ident<'src>>,
221        body: ClassBody<'arena, 'src>,
222    ) -> Self {
223        Self {
224            name,
225            modifiers: ClassModifiers::default(),
226            extends: None,
227            implements: ArenaVec::new_in(arena),
228            body,
229            attributes: ArenaVec::new_in(arena),
230            doc_comment: None,
231        }
232    }
233}
234
235/// Modifiers preceding `class`.
236#[derive(Debug, Clone, Default)]
237#[cfg_attr(feature = "serde", derive(Serialize))]
238#[non_exhaustive]
239pub struct ClassModifiers {
240    /// `true` for `abstract class`.
241    pub is_abstract: bool,
242    /// `true` for `final class`.
243    pub is_final: bool,
244    /// `true` for `readonly class`.
245    pub is_readonly: bool,
246}
247
248/// A member of a class, interface or trait body.
249#[derive(Debug)]
250#[cfg_attr(feature = "serde", derive(Serialize))]
251pub struct ClassMember<'arena, 'src> {
252    /// Kind of member.
253    pub kind: ClassMemberKind<'arena, 'src>,
254    /// Source range of this node.
255    pub span: Span,
256}
257
258/// The kinds of class-like member.
259#[derive(Debug)]
260#[cfg_attr(feature = "serde", derive(Serialize))]
261#[non_exhaustive]
262pub enum ClassMemberKind<'arena, 'src> {
263    /// Property declaration.
264    Property(PropertyDecl<'arena, 'src>),
265    /// Method declaration.
266    Method(MethodDecl<'arena, 'src>),
267    /// Class constant declaration.
268    ClassConst(ClassConstDecl<'arena, 'src>),
269    /// Trait `use` statement.
270    TraitUse(TraitUseDecl<'arena, 'src>),
271}
272
273/// A property declaration: `public int $x = 0;`.
274#[derive(Debug)]
275#[cfg_attr(feature = "serde", derive(Serialize))]
276#[non_exhaustive]
277pub struct PropertyDecl<'arena, 'src> {
278    /// Property name without `$`.
279    pub name: Ident<'src>,
280    /// Visibility, `static`, `final`, `abstract` and `readonly` modifiers.
281    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Modifiers::is_empty"))]
282    pub modifiers: Modifiers,
283    /// Declared type, if any.
284    pub type_hint: Option<TypeHint<'arena, 'src>>,
285    /// Default value expression.
286    pub default: Option<Expr<'arena, 'src>>,
287    /// `#[...]` attributes applied to this node.
288    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
289    /// Property hooks (PHP 8.4+).
290    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "ArenaVec::is_empty"))]
291    pub hooks: ArenaVec<'arena, PropertyHook<'arena, 'src>>,
292    /// `true` for an empty hook list, `{}`, which PHP rejects.
293    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
294    pub empty_hook_list: bool,
295    /// Preceding `/** */` doc-block, if any.
296    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
297    pub doc_comment: Option<Comment<'src>>,
298}
299
300impl<'arena, 'src> PropertyDecl<'arena, 'src> {
301    /// Builds a `PropertyDecl` from its identifying fields with every other field empty. Set
302    /// the rest by assignment, as with `Param::new`.
303    pub fn new(arena: &'arena bumpalo::Bump, name: Ident<'src>) -> Self {
304        Self {
305            name,
306            modifiers: Modifiers::new(),
307            type_hint: None,
308            default: None,
309            attributes: ArenaVec::new_in(arena),
310            hooks: ArenaVec::new_in(arena),
311            empty_hook_list: false,
312            doc_comment: None,
313        }
314    }
315}
316
317/// Which accessor a property hook implements.
318#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319#[cfg_attr(feature = "serde", derive(Serialize))]
320#[non_exhaustive]
321pub enum PropertyHookKind {
322    /// `get` hook — called when the property is read.
323    Get,
324    /// `set` hook — called when the property is written; receives the incoming value as `$value`.
325    Set,
326}
327
328/// The body form of a property hook.
329#[derive(Debug)]
330#[cfg_attr(feature = "serde", derive(Serialize))]
331#[non_exhaustive]
332pub enum PropertyHookBody<'arena, 'src> {
333    /// `{ stmts }` — a full statement block.
334    Block(&'arena Block<'arena, 'src>),
335    /// `=> expr` — short-form expression body.
336    Expression(Expr<'arena, 'src>),
337    /// No body — the hook is declared abstract (on an abstract class or interface).
338    Abstract,
339}
340
341/// A property hook: `get { ... }` or `set => ...` (PHP 8.4+).
342#[derive(Debug)]
343#[cfg_attr(feature = "serde", derive(Serialize))]
344#[non_exhaustive]
345pub struct PropertyHook<'arena, 'src> {
346    /// Whether this is a `get` or `set` hook.
347    pub kind: PropertyHookKind,
348    /// Hook body.
349    pub body: PropertyHookBody<'arena, 'src>,
350    /// Visibility, `static`, `final`, `abstract` and `readonly` modifiers.
351    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Modifiers::is_empty"))]
352    pub modifiers: Modifiers,
353    /// `true` for `&get`.
354    pub by_ref: bool,
355    /// Explicit parameters, e.g. `set(string $v)`.
356    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
357    /// `true` for an empty `()` parameter list, which PHP rejects on both hooks.
358    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
359    pub empty_param_list: bool,
360    /// `#[...]` attributes applied to this node.
361    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
362    /// The `get` or `set` keyword. Printer layout detail, outside semver; not serialized.
363    #[cfg_attr(feature = "serde", serde(skip))]
364    #[doc(hidden)]
365    pub name_span: Span,
366    /// Source range of this node.
367    pub span: Span,
368}
369
370impl<'arena, 'src> PropertyHook<'arena, 'src> {
371    /// Builds a `PropertyHook` from its identifying fields with every other field empty. Set
372    /// the rest by assignment, as with `Param::new`.
373    pub fn new(
374        arena: &'arena bumpalo::Bump,
375        kind: PropertyHookKind,
376        body: PropertyHookBody<'arena, 'src>,
377        span: Span,
378    ) -> Self {
379        Self {
380            kind,
381            body,
382            modifiers: Modifiers::new(),
383            by_ref: false,
384            params: ArenaVec::new_in(arena),
385            empty_param_list: false,
386            attributes: ArenaVec::new_in(arena),
387            name_span: Span::DUMMY,
388            span,
389        }
390    }
391}
392
393/// A method declaration inside a class-like body.
394#[derive(Debug)]
395#[cfg_attr(feature = "serde", derive(Serialize))]
396#[non_exhaustive]
397pub struct MethodDecl<'arena, 'src> {
398    /// Method name.
399    pub name: Ident<'src>,
400    /// Visibility, `static`, `final`, `abstract` and `readonly` modifiers.
401    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Modifiers::is_empty"))]
402    pub modifiers: Modifiers,
403    /// `true` when by reference (`&`).
404    pub by_ref: bool,
405    /// Declared parameters.
406    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
407    /// `true` when the parameter list is missing its `(`, which PHP rejects.
408    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
409    pub missing_param_list: bool,
410    /// Declared return type, if any.
411    pub return_type: Option<TypeHint<'arena, 'src>>,
412    /// `None` for an abstract/interface method.
413    pub body: Option<&'arena Block<'arena, 'src>>,
414    /// `#[...]` attributes applied to this node.
415    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
416    /// Preceding `/** */` doc-block, if any.
417    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
418    pub doc_comment: Option<Comment<'src>>,
419}
420
421impl<'arena, 'src> MethodDecl<'arena, 'src> {
422    /// A method with `name` set and every other field empty (no modifiers,
423    /// parameters, return type, body or attributes). Set the rest by
424    /// assignment; see [`Param::new`].
425    pub fn new(arena: &'arena bumpalo::Bump, name: Ident<'src>) -> Self {
426        Self {
427            name,
428            modifiers: Modifiers::new(),
429            by_ref: false,
430            params: ArenaVec::new_in(arena),
431            missing_param_list: false,
432            return_type: None,
433            body: None,
434            attributes: ArenaVec::new_in(arena),
435            doc_comment: None,
436        }
437    }
438}
439
440/// A class constant: `const X = 1;`.
441#[derive(Debug)]
442#[cfg_attr(feature = "serde", derive(Serialize))]
443#[non_exhaustive]
444pub struct ClassConstDecl<'arena, 'src> {
445    /// Constant name.
446    pub name: Ident<'src>,
447    /// Visibility, `static`, `final`, `abstract` and `readonly` modifiers.
448    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Modifiers::is_empty"))]
449    pub modifiers: Modifiers,
450    /// Typed class constant type (PHP 8.3+).
451    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
452    pub type_hint: Option<&'arena TypeHint<'arena, 'src>>,
453    /// Constant value expression.
454    pub value: Expr<'arena, 'src>,
455    /// `#[...]` attributes applied to this node.
456    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
457    /// Preceding `/** */` doc-block, if any.
458    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
459    pub doc_comment: Option<Comment<'src>>,
460}
461
462impl<'arena, 'src> ClassConstDecl<'arena, 'src> {
463    /// Builds a `ClassConstDecl` from its identifying fields with every other field empty. Set
464    /// the rest by assignment, as with `Param::new`.
465    pub fn new(arena: &'arena bumpalo::Bump, name: Ident<'src>, value: Expr<'arena, 'src>) -> Self {
466        Self {
467            name,
468            modifiers: Modifiers::new(),
469            type_hint: None,
470            value,
471            attributes: ArenaVec::new_in(arena),
472            doc_comment: None,
473        }
474    }
475}
476
477/// A `use` of traits inside a class-like body.
478#[derive(Debug)]
479#[cfg_attr(feature = "serde", derive(Serialize))]
480#[non_exhaustive]
481pub struct TraitUseDecl<'arena, 'src> {
482    /// Traits being used.
483    pub traits: ArenaVec<'arena, Name<'arena, 'src>>,
484    /// `insteadof` and `as` rules from the adaptation block.
485    pub adaptations: ArenaVec<'arena, TraitAdaptation<'arena, 'src>>,
486    /// The `{` that opens the adaptations block; `None` when there are no
487    /// adaptations. Printer layout detail, outside semver; not serialized.
488    #[cfg_attr(feature = "serde", serde(skip))]
489    #[doc(hidden)]
490    pub adaptations_brace_span: Option<Span>,
491    /// Preceding `/** */` doc-block, if any.
492    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
493    pub doc_comment: Option<Comment<'src>>,
494}
495
496impl<'arena, 'src> TraitUseDecl<'arena, 'src> {
497    /// Builds a `TraitUseDecl` from its identifying fields with every other field empty. Set
498    /// the rest by assignment, as with `Param::new`.
499    pub fn new(arena: &'arena bumpalo::Bump, traits: ArenaVec<'arena, Name<'arena, 'src>>) -> Self {
500        Self {
501            traits,
502            adaptations: ArenaVec::new_in(arena),
503            adaptations_brace_span: None,
504            doc_comment: None,
505        }
506    }
507}
508
509/// A single rule in a trait `use` adaptation block.
510#[derive(Debug)]
511#[cfg_attr(feature = "serde", derive(Serialize))]
512pub struct TraitAdaptation<'arena, 'src> {
513    /// Kind of adaptation rule.
514    pub kind: TraitAdaptationKind<'arena, 'src>,
515    /// Source range of this node.
516    pub span: Span,
517}
518
519/// The kinds of trait adaptation rule.
520#[derive(Debug)]
521#[cfg_attr(feature = "serde", derive(Serialize))]
522#[non_exhaustive]
523pub enum TraitAdaptationKind<'arena, 'src> {
524    /// `A::foo insteadof B, C;`
525    Precedence {
526        /// Trait qualifying the method, if written.
527        trait_name: Name<'arena, 'src>,
528        /// Method being adapted.
529        method: Name<'arena, 'src>,
530        /// Traits whose method is excluded.
531        insteadof: ArenaVec<'arena, Name<'arena, 'src>>,
532    },
533    /// `foo as bar;` or `A::foo as protected bar;` or `foo as protected;`
534    Alias {
535        /// Trait qualifying the method, if written.
536        trait_name: Option<Name<'arena, 'src>>,
537        /// Method being adapted.
538        method: Name<'arena, 'src>,
539        /// New visibility, if changed.
540        new_modifier: Option<Visibility>,
541        /// `true` for `foo as final bar;` (PHP 8.3+).
542        #[cfg_attr(feature = "serde", serde(skip_serializing_if = "super::is_false"))]
543        is_final: bool,
544        /// A modifier PHP rejects on an alias: `static`, `abstract` or `readonly`.
545        #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
546        invalid_modifier: Option<Ident<'src>>,
547        /// Alias name, if given.
548        new_name: Option<Name<'arena, 'src>>,
549    },
550}
551
552/// An interface declaration.
553#[derive(Debug)]
554#[cfg_attr(feature = "serde", derive(Serialize))]
555#[non_exhaustive]
556pub struct InterfaceDecl<'arena, 'src> {
557    /// Interface name.
558    pub name: Ident<'src>,
559    /// Extended interfaces.
560    pub extends: ArenaVec<'arena, Name<'arena, 'src>>,
561    /// Interface body, flattened on serialization.
562    #[cfg_attr(feature = "serde", serde(flatten))]
563    pub body: ClassBody<'arena, 'src>,
564    /// `#[...]` attributes applied to this node.
565    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
566    /// Preceding `/** */` doc-block, if any.
567    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
568    pub doc_comment: Option<Comment<'src>>,
569}
570
571impl<'arena, 'src> InterfaceDecl<'arena, 'src> {
572    /// Builds a `InterfaceDecl` from its identifying fields with every other field empty. Set
573    /// the rest by assignment, as with `Param::new`.
574    pub fn new(
575        arena: &'arena bumpalo::Bump,
576        name: Ident<'src>,
577        body: ClassBody<'arena, 'src>,
578    ) -> Self {
579        Self {
580            name,
581            extends: ArenaVec::new_in(arena),
582            body,
583            attributes: ArenaVec::new_in(arena),
584            doc_comment: None,
585        }
586    }
587}
588
589/// A trait declaration.
590#[derive(Debug)]
591#[cfg_attr(feature = "serde", derive(Serialize))]
592#[non_exhaustive]
593pub struct TraitDecl<'arena, 'src> {
594    /// Trait name.
595    pub name: Ident<'src>,
596    /// Trait body, flattened on serialization.
597    #[cfg_attr(feature = "serde", serde(flatten))]
598    pub body: ClassBody<'arena, 'src>,
599    /// `#[...]` attributes applied to this node.
600    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
601    /// Preceding `/** */` doc-block, if any.
602    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
603    pub doc_comment: Option<Comment<'src>>,
604}
605
606impl<'arena, 'src> TraitDecl<'arena, 'src> {
607    /// Builds a `TraitDecl` from its identifying fields with every other field empty. Set
608    /// the rest by assignment, as with `Param::new`.
609    pub fn new(
610        arena: &'arena bumpalo::Bump,
611        name: Ident<'src>,
612        body: ClassBody<'arena, 'src>,
613    ) -> Self {
614        Self {
615            name,
616            body,
617            attributes: ArenaVec::new_in(arena),
618            doc_comment: None,
619        }
620    }
621}
622
623/// The braced member list of an enum.
624#[derive(Debug)]
625#[cfg_attr(feature = "serde", derive(Serialize))]
626pub struct EnumBody<'arena, 'src> {
627    /// Enum members in source order.
628    pub members: ArenaVec<'arena, EnumMember<'arena, 'src>>,
629    /// Span covering `{` to `}` of the body.
630    #[cfg_attr(feature = "serde", serde(skip))]
631    pub span: Span,
632}
633
634/// An enum declaration (PHP 8.1+).
635#[derive(Debug)]
636#[cfg_attr(feature = "serde", derive(Serialize))]
637#[non_exhaustive]
638pub struct EnumDecl<'arena, 'src> {
639    /// Enum name.
640    pub name: Ident<'src>,
641    /// Backing type of a backed enum.
642    pub scalar_type: Option<Name<'arena, 'src>>,
643    /// Implemented interfaces.
644    pub implements: ArenaVec<'arena, Name<'arena, 'src>>,
645    /// Enum body, flattened on serialization.
646    #[cfg_attr(feature = "serde", serde(flatten))]
647    pub body: EnumBody<'arena, 'src>,
648    /// `#[...]` attributes applied to this node.
649    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
650    /// Preceding `/** */` doc-block, if any.
651    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
652    pub doc_comment: Option<Comment<'src>>,
653}
654
655impl<'arena, 'src> EnumDecl<'arena, 'src> {
656    /// Builds a `EnumDecl` from its identifying fields with every other field empty. Set
657    /// the rest by assignment, as with `Param::new`.
658    pub fn new(
659        arena: &'arena bumpalo::Bump,
660        name: Ident<'src>,
661        body: EnumBody<'arena, 'src>,
662    ) -> Self {
663        Self {
664            name,
665            scalar_type: None,
666            implements: ArenaVec::new_in(arena),
667            body,
668            attributes: ArenaVec::new_in(arena),
669            doc_comment: None,
670        }
671    }
672}
673
674/// A member of an enum body.
675#[derive(Debug)]
676#[cfg_attr(feature = "serde", derive(Serialize))]
677pub struct EnumMember<'arena, 'src> {
678    /// Kind of member.
679    pub kind: EnumMemberKind<'arena, 'src>,
680    /// Source range of this node.
681    pub span: Span,
682}
683
684/// The kinds of enum member.
685#[derive(Debug)]
686#[cfg_attr(feature = "serde", derive(Serialize))]
687#[non_exhaustive]
688pub enum EnumMemberKind<'arena, 'src> {
689    /// An enum case: `case Foo;` or `case Foo = 'foo';` (backed enum).
690    Case(EnumCase<'arena, 'src>),
691    /// A method defined inside the enum body.
692    Method(MethodDecl<'arena, 'src>),
693    /// A constant defined inside the enum body: `const X = 1;`.
694    ClassConst(ClassConstDecl<'arena, 'src>),
695    /// A trait use inside the enum body: `use SomeTrait;`.
696    TraitUse(TraitUseDecl<'arena, 'src>),
697    /// A property, which PHP rejects in enums: `public $x;`.
698    Property(PropertyDecl<'arena, 'src>),
699}
700
701/// A `case` inside an enum.
702#[derive(Debug)]
703#[cfg_attr(feature = "serde", derive(Serialize))]
704#[non_exhaustive]
705pub struct EnumCase<'arena, 'src> {
706    /// Case name.
707    pub name: Ident<'src>,
708    /// Backing value of a backed case.
709    pub value: Option<Expr<'arena, 'src>>,
710    /// `#[...]` attributes applied to this node.
711    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
712    /// Preceding `/** */` doc-block, if any.
713    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
714    pub doc_comment: Option<Comment<'src>>,
715}
716
717impl<'arena, 'src> EnumCase<'arena, 'src> {
718    /// Builds a `EnumCase` from its identifying fields with every other field empty. Set
719    /// the rest by assignment, as with `Param::new`.
720    pub fn new(arena: &'arena bumpalo::Bump, name: Ident<'src>) -> Self {
721        Self {
722            name,
723            value: None,
724            attributes: ArenaVec::new_in(arena),
725            doc_comment: None,
726        }
727    }
728}