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