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