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}