Skip to main content

php_ast/ast/
exprs.rs

1use serde::Serialize;
2
3use crate::Span;
4
5use super::{is_false, ArenaVec, Arg, Attribute, ClassDecl, Param, Stmt, TypeHint};
6
7#[derive(Clone, Copy, PartialEq, Eq, Hash)]
8enum NameStrInner<'arena, 'src> {
9    Src(&'src str),
10    Arena(&'arena str),
11}
12
13/// A name string from either the source buffer or the bump arena. Use [`as_str`](NameStr::as_str)
14/// or `Deref` to get the string value — the allocation origin is an internal parser detail.
15///
16/// Using this as the payload for both `ExprKind::Variable` and `ExprKind::Identifier`
17/// gives them the same binding type, so or-patterns compile natively:
18///
19/// ```
20/// # use php_ast::ast::{ExprKind, NameStr};
21/// # fn example<'a, 'b>(kind: &ExprKind<'a, 'b>) {
22/// if let ExprKind::Variable(name) | ExprKind::Identifier(name) = kind {
23///     let _s: &str = name.as_str();
24/// }
25/// # }
26/// ```
27#[derive(Clone, Copy, PartialEq, Eq, Hash)]
28pub struct NameStr<'arena, 'src>(NameStrInner<'arena, 'src>);
29
30impl<'arena, 'src> NameStr<'arena, 'src> {
31    /// Borrowed directly from the source buffer.
32    #[doc(hidden)]
33    #[inline]
34    pub fn __src(s: &'src str) -> Self {
35        Self(NameStrInner::Src(s))
36    }
37
38    /// Allocated in the bump arena (e.g. a joined qualified name or a keyword).
39    #[doc(hidden)]
40    #[inline]
41    pub fn __arena(s: &'arena str) -> Self {
42        Self(NameStrInner::Arena(s))
43    }
44
45    /// Returns the arena slice if this value was arena-allocated, otherwise `None`.
46    /// Used internally to avoid re-allocating strings already in the arena.
47    #[doc(hidden)]
48    #[inline]
49    pub fn __into_arena_str(self) -> Option<&'arena str> {
50        match self.0 {
51            NameStrInner::Arena(s) => Some(s),
52            NameStrInner::Src(_) => None,
53        }
54    }
55
56    #[inline]
57    pub fn as_str(&self) -> &str {
58        match self.0 {
59            NameStrInner::Src(s) | NameStrInner::Arena(s) => s,
60        }
61    }
62}
63
64impl<'arena, 'src> std::ops::Deref for NameStr<'arena, 'src> {
65    type Target = str;
66    #[inline]
67    fn deref(&self) -> &str {
68        self.as_str()
69    }
70}
71
72impl<'arena, 'src> std::fmt::Debug for NameStr<'arena, 'src> {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        self.as_str().fmt(f)
75    }
76}
77
78impl<'arena, 'src> serde::Serialize for NameStr<'arena, 'src> {
79    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
80        self.as_str().serialize(serializer)
81    }
82}
83
84#[derive(Debug, Serialize)]
85pub struct Expr<'arena, 'src> {
86    pub kind: ExprKind<'arena, 'src>,
87    pub span: Span,
88}
89
90#[derive(Debug, Serialize)]
91pub enum ExprKind<'arena, 'src> {
92    /// Integer literal
93    Int(i64),
94
95    /// Float literal
96    Float(f64),
97
98    /// String literal
99    String(&'arena str),
100
101    /// Interpolated string: `"Hello $name, you are {$age} years old"`
102    InterpolatedString(ArenaVec<'arena, StringPart<'arena, 'src>>),
103
104    /// Heredoc: `<<<EOT ... EOT`
105    Heredoc {
106        label: &'src str,
107        parts: ArenaVec<'arena, StringPart<'arena, 'src>>,
108    },
109
110    /// Nowdoc: `<<<'EOT' ... EOT`
111    Nowdoc {
112        label: &'src str,
113        value: &'arena str,
114    },
115
116    /// Shell execution: `` `command $var` ``
117    ShellExec(ArenaVec<'arena, StringPart<'arena, 'src>>),
118
119    /// Boolean literal
120    Bool(bool),
121
122    /// Null literal
123    Null,
124
125    /// Variable: `$name`
126    Variable(NameStr<'arena, 'src>),
127
128    /// Variable variable: `$$var`, `$$$var`, `${expr}`
129    VariableVariable(&'arena Expr<'arena, 'src>),
130
131    /// Identifier (bare name, e.g. function name in a call)
132    Identifier(NameStr<'arena, 'src>),
133
134    /// Assignment: `$x = expr` or `$x += expr`
135    Assign(AssignExpr<'arena, 'src>),
136
137    /// Binary operation: `expr op expr`
138    Binary(BinaryExpr<'arena, 'src>),
139
140    /// Unary prefix: `-expr`, `!expr`, `~expr`, `++$x`, `--$x`
141    UnaryPrefix(UnaryPrefixExpr<'arena, 'src>),
142
143    /// Unary postfix: `$x++`, `$x--`
144    UnaryPostfix(UnaryPostfixExpr<'arena, 'src>),
145
146    /// Ternary: `cond ? then : else` or short `cond ?: else`
147    Ternary(TernaryExpr<'arena, 'src>),
148
149    /// Null coalescing: `expr ?? fallback`
150    NullCoalesce(NullCoalesceExpr<'arena, 'src>),
151
152    /// Function call: `name(args)`
153    FunctionCall(FunctionCallExpr<'arena, 'src>),
154
155    /// Array literal: `[1, 2, 3]` or `['a' => 1]`
156    Array(ArenaVec<'arena, ArrayElement<'arena, 'src>>),
157
158    /// Array access: `$arr[index]`
159    ArrayAccess(ArrayAccessExpr<'arena, 'src>),
160
161    /// Print expression: `print expr`
162    Print(&'arena Expr<'arena, 'src>),
163
164    /// Parenthesized expression: `(expr)`
165    Parenthesized(&'arena Expr<'arena, 'src>),
166
167    /// Cast expression: `(int)$x`, `(string)$x`, etc.
168    Cast(CastKind, &'arena Expr<'arena, 'src>),
169
170    /// Error suppression: `@expr`
171    ErrorSuppress(&'arena Expr<'arena, 'src>),
172
173    /// Isset: `isset($a, $b)`
174    Isset(ArenaVec<'arena, Expr<'arena, 'src>>),
175
176    /// Empty: `empty($a)`
177    Empty(&'arena Expr<'arena, 'src>),
178
179    /// Include/require: `include 'file.php'`
180    Include(IncludeKind, &'arena Expr<'arena, 'src>),
181
182    /// Eval: `eval('code')`
183    Eval(&'arena Expr<'arena, 'src>),
184
185    /// Exit/die: `exit`, `exit(1)`, `die('msg')`
186    Exit(Option<&'arena Expr<'arena, 'src>>),
187
188    /// Magic constant: `__LINE__`, `__FILE__`, etc.
189    MagicConst(MagicConstKind),
190
191    /// Clone: `clone $obj`
192    Clone(&'arena Expr<'arena, 'src>),
193
194    /// Clone with property overrides: `clone($obj, ['prop' => $val])` — PHP 8.5+
195    CloneWith(&'arena Expr<'arena, 'src>, &'arena Expr<'arena, 'src>),
196
197    /// New: `new Class(args)`
198    New(NewExpr<'arena, 'src>),
199
200    /// Property access: `$obj->prop`
201    PropertyAccess(PropertyAccessExpr<'arena, 'src>),
202
203    /// Nullsafe property access: `$obj?->prop`
204    NullsafePropertyAccess(PropertyAccessExpr<'arena, 'src>),
205
206    /// Method call: `$obj->method(args)`
207    MethodCall(&'arena MethodCallExpr<'arena, 'src>),
208
209    /// Nullsafe method call: `$obj?->method(args)`
210    NullsafeMethodCall(&'arena MethodCallExpr<'arena, 'src>),
211
212    /// Static property access: `Class::$prop`
213    StaticPropertyAccess(StaticAccessExpr<'arena, 'src>),
214
215    /// Static method call: `Class::method(args)`
216    StaticMethodCall(&'arena StaticMethodCallExpr<'arena, 'src>),
217
218    /// Dynamic static method call: `Class::$method(args)`
219    StaticDynMethodCall(&'arena StaticDynMethodCallExpr<'arena, 'src>),
220
221    /// Class constant access: `Class::CONST`
222    ClassConstAccess(StaticAccessExpr<'arena, 'src>),
223
224    /// Dynamic class constant access: `Foo::{expr}`
225    ClassConstAccessDynamic {
226        class: &'arena Expr<'arena, 'src>,
227        member: &'arena Expr<'arena, 'src>,
228    },
229
230    /// Dynamic static property access: `A::$$b`, `A::${'b'}`
231    StaticPropertyAccessDynamic {
232        class: &'arena Expr<'arena, 'src>,
233        member: &'arena Expr<'arena, 'src>,
234    },
235
236    /// Closure: `function($x) use($y) { }`
237    Closure(&'arena ClosureExpr<'arena, 'src>),
238
239    /// Arrow function: `fn($x) => expr`
240    ArrowFunction(&'arena ArrowFunctionExpr<'arena, 'src>),
241
242    /// Match: `match(expr) { ... }`
243    Match(MatchExpr<'arena, 'src>),
244
245    /// Throw as expression (PHP 8)
246    ThrowExpr(&'arena Expr<'arena, 'src>),
247
248    /// Yield: `yield` / `yield $val` / `yield $key => $val`
249    Yield(YieldExpr<'arena, 'src>),
250
251    /// Anonymous class: `new class(args) extends Foo implements Bar { ... }`
252    AnonymousClass(&'arena ClassDecl<'arena, 'src>),
253
254    /// First-class callable: `strlen(...)`, `$obj->method(...)`, `Foo::bar(...)`
255    CallableCreate(CallableCreateExpr<'arena, 'src>),
256
257    /// Omitted element in destructuring: `[$a, , $c]` or `list($a, , $c)`
258    Omit,
259
260    /// Error placeholder
261    Error,
262}
263
264impl<'arena, 'src> Expr<'arena, 'src> {
265    /// Returns the name string for `Variable` and `Identifier` nodes, `None` for everything else.
266    pub fn name_str(&self) -> Option<&str> {
267        match &self.kind {
268            ExprKind::Variable(s) | ExprKind::Identifier(s) => Some(s.as_str()),
269            _ => None,
270        }
271    }
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
275pub enum CastKind {
276    /// `(int)` or `(integer)` cast.
277    Int,
278    /// `(float)`, `(double)`, or `(real)` cast.
279    Float,
280    /// `(string)` cast.
281    String,
282    /// `(bool)` or `(boolean)` cast.
283    Bool,
284    /// `(array)` cast.
285    Array,
286    /// `(object)` cast.
287    Object,
288    /// `(unset)` cast — deprecated; casts to `null`.
289    Unset,
290    /// `(void)` cast — non-standard; treated as discarding the value.
291    Void,
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
295pub enum IncludeKind {
296    /// `include 'file.php'` — emits a warning if the file is not found.
297    Include,
298    /// `include_once 'file.php'` — like `include`, but skipped if the file has already been included.
299    IncludeOnce,
300    /// `require 'file.php'` — fatal error if the file is not found.
301    Require,
302    /// `require_once 'file.php'` — like `require`, but skipped if the file has already been included.
303    RequireOnce,
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
307pub enum MagicConstKind {
308    /// `__CLASS__` — name of the current class, or empty string outside a class.
309    Class,
310    /// `__DIR__` — directory of the current file.
311    Dir,
312    /// `__FILE__` — absolute path of the current file.
313    File,
314    /// `__FUNCTION__` — name of the current function or closure.
315    Function,
316    /// `__LINE__` — current line number in the source file.
317    Line,
318    /// `__METHOD__` — name of the current method including its class: `ClassName::methodName`.
319    Method,
320    /// `__NAMESPACE__` — name of the current namespace, or empty string in the global namespace.
321    Namespace,
322    /// `__TRAIT__` — name of the current trait, or empty string outside a trait.
323    Trait,
324    /// `__PROPERTY__` — name of the current property inside a property hook (PHP 8.4+).
325    Property,
326}
327
328// --- Expression sub-types ---
329
330#[derive(Debug, Serialize)]
331pub struct AssignExpr<'arena, 'src> {
332    pub target: &'arena Expr<'arena, 'src>,
333    pub op: AssignOp,
334    pub value: &'arena Expr<'arena, 'src>,
335    #[serde(skip_serializing_if = "is_false")]
336    pub by_ref: bool,
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
340pub enum AssignOp {
341    /// `=`
342    Assign,
343    /// `+=`
344    Plus,
345    /// `-=`
346    Minus,
347    /// `*=`
348    Mul,
349    /// `/=`
350    Div,
351    /// `%=`
352    Mod,
353    /// `**=`
354    Pow,
355    /// `.=`
356    Concat,
357    /// `&=`
358    BitwiseAnd,
359    /// `|=`
360    BitwiseOr,
361    /// `^=`
362    BitwiseXor,
363    /// `<<=`
364    ShiftLeft,
365    /// `>>=`
366    ShiftRight,
367    /// `??=`
368    Coalesce,
369}
370
371#[derive(Debug, Serialize)]
372pub struct BinaryExpr<'arena, 'src> {
373    pub left: &'arena Expr<'arena, 'src>,
374    pub op: BinaryOp,
375    pub right: &'arena Expr<'arena, 'src>,
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
379pub enum BinaryOp {
380    /// `+`
381    Add,
382    /// `-`
383    Sub,
384    /// `*`
385    Mul,
386    /// `/`
387    Div,
388    /// `%`
389    Mod,
390    /// `**`
391    Pow,
392    /// `.` — string concatenation.
393    Concat,
394    /// `==` — loose equality (type-coercing).
395    Equal,
396    /// `!=` or `<>` — loose inequality.
397    NotEqual,
398    /// `===` — strict equality (type and value).
399    Identical,
400    /// `!==` — strict inequality.
401    NotIdentical,
402    /// `<`
403    Less,
404    /// `>`
405    Greater,
406    /// `<=`
407    LessOrEqual,
408    /// `>=`
409    GreaterOrEqual,
410    /// `<=>` — spaceship / three-way comparison; returns -1, 0, or 1.
411    Spaceship,
412    /// `&&` — short-circuit boolean AND (higher precedence than `and`).
413    BooleanAnd,
414    /// `||` — short-circuit boolean OR (higher precedence than `or`).
415    BooleanOr,
416    /// `&` — bitwise AND.
417    BitwiseAnd,
418    /// `|` — bitwise OR.
419    BitwiseOr,
420    /// `^` — bitwise XOR.
421    BitwiseXor,
422    /// `<<` — left bit-shift.
423    ShiftLeft,
424    /// `>>` — right bit-shift.
425    ShiftRight,
426    /// `and` — boolean AND (lower precedence than `&&`).
427    LogicalAnd,
428    /// `or` — boolean OR (lower precedence than `||`).
429    LogicalOr,
430    /// `xor` — boolean XOR.
431    LogicalXor,
432    /// `instanceof` — type-check operator; `$x instanceof Foo`.
433    Instanceof,
434    /// `|>` — pipe operator (PHP 8.5+); passes the left operand as the first argument of the right callable.
435    Pipe,
436}
437
438#[derive(Debug, Serialize)]
439pub struct UnaryPrefixExpr<'arena, 'src> {
440    pub op: UnaryPrefixOp,
441    pub operand: &'arena Expr<'arena, 'src>,
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
445pub enum UnaryPrefixOp {
446    /// `-expr` — arithmetic negation.
447    Negate,
448    /// `+expr` — unary plus (no-op for numbers, promotes to numeric).
449    Plus,
450    /// `!expr` — boolean NOT.
451    BooleanNot,
452    /// `~expr` — bitwise NOT.
453    BitwiseNot,
454    /// `++$x` — pre-increment; increments then returns the new value.
455    PreIncrement,
456    /// `--$x` — pre-decrement; decrements then returns the new value.
457    PreDecrement,
458}
459
460#[derive(Debug, Serialize)]
461pub struct UnaryPostfixExpr<'arena, 'src> {
462    pub operand: &'arena Expr<'arena, 'src>,
463    pub op: UnaryPostfixOp,
464}
465
466#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
467pub enum UnaryPostfixOp {
468    /// `$x++` — post-increment; returns the current value then increments.
469    PostIncrement,
470    /// `$x--` — post-decrement; returns the current value then decrements.
471    PostDecrement,
472}
473
474#[derive(Debug, Serialize)]
475pub struct TernaryExpr<'arena, 'src> {
476    pub condition: &'arena Expr<'arena, 'src>,
477    /// None for short ternary `$x ?: $y`
478    pub then_expr: Option<&'arena Expr<'arena, 'src>>,
479    pub else_expr: &'arena Expr<'arena, 'src>,
480}
481
482#[derive(Debug, Serialize)]
483pub struct NullCoalesceExpr<'arena, 'src> {
484    pub left: &'arena Expr<'arena, 'src>,
485    pub right: &'arena Expr<'arena, 'src>,
486}
487
488#[derive(Debug, Serialize)]
489pub struct FunctionCallExpr<'arena, 'src> {
490    pub name: &'arena Expr<'arena, 'src>,
491    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
492}
493
494#[derive(Debug, Serialize)]
495pub struct ArrayElement<'arena, 'src> {
496    pub key: Option<Expr<'arena, 'src>>,
497    pub value: Expr<'arena, 'src>,
498    pub unpack: bool,
499    #[serde(skip_serializing_if = "is_false")]
500    pub by_ref: bool,
501    pub span: Span,
502}
503
504#[derive(Debug, Serialize)]
505pub struct ArrayAccessExpr<'arena, 'src> {
506    pub array: &'arena Expr<'arena, 'src>,
507    pub index: Option<&'arena Expr<'arena, 'src>>,
508}
509
510// --- OOP Expression sub-types ---
511
512#[derive(Debug, Serialize)]
513pub struct NewExpr<'arena, 'src> {
514    pub class: &'arena Expr<'arena, 'src>,
515    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
516}
517
518#[derive(Debug, Serialize)]
519pub struct PropertyAccessExpr<'arena, 'src> {
520    pub object: &'arena Expr<'arena, 'src>,
521    pub property: &'arena Expr<'arena, 'src>,
522}
523
524#[derive(Debug, Serialize)]
525pub struct MethodCallExpr<'arena, 'src> {
526    pub object: &'arena Expr<'arena, 'src>,
527    pub method: &'arena Expr<'arena, 'src>,
528    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
529}
530
531#[derive(Debug, Serialize)]
532pub struct StaticAccessExpr<'arena, 'src> {
533    pub class: &'arena Expr<'arena, 'src>,
534    pub member: &'arena Expr<'arena, 'src>,
535}
536
537#[derive(Debug, Serialize)]
538pub struct StaticMethodCallExpr<'arena, 'src> {
539    pub class: &'arena Expr<'arena, 'src>,
540    pub method: &'arena Expr<'arena, 'src>,
541    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
542}
543
544#[derive(Debug, Serialize)]
545pub struct StaticDynMethodCallExpr<'arena, 'src> {
546    pub class: &'arena Expr<'arena, 'src>,
547    pub method: &'arena Expr<'arena, 'src>,
548    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
549}
550
551#[derive(Debug, Serialize)]
552pub struct ClosureExpr<'arena, 'src> {
553    pub is_static: bool,
554    pub by_ref: bool,
555    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
556    pub use_vars: ArenaVec<'arena, ClosureUseVar<'src>>,
557    pub return_type: Option<TypeHint<'arena, 'src>>,
558    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
559    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
560}
561
562#[derive(Debug, Clone, Serialize)]
563pub struct ClosureUseVar<'src> {
564    pub name: &'src str,
565    pub by_ref: bool,
566    pub span: Span,
567}
568
569#[derive(Debug, Serialize)]
570pub struct ArrowFunctionExpr<'arena, 'src> {
571    pub is_static: bool,
572    pub by_ref: bool,
573    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
574    pub return_type: Option<TypeHint<'arena, 'src>>,
575    pub body: &'arena Expr<'arena, 'src>,
576    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
577}
578
579#[derive(Debug, Serialize)]
580pub struct MatchExpr<'arena, 'src> {
581    pub subject: &'arena Expr<'arena, 'src>,
582    pub arms: ArenaVec<'arena, MatchArm<'arena, 'src>>,
583}
584
585#[derive(Debug, Serialize)]
586pub struct MatchArm<'arena, 'src> {
587    /// None for `default`
588    pub conditions: Option<ArenaVec<'arena, Expr<'arena, 'src>>>,
589    pub body: Expr<'arena, 'src>,
590    pub span: Span,
591}
592
593#[derive(Debug, Serialize)]
594pub struct YieldExpr<'arena, 'src> {
595    pub key: Option<&'arena Expr<'arena, 'src>>,
596    pub value: Option<&'arena Expr<'arena, 'src>>,
597    /// `true` for `yield from expr` (generator delegation), `false` for plain `yield`
598    pub is_from: bool,
599}
600
601// --- First-class callable ---
602
603#[derive(Debug, Serialize)]
604pub struct CallableCreateExpr<'arena, 'src> {
605    pub kind: CallableCreateKind<'arena, 'src>,
606}
607
608#[derive(Debug, Serialize)]
609pub enum CallableCreateKind<'arena, 'src> {
610    /// `foo(...)`, `$var(...)`, `\Ns\func(...)`
611    Function(&'arena Expr<'arena, 'src>),
612    /// `$obj->method(...)`
613    Method {
614        object: &'arena Expr<'arena, 'src>,
615        method: &'arena Expr<'arena, 'src>,
616    },
617    /// `$obj?->method(...)`
618    NullsafeMethod {
619        object: &'arena Expr<'arena, 'src>,
620        method: &'arena Expr<'arena, 'src>,
621    },
622    /// `Foo::bar(...)`
623    StaticMethod {
624        class: &'arena Expr<'arena, 'src>,
625        method: &'arena Expr<'arena, 'src>,
626    },
627}
628
629// --- String interpolation ---
630
631#[derive(Debug, Serialize)]
632pub enum StringPart<'arena, 'src> {
633    /// A plain text segment of an interpolated string or heredoc.
634    Literal(&'arena str),
635    /// An embedded expression: `$var`, `{$expr}`, or `${var}`.
636    Expr(Expr<'arena, 'src>),
637}