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, **parsed and re-emitted via
150    /// `syn::parse_str`** — trivia like `//` line comments and blank lines is
151    /// lost in the round-trip).
152    Other(String),
153    /// Verbatim Rust source bytes that must survive round-trip exactly as
154    /// authored. Unlike `Other`, the contents are **never** parsed: a
155    /// sentinel stub is emitted into the syn::File so prettyplease can lay
156    /// out neighbouring items, and `PureFile::to_source` splices the raw
157    /// bytes back over the stub afterwards. This is the structural escape
158    /// hatch for the β path (RyoLang subset + ReplaceCode) and is the only
159    /// way to preserve DSL macro spacing, `//` comments, blank lines, and
160    /// other lexer-stripped trivia through ryo's codegen pipeline.
161    Verbatim(String),
162}
163
164/// The meta content of an attribute.
165///
166/// Follows the structure of `syn::Meta`:
167/// - `Path`: Simple path attribute like `#[test]`
168/// - `List`: List attribute like `#[derive(Debug, Clone)]`
169/// - `NameValue`: Name-value attribute like `#[doc = "comment"]`
170#[derive(Debug, Clone, PartialEq, Eq)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
172pub enum PureAttrMeta {
173    /// Path only (e.g., `#[test]`, `#[inline]`)
174    Path,
175    /// List with arguments (e.g., `#[derive(Debug, Clone)]` → args = "Debug, Clone")
176    List(String),
177    /// Name-value pair (e.g., `#[doc = "comment"]` → value = "\"comment\"")
178    NameValue(String),
179}
180
181/// An attribute (e.g., `#[derive(Debug)]`).
182#[derive(Debug, Clone, PartialEq, Eq)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184pub struct PureAttribute {
185    /// The attribute path (e.g., "derive", "cfg").
186    pub path: String,
187    /// The meta content.
188    pub meta: PureAttrMeta,
189    /// Is this an inner attribute (`#![...]`)?
190    pub is_inner: bool,
191}
192
193/// A use statement.
194#[derive(Debug, Clone, PartialEq, Eq)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
196pub struct PureUse {
197    /// Visibility.
198    pub vis: PureVis,
199    /// The use tree.
200    pub tree: PureUseTree,
201}
202
203/// A use tree.
204#[derive(Debug, Clone, PartialEq, Eq)]
205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
206pub enum PureUseTree {
207    /// Simple path: `use std::io;`
208    Path {
209        /// Path segment (e.g. `std`).
210        path: String,
211        /// Continuation of the use tree after this segment.
212        tree: Box<PureUseTree>,
213    },
214    /// Name: `use std::io;` (the `io` part)
215    Name(String),
216    /// Rename: `use std::io as stdio;`
217    Rename {
218        /// Original name (`io`).
219        name: String,
220        /// Alias (`stdio`).
221        rename: String,
222    },
223    /// Glob: `use std::io::*;`
224    Glob,
225    /// Group: `use std::{io, fs};`
226    Group(Vec<PureUseTree>),
227}
228
229/// Visibility.
230#[derive(Debug, Clone, PartialEq, Eq, Default)]
231#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
232pub enum PureVis {
233    /// Private (default).
234    #[default]
235    Private,
236    /// `pub`
237    Public,
238    /// `pub(crate)`
239    Crate,
240    /// `pub(super)`
241    Super,
242    /// `pub(in path)`
243    In(String),
244}
245
246/// A function definition.
247#[derive(Debug, Clone, PartialEq, Eq)]
248#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
249pub struct PureFn {
250    /// Attributes.
251    pub attrs: Vec<PureAttribute>,
252    /// Visibility.
253    pub vis: PureVis,
254    /// Is async (explicit `async fn`)?
255    pub is_async: bool,
256    /// Is async inferred from return type (`Pin<Box<dyn Future<...>>>`)?
257    /// This is typically set when `#[async_trait]` or similar macros are used.
258    #[cfg_attr(feature = "serde", serde(default))]
259    pub is_async_inferred: bool,
260    /// Is const?
261    pub is_const: bool,
262    /// Is unsafe?
263    pub is_unsafe: bool,
264    /// ABI (e.g., `"C"`, `"Rust"`, `"system"`). None means default Rust ABI.
265    #[cfg_attr(feature = "serde", serde(default))]
266    pub abi: Option<String>,
267    /// Function name.
268    pub name: String,
269    /// Generic parameters.
270    pub generics: PureGenerics,
271    /// Parameters.
272    pub params: Vec<PureParam>,
273    /// Return type (None = unit).
274    pub ret: Option<PureType>,
275    /// Function body.
276    pub body: PureBlock,
277}
278
279impl PureFn {
280    /// Returns true if the function is async (either explicit or inferred).
281    ///
282    /// This includes:
283    /// - Explicit `async fn` declarations
284    /// - Functions with `Pin<Box<dyn Future<...>>>` return type (e.g., from `#[async_trait]`)
285    pub fn is_effectively_async(&self) -> bool {
286        self.is_async || self.is_async_inferred
287    }
288}
289
290/// Function parameter.
291#[derive(Debug, Clone, PartialEq, Eq)]
292#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
293pub enum PureParam {
294    /// `self`
295    SelfValue {
296        /// `&self` / `&mut self` (i.e. taken by reference).
297        is_ref: bool,
298        /// `mut self` / `&mut self`.
299        is_mut: bool,
300    },
301    /// Named parameter.
302    Typed {
303        /// Parameter binding name.
304        name: String,
305        /// Parameter type.
306        ty: PureType,
307        /// `mut` binding qualifier (e.g. `fn f(mut x: T)`).
308        ///
309        /// Must be preserved across roundtrip; losing it produces E0596
310        /// "cannot borrow as mutable" the next time the executor re-renders
311        /// the file, because every mutating use of `x` loses its backing
312        /// mutability.
313        is_mut: bool,
314        /// Original parameter pattern, when the source used something
315        /// richer than a bare identifier — e.g.
316        /// `fn execute_group(SpecGroup { x, .. }: SpecGroup)` or
317        /// `fn tuple((a, b): (u32, u32))`.
318        ///
319        /// `None` for the common `Pat::Ident` case (the binding name in
320        /// `name` is sufficient to re-emit). `Some` instructs `to_syn`
321        /// to render the original pattern verbatim instead of
322        /// synthesising a `Pat::Ident { name }`, which would parse as
323        /// `Pat::Path` (and the cargo "patterns aren't allowed in
324        /// functions without bodies" error) whenever the lossy
325        /// `pat_to_name` collapse produced an uppercase-leading binding
326        /// like `SpecGroup`. Retires the defensive RL061 detect filter
327        /// added in `f196d30d`.
328        pat: Option<PurePattern>,
329    },
330}
331
332/// Closure parameter with optional type annotation.
333///
334/// Unlike function parameters (`PureParam`), closure parameters:
335/// - Use patterns (destructuring, wildcards, etc.) not just names
336/// - Type annotations are optional (often inferred)
337///
338/// # Examples
339/// - `|x|` → `PureClosureParam { pattern: Ident("x"), ty: None }`
340/// - `|x: Foo|` → `PureClosureParam { pattern: Ident("x"), ty: Some(Path("Foo")) }`
341/// - `|(a, b): (Foo, Bar)|` → `PureClosureParam { pattern: Tuple(..), ty: Some(Tuple(..)) }`
342#[derive(Debug, Clone, PartialEq, Eq)]
343#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
344pub struct PureClosureParam {
345    /// The parameter pattern.
346    pub pattern: PurePattern,
347    /// Optional type annotation.
348    pub ty: Option<PureType>,
349}
350
351impl PureClosureParam {
352    /// Create an untyped closure parameter (type will be inferred).
353    pub fn untyped(pattern: PurePattern) -> Self {
354        Self { pattern, ty: None }
355    }
356
357    /// Create a typed closure parameter.
358    pub fn typed(pattern: PurePattern, ty: PureType) -> Self {
359        Self {
360            pattern,
361            ty: Some(ty),
362        }
363    }
364}
365
366/// A block of statements.
367#[derive(Debug, Clone, PartialEq, Eq, Default)]
368#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
369pub struct PureBlock {
370    /// Statements in the block.
371    pub stmts: Vec<PureStmt>,
372}
373
374impl PureBlock {
375    /// Get statement at index.
376    #[inline]
377    pub fn get_stmt(&self, index: usize) -> Option<&PureStmt> {
378        self.stmts.get(index)
379    }
380
381    /// Get mutable statement at index.
382    #[inline]
383    pub fn get_stmt_mut(&mut self, index: usize) -> Option<&mut PureStmt> {
384        self.stmts.get_mut(index)
385    }
386
387    /// Number of statements.
388    #[inline]
389    pub fn len(&self) -> usize {
390        self.stmts.len()
391    }
392
393    /// Is block empty?
394    #[inline]
395    pub fn is_empty(&self) -> bool {
396        self.stmts.is_empty()
397    }
398}
399
400/// A statement.
401#[derive(Debug, Clone, PartialEq, Eq)]
402#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
403pub enum PureStmt {
404    /// Local variable: `let x = 1;` or `let Some(x) = opt else { ... };` (let-else).
405    Local {
406        /// Binding pattern (LHS of `let`).
407        pattern: PurePattern,
408        /// Optional explicit type annotation.
409        ty: Option<PureType>,
410        /// Optional initializer expression.
411        init: Option<PureExpr>,
412        /// Optional diverging else-block for let-else (`else { <block> }`).
413        ///
414        /// Corresponds to `syn::LocalInit::diverge`. Must be present when the
415        /// pattern is refutable; losing this field causes E0005 at compile time.
416        else_branch: Option<Box<PureExpr>>,
417    },
418    /// Expression with semicolon.
419    Semi(PureExpr),
420    /// Expression without semicolon (tail expression).
421    Expr(PureExpr),
422    /// Item statement (function inside function, etc.).
423    Item(Box<PureItem>),
424    /// Verbatim raw bytes that must survive `PureFile::to_source` exactly
425    /// as authored. Same staging mechanism as `PureExpr::Verbatim` but at
426    /// the statement level: lowered to a unique sentinel
427    /// `__ryo_verbatim_stmt_<N>;` expression-statement stub, then the
428    /// post-prettyplease splice step replaces the **entire line**
429    /// containing that identifier with the raw bytes (matching the
430    /// item-level splice semantics, because a Stmt occupies its own
431    /// line in prettyplease output). Used for raw `let x = ...;`,
432    /// macro invocation stmts (`tracing::info!(...);`), and other
433    /// shapes where the entire stmt — not just the contained expr —
434    /// must carry user trivia (B-3-cont).
435    Verbatim(String),
436}
437
438impl PureStmt {
439    /// Get the expression contained in this statement.
440    ///
441    /// Returns `Some` for `Local` (init expr), `Semi`, and `Expr` variants.
442    /// Returns `None` for `Item` or if `Local` has no initializer.
443    pub fn get_expr(&self) -> Option<&PureExpr> {
444        match self {
445            PureStmt::Local { init, .. } => init.as_ref(),
446            PureStmt::Semi(e) | PureStmt::Expr(e) => Some(e),
447            PureStmt::Item(_) | PureStmt::Verbatim(_) => None,
448        }
449    }
450
451    /// Get mutable expression contained in this statement.
452    pub fn get_expr_mut(&mut self) -> Option<&mut PureExpr> {
453        match self {
454            PureStmt::Local { init, .. } => init.as_mut(),
455            PureStmt::Semi(e) | PureStmt::Expr(e) => Some(e),
456            PureStmt::Item(_) | PureStmt::Verbatim(_) => None,
457        }
458    }
459
460    /// Check if this statement contains an expression.
461    pub fn has_expr(&self) -> bool {
462        match self {
463            PureStmt::Local { init, .. } => init.is_some(),
464            PureStmt::Semi(_) | PureStmt::Expr(_) => true,
465            PureStmt::Item(_) | PureStmt::Verbatim(_) => false,
466        }
467    }
468}
469
470/// A pattern.
471#[derive(Debug, Clone, PartialEq, Eq)]
472#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
473pub enum PurePattern {
474    /// Identifier: `x`, `mut x`, `ref x`, `ref mut x`
475    Ident {
476        /// Binding name.
477        name: String,
478        /// `mut` qualifier.
479        is_mut: bool,
480        /// `ref` binding mode.
481        by_ref: bool,
482    },
483    /// Wildcard: `_`
484    Wild,
485    /// Tuple: `(a, b)`
486    Tuple(Vec<PurePattern>),
487    /// Struct: `Point { x, y }` or `Point { x, .. }`
488    Struct {
489        /// Struct path (e.g. `Point`).
490        path: String,
491        /// Field bindings (name, sub-pattern).
492        fields: Vec<(String, PurePattern)>,
493        /// Whether the pattern has a rest (`..`) at the end
494        rest: bool,
495    },
496    /// Reference: `&x`, `&mut x`
497    Ref {
498        /// `mut` qualifier (`&mut`).
499        is_mut: bool,
500        /// Inner pattern after `&` / `&mut`.
501        pattern: Box<PurePattern>,
502    },
503    /// Literal (for matching).
504    Lit(String),
505    /// Or pattern: `A | B`
506    Or(Vec<PurePattern>),
507    /// Path pattern: `Some`, `None`, `Enum::Variant`
508    Path(String),
509    /// Range pattern: `1..=5`, `'a'..='z'`
510    Range {
511        /// Start literal (None = unbounded below).
512        start: Option<String>,
513        /// End literal (None = unbounded above).
514        end: Option<String>,
515        /// `..=` (inclusive) vs `..` (exclusive).
516        inclusive: bool,
517    },
518    /// Slice pattern: `[a, b, ..]`
519    Slice(Vec<PurePattern>),
520    /// Rest pattern: `..`
521    Rest,
522    /// Other (as string) - unsupported patterns with error guidance.
523    Other(String),
524}
525
526/// An expression.
527#[derive(Debug, Clone, PartialEq, Eq)]
528#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
529pub enum PureExpr {
530    /// Literal: `1`, `"hello"`, `true`
531    Lit(String),
532    /// Path: `x`, `std::io::stdin`
533    Path(String),
534    /// Binary operation: `a + b`
535    Binary {
536        /// Operator token (e.g. `+`, `==`).
537        op: String,
538        /// Left operand.
539        left: Box<PureExpr>,
540        /// Right operand.
541        right: Box<PureExpr>,
542    },
543    /// Unary operation: `-x`, `!x`, `*x`, `&x`
544    Unary {
545        /// Operator token (`-` / `!` / `*` / `&`).
546        op: String,
547        /// Operand expression.
548        expr: Box<PureExpr>,
549    },
550    /// Function call: `foo(a, b)`
551    Call {
552        /// Callee expression.
553        func: Box<PureExpr>,
554        /// Argument expressions.
555        args: Vec<PureExpr>,
556    },
557    /// Method call: `x.foo(a, b)` or `x.foo::<T>(a, b)`
558    MethodCall {
559        /// Receiver expression (LHS of `.`).
560        receiver: Box<PureExpr>,
561        /// Method name.
562        method: String,
563        /// Turbofish type arguments: `::<T, U>` → `"< T , U >"`
564        turbofish: Option<String>,
565        /// Argument expressions.
566        args: Vec<PureExpr>,
567    },
568    /// Field access: `x.field`
569    Field {
570        /// Base expression.
571        expr: Box<PureExpr>,
572        /// Field name (or tuple index as string).
573        field: String,
574    },
575    /// Index: `x[i]`
576    Index {
577        /// Base expression (`x`).
578        expr: Box<PureExpr>,
579        /// Index expression (`i`).
580        index: Box<PureExpr>,
581    },
582    /// Block: `{ ... }` or `'label: { ... }`
583    Block {
584        /// Optional label (e.g., `'block`)
585        label: Option<String>,
586        /// Inner block.
587        block: PureBlock,
588    },
589    /// If: `if cond { ... } else { ... }`
590    If {
591        /// Condition expression.
592        cond: Box<PureExpr>,
593        /// `then` branch block.
594        then_branch: PureBlock,
595        /// Optional `else` branch (another expression).
596        else_branch: Option<Box<PureExpr>>,
597    },
598    /// Match: `match x { ... }`
599    Match {
600        /// Scrutinee expression.
601        expr: Box<PureExpr>,
602        /// Match arms.
603        arms: Vec<PureMatchArm>,
604    },
605    /// Loop: `loop { ... }` or `'label: loop { ... }`
606    Loop {
607        /// Optional label (e.g., `'outer`)
608        label: Option<String>,
609        /// Loop body block.
610        body: PureBlock,
611    },
612    /// While: `while cond { ... }` or `'label: while cond { ... }`
613    While {
614        /// Optional label (e.g., `'outer`)
615        label: Option<String>,
616        /// Loop condition.
617        cond: Box<PureExpr>,
618        /// Loop body block.
619        body: PureBlock,
620    },
621    /// For: `for pat in expr { ... }` or `'label: for pat in expr { ... }`
622    For {
623        /// Optional label (e.g., `'outer`)
624        label: Option<String>,
625        /// Iteration binding pattern.
626        pat: PurePattern,
627        /// Iterator expression.
628        expr: Box<PureExpr>,
629        /// Loop body block.
630        body: PureBlock,
631    },
632    /// Return: `return x`
633    Return(Option<Box<PureExpr>>),
634    /// Break: `break x` or `break 'label x`
635    Break {
636        /// Optional label to break to (e.g., `'outer`)
637        label: Option<String>,
638        /// Optional value carried out of the loop.
639        expr: Option<Box<PureExpr>>,
640    },
641    /// Continue: `continue` or `continue 'label`
642    Continue {
643        /// Optional label to continue to (e.g., `'outer`)
644        label: Option<String>,
645    },
646    /// Closure: `|x| x + 1`, `|x: Foo| -> Bar { x }`, `move |x| x`
647    Closure {
648        /// Is async closure?
649        is_async: bool,
650        /// Is move closure?
651        is_move: bool,
652        /// Parameters with optional type annotations.
653        params: Vec<PureClosureParam>,
654        /// Optional return type annotation.
655        ret: Option<PureType>,
656        /// Closure body expression.
657        body: Box<PureExpr>,
658    },
659    /// Struct literal: `Point { x: 1, y: 2 }` or with functional update `Foo { a: 1, ..Default::default() }`
660    Struct {
661        /// Struct path (e.g. `Point`).
662        path: String,
663        /// Field initializers (name, expr).
664        fields: Vec<(String, PureExpr)>,
665        /// Functional update base expression (`..expr`). `None` means no update.
666        rest: Option<Box<PureExpr>>,
667    },
668    /// Tuple: `(a, b, c)`
669    Tuple(Vec<PureExpr>),
670    /// Array: `[1, 2, 3]`
671    Array(Vec<PureExpr>),
672    /// Reference: `&x`, `&mut x`
673    Ref {
674        /// `mut` qualifier (`&mut`).
675        is_mut: bool,
676        /// Referent expression.
677        expr: Box<PureExpr>,
678    },
679    /// Macro invocation: `println!(...)`
680    Macro {
681        /// Macro name (without `!`).
682        name: String,
683        /// Delimiter style around tokens.
684        delimiter: MacroDelimiter,
685        /// Raw token stream as a string.
686        tokens: String,
687    },
688    /// Await: `x.await`
689    Await(Box<PureExpr>),
690    /// Try: `x?`
691    Try(Box<PureExpr>),
692    /// Range: `a..b`, `..b`, `a..`, `..`, `a..=b`
693    Range {
694        /// Start bound expression (None = unbounded).
695        start: Option<Box<PureExpr>>,
696        /// End bound expression (None = unbounded).
697        end: Option<Box<PureExpr>>,
698        /// true for `..=`, false for `..`
699        inclusive: bool,
700    },
701    /// Cast: `x as T`
702    Cast {
703        /// Source expression.
704        expr: Box<PureExpr>,
705        /// Target type.
706        ty: PureType,
707    },
708    /// Let expression (in conditions): `let Some(x) = y`
709    Let {
710        /// Pattern on the LHS of `let`.
711        pattern: PurePattern,
712        /// Scrutinee expression on the RHS.
713        expr: Box<PureExpr>,
714    },
715    /// Async block: `async { ... }` or `async move { ... }`
716    Async {
717        /// `move` qualifier.
718        is_move: bool,
719        /// Async block body.
720        body: PureBlock,
721    },
722    /// Unsafe block: `unsafe { ... }`
723    Unsafe(PureBlock),
724    /// Array repeat: `[expr; N]`
725    Repeat {
726        /// Element expression to repeat.
727        expr: Box<PureExpr>,
728        /// Repeat count expression.
729        len: Box<PureExpr>,
730    },
731    /// Other (as string).
732    Other(String),
733    /// Verbatim Rust source bytes that must survive round-trip exactly as
734    /// authored. Like `PureItem::Verbatim` but at the expression level:
735    /// during `PureFile::to_source` the expression is lowered to a
736    /// unique sentinel path stub (`__ryo_verbatim_expr_<N>`) that
737    /// prettyplease emits unchanged, then the post-process splice
738    /// step replaces that exact identifier with the raw bytes. This
739    /// is the carrier for B-3 (expr-level Verbatim) — see journal
740    /// 2026-05-30 §B-3.
741    Verbatim(String),
742}
743
744impl PureExpr {
745    /// Create a MethodCall with default turbofish (None)
746    pub fn method_call(receiver: Box<PureExpr>, method: String, args: Vec<PureExpr>) -> Self {
747        PureExpr::MethodCall {
748            receiver,
749            method,
750            turbofish: None,
751            args,
752        }
753    }
754
755    /// Create a Closure with untyped params and no return type annotation.
756    pub fn closure(params: Vec<PureClosureParam>, body: Box<PureExpr>) -> Self {
757        PureExpr::Closure {
758            is_async: false,
759            is_move: false,
760            params,
761            ret: None,
762            body,
763        }
764    }
765
766    // ========== AST Navigation ==========
767
768    /// Get child expression at index.
769    ///
770    /// Index mapping varies by variant:
771    /// - `Binary`: 0=left, 1=right
772    /// - `Unary`, `Field`, `Ref`, `Await`, `Try`, `Cast`, `Let`: 0=expr
773    /// - `Call`: 0=func, 1..=args
774    /// - `MethodCall`: 0=receiver, 1..=args
775    /// - `Index`, `Repeat`: 0=expr, 1=index/len
776    /// - `Range`: 0=start, 1=end (if present)
777    /// - `If`: 0=cond, 1=else_branch (if present)
778    /// - `While`, `For`: 0=cond/expr
779    /// - `Match`: 0=expr
780    /// - `Return`, `Break`: 0=inner (if present)
781    /// - `Closure`: 0=body
782    /// - `Tuple`, `Array`: 0..n=elements
783    /// - `Struct`: 0..n=field values
784    /// - Others: None
785    pub fn get_child(&self, index: usize) -> Option<&PureExpr> {
786        match self {
787            // Single child at index 0
788            PureExpr::Unary { expr, .. }
789            | PureExpr::Field { expr, .. }
790            | PureExpr::Ref { expr, .. }
791            | PureExpr::Await(expr)
792            | PureExpr::Try(expr)
793            | PureExpr::Cast { expr, .. }
794            | PureExpr::Let { expr, .. } => {
795                if index == 0 {
796                    Some(expr)
797                } else {
798                    None
799                }
800            }
801
802            // Two children
803            PureExpr::Binary { left, right, .. } => match index {
804                0 => Some(left),
805                1 => Some(right),
806                _ => None,
807            },
808            PureExpr::Index { expr, index: idx } => match index {
809                0 => Some(expr),
810                1 => Some(idx),
811                _ => None,
812            },
813            PureExpr::Repeat { expr, len } => match index {
814                0 => Some(expr),
815                1 => Some(len),
816                _ => None,
817            },
818
819            // func/receiver + args
820            PureExpr::Call { func, args } => {
821                if index == 0 {
822                    Some(func)
823                } else {
824                    args.get(index - 1)
825                }
826            }
827            PureExpr::MethodCall { receiver, args, .. } => {
828                if index == 0 {
829                    Some(receiver)
830                } else {
831                    args.get(index - 1)
832                }
833            }
834
835            // Optional children
836            PureExpr::Range { start, end, .. } => match index {
837                0 => start.as_deref(),
838                1 => end.as_deref(),
839                _ => None,
840            },
841            PureExpr::If {
842                cond, else_branch, ..
843            } => match index {
844                0 => Some(cond),
845                1 => else_branch.as_deref(),
846                _ => None,
847            },
848            PureExpr::Return(opt) => {
849                if index == 0 {
850                    opt.as_deref()
851                } else {
852                    None
853                }
854            }
855            PureExpr::Break { expr: opt, .. } => {
856                if index == 0 {
857                    opt.as_deref()
858                } else {
859                    None
860                }
861            }
862
863            // Control flow with expr
864            PureExpr::While { cond, .. } => {
865                if index == 0 {
866                    Some(cond)
867                } else {
868                    None
869                }
870            }
871            PureExpr::For { expr, .. } => {
872                if index == 0 {
873                    Some(expr)
874                } else {
875                    None
876                }
877            }
878            PureExpr::Match { expr, .. } => {
879                if index == 0 {
880                    Some(expr)
881                } else {
882                    None
883                }
884            }
885
886            // Closure body
887            PureExpr::Closure { body, .. } => {
888                if index == 0 {
889                    Some(body)
890                } else {
891                    None
892                }
893            }
894
895            // Collections
896            PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => exprs.get(index),
897            PureExpr::Struct { fields, .. } => fields.get(index).map(|(_, e)| e),
898
899            // No direct children (use get_block for Block, Loop, Async, Unsafe)
900            PureExpr::Lit(_)
901            | PureExpr::Path(_)
902            | PureExpr::Block { .. }
903            | PureExpr::Loop { .. }
904            | PureExpr::Async { .. }
905            | PureExpr::Unsafe(_)
906            | PureExpr::Continue { .. }
907            | PureExpr::Macro { .. }
908            | PureExpr::Other(_)
909            | PureExpr::Verbatim(_) => None,
910        }
911    }
912
913    /// Get mutable child expression at index.
914    pub fn get_child_mut(&mut self, index: usize) -> Option<&mut PureExpr> {
915        match self {
916            PureExpr::Unary { expr, .. }
917            | PureExpr::Field { expr, .. }
918            | PureExpr::Ref { expr, .. }
919            | PureExpr::Await(expr)
920            | PureExpr::Try(expr)
921            | PureExpr::Cast { expr, .. }
922            | PureExpr::Let { expr, .. } => {
923                if index == 0 {
924                    Some(expr)
925                } else {
926                    None
927                }
928            }
929
930            PureExpr::Binary { left, right, .. } => match index {
931                0 => Some(left),
932                1 => Some(right),
933                _ => None,
934            },
935            PureExpr::Index { expr, index: idx } => match index {
936                0 => Some(expr),
937                1 => Some(idx),
938                _ => None,
939            },
940            PureExpr::Repeat { expr, len } => match index {
941                0 => Some(expr),
942                1 => Some(len),
943                _ => None,
944            },
945
946            PureExpr::Call { func, args } => {
947                if index == 0 {
948                    Some(func)
949                } else {
950                    args.get_mut(index - 1)
951                }
952            }
953            PureExpr::MethodCall { receiver, args, .. } => {
954                if index == 0 {
955                    Some(receiver)
956                } else {
957                    args.get_mut(index - 1)
958                }
959            }
960
961            PureExpr::Range { start, end, .. } => match index {
962                0 => start.as_deref_mut(),
963                1 => end.as_deref_mut(),
964                _ => None,
965            },
966            PureExpr::If {
967                cond, else_branch, ..
968            } => match index {
969                0 => Some(cond.as_mut()),
970                1 => else_branch.as_deref_mut(),
971                _ => None,
972            },
973            PureExpr::Return(opt) => {
974                if index == 0 {
975                    opt.as_deref_mut()
976                } else {
977                    None
978                }
979            }
980            PureExpr::Break { expr: opt, .. } => {
981                if index == 0 {
982                    opt.as_deref_mut()
983                } else {
984                    None
985                }
986            }
987
988            PureExpr::While { cond, .. } => {
989                if index == 0 {
990                    Some(cond)
991                } else {
992                    None
993                }
994            }
995            PureExpr::For { expr, .. } => {
996                if index == 0 {
997                    Some(expr)
998                } else {
999                    None
1000                }
1001            }
1002            PureExpr::Match { expr, .. } => {
1003                if index == 0 {
1004                    Some(expr)
1005                } else {
1006                    None
1007                }
1008            }
1009
1010            PureExpr::Closure { body, .. } => {
1011                if index == 0 {
1012                    Some(body)
1013                } else {
1014                    None
1015                }
1016            }
1017
1018            PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => exprs.get_mut(index),
1019            PureExpr::Struct { fields, .. } => fields.get_mut(index).map(|(_, e)| e),
1020
1021            PureExpr::Lit(_)
1022            | PureExpr::Path(_)
1023            | PureExpr::Block { .. }
1024            | PureExpr::Loop { .. }
1025            | PureExpr::Async { .. }
1026            | PureExpr::Unsafe(_)
1027            | PureExpr::Continue { .. }
1028            | PureExpr::Macro { .. }
1029            | PureExpr::Other(_)
1030            | PureExpr::Verbatim(_) => None,
1031        }
1032    }
1033
1034    /// Get the contained block (for Block, Loop, Async, Unsafe, If, While, For).
1035    pub fn get_block(&self) -> Option<&PureBlock> {
1036        match self {
1037            PureExpr::Block { block: b, .. }
1038            | PureExpr::Loop { body: b, .. }
1039            | PureExpr::Unsafe(b) => Some(b),
1040            PureExpr::Async { body, .. } => Some(body),
1041            PureExpr::If { then_branch, .. } => Some(then_branch),
1042            PureExpr::While { body, .. } | PureExpr::For { body, .. } => Some(body),
1043            _ => None,
1044        }
1045    }
1046
1047    /// Get mutable contained block.
1048    pub fn get_block_mut(&mut self) -> Option<&mut PureBlock> {
1049        match self {
1050            PureExpr::Block { block: b, .. }
1051            | PureExpr::Loop { body: b, .. }
1052            | PureExpr::Unsafe(b) => Some(b),
1053            PureExpr::Async { body, .. } => Some(body),
1054            PureExpr::If { then_branch, .. } => Some(then_branch),
1055            PureExpr::While { body, .. } | PureExpr::For { body, .. } => Some(body),
1056            _ => None,
1057        }
1058    }
1059
1060    /// Number of direct child expressions.
1061    pub fn child_count(&self) -> usize {
1062        match self {
1063            PureExpr::Lit(_)
1064            | PureExpr::Path(_)
1065            | PureExpr::Continue { .. }
1066            | PureExpr::Macro { .. }
1067            | PureExpr::Other(_)
1068            | PureExpr::Verbatim(_)
1069            | PureExpr::Block { .. }
1070            | PureExpr::Loop { .. }
1071            | PureExpr::Async { .. }
1072            | PureExpr::Unsafe(_) => 0,
1073
1074            PureExpr::Unary { .. }
1075            | PureExpr::Field { .. }
1076            | PureExpr::Ref { .. }
1077            | PureExpr::Await(_)
1078            | PureExpr::Try(_)
1079            | PureExpr::Cast { .. }
1080            | PureExpr::Let { .. }
1081            | PureExpr::Closure { .. }
1082            | PureExpr::While { .. }
1083            | PureExpr::For { .. }
1084            | PureExpr::Match { .. } => 1,
1085
1086            PureExpr::Binary { .. } | PureExpr::Index { .. } | PureExpr::Repeat { .. } => 2,
1087
1088            PureExpr::Range { start, end, .. } => start.is_some() as usize + end.is_some() as usize,
1089            PureExpr::If { else_branch, .. } => 1 + else_branch.is_some() as usize,
1090            PureExpr::Return(opt) => opt.is_some() as usize,
1091            PureExpr::Break { expr: opt, .. } => opt.is_some() as usize,
1092
1093            PureExpr::Call { args, .. } => 1 + args.len(),
1094            PureExpr::MethodCall { args, .. } => 1 + args.len(),
1095            PureExpr::Tuple(v) | PureExpr::Array(v) => v.len(),
1096            PureExpr::Struct { fields, .. } => fields.len(),
1097        }
1098    }
1099
1100    /// Navigate to a nested expression by path (slice of indices).
1101    ///
1102    /// # Example
1103    /// ```ignore
1104    /// // For: x.foo(a + b, c)
1105    /// // Path [1, 0] navigates to: args[0].left = `a`
1106    /// expr.navigate(&[1, 0])
1107    /// ```
1108    pub fn navigate(&self, path: &[usize]) -> Option<&PureExpr> {
1109        let mut current = self;
1110        for &idx in path {
1111            current = current.get_child(idx)?;
1112        }
1113        Some(current)
1114    }
1115
1116    /// Navigate to a nested expression mutably.
1117    pub fn navigate_mut(&mut self, path: &[usize]) -> Option<&mut PureExpr> {
1118        let mut current = self;
1119        for &idx in path {
1120            current = current.get_child_mut(idx)?;
1121        }
1122        Some(current)
1123    }
1124}
1125
1126/// Match arm.
1127#[derive(Debug, Clone, PartialEq, Eq)]
1128#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1129pub struct PureMatchArm {
1130    /// Pattern.
1131    pub pattern: PurePattern,
1132    /// Guard: `if cond`
1133    pub guard: Option<PureExpr>,
1134    /// Body.
1135    pub body: PureExpr,
1136}
1137
1138/// A type.
1139#[derive(Debug, Clone, PartialEq, Eq)]
1140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1141pub enum PureType {
1142    /// Path type: `String`, `std::io::Result<T>`
1143    Path(String),
1144    /// Reference: `&T`, `&mut T`, `&'a T`
1145    Ref {
1146        /// Optional explicit lifetime (`'a`).
1147        lifetime: Option<String>,
1148        /// `mut` qualifier (`&mut`).
1149        is_mut: bool,
1150        /// Referent type.
1151        ty: Box<PureType>,
1152    },
1153    /// Tuple: `(A, B, C)`
1154    Tuple(Vec<PureType>),
1155    /// Array: `[T; N]`
1156    Array {
1157        /// Element type.
1158        ty: Box<PureType>,
1159        /// Length expression as a raw string.
1160        len: String,
1161    },
1162    /// Slice: `[T]`
1163    Slice(Box<PureType>),
1164    /// Function pointer: `fn(A) -> B`
1165    Fn {
1166        /// Parameter types.
1167        params: Vec<PureType>,
1168        /// Optional return type (None = unit).
1169        ret: Option<Box<PureType>>,
1170    },
1171    /// Impl trait: `impl Trait`
1172    ImplTrait(Vec<String>),
1173    /// Dyn trait: `dyn Trait`
1174    TraitObject(Vec<String>),
1175    /// Inferred: `_`
1176    Infer,
1177    /// Never: `!`
1178    Never,
1179    /// Other (as string).
1180    Other(String),
1181}
1182
1183/// Generic parameters.
1184#[derive(Debug, Clone, PartialEq, Eq, Default)]
1185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1186pub struct PureGenerics {
1187    /// Type parameters.
1188    pub params: Vec<PureGenericParam>,
1189    /// Where clause.
1190    pub where_clause: Vec<String>,
1191}
1192
1193/// A generic parameter.
1194#[derive(Debug, Clone, PartialEq, Eq)]
1195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1196pub enum PureGenericParam {
1197    /// Type parameter: `T`, `T: Clone`
1198    Type {
1199        /// Parameter name (`T`).
1200        name: String,
1201        /// Trait bounds (`Clone + Send`).
1202        bounds: Vec<String>,
1203    },
1204    /// Lifetime: `'a`, `'a: 'b`
1205    Lifetime {
1206        /// Lifetime name (`'a`).
1207        name: String,
1208        /// Lifetime bounds (`'b`).
1209        bounds: Vec<String>,
1210    },
1211    /// Const: `const N: usize`
1212    Const {
1213        /// Const parameter name (`N`).
1214        name: String,
1215        /// Const parameter type as a raw string (`usize`).
1216        ty: String,
1217    },
1218}
1219
1220/// A struct definition.
1221#[derive(Debug, Clone, PartialEq, Eq)]
1222#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1223pub struct PureStruct {
1224    /// Attributes.
1225    pub attrs: Vec<PureAttribute>,
1226    /// Visibility.
1227    pub vis: PureVis,
1228    /// Name.
1229    pub name: String,
1230    /// Generics.
1231    pub generics: PureGenerics,
1232    /// Fields.
1233    pub fields: PureFields,
1234}
1235
1236/// Struct fields.
1237#[derive(Debug, Clone, PartialEq, Eq)]
1238#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1239pub enum PureFields {
1240    /// Named fields: `{ x: i32, y: i32 }`
1241    Named(Vec<PureField>),
1242    /// Tuple fields: `(i32, i32)` — carried as [`PureTupleField`] so per-field
1243    /// `attrs` (e.g. `#[from]`) and `vis` survive AST round-trip.
1244    ///
1245    /// Pre-fix this variant carried `Vec<PureType>`, which silently dropped
1246    /// every `#[derive(thiserror::Error)] enum E { Foo(#[from] InnerErr) }`-
1247    /// style attribute and broke `?` operator conversions in regenerated
1248    /// files (see `test_roundtrip_tuple_variant_from_attribute`).
1249    Tuple(Vec<PureTupleField>),
1250    /// Unit struct: no fields.
1251    Unit,
1252}
1253
1254/// A struct field.
1255#[derive(Debug, Clone, PartialEq, Eq)]
1256#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1257pub struct PureField {
1258    /// Attributes.
1259    pub attrs: Vec<PureAttribute>,
1260    /// Visibility.
1261    pub vis: PureVis,
1262    /// Name.
1263    pub name: String,
1264    /// Type.
1265    pub ty: PureType,
1266}
1267
1268/// A tuple-style field (positional, no name).
1269///
1270/// Used by [`PureFields::Tuple`] for both tuple structs (`struct S(u32, u32)`)
1271/// and tuple enum variants (`enum E { V(#[from] Inner) }`). Keeps `attrs` and
1272/// `vis` alongside the field type so attributes such as `#[from]` and `#[serde(...)]`
1273/// survive the syn ↔ pure ↔ syn round-trip — critical for `?`-operator
1274/// `From` impl generation and for derive macros that inspect field-level
1275/// attributes.
1276#[derive(Debug, Clone, PartialEq, Eq)]
1277#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1278pub struct PureTupleField {
1279    /// Attributes (outer `#[...]`).
1280    pub attrs: Vec<PureAttribute>,
1281    /// Visibility (mostly relevant for tuple structs; tuple variants are
1282    /// always public alongside their parent variant).
1283    pub vis: PureVis,
1284    /// Type.
1285    pub ty: PureType,
1286}
1287
1288/// An enum definition.
1289#[derive(Debug, Clone, PartialEq, Eq)]
1290#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1291pub struct PureEnum {
1292    /// Attributes.
1293    pub attrs: Vec<PureAttribute>,
1294    /// Visibility.
1295    pub vis: PureVis,
1296    /// Name.
1297    pub name: String,
1298    /// Generics.
1299    pub generics: PureGenerics,
1300    /// Variants.
1301    pub variants: Vec<PureVariant>,
1302}
1303
1304/// An enum variant.
1305#[derive(Debug, Clone, PartialEq, Eq)]
1306#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1307pub struct PureVariant {
1308    /// Attributes.
1309    pub attrs: Vec<PureAttribute>,
1310    /// Name.
1311    pub name: String,
1312    /// Fields.
1313    pub fields: PureFields,
1314    /// Discriminant: `= 1`
1315    pub discriminant: Option<String>,
1316}
1317
1318/// An impl block.
1319#[derive(Debug, Clone, PartialEq, Eq)]
1320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1321pub struct PureImpl {
1322    /// Attributes.
1323    pub attrs: Vec<PureAttribute>,
1324    /// Generics.
1325    pub generics: PureGenerics,
1326    /// Whether this is an unsafe impl.
1327    pub is_unsafe: bool,
1328    /// Trait being implemented (if any).
1329    pub trait_: Option<String>,
1330    /// Type being implemented for.
1331    pub self_ty: String,
1332    /// Items in the impl.
1333    pub items: Vec<PureImplItem>,
1334}
1335
1336/// An item in an impl block.
1337#[derive(Debug, Clone, PartialEq, Eq)]
1338#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1339pub enum PureImplItem {
1340    /// Method.
1341    Fn(PureFn),
1342    /// Const.
1343    Const(PureConst),
1344    /// Type alias.
1345    Type(PureTypeAlias),
1346    /// Other.
1347    Other(String),
1348}
1349
1350/// A const item.
1351#[derive(Debug, Clone, PartialEq, Eq)]
1352#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1353pub struct PureConst {
1354    /// Attributes.
1355    pub attrs: Vec<PureAttribute>,
1356    /// Visibility.
1357    pub vis: PureVis,
1358    /// Name.
1359    pub name: String,
1360    /// Type.
1361    pub ty: PureType,
1362    /// Value expression (None for trait consts without default).
1363    pub value: Option<PureExpr>,
1364}
1365
1366/// A static item.
1367#[derive(Debug, Clone, PartialEq, Eq)]
1368#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1369pub struct PureStatic {
1370    /// Attributes.
1371    pub attrs: Vec<PureAttribute>,
1372    /// Visibility.
1373    pub vis: PureVis,
1374    /// Is mutable?
1375    pub is_mut: bool,
1376    /// Name.
1377    pub name: String,
1378    /// Type.
1379    pub ty: PureType,
1380    /// Value expression.
1381    pub value: PureExpr,
1382}
1383
1384/// A type alias.
1385#[derive(Debug, Clone, PartialEq, Eq)]
1386#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1387pub struct PureTypeAlias {
1388    /// Attributes.
1389    pub attrs: Vec<PureAttribute>,
1390    /// Visibility.
1391    pub vis: PureVis,
1392    /// Name.
1393    pub name: String,
1394    /// Generics.
1395    pub generics: PureGenerics,
1396    /// The aliased type.
1397    pub ty: PureType,
1398}
1399
1400/// A module definition.
1401///
1402/// Contains all items belonging to this module.
1403/// File vs inline distinction is handled at the file system layer,
1404/// not in the AST representation.
1405///
1406/// The `scope` field is the sole source of truth for `#[cfg(test)]` semantics.
1407/// Downstream layers (store, regen, emitter) must consume `scope` directly and
1408/// must not re-derive Prod/Test scope by inspecting `attrs` or `items.is_empty()`.
1409#[derive(Debug, Clone, PartialEq, Eq, Default)]
1410#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1411pub struct PureMod {
1412    /// Attributes (both outer `#[...]` and inner `#![...]`).
1413    /// Use `PureAttribute::is_inner` to distinguish.
1414    pub attrs: Vec<PureAttribute>,
1415    /// Visibility.
1416    pub vis: PureVis,
1417    /// Module name.
1418    pub name: String,
1419    /// Module items.
1420    pub items: Vec<PureItem>,
1421    /// Prod/Test scope of this module.
1422    ///
1423    /// `ModScope::Test` means this module is gated by `#[cfg(test)]`.
1424    /// Set at parse time; must not be re-derived from `attrs` or `items` in
1425    /// any downstream layer.
1426    pub scope: ModScope,
1427}
1428
1429/// A trait definition.
1430#[derive(Debug, Clone, PartialEq, Eq)]
1431#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1432pub struct PureTrait {
1433    /// Attributes.
1434    pub attrs: Vec<PureAttribute>,
1435    /// Visibility.
1436    pub vis: PureVis,
1437    /// Is unsafe?
1438    pub is_unsafe: bool,
1439    /// Is auto?
1440    pub is_auto: bool,
1441    /// Name.
1442    pub name: String,
1443    /// Generics.
1444    pub generics: PureGenerics,
1445    /// Supertraits.
1446    pub supertraits: Vec<String>,
1447    /// Items in the trait.
1448    pub items: Vec<PureTraitItem>,
1449}
1450
1451/// An item in a trait.
1452#[derive(Debug, Clone, PartialEq, Eq)]
1453#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1454pub enum PureTraitItem {
1455    /// Method (with optional default impl).
1456    Fn(PureFn),
1457    /// Const.
1458    Const(PureConst),
1459    /// Type.
1460    Type {
1461        /// Associated type name.
1462        name: String,
1463        /// Trait bounds on the associated type.
1464        bounds: Vec<String>,
1465        /// Optional default type.
1466        default: Option<PureType>,
1467    },
1468    /// Other.
1469    Other(String),
1470}
1471
1472/// A macro invocation at item level.
1473#[derive(Debug, Clone, PartialEq, Eq)]
1474#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1475pub struct PureMacro {
1476    /// Macro path.
1477    pub path: String,
1478    /// Optional definition name. Set for `macro_rules! NAME { ... }` — syn
1479    /// stores this on `ItemMacro.ident` rather than inside `mac.path`. For
1480    /// regular call-site macros (`println!`, `vec!`, `html!`) this is `None`.
1481    /// Without this field, `macro_rules! square { ... }` round-trips into
1482    /// nameless `macro_rules! { ... }`.
1483    #[cfg_attr(feature = "serde", serde(default))]
1484    pub name: Option<String>,
1485    /// Delimiter used.
1486    pub delimiter: MacroDelimiter,
1487    /// Tokens inside (as string).
1488    pub tokens: String,
1489}
1490
1491/// Macro delimiter.
1492#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1493#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1494pub enum MacroDelimiter {
1495    /// `( ... )`
1496    Paren,
1497    /// `{ ... }`
1498    Brace,
1499    /// `[ ... ]`
1500    Bracket,
1501}
1502
1503/// Prod/Test scope discriminant for `PureMod`.
1504///
1505/// 2-value enum. `#[cfg(test)]` 専用 SoT primitive.
1506///
1507/// `ModScope` is the sole source of truth for whether a module is test-scoped.
1508/// No downstream layer (store, regen, emitter) may re-derive Prod/Test scope by
1509/// inspecting raw attrs or `items.is_empty()`; each layer must consume this field
1510/// directly.
1511///
1512/// 非 binary cfg (`cfg(not(test))` / `cfg(feature="...")`) は ModScope::Prod に保持しつつ、
1513/// 元の cfg attribute は PureMod.attrs に残し透過 emit する。将来拡張需要が出た場合は
1514/// ModScope に variant を追加する設計に拡張可能。
1515///
1516/// `ryo_suggest::suggest::SymbolScope` (Lib/Bin/Test) とは意味論軸が直交
1517/// (AST primitive vs post-hoc classification)。統合は本 Issue scope 外。
1518#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1519#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1520pub enum ModScope {
1521    /// Production code: module is not gated by `#[cfg(test)]`.
1522    ///
1523    /// This is the default. Modules with non-binary cfg gates
1524    /// (`cfg(not(test))`, `cfg(feature="...")`) are also represented as `Prod`
1525    /// with the original cfg attribute preserved in `PureMod.attrs`.
1526    #[default]
1527    Prod,
1528    /// Test code: module is gated by `#[cfg(test)]`.
1529    ///
1530    /// The `#[cfg(test)]` attribute is removed from `PureMod.attrs` at parse
1531    /// time and re-injected by the emitter (`ToSyn for PureMod`).
1532    Test,
1533}
1534
1535#[cfg(test)]
1536mod tests {
1537    use super::*;
1538
1539    #[test]
1540    fn test_pure_file_send_sync() {
1541        fn assert_send_sync<T: Send + Sync>() {}
1542        assert_send_sync::<PureFile>();
1543    }
1544
1545    #[test]
1546    #[cfg(feature = "serde")]
1547    fn test_serde_json_roundtrip() {
1548        let file = PureFile::from_source(
1549            r#"
1550            use std::io;
1551
1552            fn hello(name: &str) -> String {
1553                format!("Hello, {}!", name)
1554            }
1555
1556            struct Point {
1557                x: i32,
1558                y: i32,
1559            }
1560            "#,
1561        )
1562        .unwrap();
1563
1564        // Serialize to JSON
1565        let json = serde_json::to_string_pretty(&file).unwrap();
1566        assert!(json.contains("hello"));
1567        assert!(json.contains("Point"));
1568
1569        // Deserialize back
1570        let restored: PureFile = serde_json::from_str(&json).unwrap();
1571        assert_eq!(file, restored);
1572    }
1573
1574    #[test]
1575    fn test_verbatim_item_preserves_raw_bytes() {
1576        // Boundary: PureItem::Verbatim(raw) must survive PureFile::to_source
1577        // intact — including `//` line comments, blank lines, DSL macro
1578        // spacing, and any other trivia that the syn lexer would normally
1579        // strip. This is the ReplaceCode escape hatch for the β route.
1580        let raw = "// preserved comment 1\nlet _ = html! { <div class=\"foo\">{ name }</div> };\n// preserved comment 2";
1581        let file = PureFile {
1582            attrs: vec![],
1583            items: vec![
1584                PureItem::Struct(PureStruct {
1585                    attrs: vec![],
1586                    vis: PureVis::Public,
1587                    name: "Foo".to_string(),
1588                    generics: PureGenerics::default(),
1589                    fields: PureFields::Unit,
1590                }),
1591                PureItem::Verbatim(raw.to_string()),
1592                PureItem::Struct(PureStruct {
1593                    attrs: vec![],
1594                    vis: PureVis::Public,
1595                    name: "Bar".to_string(),
1596                    generics: PureGenerics::default(),
1597                    fields: PureFields::Unit,
1598                }),
1599            ],
1600        };
1601
1602        let out = file.to_source().unwrap();
1603        assert!(
1604            out.contains("// preserved comment 1"),
1605            "line comment 1 must survive verbatim splice; got:\n{}",
1606            out
1607        );
1608        assert!(
1609            out.contains("// preserved comment 2"),
1610            "line comment 2 must survive; got:\n{}",
1611            out
1612        );
1613        assert!(
1614            out.contains("<div class=\"foo\">"),
1615            "HTML-DSL token spacing inside the verbatim chunk must survive; \
1616             got:\n{}",
1617            out
1618        );
1619        assert!(
1620            !out.contains("__ryo_verbatim_"),
1621            "sentinel marker must not leak into the final output; got:\n{}",
1622            out
1623        );
1624        assert!(out.contains("struct Foo;") && out.contains("struct Bar;"));
1625    }
1626
1627    #[test]
1628    fn test_macro_rules_name_round_trip() {
1629        // Boundary: `macro_rules! NAME { ... }` must round-trip with the NAME
1630        // preserved. Pre-fix `PureMacro` had no `name` field so the ident on
1631        // syn::ItemMacro was dropped, producing nameless `macro_rules! { ... }`
1632        // on regeneration.
1633        let pf =
1634            PureFile::from_source("macro_rules! square { ($x:expr) => { $x * $x }; }").unwrap();
1635        let out = pf.to_source().unwrap();
1636        assert!(
1637            out.contains("macro_rules! square"),
1638            "macro_rules ident must round-trip; got: {}",
1639            out
1640        );
1641    }
1642
1643    #[test]
1644    fn test_regular_macro_call_no_name_field() {
1645        // Boundary: regular call-site macros (e.g. `println!`, `vec!`) do not
1646        // set `ItemMacro.ident` at item level — only `macro_rules!` does.
1647        // The `name` field stays None and is not emitted into the output.
1648        let pf = PureFile::from_source("lazy_static! { static ref X: i32 = 1; }").unwrap();
1649        let out = pf.to_source().unwrap();
1650        // The path "lazy_static" survives as the macro path; no spurious
1651        // ident insertion before the `{`.
1652        assert!(
1653            out.contains("lazy_static!"),
1654            "macro path must survive: {}",
1655            out
1656        );
1657        assert!(
1658            !out.contains("lazy_static! lazy_static"),
1659            "no duplicated ident: {}",
1660            out
1661        );
1662    }
1663
1664    #[test]
1665    #[cfg(feature = "serde")]
1666    fn test_serde_compact() {
1667        let file = PureFile::from_source("fn main() {}").unwrap();
1668
1669        // Compact JSON
1670        let json = serde_json::to_string(&file).unwrap();
1671
1672        // Should be relatively compact
1673        assert!(json.len() < 500);
1674
1675        // Roundtrip
1676        let restored: PureFile = serde_json::from_str(&json).unwrap();
1677        assert_eq!(file, restored);
1678    }
1679
1680    #[test]
1681    #[cfg(feature = "serde")]
1682    fn test_serde_complex_ast() {
1683        let file = PureFile::from_source(
1684            r#"
1685            pub struct Config<T: Clone> {
1686                pub name: String,
1687                pub value: T,
1688            }
1689
1690            impl<T: Clone> Config<T> {
1691                pub fn new(name: String, value: T) -> Self {
1692                    Self { name, value }
1693                }
1694            }
1695
1696            pub trait Configurable {
1697                fn config(&self) -> &str;
1698            }
1699            "#,
1700        )
1701        .unwrap();
1702
1703        let json = serde_json::to_string(&file).unwrap();
1704        let restored: PureFile = serde_json::from_str(&json).unwrap();
1705        assert_eq!(file, restored);
1706
1707        // Verify structure preserved
1708        assert_eq!(restored.structs().len(), 1);
1709        assert_eq!(restored.structs()[0].name, "Config");
1710    }
1711
1712    // ========== Navigation Tests ==========
1713
1714    #[test]
1715    fn test_pure_block_navigation() {
1716        let block = PureBlock {
1717            stmts: vec![
1718                PureStmt::Semi(PureExpr::Lit("1".into())),
1719                PureStmt::Semi(PureExpr::Lit("2".into())),
1720                PureStmt::Expr(PureExpr::Lit("3".into())),
1721            ],
1722        };
1723
1724        assert_eq!(block.len(), 3);
1725        assert!(!block.is_empty());
1726
1727        let stmt0 = block.get_stmt(0).unwrap();
1728        assert!(matches!(stmt0, PureStmt::Semi(PureExpr::Lit(s)) if s == "1"));
1729
1730        let stmt2 = block.get_stmt(2).unwrap();
1731        assert!(matches!(stmt2, PureStmt::Expr(PureExpr::Lit(s)) if s == "3"));
1732
1733        assert!(block.get_stmt(3).is_none());
1734    }
1735
1736    #[test]
1737    fn test_pure_stmt_get_expr() {
1738        let semi = PureStmt::Semi(PureExpr::Lit("x".into()));
1739        assert!(semi.has_expr());
1740        assert!(matches!(semi.get_expr(), Some(PureExpr::Lit(_))));
1741
1742        let local_with_init = PureStmt::Local {
1743            pattern: PurePattern::Ident {
1744                name: "x".into(),
1745                is_mut: false,
1746                by_ref: false,
1747            },
1748            ty: None,
1749            init: Some(PureExpr::Lit("42".into())),
1750            else_branch: None,
1751        };
1752        assert!(local_with_init.has_expr());
1753        assert!(matches!(local_with_init.get_expr(), Some(PureExpr::Lit(_))));
1754
1755        let local_no_init = PureStmt::Local {
1756            pattern: PurePattern::Ident {
1757                name: "x".into(),
1758                is_mut: false,
1759                by_ref: false,
1760            },
1761            ty: None,
1762            init: None,
1763            else_branch: None,
1764        };
1765        assert!(!local_no_init.has_expr());
1766        assert!(local_no_init.get_expr().is_none());
1767    }
1768
1769    #[test]
1770    fn test_pure_expr_get_child_binary() {
1771        // a + b
1772        let expr = PureExpr::Binary {
1773            op: "+".into(),
1774            left: Box::new(PureExpr::Path("a".into())),
1775            right: Box::new(PureExpr::Path("b".into())),
1776        };
1777
1778        assert_eq!(expr.child_count(), 2);
1779
1780        let left = expr.get_child(0).unwrap();
1781        assert!(matches!(left, PureExpr::Path(s) if s == "a"));
1782
1783        let right = expr.get_child(1).unwrap();
1784        assert!(matches!(right, PureExpr::Path(s) if s == "b"));
1785
1786        assert!(expr.get_child(2).is_none());
1787    }
1788
1789    #[test]
1790    fn test_pure_expr_get_child_method_call() {
1791        // x.foo(a, b)
1792        let expr = PureExpr::MethodCall {
1793            receiver: Box::new(PureExpr::Path("x".into())),
1794            method: "foo".into(),
1795            turbofish: None,
1796            args: vec![PureExpr::Path("a".into()), PureExpr::Path("b".into())],
1797        };
1798
1799        assert_eq!(expr.child_count(), 3); // receiver + 2 args
1800
1801        let receiver = expr.get_child(0).unwrap();
1802        assert!(matches!(receiver, PureExpr::Path(s) if s == "x"));
1803
1804        let arg0 = expr.get_child(1).unwrap();
1805        assert!(matches!(arg0, PureExpr::Path(s) if s == "a"));
1806
1807        let arg1 = expr.get_child(2).unwrap();
1808        assert!(matches!(arg1, PureExpr::Path(s) if s == "b"));
1809
1810        assert!(expr.get_child(3).is_none());
1811    }
1812
1813    #[test]
1814    fn test_pure_expr_navigate() {
1815        // x.foo(a + b, c)
1816        let expr = PureExpr::MethodCall {
1817            receiver: Box::new(PureExpr::Path("x".into())),
1818            method: "foo".into(),
1819            turbofish: None,
1820            args: vec![
1821                PureExpr::Binary {
1822                    op: "+".into(),
1823                    left: Box::new(PureExpr::Path("a".into())),
1824                    right: Box::new(PureExpr::Path("b".into())),
1825                },
1826                PureExpr::Path("c".into()),
1827            ],
1828        };
1829
1830        // Navigate to receiver
1831        let r = expr.navigate(&[0]).unwrap();
1832        assert!(matches!(r, PureExpr::Path(s) if s == "x"));
1833
1834        // Navigate to first arg (a + b)
1835        let arg0 = expr.navigate(&[1]).unwrap();
1836        assert!(matches!(arg0, PureExpr::Binary { .. }));
1837
1838        // Navigate to left of first arg (a)
1839        let a = expr.navigate(&[1, 0]).unwrap();
1840        assert!(matches!(a, PureExpr::Path(s) if s == "a"));
1841
1842        // Navigate to right of first arg (b)
1843        let b = expr.navigate(&[1, 1]).unwrap();
1844        assert!(matches!(b, PureExpr::Path(s) if s == "b"));
1845
1846        // Invalid path
1847        assert!(expr.navigate(&[1, 2]).is_none());
1848        assert!(expr.navigate(&[5]).is_none());
1849    }
1850
1851    #[test]
1852    fn test_pure_expr_get_block() {
1853        let block_expr = PureExpr::Block {
1854            label: None,
1855            block: PureBlock {
1856                stmts: vec![PureStmt::Expr(PureExpr::Lit("1".into()))],
1857            },
1858        };
1859        assert!(block_expr.get_block().is_some());
1860        assert_eq!(block_expr.get_block().unwrap().len(), 1);
1861
1862        let lit = PureExpr::Lit("x".into());
1863        assert!(lit.get_block().is_none());
1864    }
1865}