Skip to main content

ryo_source/pure/
ast.rs

1//! Pure AST types - Span-free, thread-safe.
2//!
3//! All types support optional serde serialization via the `serde` feature.
4
5use std::sync::Arc;
6
7/// A complete Rust source file (pure, no spans).
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct PureFile {
11    /// Attributes on the file (e.g., `#![allow(...)]`).
12    pub attrs: Vec<PureAttribute>,
13    /// Items in the file.
14    pub items: Vec<PureItem>,
15}
16
17// SAFETY: PureFile contains no raw pointers or non-Send types
18unsafe impl Send for PureFile {}
19unsafe impl Sync for PureFile {}
20
21impl PureFile {
22    /// Create a new empty file.
23    pub fn new() -> Self {
24        Self {
25            attrs: Vec::new(),
26            items: Vec::new(),
27        }
28    }
29
30    /// Get all functions.
31    pub fn functions(&self) -> Vec<&PureFn> {
32        self.items
33            .iter()
34            .filter_map(|item| {
35                if let PureItem::Fn(f) = item {
36                    Some(f)
37                } else {
38                    None
39                }
40            })
41            .collect()
42    }
43
44    /// Get all structs.
45    pub fn structs(&self) -> Vec<&PureStruct> {
46        self.items
47            .iter()
48            .filter_map(|item| {
49                if let PureItem::Struct(s) = item {
50                    Some(s)
51                } else {
52                    None
53                }
54            })
55            .collect()
56    }
57
58    /// Get all use statements.
59    pub fn uses(&self) -> Vec<&PureUse> {
60        self.items
61            .iter()
62            .filter_map(|item| {
63                if let PureItem::Use(u) = item {
64                    Some(u)
65                } else {
66                    None
67                }
68            })
69            .collect()
70    }
71
72    /// Find a function by name.
73    pub fn find_fn(&self, name: &str) -> Option<&PureFn> {
74        self.functions().into_iter().find(|f| f.name == name)
75    }
76
77    /// Get all traits.
78    pub fn traits(&self) -> Vec<&PureTrait> {
79        self.items
80            .iter()
81            .filter_map(|item| {
82                if let PureItem::Trait(t) = item {
83                    Some(t)
84                } else {
85                    None
86                }
87            })
88            .collect()
89    }
90
91    /// Get all impl blocks.
92    pub fn impls(&self) -> Vec<&PureImpl> {
93        self.items
94            .iter()
95            .filter_map(|item| {
96                if let PureItem::Impl(i) = item {
97                    Some(i)
98                } else {
99                    None
100                }
101            })
102            .collect()
103    }
104
105    /// Wrap in Arc for thread-safe sharing.
106    pub fn into_arc(self) -> Arc<Self> {
107        Arc::new(self)
108    }
109}
110
111impl Default for PureFile {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117/// An item in a file or module.
118///
119/// # Future: PureAnyItem
120///
121/// If unified handling across PureItem, PureImplItem, and PureTraitItem
122/// is needed, consider adding a `PureAnyItem` enum that wraps all item types.
123/// This would be separate from PureItem to maintain context-specific type safety.
124#[derive(Debug, Clone, PartialEq, Eq)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126pub enum PureItem {
127    /// Use statement.
128    Use(PureUse),
129    /// Function definition.
130    Fn(PureFn),
131    /// Struct definition.
132    Struct(PureStruct),
133    /// Enum definition.
134    Enum(PureEnum),
135    /// Impl block.
136    Impl(PureImpl),
137    /// Const item.
138    Const(PureConst),
139    /// Static item.
140    Static(PureStatic),
141    /// Type alias.
142    Type(PureTypeAlias),
143    /// Module.
144    Mod(PureMod),
145    /// Trait definition.
146    Trait(PureTrait),
147    /// Macro invocation.
148    Macro(PureMacro),
149    /// Other/unsupported item (stored as string).
150    Other(String),
151}
152
153/// The meta content of an attribute.
154///
155/// Follows the structure of `syn::Meta`:
156/// - `Path`: Simple path attribute like `#[test]`
157/// - `List`: List attribute like `#[derive(Debug, Clone)]`
158/// - `NameValue`: Name-value attribute like `#[doc = "comment"]`
159#[derive(Debug, Clone, PartialEq, Eq)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
161pub enum PureAttrMeta {
162    /// Path only (e.g., `#[test]`, `#[inline]`)
163    Path,
164    /// List with arguments (e.g., `#[derive(Debug, Clone)]` → args = "Debug, Clone")
165    List(String),
166    /// Name-value pair (e.g., `#[doc = "comment"]` → value = "\"comment\"")
167    NameValue(String),
168}
169
170/// An attribute (e.g., `#[derive(Debug)]`).
171#[derive(Debug, Clone, PartialEq, Eq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173pub struct PureAttribute {
174    /// The attribute path (e.g., "derive", "cfg").
175    pub path: String,
176    /// The meta content.
177    pub meta: PureAttrMeta,
178    /// Is this an inner attribute (`#![...]`)?
179    pub is_inner: bool,
180}
181
182/// A use statement.
183#[derive(Debug, Clone, PartialEq, Eq)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
185pub struct PureUse {
186    /// Visibility.
187    pub vis: PureVis,
188    /// The use tree.
189    pub tree: PureUseTree,
190}
191
192/// A use tree.
193#[derive(Debug, Clone, PartialEq, Eq)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
195pub enum PureUseTree {
196    /// Simple path: `use std::io;`
197    Path {
198        /// Path segment (e.g. `std`).
199        path: String,
200        /// Continuation of the use tree after this segment.
201        tree: Box<PureUseTree>,
202    },
203    /// Name: `use std::io;` (the `io` part)
204    Name(String),
205    /// Rename: `use std::io as stdio;`
206    Rename {
207        /// Original name (`io`).
208        name: String,
209        /// Alias (`stdio`).
210        rename: String,
211    },
212    /// Glob: `use std::io::*;`
213    Glob,
214    /// Group: `use std::{io, fs};`
215    Group(Vec<PureUseTree>),
216}
217
218/// Visibility.
219#[derive(Debug, Clone, PartialEq, Eq, Default)]
220#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
221pub enum PureVis {
222    /// Private (default).
223    #[default]
224    Private,
225    /// `pub`
226    Public,
227    /// `pub(crate)`
228    Crate,
229    /// `pub(super)`
230    Super,
231    /// `pub(in path)`
232    In(String),
233}
234
235/// A function definition.
236#[derive(Debug, Clone, PartialEq, Eq)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238pub struct PureFn {
239    /// Attributes.
240    pub attrs: Vec<PureAttribute>,
241    /// Visibility.
242    pub vis: PureVis,
243    /// Is async (explicit `async fn`)?
244    pub is_async: bool,
245    /// Is async inferred from return type (`Pin<Box<dyn Future<...>>>`)?
246    /// This is typically set when `#[async_trait]` or similar macros are used.
247    #[cfg_attr(feature = "serde", serde(default))]
248    pub is_async_inferred: bool,
249    /// Is const?
250    pub is_const: bool,
251    /// Is unsafe?
252    pub is_unsafe: bool,
253    /// ABI (e.g., `"C"`, `"Rust"`, `"system"`). None means default Rust ABI.
254    #[cfg_attr(feature = "serde", serde(default))]
255    pub abi: Option<String>,
256    /// Function name.
257    pub name: String,
258    /// Generic parameters.
259    pub generics: PureGenerics,
260    /// Parameters.
261    pub params: Vec<PureParam>,
262    /// Return type (None = unit).
263    pub ret: Option<PureType>,
264    /// Function body.
265    pub body: PureBlock,
266}
267
268impl PureFn {
269    /// Returns true if the function is async (either explicit or inferred).
270    ///
271    /// This includes:
272    /// - Explicit `async fn` declarations
273    /// - Functions with `Pin<Box<dyn Future<...>>>` return type (e.g., from `#[async_trait]`)
274    pub fn is_effectively_async(&self) -> bool {
275        self.is_async || self.is_async_inferred
276    }
277}
278
279/// Function parameter.
280#[derive(Debug, Clone, PartialEq, Eq)]
281#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
282pub enum PureParam {
283    /// `self`
284    SelfValue {
285        /// `&self` / `&mut self` (i.e. taken by reference).
286        is_ref: bool,
287        /// `mut self` / `&mut self`.
288        is_mut: bool,
289    },
290    /// Named parameter.
291    Typed {
292        /// Parameter binding name.
293        name: String,
294        /// Parameter type.
295        ty: PureType,
296    },
297}
298
299/// Closure parameter with optional type annotation.
300///
301/// Unlike function parameters (`PureParam`), closure parameters:
302/// - Use patterns (destructuring, wildcards, etc.) not just names
303/// - Type annotations are optional (often inferred)
304///
305/// # Examples
306/// - `|x|` → `PureClosureParam { pattern: Ident("x"), ty: None }`
307/// - `|x: Foo|` → `PureClosureParam { pattern: Ident("x"), ty: Some(Path("Foo")) }`
308/// - `|(a, b): (Foo, Bar)|` → `PureClosureParam { pattern: Tuple(..), ty: Some(Tuple(..)) }`
309#[derive(Debug, Clone, PartialEq, Eq)]
310#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
311pub struct PureClosureParam {
312    /// The parameter pattern.
313    pub pattern: PurePattern,
314    /// Optional type annotation.
315    pub ty: Option<PureType>,
316}
317
318impl PureClosureParam {
319    /// Create an untyped closure parameter (type will be inferred).
320    pub fn untyped(pattern: PurePattern) -> Self {
321        Self { pattern, ty: None }
322    }
323
324    /// Create a typed closure parameter.
325    pub fn typed(pattern: PurePattern, ty: PureType) -> Self {
326        Self {
327            pattern,
328            ty: Some(ty),
329        }
330    }
331}
332
333/// A block of statements.
334#[derive(Debug, Clone, PartialEq, Eq, Default)]
335#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
336pub struct PureBlock {
337    /// Statements in the block.
338    pub stmts: Vec<PureStmt>,
339}
340
341impl PureBlock {
342    /// Get statement at index.
343    #[inline]
344    pub fn get_stmt(&self, index: usize) -> Option<&PureStmt> {
345        self.stmts.get(index)
346    }
347
348    /// Get mutable statement at index.
349    #[inline]
350    pub fn get_stmt_mut(&mut self, index: usize) -> Option<&mut PureStmt> {
351        self.stmts.get_mut(index)
352    }
353
354    /// Number of statements.
355    #[inline]
356    pub fn len(&self) -> usize {
357        self.stmts.len()
358    }
359
360    /// Is block empty?
361    #[inline]
362    pub fn is_empty(&self) -> bool {
363        self.stmts.is_empty()
364    }
365}
366
367/// A statement.
368#[derive(Debug, Clone, PartialEq, Eq)]
369#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
370pub enum PureStmt {
371    /// Local variable: `let x = 1;`
372    Local {
373        /// Binding pattern (LHS of `let`).
374        pattern: PurePattern,
375        /// Optional explicit type annotation.
376        ty: Option<PureType>,
377        /// Optional initializer expression.
378        init: Option<PureExpr>,
379    },
380    /// Expression with semicolon.
381    Semi(PureExpr),
382    /// Expression without semicolon (tail expression).
383    Expr(PureExpr),
384    /// Item statement (function inside function, etc.).
385    Item(Box<PureItem>),
386}
387
388impl PureStmt {
389    /// Get the expression contained in this statement.
390    ///
391    /// Returns `Some` for `Local` (init expr), `Semi`, and `Expr` variants.
392    /// Returns `None` for `Item` or if `Local` has no initializer.
393    pub fn get_expr(&self) -> Option<&PureExpr> {
394        match self {
395            PureStmt::Local { init, .. } => init.as_ref(),
396            PureStmt::Semi(e) | PureStmt::Expr(e) => Some(e),
397            PureStmt::Item(_) => None,
398        }
399    }
400
401    /// Get mutable expression contained in this statement.
402    pub fn get_expr_mut(&mut self) -> Option<&mut PureExpr> {
403        match self {
404            PureStmt::Local { init, .. } => init.as_mut(),
405            PureStmt::Semi(e) | PureStmt::Expr(e) => Some(e),
406            PureStmt::Item(_) => None,
407        }
408    }
409
410    /// Check if this statement contains an expression.
411    pub fn has_expr(&self) -> bool {
412        match self {
413            PureStmt::Local { init, .. } => init.is_some(),
414            PureStmt::Semi(_) | PureStmt::Expr(_) => true,
415            PureStmt::Item(_) => false,
416        }
417    }
418}
419
420/// A pattern.
421#[derive(Debug, Clone, PartialEq, Eq)]
422#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
423pub enum PurePattern {
424    /// Identifier: `x`, `mut x`
425    Ident {
426        /// Binding name.
427        name: String,
428        /// `mut` qualifier.
429        is_mut: bool,
430    },
431    /// Wildcard: `_`
432    Wild,
433    /// Tuple: `(a, b)`
434    Tuple(Vec<PurePattern>),
435    /// Struct: `Point { x, y }` or `Point { x, .. }`
436    Struct {
437        /// Struct path (e.g. `Point`).
438        path: String,
439        /// Field bindings (name, sub-pattern).
440        fields: Vec<(String, PurePattern)>,
441        /// Whether the pattern has a rest (`..`) at the end
442        rest: bool,
443    },
444    /// Reference: `&x`, `&mut x`
445    Ref {
446        /// `mut` qualifier (`&mut`).
447        is_mut: bool,
448        /// Inner pattern after `&` / `&mut`.
449        pattern: Box<PurePattern>,
450    },
451    /// Literal (for matching).
452    Lit(String),
453    /// Or pattern: `A | B`
454    Or(Vec<PurePattern>),
455    /// Path pattern: `Some`, `None`, `Enum::Variant`
456    Path(String),
457    /// Range pattern: `1..=5`, `'a'..='z'`
458    Range {
459        /// Start literal (None = unbounded below).
460        start: Option<String>,
461        /// End literal (None = unbounded above).
462        end: Option<String>,
463        /// `..=` (inclusive) vs `..` (exclusive).
464        inclusive: bool,
465    },
466    /// Slice pattern: `[a, b, ..]`
467    Slice(Vec<PurePattern>),
468    /// Rest pattern: `..`
469    Rest,
470    /// Other (as string) - unsupported patterns with error guidance.
471    Other(String),
472}
473
474/// An expression.
475#[derive(Debug, Clone, PartialEq, Eq)]
476#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
477pub enum PureExpr {
478    /// Literal: `1`, `"hello"`, `true`
479    Lit(String),
480    /// Path: `x`, `std::io::stdin`
481    Path(String),
482    /// Binary operation: `a + b`
483    Binary {
484        /// Operator token (e.g. `+`, `==`).
485        op: String,
486        /// Left operand.
487        left: Box<PureExpr>,
488        /// Right operand.
489        right: Box<PureExpr>,
490    },
491    /// Unary operation: `-x`, `!x`, `*x`, `&x`
492    Unary {
493        /// Operator token (`-` / `!` / `*` / `&`).
494        op: String,
495        /// Operand expression.
496        expr: Box<PureExpr>,
497    },
498    /// Function call: `foo(a, b)`
499    Call {
500        /// Callee expression.
501        func: Box<PureExpr>,
502        /// Argument expressions.
503        args: Vec<PureExpr>,
504    },
505    /// Method call: `x.foo(a, b)` or `x.foo::<T>(a, b)`
506    MethodCall {
507        /// Receiver expression (LHS of `.`).
508        receiver: Box<PureExpr>,
509        /// Method name.
510        method: String,
511        /// Turbofish type arguments: `::<T, U>` → `"< T , U >"`
512        turbofish: Option<String>,
513        /// Argument expressions.
514        args: Vec<PureExpr>,
515    },
516    /// Field access: `x.field`
517    Field {
518        /// Base expression.
519        expr: Box<PureExpr>,
520        /// Field name (or tuple index as string).
521        field: String,
522    },
523    /// Index: `x[i]`
524    Index {
525        /// Base expression (`x`).
526        expr: Box<PureExpr>,
527        /// Index expression (`i`).
528        index: Box<PureExpr>,
529    },
530    /// Block: `{ ... }` or `'label: { ... }`
531    Block {
532        /// Optional label (e.g., `'block`)
533        label: Option<String>,
534        /// Inner block.
535        block: PureBlock,
536    },
537    /// If: `if cond { ... } else { ... }`
538    If {
539        /// Condition expression.
540        cond: Box<PureExpr>,
541        /// `then` branch block.
542        then_branch: PureBlock,
543        /// Optional `else` branch (another expression).
544        else_branch: Option<Box<PureExpr>>,
545    },
546    /// Match: `match x { ... }`
547    Match {
548        /// Scrutinee expression.
549        expr: Box<PureExpr>,
550        /// Match arms.
551        arms: Vec<PureMatchArm>,
552    },
553    /// Loop: `loop { ... }` or `'label: loop { ... }`
554    Loop {
555        /// Optional label (e.g., `'outer`)
556        label: Option<String>,
557        /// Loop body block.
558        body: PureBlock,
559    },
560    /// While: `while cond { ... }` or `'label: while cond { ... }`
561    While {
562        /// Optional label (e.g., `'outer`)
563        label: Option<String>,
564        /// Loop condition.
565        cond: Box<PureExpr>,
566        /// Loop body block.
567        body: PureBlock,
568    },
569    /// For: `for pat in expr { ... }` or `'label: for pat in expr { ... }`
570    For {
571        /// Optional label (e.g., `'outer`)
572        label: Option<String>,
573        /// Iteration binding pattern.
574        pat: PurePattern,
575        /// Iterator expression.
576        expr: Box<PureExpr>,
577        /// Loop body block.
578        body: PureBlock,
579    },
580    /// Return: `return x`
581    Return(Option<Box<PureExpr>>),
582    /// Break: `break x` or `break 'label x`
583    Break {
584        /// Optional label to break to (e.g., `'outer`)
585        label: Option<String>,
586        /// Optional value carried out of the loop.
587        expr: Option<Box<PureExpr>>,
588    },
589    /// Continue: `continue` or `continue 'label`
590    Continue {
591        /// Optional label to continue to (e.g., `'outer`)
592        label: Option<String>,
593    },
594    /// Closure: `|x| x + 1`, `|x: Foo| -> Bar { x }`, `move |x| x`
595    Closure {
596        /// Is async closure?
597        is_async: bool,
598        /// Is move closure?
599        is_move: bool,
600        /// Parameters with optional type annotations.
601        params: Vec<PureClosureParam>,
602        /// Optional return type annotation.
603        ret: Option<PureType>,
604        /// Closure body expression.
605        body: Box<PureExpr>,
606    },
607    /// Struct literal: `Point { x: 1, y: 2 }`
608    Struct {
609        /// Struct path (e.g. `Point`).
610        path: String,
611        /// Field initializers (name, expr).
612        fields: Vec<(String, PureExpr)>,
613    },
614    /// Tuple: `(a, b, c)`
615    Tuple(Vec<PureExpr>),
616    /// Array: `[1, 2, 3]`
617    Array(Vec<PureExpr>),
618    /// Reference: `&x`, `&mut x`
619    Ref {
620        /// `mut` qualifier (`&mut`).
621        is_mut: bool,
622        /// Referent expression.
623        expr: Box<PureExpr>,
624    },
625    /// Macro invocation: `println!(...)`
626    Macro {
627        /// Macro name (without `!`).
628        name: String,
629        /// Delimiter style around tokens.
630        delimiter: MacroDelimiter,
631        /// Raw token stream as a string.
632        tokens: String,
633    },
634    /// Await: `x.await`
635    Await(Box<PureExpr>),
636    /// Try: `x?`
637    Try(Box<PureExpr>),
638    /// Range: `a..b`, `..b`, `a..`, `..`, `a..=b`
639    Range {
640        /// Start bound expression (None = unbounded).
641        start: Option<Box<PureExpr>>,
642        /// End bound expression (None = unbounded).
643        end: Option<Box<PureExpr>>,
644        /// true for `..=`, false for `..`
645        inclusive: bool,
646    },
647    /// Cast: `x as T`
648    Cast {
649        /// Source expression.
650        expr: Box<PureExpr>,
651        /// Target type.
652        ty: PureType,
653    },
654    /// Let expression (in conditions): `let Some(x) = y`
655    Let {
656        /// Pattern on the LHS of `let`.
657        pattern: PurePattern,
658        /// Scrutinee expression on the RHS.
659        expr: Box<PureExpr>,
660    },
661    /// Async block: `async { ... }` or `async move { ... }`
662    Async {
663        /// `move` qualifier.
664        is_move: bool,
665        /// Async block body.
666        body: PureBlock,
667    },
668    /// Unsafe block: `unsafe { ... }`
669    Unsafe(PureBlock),
670    /// Array repeat: `[expr; N]`
671    Repeat {
672        /// Element expression to repeat.
673        expr: Box<PureExpr>,
674        /// Repeat count expression.
675        len: Box<PureExpr>,
676    },
677    /// Other (as string).
678    Other(String),
679}
680
681impl PureExpr {
682    /// Create a MethodCall with default turbofish (None)
683    pub fn method_call(receiver: Box<PureExpr>, method: String, args: Vec<PureExpr>) -> Self {
684        PureExpr::MethodCall {
685            receiver,
686            method,
687            turbofish: None,
688            args,
689        }
690    }
691
692    /// Create a Closure with untyped params and no return type annotation.
693    pub fn closure(params: Vec<PureClosureParam>, body: Box<PureExpr>) -> Self {
694        PureExpr::Closure {
695            is_async: false,
696            is_move: false,
697            params,
698            ret: None,
699            body,
700        }
701    }
702
703    // ========== AST Navigation ==========
704
705    /// Get child expression at index.
706    ///
707    /// Index mapping varies by variant:
708    /// - `Binary`: 0=left, 1=right
709    /// - `Unary`, `Field`, `Ref`, `Await`, `Try`, `Cast`, `Let`: 0=expr
710    /// - `Call`: 0=func, 1..=args
711    /// - `MethodCall`: 0=receiver, 1..=args
712    /// - `Index`, `Repeat`: 0=expr, 1=index/len
713    /// - `Range`: 0=start, 1=end (if present)
714    /// - `If`: 0=cond, 1=else_branch (if present)
715    /// - `While`, `For`: 0=cond/expr
716    /// - `Match`: 0=expr
717    /// - `Return`, `Break`: 0=inner (if present)
718    /// - `Closure`: 0=body
719    /// - `Tuple`, `Array`: 0..n=elements
720    /// - `Struct`: 0..n=field values
721    /// - Others: None
722    pub fn get_child(&self, index: usize) -> Option<&PureExpr> {
723        match self {
724            // Single child at index 0
725            PureExpr::Unary { expr, .. }
726            | PureExpr::Field { expr, .. }
727            | PureExpr::Ref { expr, .. }
728            | PureExpr::Await(expr)
729            | PureExpr::Try(expr)
730            | PureExpr::Cast { expr, .. }
731            | PureExpr::Let { expr, .. } => {
732                if index == 0 {
733                    Some(expr)
734                } else {
735                    None
736                }
737            }
738
739            // Two children
740            PureExpr::Binary { left, right, .. } => match index {
741                0 => Some(left),
742                1 => Some(right),
743                _ => None,
744            },
745            PureExpr::Index { expr, index: idx } => match index {
746                0 => Some(expr),
747                1 => Some(idx),
748                _ => None,
749            },
750            PureExpr::Repeat { expr, len } => match index {
751                0 => Some(expr),
752                1 => Some(len),
753                _ => None,
754            },
755
756            // func/receiver + args
757            PureExpr::Call { func, args } => {
758                if index == 0 {
759                    Some(func)
760                } else {
761                    args.get(index - 1)
762                }
763            }
764            PureExpr::MethodCall { receiver, args, .. } => {
765                if index == 0 {
766                    Some(receiver)
767                } else {
768                    args.get(index - 1)
769                }
770            }
771
772            // Optional children
773            PureExpr::Range { start, end, .. } => match index {
774                0 => start.as_deref(),
775                1 => end.as_deref(),
776                _ => None,
777            },
778            PureExpr::If {
779                cond, else_branch, ..
780            } => match index {
781                0 => Some(cond),
782                1 => else_branch.as_deref(),
783                _ => None,
784            },
785            PureExpr::Return(opt) => {
786                if index == 0 {
787                    opt.as_deref()
788                } else {
789                    None
790                }
791            }
792            PureExpr::Break { expr: opt, .. } => {
793                if index == 0 {
794                    opt.as_deref()
795                } else {
796                    None
797                }
798            }
799
800            // Control flow with expr
801            PureExpr::While { cond, .. } => {
802                if index == 0 {
803                    Some(cond)
804                } else {
805                    None
806                }
807            }
808            PureExpr::For { expr, .. } => {
809                if index == 0 {
810                    Some(expr)
811                } else {
812                    None
813                }
814            }
815            PureExpr::Match { expr, .. } => {
816                if index == 0 {
817                    Some(expr)
818                } else {
819                    None
820                }
821            }
822
823            // Closure body
824            PureExpr::Closure { body, .. } => {
825                if index == 0 {
826                    Some(body)
827                } else {
828                    None
829                }
830            }
831
832            // Collections
833            PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => exprs.get(index),
834            PureExpr::Struct { fields, .. } => fields.get(index).map(|(_, e)| e),
835
836            // No direct children (use get_block for Block, Loop, Async, Unsafe)
837            PureExpr::Lit(_)
838            | PureExpr::Path(_)
839            | PureExpr::Block { .. }
840            | PureExpr::Loop { .. }
841            | PureExpr::Async { .. }
842            | PureExpr::Unsafe(_)
843            | PureExpr::Continue { .. }
844            | PureExpr::Macro { .. }
845            | PureExpr::Other(_) => None,
846        }
847    }
848
849    /// Get mutable child expression at index.
850    pub fn get_child_mut(&mut self, index: usize) -> Option<&mut PureExpr> {
851        match self {
852            PureExpr::Unary { expr, .. }
853            | PureExpr::Field { expr, .. }
854            | PureExpr::Ref { expr, .. }
855            | PureExpr::Await(expr)
856            | PureExpr::Try(expr)
857            | PureExpr::Cast { expr, .. }
858            | PureExpr::Let { expr, .. } => {
859                if index == 0 {
860                    Some(expr)
861                } else {
862                    None
863                }
864            }
865
866            PureExpr::Binary { left, right, .. } => match index {
867                0 => Some(left),
868                1 => Some(right),
869                _ => None,
870            },
871            PureExpr::Index { expr, index: idx } => match index {
872                0 => Some(expr),
873                1 => Some(idx),
874                _ => None,
875            },
876            PureExpr::Repeat { expr, len } => match index {
877                0 => Some(expr),
878                1 => Some(len),
879                _ => None,
880            },
881
882            PureExpr::Call { func, args } => {
883                if index == 0 {
884                    Some(func)
885                } else {
886                    args.get_mut(index - 1)
887                }
888            }
889            PureExpr::MethodCall { receiver, args, .. } => {
890                if index == 0 {
891                    Some(receiver)
892                } else {
893                    args.get_mut(index - 1)
894                }
895            }
896
897            PureExpr::Range { start, end, .. } => match index {
898                0 => start.as_deref_mut(),
899                1 => end.as_deref_mut(),
900                _ => None,
901            },
902            PureExpr::If {
903                cond, else_branch, ..
904            } => match index {
905                0 => Some(cond.as_mut()),
906                1 => else_branch.as_deref_mut(),
907                _ => None,
908            },
909            PureExpr::Return(opt) => {
910                if index == 0 {
911                    opt.as_deref_mut()
912                } else {
913                    None
914                }
915            }
916            PureExpr::Break { expr: opt, .. } => {
917                if index == 0 {
918                    opt.as_deref_mut()
919                } else {
920                    None
921                }
922            }
923
924            PureExpr::While { cond, .. } => {
925                if index == 0 {
926                    Some(cond)
927                } else {
928                    None
929                }
930            }
931            PureExpr::For { expr, .. } => {
932                if index == 0 {
933                    Some(expr)
934                } else {
935                    None
936                }
937            }
938            PureExpr::Match { expr, .. } => {
939                if index == 0 {
940                    Some(expr)
941                } else {
942                    None
943                }
944            }
945
946            PureExpr::Closure { body, .. } => {
947                if index == 0 {
948                    Some(body)
949                } else {
950                    None
951                }
952            }
953
954            PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => exprs.get_mut(index),
955            PureExpr::Struct { fields, .. } => fields.get_mut(index).map(|(_, e)| e),
956
957            PureExpr::Lit(_)
958            | PureExpr::Path(_)
959            | PureExpr::Block { .. }
960            | PureExpr::Loop { .. }
961            | PureExpr::Async { .. }
962            | PureExpr::Unsafe(_)
963            | PureExpr::Continue { .. }
964            | PureExpr::Macro { .. }
965            | PureExpr::Other(_) => None,
966        }
967    }
968
969    /// Get the contained block (for Block, Loop, Async, Unsafe, If, While, For).
970    pub fn get_block(&self) -> Option<&PureBlock> {
971        match self {
972            PureExpr::Block { block: b, .. }
973            | PureExpr::Loop { body: b, .. }
974            | PureExpr::Unsafe(b) => Some(b),
975            PureExpr::Async { body, .. } => Some(body),
976            PureExpr::If { then_branch, .. } => Some(then_branch),
977            PureExpr::While { body, .. } | PureExpr::For { body, .. } => Some(body),
978            _ => None,
979        }
980    }
981
982    /// Get mutable contained block.
983    pub fn get_block_mut(&mut self) -> Option<&mut PureBlock> {
984        match self {
985            PureExpr::Block { block: b, .. }
986            | PureExpr::Loop { body: b, .. }
987            | PureExpr::Unsafe(b) => Some(b),
988            PureExpr::Async { body, .. } => Some(body),
989            PureExpr::If { then_branch, .. } => Some(then_branch),
990            PureExpr::While { body, .. } | PureExpr::For { body, .. } => Some(body),
991            _ => None,
992        }
993    }
994
995    /// Number of direct child expressions.
996    pub fn child_count(&self) -> usize {
997        match self {
998            PureExpr::Lit(_)
999            | PureExpr::Path(_)
1000            | PureExpr::Continue { .. }
1001            | PureExpr::Macro { .. }
1002            | PureExpr::Other(_)
1003            | PureExpr::Block { .. }
1004            | PureExpr::Loop { .. }
1005            | PureExpr::Async { .. }
1006            | PureExpr::Unsafe(_) => 0,
1007
1008            PureExpr::Unary { .. }
1009            | PureExpr::Field { .. }
1010            | PureExpr::Ref { .. }
1011            | PureExpr::Await(_)
1012            | PureExpr::Try(_)
1013            | PureExpr::Cast { .. }
1014            | PureExpr::Let { .. }
1015            | PureExpr::Closure { .. }
1016            | PureExpr::While { .. }
1017            | PureExpr::For { .. }
1018            | PureExpr::Match { .. } => 1,
1019
1020            PureExpr::Binary { .. } | PureExpr::Index { .. } | PureExpr::Repeat { .. } => 2,
1021
1022            PureExpr::Range { start, end, .. } => start.is_some() as usize + end.is_some() as usize,
1023            PureExpr::If { else_branch, .. } => 1 + else_branch.is_some() as usize,
1024            PureExpr::Return(opt) => opt.is_some() as usize,
1025            PureExpr::Break { expr: opt, .. } => opt.is_some() as usize,
1026
1027            PureExpr::Call { args, .. } => 1 + args.len(),
1028            PureExpr::MethodCall { args, .. } => 1 + args.len(),
1029            PureExpr::Tuple(v) | PureExpr::Array(v) => v.len(),
1030            PureExpr::Struct { fields, .. } => fields.len(),
1031        }
1032    }
1033
1034    /// Navigate to a nested expression by path (slice of indices).
1035    ///
1036    /// # Example
1037    /// ```ignore
1038    /// // For: x.foo(a + b, c)
1039    /// // Path [1, 0] navigates to: args[0].left = `a`
1040    /// expr.navigate(&[1, 0])
1041    /// ```
1042    pub fn navigate(&self, path: &[usize]) -> Option<&PureExpr> {
1043        let mut current = self;
1044        for &idx in path {
1045            current = current.get_child(idx)?;
1046        }
1047        Some(current)
1048    }
1049
1050    /// Navigate to a nested expression mutably.
1051    pub fn navigate_mut(&mut self, path: &[usize]) -> Option<&mut PureExpr> {
1052        let mut current = self;
1053        for &idx in path {
1054            current = current.get_child_mut(idx)?;
1055        }
1056        Some(current)
1057    }
1058}
1059
1060/// Match arm.
1061#[derive(Debug, Clone, PartialEq, Eq)]
1062#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1063pub struct PureMatchArm {
1064    /// Pattern.
1065    pub pattern: PurePattern,
1066    /// Guard: `if cond`
1067    pub guard: Option<PureExpr>,
1068    /// Body.
1069    pub body: PureExpr,
1070}
1071
1072/// A type.
1073#[derive(Debug, Clone, PartialEq, Eq)]
1074#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1075pub enum PureType {
1076    /// Path type: `String`, `std::io::Result<T>`
1077    Path(String),
1078    /// Reference: `&T`, `&mut T`, `&'a T`
1079    Ref {
1080        /// Optional explicit lifetime (`'a`).
1081        lifetime: Option<String>,
1082        /// `mut` qualifier (`&mut`).
1083        is_mut: bool,
1084        /// Referent type.
1085        ty: Box<PureType>,
1086    },
1087    /// Tuple: `(A, B, C)`
1088    Tuple(Vec<PureType>),
1089    /// Array: `[T; N]`
1090    Array {
1091        /// Element type.
1092        ty: Box<PureType>,
1093        /// Length expression as a raw string.
1094        len: String,
1095    },
1096    /// Slice: `[T]`
1097    Slice(Box<PureType>),
1098    /// Function pointer: `fn(A) -> B`
1099    Fn {
1100        /// Parameter types.
1101        params: Vec<PureType>,
1102        /// Optional return type (None = unit).
1103        ret: Option<Box<PureType>>,
1104    },
1105    /// Impl trait: `impl Trait`
1106    ImplTrait(Vec<String>),
1107    /// Dyn trait: `dyn Trait`
1108    TraitObject(Vec<String>),
1109    /// Inferred: `_`
1110    Infer,
1111    /// Never: `!`
1112    Never,
1113    /// Other (as string).
1114    Other(String),
1115}
1116
1117/// Generic parameters.
1118#[derive(Debug, Clone, PartialEq, Eq, Default)]
1119#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1120pub struct PureGenerics {
1121    /// Type parameters.
1122    pub params: Vec<PureGenericParam>,
1123    /// Where clause.
1124    pub where_clause: Vec<String>,
1125}
1126
1127/// A generic parameter.
1128#[derive(Debug, Clone, PartialEq, Eq)]
1129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1130pub enum PureGenericParam {
1131    /// Type parameter: `T`, `T: Clone`
1132    Type {
1133        /// Parameter name (`T`).
1134        name: String,
1135        /// Trait bounds (`Clone + Send`).
1136        bounds: Vec<String>,
1137    },
1138    /// Lifetime: `'a`, `'a: 'b`
1139    Lifetime {
1140        /// Lifetime name (`'a`).
1141        name: String,
1142        /// Lifetime bounds (`'b`).
1143        bounds: Vec<String>,
1144    },
1145    /// Const: `const N: usize`
1146    Const {
1147        /// Const parameter name (`N`).
1148        name: String,
1149        /// Const parameter type as a raw string (`usize`).
1150        ty: String,
1151    },
1152}
1153
1154/// A struct definition.
1155#[derive(Debug, Clone, PartialEq, Eq)]
1156#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1157pub struct PureStruct {
1158    /// Attributes.
1159    pub attrs: Vec<PureAttribute>,
1160    /// Visibility.
1161    pub vis: PureVis,
1162    /// Name.
1163    pub name: String,
1164    /// Generics.
1165    pub generics: PureGenerics,
1166    /// Fields.
1167    pub fields: PureFields,
1168}
1169
1170/// Struct fields.
1171#[derive(Debug, Clone, PartialEq, Eq)]
1172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1173pub enum PureFields {
1174    /// Named fields: `{ x: i32, y: i32 }`
1175    Named(Vec<PureField>),
1176    /// Tuple fields: `(i32, i32)`
1177    Tuple(Vec<PureType>),
1178    /// Unit struct: no fields.
1179    Unit,
1180}
1181
1182/// A struct field.
1183#[derive(Debug, Clone, PartialEq, Eq)]
1184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1185pub struct PureField {
1186    /// Attributes.
1187    pub attrs: Vec<PureAttribute>,
1188    /// Visibility.
1189    pub vis: PureVis,
1190    /// Name.
1191    pub name: String,
1192    /// Type.
1193    pub ty: PureType,
1194}
1195
1196/// An enum definition.
1197#[derive(Debug, Clone, PartialEq, Eq)]
1198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1199pub struct PureEnum {
1200    /// Attributes.
1201    pub attrs: Vec<PureAttribute>,
1202    /// Visibility.
1203    pub vis: PureVis,
1204    /// Name.
1205    pub name: String,
1206    /// Generics.
1207    pub generics: PureGenerics,
1208    /// Variants.
1209    pub variants: Vec<PureVariant>,
1210}
1211
1212/// An enum variant.
1213#[derive(Debug, Clone, PartialEq, Eq)]
1214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1215pub struct PureVariant {
1216    /// Attributes.
1217    pub attrs: Vec<PureAttribute>,
1218    /// Name.
1219    pub name: String,
1220    /// Fields.
1221    pub fields: PureFields,
1222    /// Discriminant: `= 1`
1223    pub discriminant: Option<String>,
1224}
1225
1226/// An impl block.
1227#[derive(Debug, Clone, PartialEq, Eq)]
1228#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1229pub struct PureImpl {
1230    /// Attributes.
1231    pub attrs: Vec<PureAttribute>,
1232    /// Generics.
1233    pub generics: PureGenerics,
1234    /// Whether this is an unsafe impl.
1235    pub is_unsafe: bool,
1236    /// Trait being implemented (if any).
1237    pub trait_: Option<String>,
1238    /// Type being implemented for.
1239    pub self_ty: String,
1240    /// Items in the impl.
1241    pub items: Vec<PureImplItem>,
1242}
1243
1244/// An item in an impl block.
1245#[derive(Debug, Clone, PartialEq, Eq)]
1246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1247pub enum PureImplItem {
1248    /// Method.
1249    Fn(PureFn),
1250    /// Const.
1251    Const(PureConst),
1252    /// Type alias.
1253    Type(PureTypeAlias),
1254    /// Other.
1255    Other(String),
1256}
1257
1258/// A const item.
1259#[derive(Debug, Clone, PartialEq, Eq)]
1260#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1261pub struct PureConst {
1262    /// Attributes.
1263    pub attrs: Vec<PureAttribute>,
1264    /// Visibility.
1265    pub vis: PureVis,
1266    /// Name.
1267    pub name: String,
1268    /// Type.
1269    pub ty: PureType,
1270    /// Value expression (None for trait consts without default).
1271    pub value: Option<PureExpr>,
1272}
1273
1274/// A static item.
1275#[derive(Debug, Clone, PartialEq, Eq)]
1276#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1277pub struct PureStatic {
1278    /// Attributes.
1279    pub attrs: Vec<PureAttribute>,
1280    /// Visibility.
1281    pub vis: PureVis,
1282    /// Is mutable?
1283    pub is_mut: bool,
1284    /// Name.
1285    pub name: String,
1286    /// Type.
1287    pub ty: PureType,
1288    /// Value expression.
1289    pub value: PureExpr,
1290}
1291
1292/// A type alias.
1293#[derive(Debug, Clone, PartialEq, Eq)]
1294#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1295pub struct PureTypeAlias {
1296    /// Attributes.
1297    pub attrs: Vec<PureAttribute>,
1298    /// Visibility.
1299    pub vis: PureVis,
1300    /// Name.
1301    pub name: String,
1302    /// Generics.
1303    pub generics: PureGenerics,
1304    /// The aliased type.
1305    pub ty: PureType,
1306}
1307
1308/// A module definition.
1309///
1310/// Contains all items belonging to this module.
1311/// File vs inline distinction is handled at the file system layer,
1312/// not in the AST representation.
1313#[derive(Debug, Clone, PartialEq, Eq)]
1314#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1315pub struct PureMod {
1316    /// Attributes (both outer `#[...]` and inner `#![...]`).
1317    /// Use `PureAttribute::is_inner` to distinguish.
1318    pub attrs: Vec<PureAttribute>,
1319    /// Visibility.
1320    pub vis: PureVis,
1321    /// Module name.
1322    pub name: String,
1323    /// Module items.
1324    pub items: Vec<PureItem>,
1325}
1326
1327/// A trait definition.
1328#[derive(Debug, Clone, PartialEq, Eq)]
1329#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1330pub struct PureTrait {
1331    /// Attributes.
1332    pub attrs: Vec<PureAttribute>,
1333    /// Visibility.
1334    pub vis: PureVis,
1335    /// Is unsafe?
1336    pub is_unsafe: bool,
1337    /// Is auto?
1338    pub is_auto: bool,
1339    /// Name.
1340    pub name: String,
1341    /// Generics.
1342    pub generics: PureGenerics,
1343    /// Supertraits.
1344    pub supertraits: Vec<String>,
1345    /// Items in the trait.
1346    pub items: Vec<PureTraitItem>,
1347}
1348
1349/// An item in a trait.
1350#[derive(Debug, Clone, PartialEq, Eq)]
1351#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1352pub enum PureTraitItem {
1353    /// Method (with optional default impl).
1354    Fn(PureFn),
1355    /// Const.
1356    Const(PureConst),
1357    /// Type.
1358    Type {
1359        /// Associated type name.
1360        name: String,
1361        /// Trait bounds on the associated type.
1362        bounds: Vec<String>,
1363        /// Optional default type.
1364        default: Option<PureType>,
1365    },
1366    /// Other.
1367    Other(String),
1368}
1369
1370/// A macro invocation at item level.
1371#[derive(Debug, Clone, PartialEq, Eq)]
1372#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1373pub struct PureMacro {
1374    /// Macro path.
1375    pub path: String,
1376    /// Delimiter used.
1377    pub delimiter: MacroDelimiter,
1378    /// Tokens inside (as string).
1379    pub tokens: String,
1380}
1381
1382/// Macro delimiter.
1383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1384#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1385pub enum MacroDelimiter {
1386    /// `( ... )`
1387    Paren,
1388    /// `{ ... }`
1389    Brace,
1390    /// `[ ... ]`
1391    Bracket,
1392}
1393
1394#[cfg(test)]
1395mod tests {
1396    use super::*;
1397
1398    #[test]
1399    fn test_pure_file_send_sync() {
1400        fn assert_send_sync<T: Send + Sync>() {}
1401        assert_send_sync::<PureFile>();
1402    }
1403
1404    #[test]
1405    #[cfg(feature = "serde")]
1406    fn test_serde_json_roundtrip() {
1407        let file = PureFile::from_source(
1408            r#"
1409            use std::io;
1410
1411            fn hello(name: &str) -> String {
1412                format!("Hello, {}!", name)
1413            }
1414
1415            struct Point {
1416                x: i32,
1417                y: i32,
1418            }
1419            "#,
1420        )
1421        .unwrap();
1422
1423        // Serialize to JSON
1424        let json = serde_json::to_string_pretty(&file).unwrap();
1425        assert!(json.contains("hello"));
1426        assert!(json.contains("Point"));
1427
1428        // Deserialize back
1429        let restored: PureFile = serde_json::from_str(&json).unwrap();
1430        assert_eq!(file, restored);
1431    }
1432
1433    #[test]
1434    #[cfg(feature = "serde")]
1435    fn test_serde_compact() {
1436        let file = PureFile::from_source("fn main() {}").unwrap();
1437
1438        // Compact JSON
1439        let json = serde_json::to_string(&file).unwrap();
1440
1441        // Should be relatively compact
1442        assert!(json.len() < 500);
1443
1444        // Roundtrip
1445        let restored: PureFile = serde_json::from_str(&json).unwrap();
1446        assert_eq!(file, restored);
1447    }
1448
1449    #[test]
1450    #[cfg(feature = "serde")]
1451    fn test_serde_complex_ast() {
1452        let file = PureFile::from_source(
1453            r#"
1454            pub struct Config<T: Clone> {
1455                pub name: String,
1456                pub value: T,
1457            }
1458
1459            impl<T: Clone> Config<T> {
1460                pub fn new(name: String, value: T) -> Self {
1461                    Self { name, value }
1462                }
1463            }
1464
1465            pub trait Configurable {
1466                fn config(&self) -> &str;
1467            }
1468            "#,
1469        )
1470        .unwrap();
1471
1472        let json = serde_json::to_string(&file).unwrap();
1473        let restored: PureFile = serde_json::from_str(&json).unwrap();
1474        assert_eq!(file, restored);
1475
1476        // Verify structure preserved
1477        assert_eq!(restored.structs().len(), 1);
1478        assert_eq!(restored.structs()[0].name, "Config");
1479    }
1480
1481    // ========== Navigation Tests ==========
1482
1483    #[test]
1484    fn test_pure_block_navigation() {
1485        let block = PureBlock {
1486            stmts: vec![
1487                PureStmt::Semi(PureExpr::Lit("1".into())),
1488                PureStmt::Semi(PureExpr::Lit("2".into())),
1489                PureStmt::Expr(PureExpr::Lit("3".into())),
1490            ],
1491        };
1492
1493        assert_eq!(block.len(), 3);
1494        assert!(!block.is_empty());
1495
1496        let stmt0 = block.get_stmt(0).unwrap();
1497        assert!(matches!(stmt0, PureStmt::Semi(PureExpr::Lit(s)) if s == "1"));
1498
1499        let stmt2 = block.get_stmt(2).unwrap();
1500        assert!(matches!(stmt2, PureStmt::Expr(PureExpr::Lit(s)) if s == "3"));
1501
1502        assert!(block.get_stmt(3).is_none());
1503    }
1504
1505    #[test]
1506    fn test_pure_stmt_get_expr() {
1507        let semi = PureStmt::Semi(PureExpr::Lit("x".into()));
1508        assert!(semi.has_expr());
1509        assert!(matches!(semi.get_expr(), Some(PureExpr::Lit(_))));
1510
1511        let local_with_init = PureStmt::Local {
1512            pattern: PurePattern::Ident {
1513                name: "x".into(),
1514                is_mut: false,
1515            },
1516            ty: None,
1517            init: Some(PureExpr::Lit("42".into())),
1518        };
1519        assert!(local_with_init.has_expr());
1520        assert!(matches!(local_with_init.get_expr(), Some(PureExpr::Lit(_))));
1521
1522        let local_no_init = PureStmt::Local {
1523            pattern: PurePattern::Ident {
1524                name: "x".into(),
1525                is_mut: false,
1526            },
1527            ty: None,
1528            init: None,
1529        };
1530        assert!(!local_no_init.has_expr());
1531        assert!(local_no_init.get_expr().is_none());
1532    }
1533
1534    #[test]
1535    fn test_pure_expr_get_child_binary() {
1536        // a + b
1537        let expr = PureExpr::Binary {
1538            op: "+".into(),
1539            left: Box::new(PureExpr::Path("a".into())),
1540            right: Box::new(PureExpr::Path("b".into())),
1541        };
1542
1543        assert_eq!(expr.child_count(), 2);
1544
1545        let left = expr.get_child(0).unwrap();
1546        assert!(matches!(left, PureExpr::Path(s) if s == "a"));
1547
1548        let right = expr.get_child(1).unwrap();
1549        assert!(matches!(right, PureExpr::Path(s) if s == "b"));
1550
1551        assert!(expr.get_child(2).is_none());
1552    }
1553
1554    #[test]
1555    fn test_pure_expr_get_child_method_call() {
1556        // x.foo(a, b)
1557        let expr = PureExpr::MethodCall {
1558            receiver: Box::new(PureExpr::Path("x".into())),
1559            method: "foo".into(),
1560            turbofish: None,
1561            args: vec![PureExpr::Path("a".into()), PureExpr::Path("b".into())],
1562        };
1563
1564        assert_eq!(expr.child_count(), 3); // receiver + 2 args
1565
1566        let receiver = expr.get_child(0).unwrap();
1567        assert!(matches!(receiver, PureExpr::Path(s) if s == "x"));
1568
1569        let arg0 = expr.get_child(1).unwrap();
1570        assert!(matches!(arg0, PureExpr::Path(s) if s == "a"));
1571
1572        let arg1 = expr.get_child(2).unwrap();
1573        assert!(matches!(arg1, PureExpr::Path(s) if s == "b"));
1574
1575        assert!(expr.get_child(3).is_none());
1576    }
1577
1578    #[test]
1579    fn test_pure_expr_navigate() {
1580        // x.foo(a + b, c)
1581        let expr = PureExpr::MethodCall {
1582            receiver: Box::new(PureExpr::Path("x".into())),
1583            method: "foo".into(),
1584            turbofish: None,
1585            args: vec![
1586                PureExpr::Binary {
1587                    op: "+".into(),
1588                    left: Box::new(PureExpr::Path("a".into())),
1589                    right: Box::new(PureExpr::Path("b".into())),
1590                },
1591                PureExpr::Path("c".into()),
1592            ],
1593        };
1594
1595        // Navigate to receiver
1596        let r = expr.navigate(&[0]).unwrap();
1597        assert!(matches!(r, PureExpr::Path(s) if s == "x"));
1598
1599        // Navigate to first arg (a + b)
1600        let arg0 = expr.navigate(&[1]).unwrap();
1601        assert!(matches!(arg0, PureExpr::Binary { .. }));
1602
1603        // Navigate to left of first arg (a)
1604        let a = expr.navigate(&[1, 0]).unwrap();
1605        assert!(matches!(a, PureExpr::Path(s) if s == "a"));
1606
1607        // Navigate to right of first arg (b)
1608        let b = expr.navigate(&[1, 1]).unwrap();
1609        assert!(matches!(b, PureExpr::Path(s) if s == "b"));
1610
1611        // Invalid path
1612        assert!(expr.navigate(&[1, 2]).is_none());
1613        assert!(expr.navigate(&[5]).is_none());
1614    }
1615
1616    #[test]
1617    fn test_pure_expr_get_block() {
1618        let block_expr = PureExpr::Block {
1619            label: None,
1620            block: PureBlock {
1621                stmts: vec![PureStmt::Expr(PureExpr::Lit("1".into()))],
1622            },
1623        };
1624        assert!(block_expr.get_block().is_some());
1625        assert_eq!(block_expr.get_block().unwrap().len(), 1);
1626
1627        let lit = PureExpr::Lit("x".into());
1628        assert!(lit.get_block().is_none());
1629    }
1630}