Skip to main content

ryo_source/pure/
convert.rs

1//! Conversion between syn types and Pure types.
2
3use quote::ToTokens;
4
5use super::ast::*;
6use crate::SourceResult;
7
8/// Trait for converting syn types to pure types.
9pub trait ToPure {
10    /// Pure AST output type produced from `self`.
11    type Output;
12    /// Convert `self` into its pure-AST representation.
13    fn to_pure(&self) -> Self::Output;
14}
15
16/// Convert syn::Abi to string (e.g., "C", "Rust", "system").
17fn abi_to_string(abi: &syn::Abi) -> String {
18    abi.name
19        .as_ref()
20        .map(|lit| lit.value())
21        .unwrap_or_else(|| "C".to_string()) // `extern fn` without explicit ABI defaults to "C"
22}
23
24/// Convert syn::Label to string (e.g., "'outer").
25fn label_to_string(label: &syn::Label) -> String {
26    label.name.ident.to_string()
27}
28
29/// Infer if a return type represents an async function from `#[async_trait]` or similar macros.
30///
31/// Detects patterns like:
32/// - `Pin<Box<dyn Future<Output = T> + Send>>`
33/// - `Pin<Box<dyn Future<Output = T>>>`
34///
35/// This is needed because `#[async_trait]` transforms `async fn` into regular `fn`
36/// with a `Pin<Box<dyn Future<...>>>` return type at the syn parsing stage.
37fn infer_async_from_return_type(ret: &syn::ReturnType) -> bool {
38    let ty = match ret {
39        syn::ReturnType::Default => return false,
40        syn::ReturnType::Type(_, ty) => ty,
41    };
42
43    // Convert to string for pattern matching
44    let ty_str = ty.to_token_stream().to_string();
45
46    // Check for Pin<Box<dyn Future<...>>> pattern
47    // The string representation includes spaces, e.g.:
48    // "Pin < Box < dyn Future < Output = Result < ... > > + Send > >"
49    is_pinned_boxed_future(&ty_str)
50}
51
52/// Check if a type string represents `Pin<Box<dyn Future<...>>>`.
53fn is_pinned_boxed_future(ty_str: &str) -> bool {
54    // Remove all whitespace for easier matching
55    let normalized: String = ty_str.chars().filter(|c| !c.is_whitespace()).collect();
56
57    // Check for the pattern: Pin<Box<dyn Future<...>>>
58    // Variants:
59    // - Pin<Box<dyn Future<Output=T>>>
60    // - Pin<Box<dyn Future<Output=T>+Send>>
61    // - Pin<Box<dyn Future<Output=T>+Send+'static>>
62    normalized.starts_with("Pin<Box<dynFuture<")
63        || normalized.starts_with("::core::pin::Pin<Box<dynFuture<")
64        || normalized.starts_with("core::pin::Pin<Box<dynFuture<")
65        || normalized.starts_with("std::pin::Pin<Box<dynFuture<")
66        || normalized.starts_with("::std::pin::Pin<Box<dynFuture<")
67}
68
69impl PureFile {
70    /// Parse source code directly into PureFile.
71    pub fn from_source(source: &str) -> SourceResult<Self> {
72        let file = syn::parse_file(source)?;
73        Ok(file.to_pure())
74    }
75}
76
77impl ToPure for syn::File {
78    type Output = PureFile;
79
80    fn to_pure(&self) -> PureFile {
81        PureFile {
82            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
83            items: self.items.iter().map(|i| i.to_pure()).collect(),
84        }
85    }
86}
87
88impl ToPure for syn::Attribute {
89    type Output = PureAttribute;
90
91    fn to_pure(&self) -> PureAttribute {
92        let meta = match &self.meta {
93            syn::Meta::Path(_) => PureAttrMeta::Path,
94            syn::Meta::List(list) => {
95                // Extract just the tokens inside the parentheses
96                PureAttrMeta::List(list.tokens.to_string())
97            }
98            syn::Meta::NameValue(nv) => {
99                // Extract the value part
100                PureAttrMeta::NameValue(nv.value.to_token_stream().to_string())
101            }
102        };
103
104        PureAttribute {
105            path: path_to_string(self.path()),
106            meta,
107            is_inner: matches!(self.style, syn::AttrStyle::Inner(_)),
108        }
109    }
110}
111
112impl ToPure for syn::Item {
113    type Output = PureItem;
114
115    fn to_pure(&self) -> PureItem {
116        match self {
117            syn::Item::Use(u) => PureItem::Use(u.to_pure()),
118            syn::Item::Fn(f) => PureItem::Fn(f.to_pure()),
119            syn::Item::Struct(s) => PureItem::Struct(s.to_pure()),
120            syn::Item::Enum(e) => PureItem::Enum(e.to_pure()),
121            syn::Item::Impl(i) => PureItem::Impl(i.to_pure()),
122            syn::Item::Const(c) => PureItem::Const(c.to_pure()),
123            syn::Item::Static(s) => PureItem::Static(s.to_pure()),
124            syn::Item::Type(t) => PureItem::Type(t.to_pure()),
125            syn::Item::Mod(m) => PureItem::Mod(m.to_pure()),
126            syn::Item::Trait(t) => PureItem::Trait(t.to_pure()),
127            syn::Item::Macro(m) => PureItem::Macro(m.to_pure()),
128            other => PureItem::Other(other.to_token_stream().to_string()),
129        }
130    }
131}
132
133impl ToPure for syn::ItemUse {
134    type Output = PureUse;
135
136    fn to_pure(&self) -> PureUse {
137        PureUse {
138            vis: self.vis.to_pure(),
139            tree: self.tree.to_pure(),
140        }
141    }
142}
143
144impl ToPure for syn::UseTree {
145    type Output = PureUseTree;
146
147    fn to_pure(&self) -> PureUseTree {
148        match self {
149            syn::UseTree::Path(p) => PureUseTree::Path {
150                path: p.ident.to_string(),
151                tree: Box::new(p.tree.to_pure()),
152            },
153            syn::UseTree::Name(n) => PureUseTree::Name(n.ident.to_string()),
154            syn::UseTree::Rename(r) => PureUseTree::Rename {
155                name: r.ident.to_string(),
156                rename: r.rename.to_string(),
157            },
158            syn::UseTree::Glob(_) => PureUseTree::Glob,
159            syn::UseTree::Group(g) => {
160                PureUseTree::Group(g.items.iter().map(|t| t.to_pure()).collect())
161            }
162        }
163    }
164}
165
166impl ToPure for syn::Visibility {
167    type Output = PureVis;
168
169    fn to_pure(&self) -> PureVis {
170        match self {
171            syn::Visibility::Public(_) => PureVis::Public,
172            syn::Visibility::Restricted(r) => {
173                let path = path_to_string(&r.path);
174                match path.as_str() {
175                    "crate" => PureVis::Crate,
176                    "super" => PureVis::Super,
177                    _ => PureVis::In(path),
178                }
179            }
180            syn::Visibility::Inherited => PureVis::Private,
181        }
182    }
183}
184
185impl ToPure for syn::ItemFn {
186    type Output = PureFn;
187
188    fn to_pure(&self) -> PureFn {
189        let is_async = self.sig.asyncness.is_some();
190        let is_async_inferred = !is_async && infer_async_from_return_type(&self.sig.output);
191
192        PureFn {
193            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
194            vis: self.vis.to_pure(),
195            is_async,
196            is_async_inferred,
197            is_const: self.sig.constness.is_some(),
198            is_unsafe: self.sig.unsafety.is_some(),
199            abi: self.sig.abi.as_ref().map(abi_to_string),
200            name: self.sig.ident.to_string(),
201            generics: self.sig.generics.to_pure(),
202            params: self.sig.inputs.iter().map(|p| p.to_pure()).collect(),
203            ret: match &self.sig.output {
204                syn::ReturnType::Default => None,
205                syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
206            },
207            body: self.block.to_pure(),
208        }
209    }
210}
211
212impl ToPure for syn::FnArg {
213    type Output = PureParam;
214
215    fn to_pure(&self) -> PureParam {
216        match self {
217            syn::FnArg::Receiver(r) => PureParam::SelfValue {
218                is_ref: r.reference.is_some(),
219                is_mut: r.mutability.is_some(),
220            },
221            syn::FnArg::Typed(t) => PureParam::Typed {
222                name: pat_to_name(&t.pat),
223                ty: t.ty.to_pure(),
224            },
225        }
226    }
227}
228
229impl ToPure for syn::Generics {
230    type Output = PureGenerics;
231
232    fn to_pure(&self) -> PureGenerics {
233        PureGenerics {
234            params: self.params.iter().map(|p| p.to_pure()).collect(),
235            where_clause: self
236                .where_clause
237                .as_ref()
238                .map(|w| {
239                    w.predicates
240                        .iter()
241                        .map(|p| p.to_token_stream().to_string())
242                        .collect()
243                })
244                .unwrap_or_default(),
245        }
246    }
247}
248
249impl ToPure for syn::GenericParam {
250    type Output = PureGenericParam;
251
252    fn to_pure(&self) -> PureGenericParam {
253        match self {
254            syn::GenericParam::Type(t) => PureGenericParam::Type {
255                name: t.ident.to_string(),
256                bounds: t
257                    .bounds
258                    .iter()
259                    .map(|b| b.to_token_stream().to_string())
260                    .collect(),
261            },
262            syn::GenericParam::Lifetime(l) => PureGenericParam::Lifetime {
263                name: l.lifetime.to_string(),
264                bounds: l.bounds.iter().map(|b| b.to_string()).collect(),
265            },
266            syn::GenericParam::Const(c) => PureGenericParam::Const {
267                name: c.ident.to_string(),
268                ty: c.ty.to_token_stream().to_string(),
269            },
270        }
271    }
272}
273
274impl ToPure for syn::Block {
275    type Output = PureBlock;
276
277    fn to_pure(&self) -> PureBlock {
278        PureBlock {
279            stmts: self.stmts.iter().map(|s| s.to_pure()).collect(),
280        }
281    }
282}
283
284impl ToPure for syn::Stmt {
285    type Output = PureStmt;
286
287    fn to_pure(&self) -> PureStmt {
288        match self {
289            syn::Stmt::Local(l) => {
290                // Extract type annotation from Pat::Type if present
291                let (pattern, ty) = match &l.pat {
292                    syn::Pat::Type(pt) => (pt.pat.to_pure(), Some(pt.ty.to_pure())),
293                    other => (other.to_pure(), None),
294                };
295                PureStmt::Local {
296                    pattern,
297                    ty,
298                    init: l.init.as_ref().map(|i| i.expr.to_pure()),
299                }
300            }
301            syn::Stmt::Item(i) => PureStmt::Item(Box::new(i.to_pure())),
302            syn::Stmt::Expr(e, semi) => {
303                if semi.is_some() {
304                    PureStmt::Semi(e.to_pure())
305                } else {
306                    PureStmt::Expr(e.to_pure())
307                }
308            }
309            syn::Stmt::Macro(m) => PureStmt::Semi(PureExpr::Macro {
310                name: path_to_string(&m.mac.path),
311                delimiter: match m.mac.delimiter {
312                    syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
313                    syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
314                    syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
315                },
316                tokens: m.mac.tokens.to_string(),
317            }),
318        }
319    }
320}
321
322impl ToPure for syn::Pat {
323    type Output = PurePattern;
324
325    fn to_pure(&self) -> PurePattern {
326        match self {
327            syn::Pat::Ident(i) => PurePattern::Ident {
328                name: i.ident.to_string(),
329                is_mut: i.mutability.is_some(),
330            },
331            syn::Pat::Wild(_) => PurePattern::Wild,
332            syn::Pat::Tuple(t) => PurePattern::Tuple(t.elems.iter().map(|p| p.to_pure()).collect()),
333            syn::Pat::TupleStruct(ts) => PurePattern::Struct {
334                path: path_to_string(&ts.path),
335                fields: ts
336                    .elems
337                    .iter()
338                    .enumerate()
339                    .map(|(i, p)| (i.to_string(), p.to_pure()))
340                    .collect(),
341                rest: false,
342            },
343            syn::Pat::Struct(s) => PurePattern::Struct {
344                path: path_to_string(&s.path),
345                fields: s
346                    .fields
347                    .iter()
348                    .map(|f| {
349                        let name = f.member.to_token_stream().to_string();
350                        (name, f.pat.to_pure())
351                    })
352                    .collect(),
353                rest: s.rest.is_some(),
354            },
355            syn::Pat::Reference(r) => PurePattern::Ref {
356                is_mut: r.mutability.is_some(),
357                pattern: Box::new(r.pat.to_pure()),
358            },
359            syn::Pat::Lit(l) => PurePattern::Lit(l.lit.to_token_stream().to_string()),
360            syn::Pat::Or(o) => PurePattern::Or(o.cases.iter().map(|p| p.to_pure()).collect()),
361            syn::Pat::Type(t) => t.pat.to_pure(),
362            syn::Pat::Path(p) => PurePattern::Path(path_to_string(&p.path)),
363            syn::Pat::Range(r) => PurePattern::Range {
364                start: r.start.as_ref().map(|e| e.to_token_stream().to_string()),
365                end: r.end.as_ref().map(|e| e.to_token_stream().to_string()),
366                inclusive: matches!(r.limits, syn::RangeLimits::Closed(_)),
367            },
368            syn::Pat::Slice(s) => PurePattern::Slice(s.elems.iter().map(|p| p.to_pure()).collect()),
369            syn::Pat::Rest(_) => PurePattern::Rest,
370            syn::Pat::Paren(p) => p.pat.to_pure(),
371            other => PurePattern::Other(other.to_token_stream().to_string()),
372        }
373    }
374}
375
376impl ToPure for syn::Expr {
377    type Output = PureExpr;
378
379    fn to_pure(&self) -> PureExpr {
380        match self {
381            syn::Expr::Lit(l) => PureExpr::Lit(l.lit.to_token_stream().to_string()),
382            syn::Expr::Path(p) => PureExpr::Path(path_to_string(&p.path)),
383            syn::Expr::Binary(b) => PureExpr::Binary {
384                op: b.op.to_token_stream().to_string(),
385                left: Box::new(b.left.to_pure()),
386                right: Box::new(b.right.to_pure()),
387            },
388            syn::Expr::Unary(u) => PureExpr::Unary {
389                op: u.op.to_token_stream().to_string(),
390                expr: Box::new(u.expr.to_pure()),
391            },
392            syn::Expr::Call(c) => PureExpr::Call {
393                func: Box::new(c.func.to_pure()),
394                args: c.args.iter().map(|a| a.to_pure()).collect(),
395            },
396            syn::Expr::MethodCall(m) => PureExpr::MethodCall {
397                receiver: Box::new(m.receiver.to_pure()),
398                method: m.method.to_string(),
399                turbofish: m
400                    .turbofish
401                    .as_ref()
402                    .map(|t| t.args.to_token_stream().to_string()),
403                args: m.args.iter().map(|a| a.to_pure()).collect(),
404            },
405            syn::Expr::Field(f) => PureExpr::Field {
406                expr: Box::new(f.base.to_pure()),
407                field: f.member.to_token_stream().to_string(),
408            },
409            syn::Expr::Index(i) => PureExpr::Index {
410                expr: Box::new(i.expr.to_pure()),
411                index: Box::new(i.index.to_pure()),
412            },
413            syn::Expr::Block(b) => PureExpr::Block {
414                label: b.label.as_ref().map(label_to_string),
415                block: b.block.to_pure(),
416            },
417            syn::Expr::If(i) => PureExpr::If {
418                cond: Box::new(i.cond.to_pure()),
419                then_branch: i.then_branch.to_pure(),
420                else_branch: i.else_branch.as_ref().map(|(_, e)| Box::new(e.to_pure())),
421            },
422            syn::Expr::Match(m) => PureExpr::Match {
423                expr: Box::new(m.expr.to_pure()),
424                arms: m.arms.iter().map(|a| a.to_pure()).collect(),
425            },
426            syn::Expr::Loop(l) => PureExpr::Loop {
427                label: l.label.as_ref().map(label_to_string),
428                body: l.body.to_pure(),
429            },
430            syn::Expr::While(w) => PureExpr::While {
431                label: w.label.as_ref().map(label_to_string),
432                cond: Box::new(w.cond.to_pure()),
433                body: w.body.to_pure(),
434            },
435            syn::Expr::ForLoop(f) => PureExpr::For {
436                label: f.label.as_ref().map(label_to_string),
437                pat: f.pat.to_pure(),
438                expr: Box::new(f.expr.to_pure()),
439                body: f.body.to_pure(),
440            },
441            syn::Expr::Return(r) => {
442                PureExpr::Return(r.expr.as_ref().map(|e| Box::new(e.to_pure())))
443            }
444            syn::Expr::Break(b) => PureExpr::Break {
445                label: b.label.as_ref().map(|l| l.ident.to_string()),
446                expr: b.expr.as_ref().map(|e| Box::new(e.to_pure())),
447            },
448            syn::Expr::Continue(c) => PureExpr::Continue {
449                label: c.label.as_ref().map(|l| l.ident.to_string()),
450            },
451            syn::Expr::Closure(c) => PureExpr::Closure {
452                is_async: c.asyncness.is_some(),
453                is_move: c.capture.is_some(),
454                params: c
455                    .inputs
456                    .iter()
457                    .map(|p| match p {
458                        syn::Pat::Type(pt) => {
459                            PureClosureParam::typed(pt.pat.to_pure(), pt.ty.to_pure())
460                        }
461                        other => PureClosureParam::untyped(other.to_pure()),
462                    })
463                    .collect(),
464                ret: match &c.output {
465                    syn::ReturnType::Default => None,
466                    syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
467                },
468                body: Box::new(c.body.to_pure()),
469            },
470            syn::Expr::Struct(s) => PureExpr::Struct {
471                path: path_to_string(&s.path),
472                fields: s
473                    .fields
474                    .iter()
475                    .map(|f| {
476                        let name = f.member.to_token_stream().to_string();
477                        (name, f.expr.to_pure())
478                    })
479                    .collect(),
480            },
481            syn::Expr::Tuple(t) => PureExpr::Tuple(t.elems.iter().map(|e| e.to_pure()).collect()),
482            syn::Expr::Array(a) => PureExpr::Array(a.elems.iter().map(|e| e.to_pure()).collect()),
483            syn::Expr::Reference(r) => PureExpr::Ref {
484                is_mut: r.mutability.is_some(),
485                expr: Box::new(r.expr.to_pure()),
486            },
487            syn::Expr::Macro(m) => PureExpr::Macro {
488                name: path_to_string(&m.mac.path),
489                delimiter: match m.mac.delimiter {
490                    syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
491                    syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
492                    syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
493                },
494                tokens: m.mac.tokens.to_string(),
495            },
496            syn::Expr::Await(a) => PureExpr::Await(Box::new(a.base.to_pure())),
497            syn::Expr::Try(t) => PureExpr::Try(Box::new(t.expr.to_pure())),
498            syn::Expr::Paren(p) => p.expr.to_pure(),
499            // Assignment: `a = b` - treated as Binary with "="
500            syn::Expr::Assign(a) => PureExpr::Binary {
501                op: "=".to_string(),
502                left: Box::new(a.left.to_pure()),
503                right: Box::new(a.right.to_pure()),
504            },
505            // Range: `a..b`, `..b`, `a..`, `..`, `a..=b`
506            syn::Expr::Range(r) => PureExpr::Range {
507                start: r.start.as_ref().map(|e| Box::new(e.to_pure())),
508                end: r.end.as_ref().map(|e| Box::new(e.to_pure())),
509                inclusive: matches!(r.limits, syn::RangeLimits::Closed(_)),
510            },
511            // Cast: `x as T`
512            syn::Expr::Cast(c) => PureExpr::Cast {
513                expr: Box::new(c.expr.to_pure()),
514                ty: c.ty.to_pure(),
515            },
516            // Let expression (in conditions): `let Some(x) = y`
517            syn::Expr::Let(l) => PureExpr::Let {
518                pattern: l.pat.to_pure(),
519                expr: Box::new(l.expr.to_pure()),
520            },
521            // Async block: `async { ... }` or `async move { ... }`
522            syn::Expr::Async(a) => PureExpr::Async {
523                is_move: a.capture.is_some(),
524                body: a.block.to_pure(),
525            },
526            // Unsafe block: `unsafe { ... }`
527            syn::Expr::Unsafe(u) => PureExpr::Unsafe(u.block.to_pure()),
528            // Array repeat: `[expr; N]`
529            syn::Expr::Repeat(r) => PureExpr::Repeat {
530                expr: Box::new(r.expr.to_pure()),
531                len: Box::new(r.len.to_pure()),
532            },
533            other => PureExpr::Other(other.to_token_stream().to_string()),
534        }
535    }
536}
537
538impl ToPure for syn::Arm {
539    type Output = PureMatchArm;
540
541    fn to_pure(&self) -> PureMatchArm {
542        PureMatchArm {
543            pattern: self.pat.to_pure(),
544            guard: self.guard.as_ref().map(|(_, e)| e.to_pure()),
545            body: self.body.to_pure(),
546        }
547    }
548}
549
550impl ToPure for syn::Type {
551    type Output = PureType;
552
553    fn to_pure(&self) -> PureType {
554        match self {
555            syn::Type::Path(p) => PureType::Path(path_to_string(&p.path)),
556            syn::Type::Reference(r) => PureType::Ref {
557                lifetime: r.lifetime.as_ref().map(|l| l.to_string()),
558                is_mut: r.mutability.is_some(),
559                ty: Box::new(r.elem.to_pure()),
560            },
561            syn::Type::Tuple(t) => PureType::Tuple(t.elems.iter().map(|t| t.to_pure()).collect()),
562            syn::Type::Array(a) => PureType::Array {
563                ty: Box::new(a.elem.to_pure()),
564                len: a.len.to_token_stream().to_string(),
565            },
566            syn::Type::Slice(s) => PureType::Slice(Box::new(s.elem.to_pure())),
567            syn::Type::BareFn(f) => PureType::Fn {
568                params: f.inputs.iter().map(|i| i.ty.to_pure()).collect(),
569                ret: match &f.output {
570                    syn::ReturnType::Default => None,
571                    syn::ReturnType::Type(_, ty) => Some(Box::new(ty.to_pure())),
572                },
573            },
574            syn::Type::ImplTrait(i) => PureType::ImplTrait(
575                i.bounds
576                    .iter()
577                    .map(|b| b.to_token_stream().to_string())
578                    .collect(),
579            ),
580            syn::Type::TraitObject(t) => PureType::TraitObject(
581                t.bounds
582                    .iter()
583                    .map(|b| b.to_token_stream().to_string())
584                    .collect(),
585            ),
586            syn::Type::Infer(_) => PureType::Infer,
587            syn::Type::Never(_) => PureType::Never,
588            syn::Type::Paren(p) => p.elem.to_pure(),
589            other => PureType::Other(other.to_token_stream().to_string()),
590        }
591    }
592}
593
594impl ToPure for syn::ItemStruct {
595    type Output = PureStruct;
596
597    fn to_pure(&self) -> PureStruct {
598        PureStruct {
599            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
600            vis: self.vis.to_pure(),
601            name: self.ident.to_string(),
602            generics: self.generics.to_pure(),
603            fields: self.fields.to_pure(),
604        }
605    }
606}
607
608impl ToPure for syn::Fields {
609    type Output = PureFields;
610
611    fn to_pure(&self) -> PureFields {
612        match self {
613            syn::Fields::Named(n) => {
614                PureFields::Named(n.named.iter().map(|f| f.to_pure()).collect())
615            }
616            syn::Fields::Unnamed(u) => {
617                PureFields::Tuple(u.unnamed.iter().map(|f| f.ty.to_pure()).collect())
618            }
619            syn::Fields::Unit => PureFields::Unit,
620        }
621    }
622}
623
624impl ToPure for syn::Field {
625    type Output = PureField;
626
627    fn to_pure(&self) -> PureField {
628        PureField {
629            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
630            vis: self.vis.to_pure(),
631            name: self
632                .ident
633                .as_ref()
634                .map(|i| i.to_string())
635                .unwrap_or_default(),
636            ty: self.ty.to_pure(),
637        }
638    }
639}
640
641impl ToPure for syn::ItemEnum {
642    type Output = PureEnum;
643
644    fn to_pure(&self) -> PureEnum {
645        PureEnum {
646            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
647            vis: self.vis.to_pure(),
648            name: self.ident.to_string(),
649            generics: self.generics.to_pure(),
650            variants: self.variants.iter().map(|v| v.to_pure()).collect(),
651        }
652    }
653}
654
655impl ToPure for syn::Variant {
656    type Output = PureVariant;
657
658    fn to_pure(&self) -> PureVariant {
659        PureVariant {
660            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
661            name: self.ident.to_string(),
662            fields: self.fields.to_pure(),
663            discriminant: self
664                .discriminant
665                .as_ref()
666                .map(|(_, e)| e.to_token_stream().to_string()),
667        }
668    }
669}
670
671impl ToPure for syn::ItemImpl {
672    type Output = PureImpl;
673
674    fn to_pure(&self) -> PureImpl {
675        PureImpl {
676            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
677            generics: self.generics.to_pure(),
678            is_unsafe: self.unsafety.is_some(),
679            trait_: self.trait_.as_ref().map(|(_, p, _)| path_to_string(p)),
680            self_ty: self.self_ty.to_token_stream().to_string(),
681            items: self.items.iter().map(|i| i.to_pure()).collect(),
682        }
683    }
684}
685
686impl ToPure for syn::ImplItem {
687    type Output = PureImplItem;
688
689    fn to_pure(&self) -> PureImplItem {
690        match self {
691            syn::ImplItem::Fn(f) => PureImplItem::Fn(f.to_pure()),
692            syn::ImplItem::Const(c) => PureImplItem::Const(c.to_pure()),
693            syn::ImplItem::Type(t) => PureImplItem::Type(t.to_pure()),
694            other => PureImplItem::Other(other.to_token_stream().to_string()),
695        }
696    }
697}
698
699impl ToPure for syn::ImplItemFn {
700    type Output = PureFn;
701
702    fn to_pure(&self) -> PureFn {
703        let is_async = self.sig.asyncness.is_some();
704        let is_async_inferred = !is_async && infer_async_from_return_type(&self.sig.output);
705
706        PureFn {
707            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
708            vis: self.vis.to_pure(),
709            is_async,
710            is_async_inferred,
711            is_const: self.sig.constness.is_some(),
712            is_unsafe: self.sig.unsafety.is_some(),
713            abi: self.sig.abi.as_ref().map(abi_to_string),
714            name: self.sig.ident.to_string(),
715            generics: self.sig.generics.to_pure(),
716            params: self.sig.inputs.iter().map(|p| p.to_pure()).collect(),
717            ret: match &self.sig.output {
718                syn::ReturnType::Default => None,
719                syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
720            },
721            body: self.block.to_pure(),
722        }
723    }
724}
725
726impl ToPure for syn::ImplItemConst {
727    type Output = PureConst;
728
729    fn to_pure(&self) -> PureConst {
730        PureConst {
731            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
732            vis: self.vis.to_pure(),
733            name: self.ident.to_string(),
734            ty: self.ty.to_pure(),
735            value: Some(self.expr.to_pure()),
736        }
737    }
738}
739
740impl ToPure for syn::ImplItemType {
741    type Output = PureTypeAlias;
742
743    fn to_pure(&self) -> PureTypeAlias {
744        PureTypeAlias {
745            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
746            vis: self.vis.to_pure(),
747            name: self.ident.to_string(),
748            generics: self.generics.to_pure(),
749            ty: self.ty.to_pure(),
750        }
751    }
752}
753
754impl ToPure for syn::ItemConst {
755    type Output = PureConst;
756
757    fn to_pure(&self) -> PureConst {
758        PureConst {
759            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
760            vis: self.vis.to_pure(),
761            name: self.ident.to_string(),
762            ty: self.ty.to_pure(),
763            value: Some(self.expr.to_pure()),
764        }
765    }
766}
767
768impl ToPure for syn::ItemStatic {
769    type Output = PureStatic;
770
771    fn to_pure(&self) -> PureStatic {
772        PureStatic {
773            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
774            vis: self.vis.to_pure(),
775            is_mut: matches!(self.mutability, syn::StaticMutability::Mut(_)),
776            name: self.ident.to_string(),
777            ty: self.ty.to_pure(),
778            value: self.expr.to_pure(),
779        }
780    }
781}
782
783impl ToPure for syn::ItemType {
784    type Output = PureTypeAlias;
785
786    fn to_pure(&self) -> PureTypeAlias {
787        PureTypeAlias {
788            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
789            vis: self.vis.to_pure(),
790            name: self.ident.to_string(),
791            generics: self.generics.to_pure(),
792            ty: self.ty.to_pure(),
793        }
794    }
795}
796
797impl ToPure for syn::ItemMod {
798    type Output = PureMod;
799
800    fn to_pure(&self) -> PureMod {
801        // Convert all items (inline module) or empty (file module)
802        let items = if let Some((_, items)) = &self.content {
803            items.iter().map(|item| item.to_pure()).collect()
804        } else {
805            // File module (mod foo;) - no inline content
806            Vec::new()
807        };
808
809        PureMod {
810            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
811            vis: self.vis.to_pure(),
812            name: self.ident.to_string(),
813            items,
814        }
815    }
816}
817
818impl ToPure for syn::ItemTrait {
819    type Output = PureTrait;
820
821    fn to_pure(&self) -> PureTrait {
822        PureTrait {
823            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
824            vis: self.vis.to_pure(),
825            is_unsafe: self.unsafety.is_some(),
826            is_auto: self.auto_token.is_some(),
827            name: self.ident.to_string(),
828            generics: self.generics.to_pure(),
829            supertraits: self
830                .supertraits
831                .iter()
832                .map(|b| b.to_token_stream().to_string())
833                .collect(),
834            items: self.items.iter().map(|i| i.to_pure()).collect(),
835        }
836    }
837}
838
839impl ToPure for syn::TraitItem {
840    type Output = PureTraitItem;
841
842    fn to_pure(&self) -> PureTraitItem {
843        match self {
844            syn::TraitItem::Fn(f) => PureTraitItem::Fn(f.to_pure()),
845            syn::TraitItem::Const(c) => PureTraitItem::Const(c.to_pure()),
846            syn::TraitItem::Type(t) => PureTraitItem::Type {
847                name: t.ident.to_string(),
848                bounds: t
849                    .bounds
850                    .iter()
851                    .map(|b| b.to_token_stream().to_string())
852                    .collect(),
853                default: t.default.as_ref().map(|(_, ty)| ty.to_pure()),
854            },
855            other => PureTraitItem::Other(other.to_token_stream().to_string()),
856        }
857    }
858}
859
860impl ToPure for syn::TraitItemFn {
861    type Output = PureFn;
862
863    fn to_pure(&self) -> PureFn {
864        let is_async = self.sig.asyncness.is_some();
865        let is_async_inferred = !is_async && infer_async_from_return_type(&self.sig.output);
866
867        PureFn {
868            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
869            vis: PureVis::Private, // Trait methods don't have visibility
870            is_async,
871            is_async_inferred,
872            is_const: self.sig.constness.is_some(),
873            is_unsafe: self.sig.unsafety.is_some(),
874            abi: self.sig.abi.as_ref().map(abi_to_string),
875            name: self.sig.ident.to_string(),
876            generics: self.sig.generics.to_pure(),
877            params: self.sig.inputs.iter().map(|p| p.to_pure()).collect(),
878            ret: match &self.sig.output {
879                syn::ReturnType::Default => None,
880                syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
881            },
882            body: self
883                .default
884                .as_ref()
885                .map(|b| b.to_pure())
886                .unwrap_or_default(),
887        }
888    }
889}
890
891impl ToPure for syn::TraitItemConst {
892    type Output = PureConst;
893
894    fn to_pure(&self) -> PureConst {
895        PureConst {
896            attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
897            vis: PureVis::Private,
898            name: self.ident.to_string(),
899            ty: self.ty.to_pure(),
900            value: self.default.as_ref().map(|(_, e)| e.to_pure()),
901        }
902    }
903}
904
905impl ToPure for syn::ItemMacro {
906    type Output = PureMacro;
907
908    fn to_pure(&self) -> PureMacro {
909        PureMacro {
910            path: path_to_string(&self.mac.path),
911            delimiter: match self.mac.delimiter {
912                syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
913                syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
914                syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
915            },
916            tokens: self.mac.tokens.to_string(),
917        }
918    }
919}
920
921// Helper functions
922
923fn path_to_string(path: &syn::Path) -> String {
924    path.segments
925        .iter()
926        .map(|s| {
927            let ident = s.ident.to_string();
928            match &s.arguments {
929                syn::PathArguments::None => ident,
930                syn::PathArguments::AngleBracketed(args) => {
931                    format!("{}{}", ident, args.to_token_stream())
932                }
933                syn::PathArguments::Parenthesized(args) => {
934                    format!("{}{}", ident, args.to_token_stream())
935                }
936            }
937        })
938        .collect::<Vec<_>>()
939        .join("::")
940}
941
942fn pat_to_name(pat: &syn::Pat) -> String {
943    match pat {
944        syn::Pat::Ident(i) => i.ident.to_string(),
945        syn::Pat::Type(t) => pat_to_name(&t.pat),
946        syn::Pat::Tuple(t) => {
947            // Tuple patterns like (a, b) - use placeholder name
948            format!("_tuple{}", t.elems.len())
949        }
950        syn::Pat::TupleStruct(ts) => {
951            // TupleStruct patterns like Some(x) - use struct name
952            ts.path
953                .segments
954                .last()
955                .map(|s| s.ident.to_string())
956                .unwrap_or_else(|| "_pattern".to_string())
957        }
958        syn::Pat::Struct(s) => {
959            // Struct patterns like Point { x, y } - use struct name
960            s.path
961                .segments
962                .last()
963                .map(|seg| seg.ident.to_string())
964                .unwrap_or_else(|| "_pattern".to_string())
965        }
966        syn::Pat::Wild(_) => "_".to_string(),
967        syn::Pat::Rest(_) => "_rest".to_string(),
968        syn::Pat::Slice(_) => "_slice".to_string(),
969        syn::Pat::Reference(r) => pat_to_name(&r.pat),
970        syn::Pat::Or(o) => {
971            // Or patterns like A | B - use first pattern's name
972            o.cases
973                .first()
974                .map(pat_to_name)
975                .unwrap_or_else(|| "_or".to_string())
976        }
977        _other => "_param".to_string(),
978    }
979}
980
981#[cfg(test)]
982mod tests {
983    use super::*;
984
985    #[test]
986    fn test_parse_simple_fn() {
987        let pure = PureFile::from_source("fn main() {}").unwrap();
988        assert_eq!(pure.functions().len(), 1);
989        assert_eq!(pure.functions()[0].name, "main");
990    }
991
992    #[test]
993    fn test_parse_struct() {
994        let pure = PureFile::from_source(
995            r#"
996            struct Point {
997                x: i32,
998                y: i32,
999            }
1000            "#,
1001        )
1002        .unwrap();
1003
1004        assert_eq!(pure.structs().len(), 1);
1005        let s = &pure.structs()[0];
1006        assert_eq!(s.name, "Point");
1007
1008        if let PureFields::Named(fields) = &s.fields {
1009            assert_eq!(fields.len(), 2);
1010            assert_eq!(fields[0].name, "x");
1011            assert_eq!(fields[1].name, "y");
1012        } else {
1013            panic!("Expected named fields");
1014        }
1015    }
1016
1017    #[test]
1018    fn test_parse_use() {
1019        let pure = PureFile::from_source("use std::io;").unwrap();
1020        assert_eq!(pure.uses().len(), 1);
1021    }
1022
1023    #[test]
1024    fn test_thread_safe() {
1025        use std::sync::Arc;
1026        use std::thread;
1027
1028        let pure = PureFile::from_source(
1029            r#"
1030            fn foo() {}
1031            fn bar() {}
1032            "#,
1033        )
1034        .unwrap();
1035
1036        let shared = Arc::new(pure);
1037
1038        let s1 = Arc::clone(&shared);
1039        let s2 = Arc::clone(&shared);
1040
1041        let h1 = thread::spawn(move || s1.functions().len());
1042        let h2 = thread::spawn(move || s2.find_fn("foo").map(|f| f.name.clone()));
1043
1044        assert_eq!(h1.join().unwrap(), 2);
1045        assert_eq!(h2.join().unwrap(), Some("foo".to_string()));
1046    }
1047
1048    // ========== Expression Tests ==========
1049
1050    #[test]
1051    fn test_expr_binary() {
1052        let pure = PureFile::from_source("fn f() { let _ = 1 + 2; }").unwrap();
1053        let f = &pure.functions()[0];
1054        if let PureStmt::Local {
1055            init: Some(expr), ..
1056        } = &f.body.stmts[0]
1057        {
1058            if let PureExpr::Binary { op, .. } = expr {
1059                assert_eq!(op, "+");
1060            } else {
1061                panic!("Expected Binary expr");
1062            }
1063        } else {
1064            panic!("Expected Local stmt");
1065        }
1066    }
1067
1068    #[test]
1069    fn test_expr_unary() {
1070        let pure = PureFile::from_source("fn f() { let _ = -x; }").unwrap();
1071        let f = &pure.functions()[0];
1072        if let PureStmt::Local {
1073            init: Some(expr), ..
1074        } = &f.body.stmts[0]
1075        {
1076            if let PureExpr::Unary { op, .. } = expr {
1077                assert_eq!(op, "-");
1078            } else {
1079                panic!("Expected Unary expr");
1080            }
1081        } else {
1082            panic!("Expected Local stmt");
1083        }
1084    }
1085
1086    #[test]
1087    fn test_expr_method_call() {
1088        let pure = PureFile::from_source("fn f() { x.foo(1, 2); }").unwrap();
1089        let f = &pure.functions()[0];
1090        if let PureStmt::Semi(PureExpr::MethodCall { method, args, .. }) = &f.body.stmts[0] {
1091            assert_eq!(method, "foo");
1092            assert_eq!(args.len(), 2);
1093        } else {
1094            panic!("Expected MethodCall expr");
1095        }
1096    }
1097
1098    #[test]
1099    fn test_expr_method_call_with_turbofish() {
1100        let pure = PureFile::from_source("fn f() { x.collect::<Vec<_>>(); }").unwrap();
1101        let f = &pure.functions()[0];
1102        if let PureStmt::Semi(PureExpr::MethodCall {
1103            method, turbofish, ..
1104        }) = &f.body.stmts[0]
1105        {
1106            assert_eq!(method, "collect");
1107            assert!(turbofish.is_some());
1108            assert!(turbofish.as_ref().unwrap().contains("Vec"));
1109        } else {
1110            panic!("Expected MethodCall expr with turbofish");
1111        }
1112    }
1113
1114    #[test]
1115    fn test_expr_closure() {
1116        let pure = PureFile::from_source("fn f() { let _ = |x| x + 1; }").unwrap();
1117        let f = &pure.functions()[0];
1118        if let PureStmt::Local {
1119            init: Some(expr), ..
1120        } = &f.body.stmts[0]
1121        {
1122            if let PureExpr::Closure {
1123                params, is_move, ..
1124            } = expr
1125            {
1126                assert!(!is_move);
1127                assert_eq!(params.len(), 1);
1128            } else {
1129                panic!("Expected Closure expr");
1130            }
1131        } else {
1132            panic!("Expected Local stmt");
1133        }
1134    }
1135
1136    #[test]
1137    fn test_expr_closure_move() {
1138        let pure = PureFile::from_source("fn f() { let _ = move |x| x; }").unwrap();
1139        let f = &pure.functions()[0];
1140        if let PureStmt::Local {
1141            init: Some(PureExpr::Closure { is_move, .. }),
1142            ..
1143        } = &f.body.stmts[0]
1144        {
1145            assert!(is_move);
1146        } else {
1147            panic!("Expected move Closure");
1148        }
1149    }
1150
1151    #[test]
1152    fn test_expr_async_closure() {
1153        let pure = PureFile::from_source("fn f() { let _ = async || {}; }").unwrap();
1154        let f = &pure.functions()[0];
1155        if let PureStmt::Local {
1156            init: Some(PureExpr::Closure { is_async, .. }),
1157            ..
1158        } = &f.body.stmts[0]
1159        {
1160            assert!(is_async);
1161        } else {
1162            panic!("Expected async Closure");
1163        }
1164    }
1165
1166    #[test]
1167    fn test_expr_closure_typed_params() {
1168        let pure =
1169            PureFile::from_source("fn f() { let _ = |x: i32, y: String| -> bool { true }; }")
1170                .unwrap();
1171        let f = &pure.functions()[0];
1172        if let PureStmt::Local {
1173            init: Some(expr), ..
1174        } = &f.body.stmts[0]
1175        {
1176            if let PureExpr::Closure { params, ret, .. } = expr {
1177                assert_eq!(params.len(), 2);
1178                // First param: x: i32
1179                assert!(params[0].ty.is_some());
1180                assert_eq!(
1181                    params[0].ty.as_ref().unwrap(),
1182                    &PureType::Path("i32".to_string())
1183                );
1184                // Second param: y: String
1185                assert!(params[1].ty.is_some());
1186                assert_eq!(
1187                    params[1].ty.as_ref().unwrap(),
1188                    &PureType::Path("String".to_string())
1189                );
1190                // Return type: -> bool
1191                assert!(ret.is_some());
1192                assert_eq!(ret.as_ref().unwrap(), &PureType::Path("bool".to_string()));
1193            } else {
1194                panic!("Expected Closure expr");
1195            }
1196        } else {
1197            panic!("Expected Local stmt");
1198        }
1199    }
1200
1201    #[test]
1202    fn test_expr_closure_untyped_params_no_ret() {
1203        let pure = PureFile::from_source("fn f() { let _ = |x| x + 1; }").unwrap();
1204        let f = &pure.functions()[0];
1205        if let PureStmt::Local {
1206            init: Some(PureExpr::Closure { params, ret, .. }),
1207            ..
1208        } = &f.body.stmts[0]
1209        {
1210            assert_eq!(params.len(), 1);
1211            assert!(params[0].ty.is_none());
1212            assert!(ret.is_none());
1213        } else {
1214            panic!("Expected Closure");
1215        }
1216    }
1217
1218    #[test]
1219    fn test_expr_if() {
1220        let pure = PureFile::from_source("fn f() { if true { 1 } else { 2 } }").unwrap();
1221        let f = &pure.functions()[0];
1222        if let PureStmt::Expr(PureExpr::If { else_branch, .. }) = &f.body.stmts[0] {
1223            assert!(else_branch.is_some());
1224        } else {
1225            panic!("Expected If expr");
1226        }
1227    }
1228
1229    #[test]
1230    fn test_expr_match() {
1231        let pure =
1232            PureFile::from_source(r#"fn f() { match x { 1 => "one", _ => "other" } }"#).unwrap();
1233        let f = &pure.functions()[0];
1234        if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1235            assert_eq!(arms.len(), 2);
1236        } else {
1237            panic!("Expected Match expr");
1238        }
1239    }
1240
1241    #[test]
1242    fn test_expr_for_loop() {
1243        let pure = PureFile::from_source("fn f() { for i in 0..10 {} }").unwrap();
1244        let f = &pure.functions()[0];
1245        // For loop without semicolon is Expr, with semicolon is Semi
1246        match &f.body.stmts[0] {
1247            PureStmt::Expr(PureExpr::For { pat, .. })
1248            | PureStmt::Semi(PureExpr::For { pat, .. }) => {
1249                if let PurePattern::Ident { name, .. } = pat {
1250                    assert_eq!(name, "i");
1251                } else {
1252                    panic!("Expected Ident pattern");
1253                }
1254            }
1255            _ => panic!("Expected For expr"),
1256        }
1257    }
1258
1259    #[test]
1260    fn test_expr_while() {
1261        let pure = PureFile::from_source("fn f() { while x > 0 { x -= 1; } }").unwrap();
1262        let f = &pure.functions()[0];
1263        match &f.body.stmts[0] {
1264            PureStmt::Expr(PureExpr::While { .. }) | PureStmt::Semi(PureExpr::While { .. }) => {}
1265            _ => panic!("Expected While expr"),
1266        }
1267    }
1268
1269    #[test]
1270    fn test_expr_loop() {
1271        let pure = PureFile::from_source("fn f() { loop { break; } }").unwrap();
1272        let f = &pure.functions()[0];
1273        match &f.body.stmts[0] {
1274            PureStmt::Expr(PureExpr::Loop { .. }) | PureStmt::Semi(PureExpr::Loop { .. }) => {}
1275            _ => panic!("Expected Loop expr"),
1276        }
1277    }
1278
1279    #[test]
1280    fn test_expr_return() {
1281        let pure = PureFile::from_source("fn f() -> i32 { return 42; }").unwrap();
1282        let f = &pure.functions()[0];
1283        if let PureStmt::Semi(PureExpr::Return(Some(_))) = &f.body.stmts[0] {
1284            // ok
1285        } else {
1286            panic!("Expected Return expr");
1287        }
1288    }
1289
1290    #[test]
1291    fn test_expr_struct() {
1292        let pure = PureFile::from_source("fn f() { Point { x: 1, y: 2 } }").unwrap();
1293        let f = &pure.functions()[0];
1294        if let PureStmt::Expr(PureExpr::Struct { path, fields }) = &f.body.stmts[0] {
1295            assert_eq!(path, "Point");
1296            assert_eq!(fields.len(), 2);
1297        } else {
1298            panic!("Expected Struct expr");
1299        }
1300    }
1301
1302    #[test]
1303    fn test_expr_tuple() {
1304        let pure = PureFile::from_source("fn f() { (1, 2, 3) }").unwrap();
1305        let f = &pure.functions()[0];
1306        if let PureStmt::Expr(PureExpr::Tuple(elems)) = &f.body.stmts[0] {
1307            assert_eq!(elems.len(), 3);
1308        } else {
1309            panic!("Expected Tuple expr");
1310        }
1311    }
1312
1313    #[test]
1314    fn test_expr_array() {
1315        let pure = PureFile::from_source("fn f() { [1, 2, 3] }").unwrap();
1316        let f = &pure.functions()[0];
1317        if let PureStmt::Expr(PureExpr::Array(elems)) = &f.body.stmts[0] {
1318            assert_eq!(elems.len(), 3);
1319        } else {
1320            panic!("Expected Array expr");
1321        }
1322    }
1323
1324    #[test]
1325    fn test_expr_repeat() {
1326        let pure = PureFile::from_source("fn f() { [0; 10] }").unwrap();
1327        let f = &pure.functions()[0];
1328        if let PureStmt::Expr(PureExpr::Repeat { .. }) = &f.body.stmts[0] {
1329            // ok
1330        } else {
1331            panic!("Expected Repeat expr");
1332        }
1333    }
1334
1335    #[test]
1336    fn test_expr_reference() {
1337        let pure = PureFile::from_source("fn f() { let _ = &x; }").unwrap();
1338        let f = &pure.functions()[0];
1339        if let PureStmt::Local {
1340            init: Some(PureExpr::Ref { is_mut, .. }),
1341            ..
1342        } = &f.body.stmts[0]
1343        {
1344            assert!(!is_mut);
1345        } else {
1346            panic!("Expected Ref expr");
1347        }
1348    }
1349
1350    #[test]
1351    fn test_expr_reference_mut() {
1352        let pure = PureFile::from_source("fn f() { let _ = &mut x; }").unwrap();
1353        let f = &pure.functions()[0];
1354        if let PureStmt::Local {
1355            init: Some(PureExpr::Ref { is_mut, .. }),
1356            ..
1357        } = &f.body.stmts[0]
1358        {
1359            assert!(is_mut);
1360        } else {
1361            panic!("Expected mut Ref expr");
1362        }
1363    }
1364
1365    #[test]
1366    fn test_expr_await() {
1367        let pure = PureFile::from_source("async fn f() { x.await }").unwrap();
1368        let f = &pure.functions()[0];
1369        if let PureStmt::Expr(PureExpr::Await(_)) = &f.body.stmts[0] {
1370            // ok
1371        } else {
1372            panic!("Expected Await expr");
1373        }
1374    }
1375
1376    #[test]
1377    fn test_expr_try() {
1378        let pure = PureFile::from_source("fn f() -> Result<(), ()> { x?; Ok(()) }").unwrap();
1379        let f = &pure.functions()[0];
1380        if let PureStmt::Semi(PureExpr::Try(_)) = &f.body.stmts[0] {
1381            // ok
1382        } else {
1383            panic!("Expected Try expr");
1384        }
1385    }
1386
1387    #[test]
1388    fn test_expr_range() {
1389        let pure = PureFile::from_source("fn f() { let _ = 0..10; }").unwrap();
1390        let f = &pure.functions()[0];
1391        if let PureStmt::Local {
1392            init:
1393                Some(PureExpr::Range {
1394                    start,
1395                    end,
1396                    inclusive,
1397                }),
1398            ..
1399        } = &f.body.stmts[0]
1400        {
1401            assert!(start.is_some());
1402            assert!(end.is_some());
1403            assert!(!inclusive);
1404        } else {
1405            panic!("Expected Range expr");
1406        }
1407    }
1408
1409    #[test]
1410    fn test_expr_range_inclusive() {
1411        let pure = PureFile::from_source("fn f() { let _ = 0..=10; }").unwrap();
1412        let f = &pure.functions()[0];
1413        if let PureStmt::Local {
1414            init: Some(PureExpr::Range { inclusive, .. }),
1415            ..
1416        } = &f.body.stmts[0]
1417        {
1418            assert!(inclusive);
1419        } else {
1420            panic!("Expected inclusive Range expr");
1421        }
1422    }
1423
1424    #[test]
1425    fn test_expr_cast() {
1426        let pure = PureFile::from_source("fn f() { let _ = x as u32; }").unwrap();
1427        let f = &pure.functions()[0];
1428        if let PureStmt::Local {
1429            init: Some(PureExpr::Cast { ty, .. }),
1430            ..
1431        } = &f.body.stmts[0]
1432        {
1433            if let PureType::Path(p) = ty {
1434                assert_eq!(p, "u32");
1435            } else {
1436                panic!("Expected Path type");
1437            }
1438        } else {
1439            panic!("Expected Cast expr");
1440        }
1441    }
1442
1443    #[test]
1444    fn test_expr_let() {
1445        let pure = PureFile::from_source("fn f() { if let Some(x) = y { } }").unwrap();
1446        let f = &pure.functions()[0];
1447        let if_expr = match &f.body.stmts[0] {
1448            PureStmt::Expr(e) | PureStmt::Semi(e) => e,
1449            _ => panic!("Expected expression statement"),
1450        };
1451        if let PureExpr::If { cond, .. } = if_expr {
1452            if let PureExpr::Let { pattern, .. } = cond.as_ref() {
1453                if let PurePattern::Struct { path, .. } = pattern {
1454                    assert_eq!(path, "Some");
1455                } else {
1456                    panic!("Expected Struct pattern");
1457                }
1458            } else {
1459                panic!("Expected Let expr in condition");
1460            }
1461        } else {
1462            panic!("Expected If expr");
1463        }
1464    }
1465
1466    #[test]
1467    fn test_expr_async_block() {
1468        let pure = PureFile::from_source("fn f() { async { 42 } }").unwrap();
1469        let f = &pure.functions()[0];
1470        if let PureStmt::Expr(PureExpr::Async { is_move, .. }) = &f.body.stmts[0] {
1471            assert!(!is_move);
1472        } else {
1473            panic!("Expected Async block");
1474        }
1475    }
1476
1477    #[test]
1478    fn test_expr_unsafe_block() {
1479        let pure = PureFile::from_source("fn f() { unsafe { dangerous() } }").unwrap();
1480        let f = &pure.functions()[0];
1481        if let PureStmt::Expr(PureExpr::Unsafe(_)) = &f.body.stmts[0] {
1482            // ok
1483        } else {
1484            panic!("Expected Unsafe block");
1485        }
1486    }
1487
1488    #[test]
1489    fn test_expr_macro() {
1490        let pure = PureFile::from_source("fn f() { println!(\"hello\"); }").unwrap();
1491        let f = &pure.functions()[0];
1492        if let PureStmt::Semi(PureExpr::Macro { name, .. }) = &f.body.stmts[0] {
1493            assert_eq!(name, "println");
1494        } else {
1495            panic!("Expected Macro expr");
1496        }
1497    }
1498
1499    // ========== Pattern Tests ==========
1500
1501    #[test]
1502    fn test_pattern_ident() {
1503        let pure = PureFile::from_source("fn f() { let x = 1; }").unwrap();
1504        let f = &pure.functions()[0];
1505        if let PureStmt::Local {
1506            pattern: PurePattern::Ident { name, is_mut },
1507            ..
1508        } = &f.body.stmts[0]
1509        {
1510            assert_eq!(name, "x");
1511            assert!(!is_mut);
1512        } else {
1513            panic!("Expected Ident pattern");
1514        }
1515    }
1516
1517    #[test]
1518    fn test_pattern_ident_mut() {
1519        let pure = PureFile::from_source("fn f() { let mut x = 1; }").unwrap();
1520        let f = &pure.functions()[0];
1521        if let PureStmt::Local {
1522            pattern: PurePattern::Ident { is_mut, .. },
1523            ..
1524        } = &f.body.stmts[0]
1525        {
1526            assert!(is_mut);
1527        } else {
1528            panic!("Expected mut Ident pattern");
1529        }
1530    }
1531
1532    #[test]
1533    fn test_pattern_wild() {
1534        let pure = PureFile::from_source("fn f() { let _ = 1; }").unwrap();
1535        let f = &pure.functions()[0];
1536        if let PureStmt::Local {
1537            pattern: PurePattern::Wild,
1538            ..
1539        } = &f.body.stmts[0]
1540        {
1541            // ok
1542        } else {
1543            panic!("Expected Wild pattern");
1544        }
1545    }
1546
1547    #[test]
1548    fn test_pattern_tuple() {
1549        let pure = PureFile::from_source("fn f() { let (a, b) = (1, 2); }").unwrap();
1550        let f = &pure.functions()[0];
1551        if let PureStmt::Local {
1552            pattern: PurePattern::Tuple(elems),
1553            ..
1554        } = &f.body.stmts[0]
1555        {
1556            assert_eq!(elems.len(), 2);
1557        } else {
1558            panic!("Expected Tuple pattern");
1559        }
1560    }
1561
1562    #[test]
1563    fn test_pattern_struct() {
1564        let pure = PureFile::from_source("fn f() { let Point { x, y } = p; }").unwrap();
1565        let f = &pure.functions()[0];
1566        if let PureStmt::Local {
1567            pattern: PurePattern::Struct { path, fields, rest },
1568            ..
1569        } = &f.body.stmts[0]
1570        {
1571            assert_eq!(path, "Point");
1572            assert_eq!(fields.len(), 2);
1573            assert!(!rest);
1574        } else {
1575            panic!("Expected Struct pattern");
1576        }
1577    }
1578
1579    #[test]
1580    fn test_pattern_struct_with_rest() {
1581        let pure = PureFile::from_source("fn f() { let Point { x, .. } = p; }").unwrap();
1582        let f = &pure.functions()[0];
1583        if let PureStmt::Local {
1584            pattern: PurePattern::Struct { rest, .. },
1585            ..
1586        } = &f.body.stmts[0]
1587        {
1588            assert!(rest);
1589        } else {
1590            panic!("Expected Struct pattern with rest");
1591        }
1592    }
1593
1594    #[test]
1595    fn test_pattern_tuple_struct() {
1596        let pure = PureFile::from_source("fn f() { let Some(x) = opt; }").unwrap();
1597        let f = &pure.functions()[0];
1598        if let PureStmt::Local {
1599            pattern: PurePattern::Struct { path, .. },
1600            ..
1601        } = &f.body.stmts[0]
1602        {
1603            assert_eq!(path, "Some");
1604        } else {
1605            panic!("Expected TupleStruct pattern");
1606        }
1607    }
1608
1609    #[test]
1610    fn test_pattern_reference() {
1611        let pure = PureFile::from_source("fn f() { let &x = r; }").unwrap();
1612        let f = &pure.functions()[0];
1613        if let PureStmt::Local {
1614            pattern: PurePattern::Ref { is_mut, .. },
1615            ..
1616        } = &f.body.stmts[0]
1617        {
1618            assert!(!is_mut);
1619        } else {
1620            panic!("Expected Ref pattern");
1621        }
1622    }
1623
1624    #[test]
1625    fn test_pattern_or() {
1626        // Or patterns work in match arms
1627        let pure =
1628            PureFile::from_source("fn f(x: i32) { match x { 1 | 2 | 3 => {} _ => {} } }").unwrap();
1629        let f = &pure.functions()[0];
1630        if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1631            if let PurePattern::Or(cases) = &arms[0].pattern {
1632                assert_eq!(cases.len(), 3);
1633            } else {
1634                panic!("Expected Or pattern");
1635            }
1636        } else {
1637            panic!("Expected Match expr");
1638        }
1639    }
1640
1641    #[test]
1642    fn test_pattern_range() {
1643        let pure =
1644            PureFile::from_source("fn f(x: i32) { match x { 0..=10 => {} _ => {} } }").unwrap();
1645        let f = &pure.functions()[0];
1646        if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1647            if let PurePattern::Range { inclusive, .. } = &arms[0].pattern {
1648                assert!(inclusive);
1649            } else {
1650                panic!("Expected Range pattern");
1651            }
1652        } else {
1653            panic!("Expected Match expr");
1654        }
1655    }
1656
1657    #[test]
1658    fn test_pattern_slice() {
1659        let pure = PureFile::from_source("fn f() { let [a, b, c] = arr; }").unwrap();
1660        let f = &pure.functions()[0];
1661        if let PureStmt::Local {
1662            pattern: PurePattern::Slice(elems),
1663            ..
1664        } = &f.body.stmts[0]
1665        {
1666            assert_eq!(elems.len(), 3);
1667        } else {
1668            panic!("Expected Slice pattern");
1669        }
1670    }
1671
1672    #[test]
1673    fn test_pattern_path() {
1674        // Test path patterns like std::cmp::Ordering::Less
1675        let pure = PureFile::from_source(
1676            "fn f(x: std::cmp::Ordering) { match x { std::cmp::Ordering::Less => {} _ => {} } }",
1677        )
1678        .unwrap();
1679        let f = &pure.functions()[0];
1680        if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1681            if let PurePattern::Path(path) = &arms[0].pattern {
1682                assert!(path.contains("Ordering"));
1683                assert!(path.contains("Less"));
1684            } else {
1685                panic!("Expected Path pattern, got {:?}", arms[0].pattern);
1686            }
1687        } else {
1688            panic!("Expected Match expr");
1689        }
1690    }
1691
1692    // ========== Type Tests ==========
1693
1694    #[test]
1695    fn test_type_path() {
1696        let pure = PureFile::from_source("fn f(x: i32) {}").unwrap();
1697        let f = &pure.functions()[0];
1698        if let PureParam::Typed {
1699            ty: PureType::Path(p),
1700            ..
1701        } = &f.params[0]
1702        {
1703            assert_eq!(p, "i32");
1704        } else {
1705            panic!("Expected Path type");
1706        }
1707    }
1708
1709    #[test]
1710    fn test_type_reference() {
1711        let pure = PureFile::from_source("fn f(x: &str) {}").unwrap();
1712        let f = &pure.functions()[0];
1713        if let PureParam::Typed {
1714            ty: PureType::Ref { is_mut, .. },
1715            ..
1716        } = &f.params[0]
1717        {
1718            assert!(!is_mut);
1719        } else {
1720            panic!("Expected Ref type");
1721        }
1722    }
1723
1724    #[test]
1725    fn test_type_reference_mut() {
1726        let pure = PureFile::from_source("fn f(x: &mut String) {}").unwrap();
1727        let f = &pure.functions()[0];
1728        if let PureParam::Typed {
1729            ty: PureType::Ref { is_mut, .. },
1730            ..
1731        } = &f.params[0]
1732        {
1733            assert!(is_mut);
1734        } else {
1735            panic!("Expected mut Ref type");
1736        }
1737    }
1738
1739    #[test]
1740    fn test_type_reference_with_lifetime() {
1741        let pure = PureFile::from_source("fn f<'a>(x: &'a str) {}").unwrap();
1742        let f = &pure.functions()[0];
1743        if let PureParam::Typed {
1744            ty: PureType::Ref { lifetime, .. },
1745            ..
1746        } = &f.params[0]
1747        {
1748            assert!(lifetime.is_some());
1749            assert!(lifetime.as_ref().unwrap().contains("'a"));
1750        } else {
1751            panic!("Expected Ref type with lifetime");
1752        }
1753    }
1754
1755    #[test]
1756    fn test_type_tuple() {
1757        let pure = PureFile::from_source("fn f(x: (i32, i32)) {}").unwrap();
1758        let f = &pure.functions()[0];
1759        if let PureParam::Typed {
1760            ty: PureType::Tuple(elems),
1761            ..
1762        } = &f.params[0]
1763        {
1764            assert_eq!(elems.len(), 2);
1765        } else {
1766            panic!("Expected Tuple type");
1767        }
1768    }
1769
1770    #[test]
1771    fn test_type_array() {
1772        let pure = PureFile::from_source("fn f(x: [i32; 10]) {}").unwrap();
1773        let f = &pure.functions()[0];
1774        if let PureParam::Typed {
1775            ty: PureType::Array { len, .. },
1776            ..
1777        } = &f.params[0]
1778        {
1779            assert_eq!(len, "10");
1780        } else {
1781            panic!("Expected Array type");
1782        }
1783    }
1784
1785    #[test]
1786    fn test_type_slice() {
1787        let pure = PureFile::from_source("fn f(x: &[i32]) {}").unwrap();
1788        let f = &pure.functions()[0];
1789        if let PureParam::Typed {
1790            ty: PureType::Ref { ty, .. },
1791            ..
1792        } = &f.params[0]
1793        {
1794            if let PureType::Slice(_) = ty.as_ref() {
1795                // ok
1796            } else {
1797                panic!("Expected Slice type");
1798            }
1799        } else {
1800            panic!("Expected Ref to Slice type");
1801        }
1802    }
1803
1804    #[test]
1805    fn test_type_fn() {
1806        let pure = PureFile::from_source("fn f(x: fn(i32) -> i32) {}").unwrap();
1807        let f = &pure.functions()[0];
1808        if let PureParam::Typed {
1809            ty: PureType::Fn { params, ret },
1810            ..
1811        } = &f.params[0]
1812        {
1813            assert_eq!(params.len(), 1);
1814            assert!(ret.is_some());
1815        } else {
1816            panic!("Expected Fn type");
1817        }
1818    }
1819
1820    #[test]
1821    fn test_type_impl_trait() {
1822        let pure =
1823            PureFile::from_source("fn f() -> impl Iterator<Item = i32> { todo!() }").unwrap();
1824        let f = &pure.functions()[0];
1825        if let Some(PureType::ImplTrait(bounds)) = &f.ret {
1826            assert!(!bounds.is_empty());
1827        } else {
1828            panic!("Expected ImplTrait type");
1829        }
1830    }
1831
1832    #[test]
1833    fn test_type_never() {
1834        let pure = PureFile::from_source("fn f() -> ! { panic!() }").unwrap();
1835        let f = &pure.functions()[0];
1836        if let Some(PureType::Never) = &f.ret {
1837            // ok
1838        } else {
1839            panic!("Expected Never type");
1840        }
1841    }
1842
1843    // ========== Generics Tests ==========
1844
1845    #[test]
1846    fn test_generics_type_param() {
1847        let pure = PureFile::from_source("fn f<T>(x: T) {}").unwrap();
1848        let f = &pure.functions()[0];
1849        assert_eq!(f.generics.params.len(), 1);
1850        if let PureGenericParam::Type { name, .. } = &f.generics.params[0] {
1851            assert_eq!(name, "T");
1852        } else {
1853            panic!("Expected Type param");
1854        }
1855    }
1856
1857    #[test]
1858    fn test_generics_type_param_with_bound() {
1859        let pure = PureFile::from_source("fn f<T: Clone>(x: T) {}").unwrap();
1860        let f = &pure.functions()[0];
1861        if let PureGenericParam::Type { bounds, .. } = &f.generics.params[0] {
1862            assert!(!bounds.is_empty());
1863            assert!(bounds[0].contains("Clone"));
1864        } else {
1865            panic!("Expected Type param with bound");
1866        }
1867    }
1868
1869    #[test]
1870    fn test_generics_lifetime_param() {
1871        let pure = PureFile::from_source("fn f<'a>(x: &'a str) {}").unwrap();
1872        let f = &pure.functions()[0];
1873        if let PureGenericParam::Lifetime { name, .. } = &f.generics.params[0] {
1874            assert!(name.contains("'a"));
1875        } else {
1876            panic!("Expected Lifetime param");
1877        }
1878    }
1879
1880    #[test]
1881    fn test_generics_const_param() {
1882        let pure = PureFile::from_source("fn f<const N: usize>() {}").unwrap();
1883        let f = &pure.functions()[0];
1884        if let PureGenericParam::Const { name, ty } = &f.generics.params[0] {
1885            assert_eq!(name, "N");
1886            assert!(ty.contains("usize"));
1887        } else {
1888            panic!("Expected Const param");
1889        }
1890    }
1891
1892    #[test]
1893    fn test_generics_where_clause() {
1894        let pure = PureFile::from_source("fn f<T>(x: T) where T: Clone {}").unwrap();
1895        let f = &pure.functions()[0];
1896        assert!(!f.generics.where_clause.is_empty());
1897        assert!(f.generics.where_clause[0].contains("Clone"));
1898    }
1899
1900    // ========== Function Tests ==========
1901
1902    #[test]
1903    fn test_fn_async() {
1904        let pure = PureFile::from_source("async fn f() {}").unwrap();
1905        assert!(pure.functions()[0].is_async);
1906    }
1907
1908    #[test]
1909    fn test_fn_const() {
1910        let pure = PureFile::from_source("const fn f() {}").unwrap();
1911        assert!(pure.functions()[0].is_const);
1912    }
1913
1914    #[test]
1915    fn test_fn_unsafe() {
1916        let pure = PureFile::from_source("unsafe fn f() {}").unwrap();
1917        assert!(pure.functions()[0].is_unsafe);
1918    }
1919
1920    #[test]
1921    fn test_fn_self_param() {
1922        let pure = PureFile::from_source("impl Foo { fn f(&self) {} }").unwrap();
1923        let impls = pure.impls();
1924        if let PureImplItem::Fn(f) = &impls[0].items[0] {
1925            if let PureParam::SelfValue { is_ref, is_mut } = &f.params[0] {
1926                assert!(is_ref);
1927                assert!(!is_mut);
1928            } else {
1929                panic!("Expected SelfValue param");
1930            }
1931        } else {
1932            panic!("Expected Fn item");
1933        }
1934    }
1935
1936    #[test]
1937    fn test_fn_self_mut_param() {
1938        let pure = PureFile::from_source("impl Foo { fn f(&mut self) {} }").unwrap();
1939        let impls = pure.impls();
1940        if let PureImplItem::Fn(f) = &impls[0].items[0] {
1941            if let PureParam::SelfValue { is_ref, is_mut } = &f.params[0] {
1942                assert!(is_ref);
1943                assert!(is_mut);
1944            } else {
1945                panic!("Expected mut SelfValue param");
1946            }
1947        } else {
1948            panic!("Expected Fn item");
1949        }
1950    }
1951
1952    // ========== Impl Tests ==========
1953
1954    #[test]
1955    fn test_impl_inherent() {
1956        let pure = PureFile::from_source("impl Foo { fn new() -> Self { Foo } }").unwrap();
1957        let impls = pure.impls();
1958        assert_eq!(impls.len(), 1);
1959        assert!(impls[0].trait_.is_none());
1960        assert_eq!(impls[0].self_ty, "Foo");
1961    }
1962
1963    #[test]
1964    fn test_impl_trait() {
1965        let pure = PureFile::from_source("impl Clone for Foo { fn clone(&self) -> Self { Foo } }")
1966            .unwrap();
1967        let impls = pure.impls();
1968        assert_eq!(impls[0].trait_.as_ref().unwrap(), "Clone");
1969    }
1970
1971    #[test]
1972    fn test_impl_unsafe() {
1973        let pure = PureFile::from_source("unsafe impl Send for Foo {}").unwrap();
1974        let impls = pure.impls();
1975        assert!(impls[0].is_unsafe);
1976    }
1977
1978    #[test]
1979    fn test_impl_const() {
1980        let pure = PureFile::from_source("impl Foo { const X: i32 = 42; }").unwrap();
1981        let impls = pure.impls();
1982        if let PureImplItem::Const(c) = &impls[0].items[0] {
1983            assert_eq!(c.name, "X");
1984        } else {
1985            panic!("Expected Const item");
1986        }
1987    }
1988
1989    #[test]
1990    fn test_impl_type() {
1991        let pure = PureFile::from_source(
1992            "impl Iterator for Foo { type Item = i32; fn next(&mut self) -> Option<i32> { None } }",
1993        )
1994        .unwrap();
1995        let impls = pure.impls();
1996        if let PureImplItem::Type(t) = &impls[0].items[0] {
1997            assert_eq!(t.name, "Item");
1998        } else {
1999            panic!("Expected Type item");
2000        }
2001    }
2002
2003    // ========== Trait Tests ==========
2004
2005    #[test]
2006    fn test_trait_simple() {
2007        let pure = PureFile::from_source("trait Foo {}").unwrap();
2008        let traits = pure.traits();
2009        assert_eq!(traits.len(), 1);
2010        assert_eq!(traits[0].name, "Foo");
2011    }
2012
2013    #[test]
2014    fn test_trait_unsafe() {
2015        let pure = PureFile::from_source("unsafe trait Foo {}").unwrap();
2016        assert!(pure.traits()[0].is_unsafe);
2017    }
2018
2019    #[test]
2020    fn test_trait_with_supertraits() {
2021        let pure = PureFile::from_source("trait Foo: Clone + Send {}").unwrap();
2022        let traits = pure.traits();
2023        assert_eq!(traits[0].supertraits.len(), 2);
2024    }
2025
2026    #[test]
2027    fn test_trait_with_method() {
2028        let pure = PureFile::from_source("trait Foo { fn bar(&self); }").unwrap();
2029        let traits = pure.traits();
2030        if let PureTraitItem::Fn(f) = &traits[0].items[0] {
2031            assert_eq!(f.name, "bar");
2032        } else {
2033            panic!("Expected Fn item");
2034        }
2035    }
2036
2037    #[test]
2038    fn test_trait_with_default_method() {
2039        let pure = PureFile::from_source("trait Foo { fn bar(&self) { } }").unwrap();
2040        let traits = pure.traits();
2041        if let PureTraitItem::Fn(f) = &traits[0].items[0] {
2042            assert!(!f.body.stmts.is_empty() || f.body.stmts.is_empty()); // has body
2043        } else {
2044            panic!("Expected Fn item");
2045        }
2046    }
2047
2048    #[test]
2049    fn test_trait_with_associated_type() {
2050        let pure = PureFile::from_source("trait Foo { type Item; }").unwrap();
2051        let traits = pure.traits();
2052        if let PureTraitItem::Type { name, .. } = &traits[0].items[0] {
2053            assert_eq!(name, "Item");
2054        } else {
2055            panic!("Expected Type item");
2056        }
2057    }
2058
2059    #[test]
2060    fn test_trait_with_associated_const() {
2061        let pure = PureFile::from_source("trait Foo { const X: i32; }").unwrap();
2062        let traits = pure.traits();
2063        if let PureTraitItem::Const(c) = &traits[0].items[0] {
2064            assert_eq!(c.name, "X");
2065        } else {
2066            panic!("Expected Const item");
2067        }
2068    }
2069
2070    // ========== Enum Tests ==========
2071
2072    #[test]
2073    fn test_enum_simple() {
2074        let pure = PureFile::from_source("enum Color { Red, Green, Blue }").unwrap();
2075        let enums: Vec<_> = pure
2076            .items
2077            .iter()
2078            .filter_map(|i| {
2079                if let PureItem::Enum(e) = i {
2080                    Some(e)
2081                } else {
2082                    None
2083                }
2084            })
2085            .collect();
2086        assert_eq!(enums.len(), 1);
2087        assert_eq!(enums[0].variants.len(), 3);
2088    }
2089
2090    #[test]
2091    fn test_enum_with_discriminant() {
2092        let pure = PureFile::from_source("enum Num { One = 1, Two = 2 }").unwrap();
2093        if let PureItem::Enum(e) = &pure.items[0] {
2094            assert!(e.variants[0].discriminant.is_some());
2095        } else {
2096            panic!("Expected Enum item");
2097        }
2098    }
2099
2100    #[test]
2101    fn test_enum_tuple_variant() {
2102        let pure = PureFile::from_source("enum Opt { Some(i32), None }").unwrap();
2103        if let PureItem::Enum(e) = &pure.items[0] {
2104            if let PureFields::Tuple(types) = &e.variants[0].fields {
2105                assert_eq!(types.len(), 1);
2106            } else {
2107                panic!("Expected Tuple fields");
2108            }
2109        } else {
2110            panic!("Expected Enum item");
2111        }
2112    }
2113
2114    #[test]
2115    fn test_enum_struct_variant() {
2116        let pure = PureFile::from_source("enum Msg { Move { x: i32, y: i32 } }").unwrap();
2117        if let PureItem::Enum(e) = &pure.items[0] {
2118            if let PureFields::Named(fields) = &e.variants[0].fields {
2119                assert_eq!(fields.len(), 2);
2120            } else {
2121                panic!("Expected Named fields");
2122            }
2123        } else {
2124            panic!("Expected Enum item");
2125        }
2126    }
2127
2128    // ========== Struct Tests ==========
2129
2130    #[test]
2131    fn test_struct_unit() {
2132        let pure = PureFile::from_source("struct Unit;").unwrap();
2133        let structs = pure.structs();
2134        assert!(matches!(structs[0].fields, PureFields::Unit));
2135    }
2136
2137    #[test]
2138    fn test_struct_tuple() {
2139        let pure = PureFile::from_source("struct Point(i32, i32);").unwrap();
2140        let structs = pure.structs();
2141        if let PureFields::Tuple(types) = &structs[0].fields {
2142            assert_eq!(types.len(), 2);
2143        } else {
2144            panic!("Expected Tuple fields");
2145        }
2146    }
2147
2148    // ========== Visibility Tests ==========
2149
2150    #[test]
2151    fn test_vis_public() {
2152        let pure = PureFile::from_source("pub fn f() {}").unwrap();
2153        assert!(matches!(pure.functions()[0].vis, PureVis::Public));
2154    }
2155
2156    #[test]
2157    fn test_vis_private() {
2158        let pure = PureFile::from_source("fn f() {}").unwrap();
2159        assert!(matches!(pure.functions()[0].vis, PureVis::Private));
2160    }
2161
2162    #[test]
2163    fn test_vis_crate() {
2164        let pure = PureFile::from_source("pub(crate) fn f() {}").unwrap();
2165        assert!(matches!(pure.functions()[0].vis, PureVis::Crate));
2166    }
2167
2168    #[test]
2169    fn test_vis_super() {
2170        let pure = PureFile::from_source("pub(super) fn f() {}").unwrap();
2171        assert!(matches!(pure.functions()[0].vis, PureVis::Super));
2172    }
2173
2174    #[test]
2175    fn test_vis_in_path() {
2176        let pure = PureFile::from_source("pub(in crate::foo) fn f() {}").unwrap();
2177        if let PureVis::In(path) = &pure.functions()[0].vis {
2178            assert!(path.contains("crate"));
2179        } else {
2180            panic!("Expected In visibility");
2181        }
2182    }
2183
2184    // ========== Attribute Tests ==========
2185
2186    #[test]
2187    fn test_attr_derive() {
2188        let pure = PureFile::from_source("#[derive(Clone, Debug)] struct Foo;").unwrap();
2189        let attrs = &pure.structs()[0].attrs;
2190        assert_eq!(attrs.len(), 1);
2191        assert_eq!(attrs[0].path, "derive");
2192    }
2193
2194    #[test]
2195    fn test_attr_simple() {
2196        let pure = PureFile::from_source("#[test] fn f() {}").unwrap();
2197        let attrs = &pure.functions()[0].attrs;
2198        assert_eq!(attrs.len(), 1);
2199        assert_eq!(attrs[0].path, "test");
2200    }
2201
2202    #[test]
2203    fn test_attr_inner() {
2204        let pure = PureFile::from_source("#![allow(unused)]").unwrap();
2205        assert!(pure.attrs[0].is_inner);
2206    }
2207
2208    #[test]
2209    fn test_attr_outer() {
2210        let pure = PureFile::from_source("#[allow(unused)] fn f() {}").unwrap();
2211        assert!(!pure.functions()[0].attrs[0].is_inner);
2212    }
2213
2214    // ========== Use Tests ==========
2215
2216    #[test]
2217    fn test_use_path() {
2218        let pure = PureFile::from_source("use std::io::Read;").unwrap();
2219        let uses = pure.uses();
2220        // Navigate the tree: std -> io -> Read
2221        if let PureUseTree::Path { path, tree } = &uses[0].tree {
2222            assert_eq!(path, "std");
2223            if let PureUseTree::Path { path, tree } = tree.as_ref() {
2224                assert_eq!(path, "io");
2225                if let PureUseTree::Name(name) = tree.as_ref() {
2226                    assert_eq!(name, "Read");
2227                } else {
2228                    panic!("Expected Name");
2229                }
2230            } else {
2231                panic!("Expected Path");
2232            }
2233        } else {
2234            panic!("Expected Path");
2235        }
2236    }
2237
2238    #[test]
2239    fn test_use_glob() {
2240        let pure = PureFile::from_source("use std::io::*;").unwrap();
2241        let uses = pure.uses();
2242        if let PureUseTree::Path { tree, .. } = &uses[0].tree {
2243            if let PureUseTree::Path { tree, .. } = tree.as_ref() {
2244                assert!(matches!(tree.as_ref(), PureUseTree::Glob));
2245            } else {
2246                panic!("Expected Path");
2247            }
2248        } else {
2249            panic!("Expected Path");
2250        }
2251    }
2252
2253    #[test]
2254    fn test_use_group() {
2255        let pure = PureFile::from_source("use std::{io, fs};").unwrap();
2256        let uses = pure.uses();
2257        if let PureUseTree::Path { tree, .. } = &uses[0].tree {
2258            if let PureUseTree::Group(items) = tree.as_ref() {
2259                assert_eq!(items.len(), 2);
2260            } else {
2261                panic!("Expected Group");
2262            }
2263        } else {
2264            panic!("Expected Path");
2265        }
2266    }
2267
2268    #[test]
2269    fn test_use_rename() {
2270        let pure = PureFile::from_source("use std::io::Result as IoResult;").unwrap();
2271        let uses = pure.uses();
2272        // Navigate to the rename
2273        if let PureUseTree::Path { tree, .. } = &uses[0].tree {
2274            if let PureUseTree::Path { tree, .. } = tree.as_ref() {
2275                if let PureUseTree::Rename { name, rename } = tree.as_ref() {
2276                    assert_eq!(name, "Result");
2277                    assert_eq!(rename, "IoResult");
2278                } else {
2279                    panic!("Expected Rename");
2280                }
2281            } else {
2282                panic!("Expected Path");
2283            }
2284        } else {
2285            panic!("Expected Path");
2286        }
2287    }
2288
2289    // ========== Const/Static Tests ==========
2290
2291    #[test]
2292    fn test_const_item() {
2293        let pure = PureFile::from_source("const MAX: i32 = 100;").unwrap();
2294        if let PureItem::Const(c) = &pure.items[0] {
2295            assert_eq!(c.name, "MAX");
2296        } else {
2297            panic!("Expected Const item");
2298        }
2299    }
2300
2301    #[test]
2302    fn test_static_item() {
2303        let pure = PureFile::from_source("static mut COUNTER: i32 = 0;").unwrap();
2304        if let PureItem::Static(s) = &pure.items[0] {
2305            assert!(s.is_mut);
2306        } else {
2307            panic!("Expected Static item");
2308        }
2309    }
2310
2311    // ========== Type Alias Tests ==========
2312
2313    #[test]
2314    fn test_type_alias() {
2315        let pure =
2316            PureFile::from_source("type Result<T> = std::result::Result<T, Error>;").unwrap();
2317        if let PureItem::Type(t) = &pure.items[0] {
2318            assert_eq!(t.name, "Result");
2319        } else {
2320            panic!("Expected Type item");
2321        }
2322    }
2323
2324    // ========== Module Tests ==========
2325
2326    #[test]
2327    fn test_mod_declaration() {
2328        let pure = PureFile::from_source("mod foo;").unwrap();
2329        if let PureItem::Mod(m) = &pure.items[0] {
2330            assert!(m.items.is_empty());
2331        } else {
2332            panic!("Expected Mod item");
2333        }
2334    }
2335
2336    #[test]
2337    fn test_mod_inline() {
2338        let pure = PureFile::from_source("mod foo { fn bar() {} }").unwrap();
2339        if let PureItem::Mod(m) = &pure.items[0] {
2340            assert!(!m.items.is_empty());
2341            assert_eq!(m.items.len(), 1);
2342        } else {
2343            panic!("Expected Mod item");
2344        }
2345    }
2346
2347    // ========== Macro Tests ==========
2348
2349    #[test]
2350    fn test_macro_invocation() {
2351        let pure = PureFile::from_source("include!(\"foo.rs\");").unwrap();
2352        if let PureItem::Macro(m) = &pure.items[0] {
2353            assert_eq!(m.path, "include");
2354        } else {
2355            panic!("Expected Macro item");
2356        }
2357    }
2358
2359    // ========== Match Arm Tests ==========
2360
2361    #[test]
2362    fn test_match_arm_guard() {
2363        let pure =
2364            PureFile::from_source("fn f(x: i32) { match x { n if n > 0 => {} _ => {} } }").unwrap();
2365        let f = &pure.functions()[0];
2366        if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
2367            assert!(arms[0].guard.is_some());
2368        } else {
2369            panic!("Expected Match expr");
2370        }
2371    }
2372
2373    // ========== Helper Function Tests ==========
2374
2375    #[test]
2376    fn test_path_to_string_simple() {
2377        assert_eq!(
2378            path_to_string(&syn::parse_str::<syn::Path>("std").unwrap()),
2379            "std"
2380        );
2381    }
2382
2383    #[test]
2384    fn test_path_to_string_nested() {
2385        assert_eq!(
2386            path_to_string(&syn::parse_str::<syn::Path>("std::io::Read").unwrap()),
2387            "std::io::Read"
2388        );
2389    }
2390
2391    #[test]
2392    fn test_path_to_string_with_generics() {
2393        let path = syn::parse_str::<syn::Path>("Vec<i32>").unwrap();
2394        let result = path_to_string(&path);
2395        assert!(result.contains("Vec"));
2396        assert!(result.contains("i32"));
2397    }
2398
2399    // ========== Async Inference Tests ==========
2400
2401    #[test]
2402    fn test_is_pinned_boxed_future_basic() {
2403        assert!(is_pinned_boxed_future("Pin<Box<dyn Future<Output = ()>>>"));
2404        assert!(is_pinned_boxed_future(
2405            "Pin<Box<dyn Future<Output = Result<T, E>>>>"
2406        ));
2407        assert!(is_pinned_boxed_future(
2408            "Pin<Box<dyn Future<Output = ()> + Send>>"
2409        ));
2410        assert!(is_pinned_boxed_future(
2411            "Pin<Box<dyn Future<Output = ()> + Send + 'static>>"
2412        ));
2413    }
2414
2415    #[test]
2416    fn test_is_pinned_boxed_future_with_spaces() {
2417        // Token stream adds spaces between tokens
2418        assert!(is_pinned_boxed_future(
2419            "Pin < Box < dyn Future < Output = () > > >"
2420        ));
2421        assert!(is_pinned_boxed_future(
2422            "Pin < Box < dyn Future < Output = Result < T , E > > + Send > >"
2423        ));
2424    }
2425
2426    #[test]
2427    fn test_is_pinned_boxed_future_with_path_prefix() {
2428        assert!(is_pinned_boxed_future(
2429            "::core::pin::Pin<Box<dyn Future<Output = ()>>>"
2430        ));
2431        assert!(is_pinned_boxed_future(
2432            "core::pin::Pin<Box<dyn Future<Output = ()>>>"
2433        ));
2434        assert!(is_pinned_boxed_future(
2435            "std::pin::Pin<Box<dyn Future<Output = ()>>>"
2436        ));
2437        assert!(is_pinned_boxed_future(
2438            "::std::pin::Pin<Box<dyn Future<Output = ()>>>"
2439        ));
2440    }
2441
2442    #[test]
2443    fn test_is_pinned_boxed_future_negative() {
2444        assert!(!is_pinned_boxed_future("Result<T, E>"));
2445        assert!(!is_pinned_boxed_future("Option<T>"));
2446        assert!(!is_pinned_boxed_future("Box<dyn Future<Output = ()>>"));
2447        assert!(!is_pinned_boxed_future("Pin<Box<T>>"));
2448        assert!(!is_pinned_boxed_future("Future<Output = ()>"));
2449    }
2450
2451    #[test]
2452    fn test_async_fn_is_async() {
2453        let pure = PureFile::from_source("async fn foo() {}").unwrap();
2454        let f = &pure.functions()[0];
2455        assert!(f.is_async);
2456        assert!(!f.is_async_inferred);
2457        assert!(f.is_effectively_async());
2458    }
2459
2460    #[test]
2461    fn test_sync_fn_not_async() {
2462        let pure = PureFile::from_source("fn foo() {}").unwrap();
2463        let f = &pure.functions()[0];
2464        assert!(!f.is_async);
2465        assert!(!f.is_async_inferred);
2466        assert!(!f.is_effectively_async());
2467    }
2468
2469    #[test]
2470    fn test_async_trait_style_fn_inferred() {
2471        // Simulates what #[async_trait] produces after macro expansion
2472        let code = r#"
2473            fn foo(&self) -> Pin<Box<dyn Future<Output = ()> + Send>> {
2474                Box::pin(async { })
2475            }
2476        "#;
2477        let pure = PureFile::from_source(code).unwrap();
2478        let f = &pure.functions()[0];
2479        assert!(!f.is_async, "Should not be explicitly async");
2480        assert!(
2481            f.is_async_inferred,
2482            "Should be inferred as async from return type"
2483        );
2484        assert!(f.is_effectively_async());
2485    }
2486
2487    #[test]
2488    fn test_async_trait_in_impl() {
2489        let code = r#"
2490            struct Foo;
2491            impl Foo {
2492                fn bar(&self) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send>> {
2493                    Box::pin(async { Ok(()) })
2494                }
2495            }
2496        "#;
2497        let pure = PureFile::from_source(code).unwrap();
2498        let impls = pure.impls();
2499        assert_eq!(impls.len(), 1);
2500
2501        if let PureImplItem::Fn(method) = &impls[0].items[0] {
2502            assert!(!method.is_async);
2503            assert!(method.is_async_inferred);
2504            assert!(method.is_effectively_async());
2505        } else {
2506            panic!("Expected Fn in impl");
2507        }
2508    }
2509}