Skip to main content

php_ast/ast/
decls.rs

1use serde::Serialize;
2
3use crate::Span;
4
5use super::{ArenaVec, Attribute, Block, Comment, Expr, Ident, Name, TypeHint};
6
7#[derive(Debug, Serialize)]
8pub struct FunctionDecl<'arena, 'src> {
9    pub name: Ident<'src>,
10    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
11    pub body: &'arena Block<'arena, 'src>,
12    pub return_type: Option<TypeHint<'arena, 'src>>,
13    pub by_ref: bool,
14    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
15    /// The immediately preceding `/** */` doc-block, if any.
16    ///
17    /// When present, this comment is **removed** from
18    /// [`ParseResult::comments`](php_rs_parser::ParseResult::comments) — the
19    /// two collections are disjoint. All other comment forms (line, hash,
20    /// block) remain in `ParseResult::comments` regardless of position.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub doc_comment: Option<Comment<'src>>,
23}
24
25#[derive(Debug, Serialize)]
26pub struct Param<'arena, 'src> {
27    pub name: Ident<'src>,
28    pub type_hint: Option<TypeHint<'arena, 'src>>,
29    pub default: Option<Expr<'arena, 'src>>,
30    pub by_ref: bool,
31    pub variadic: bool,
32    pub is_readonly: bool,
33    pub is_final: bool,
34    pub visibility: Option<Visibility>,
35    pub set_visibility: Option<Visibility>,
36    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
37    #[serde(skip_serializing_if = "ArenaVec::is_empty")]
38    pub hooks: ArenaVec<'arena, PropertyHook<'arena, 'src>>,
39    /// A doc-block attached directly to this parameter (PHP 8.6 recognizes
40    /// this for reflection purposes), either immediately before it or
41    /// trailing it up to the comma. Parsed at every version since a bare
42    /// comment is legal PHP regardless of target version.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub doc_comment: Option<Comment<'src>>,
45    pub span: Span,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
49pub enum Visibility {
50    /// `public` — accessible from anywhere.
51    Public,
52    /// `protected` — accessible within the class and its subclasses.
53    Protected,
54    /// `private` — accessible only within the declaring class.
55    Private,
56}
57
58#[derive(Debug, Serialize)]
59pub struct ClassBody<'arena, 'src> {
60    pub members: ArenaVec<'arena, ClassMember<'arena, 'src>>,
61    /// Span covering `{` to `}` of the body.
62    #[serde(skip)]
63    pub span: Span,
64}
65
66#[derive(Debug, Serialize)]
67pub struct ClassDecl<'arena, 'src> {
68    pub name: Option<Ident<'src>>,
69    pub modifiers: ClassModifiers,
70    pub extends: Option<Name<'arena, 'src>>,
71    pub implements: ArenaVec<'arena, Name<'arena, 'src>>,
72    #[serde(flatten)]
73    pub body: ClassBody<'arena, 'src>,
74    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub doc_comment: Option<Comment<'src>>,
77}
78
79#[derive(Debug, Clone, Serialize, Default)]
80pub struct ClassModifiers {
81    pub is_abstract: bool,
82    pub is_final: bool,
83    pub is_readonly: bool,
84}
85
86#[derive(Debug, Serialize)]
87pub struct ClassMember<'arena, 'src> {
88    pub kind: ClassMemberKind<'arena, 'src>,
89    pub span: Span,
90}
91
92#[derive(Debug, Serialize)]
93pub enum ClassMemberKind<'arena, 'src> {
94    Property(PropertyDecl<'arena, 'src>),
95    Method(MethodDecl<'arena, 'src>),
96    ClassConst(ClassConstDecl<'arena, 'src>),
97    TraitUse(TraitUseDecl<'arena, 'src>),
98}
99
100#[derive(Debug, Serialize)]
101pub struct PropertyDecl<'arena, 'src> {
102    pub name: Ident<'src>,
103    pub visibility: Option<Visibility>,
104    pub set_visibility: Option<Visibility>,
105    pub is_static: bool,
106    pub is_readonly: bool,
107    pub type_hint: Option<TypeHint<'arena, 'src>>,
108    pub default: Option<Expr<'arena, 'src>>,
109    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
110    #[serde(skip_serializing_if = "ArenaVec::is_empty")]
111    pub hooks: ArenaVec<'arena, PropertyHook<'arena, 'src>>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub doc_comment: Option<Comment<'src>>,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
117pub enum PropertyHookKind {
118    /// `get` hook — called when the property is read.
119    Get,
120    /// `set` hook — called when the property is written; receives the incoming value as `$value`.
121    Set,
122}
123
124#[derive(Debug, Serialize)]
125pub enum PropertyHookBody<'arena, 'src> {
126    /// `{ stmts }` — a full statement block.
127    Block(&'arena Block<'arena, 'src>),
128    /// `=> expr` — short-form expression body.
129    Expression(Expr<'arena, 'src>),
130    /// No body — the hook is declared abstract (on an abstract class or interface).
131    Abstract,
132}
133
134#[derive(Debug, Serialize)]
135pub struct PropertyHook<'arena, 'src> {
136    pub kind: PropertyHookKind,
137    pub body: PropertyHookBody<'arena, 'src>,
138    pub is_final: bool,
139    pub by_ref: bool,
140    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
141    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
142    pub span: Span,
143}
144
145#[derive(Debug, Serialize)]
146pub struct MethodDecl<'arena, 'src> {
147    pub name: Ident<'src>,
148    pub visibility: Option<Visibility>,
149    pub is_static: bool,
150    pub is_abstract: bool,
151    pub is_final: bool,
152    pub by_ref: bool,
153    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
154    pub return_type: Option<TypeHint<'arena, 'src>>,
155    /// `None` for an abstract/interface method.
156    pub body: Option<&'arena Block<'arena, 'src>>,
157    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub doc_comment: Option<Comment<'src>>,
160}
161
162#[derive(Debug, Serialize)]
163pub struct ClassConstDecl<'arena, 'src> {
164    pub name: Ident<'src>,
165    pub visibility: Option<Visibility>,
166    pub is_final: bool,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub type_hint: Option<&'arena TypeHint<'arena, 'src>>,
169    pub value: Expr<'arena, 'src>,
170    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub doc_comment: Option<Comment<'src>>,
173}
174
175#[derive(Debug, Serialize)]
176pub struct TraitUseDecl<'arena, 'src> {
177    pub traits: ArenaVec<'arena, Name<'arena, 'src>>,
178    pub adaptations: ArenaVec<'arena, TraitAdaptation<'arena, 'src>>,
179    /// Start byte offset of the `{` that opens the adaptations block; `None` when there are no adaptations.
180    #[serde(skip)]
181    pub adaptations_brace_start: Option<u32>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub doc_comment: Option<Comment<'src>>,
184}
185
186#[derive(Debug, Serialize)]
187pub struct TraitAdaptation<'arena, 'src> {
188    pub kind: TraitAdaptationKind<'arena, 'src>,
189    pub span: Span,
190}
191
192#[derive(Debug, Serialize)]
193pub enum TraitAdaptationKind<'arena, 'src> {
194    /// `A::foo insteadof B, C;`
195    Precedence {
196        trait_name: Name<'arena, 'src>,
197        method: Name<'arena, 'src>,
198        insteadof: ArenaVec<'arena, Name<'arena, 'src>>,
199    },
200    /// `foo as bar;` or `A::foo as protected bar;` or `foo as protected;`
201    Alias {
202        trait_name: Option<Name<'arena, 'src>>,
203        method: Name<'arena, 'src>,
204        new_modifier: Option<Visibility>,
205        new_name: Option<Name<'arena, 'src>>,
206    },
207}
208
209#[derive(Debug, Serialize)]
210pub struct InterfaceDecl<'arena, 'src> {
211    pub name: Ident<'src>,
212    pub extends: ArenaVec<'arena, Name<'arena, 'src>>,
213    #[serde(flatten)]
214    pub body: ClassBody<'arena, 'src>,
215    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub doc_comment: Option<Comment<'src>>,
218}
219
220#[derive(Debug, Serialize)]
221pub struct TraitDecl<'arena, 'src> {
222    pub name: Ident<'src>,
223    #[serde(flatten)]
224    pub body: ClassBody<'arena, 'src>,
225    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub doc_comment: Option<Comment<'src>>,
228}
229
230#[derive(Debug, Serialize)]
231pub struct EnumBody<'arena, 'src> {
232    pub members: ArenaVec<'arena, EnumMember<'arena, 'src>>,
233    /// Span covering `{` to `}` of the body.
234    #[serde(skip)]
235    pub span: Span,
236}
237
238#[derive(Debug, Serialize)]
239pub struct EnumDecl<'arena, 'src> {
240    pub name: Ident<'src>,
241    pub scalar_type: Option<Name<'arena, 'src>>,
242    pub implements: ArenaVec<'arena, Name<'arena, 'src>>,
243    #[serde(flatten)]
244    pub body: EnumBody<'arena, 'src>,
245    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub doc_comment: Option<Comment<'src>>,
248}
249
250#[derive(Debug, Serialize)]
251pub struct EnumMember<'arena, 'src> {
252    pub kind: EnumMemberKind<'arena, 'src>,
253    pub span: Span,
254}
255
256#[derive(Debug, Serialize)]
257pub enum EnumMemberKind<'arena, 'src> {
258    /// An enum case: `case Foo;` or `case Foo = 'foo';` (backed enum).
259    Case(EnumCase<'arena, 'src>),
260    /// A method defined inside the enum body.
261    Method(MethodDecl<'arena, 'src>),
262    /// A constant defined inside the enum body: `const X = 1;`.
263    ClassConst(ClassConstDecl<'arena, 'src>),
264    /// A trait use inside the enum body: `use SomeTrait;`.
265    TraitUse(TraitUseDecl<'arena, 'src>),
266}
267
268#[derive(Debug, Serialize)]
269pub struct EnumCase<'arena, 'src> {
270    pub name: Ident<'src>,
271    pub value: Option<Expr<'arena, 'src>>,
272    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub doc_comment: Option<Comment<'src>>,
275}