1use quote::ToTokens;
4
5use super::ast::*;
6use crate::SourceResult;
7
8pub trait ToPure {
10 type Output;
12 fn to_pure(&self) -> Self::Output;
14}
15
16fn 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()) }
23
24fn label_to_string(label: &syn::Label) -> String {
26 label.name.ident.to_string()
27}
28
29fn 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 let ty_str = ty.to_token_stream().to_string();
45
46 is_pinned_boxed_future(&ty_str)
50}
51
52fn is_pinned_boxed_future(ty_str: &str) -> bool {
54 let normalized: String = ty_str.chars().filter(|c| !c.is_whitespace()).collect();
56
57 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 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 PureAttrMeta::List(list.tokens.to_string())
97 }
98 syn::Meta::NameValue(nv) => {
99 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) => {
222 let pat = match &*t.pat {
227 syn::Pat::Ident(_) => None,
228 other => Some(other.to_pure()),
229 };
230 PureParam::Typed {
231 name: pat_to_name(&t.pat),
232 ty: t.ty.to_pure(),
233 is_mut: matches!(
234 &*t.pat,
235 syn::Pat::Ident(syn::PatIdent {
236 mutability: Some(_),
237 ..
238 })
239 ),
240 pat,
241 }
242 }
243 }
244 }
245}
246
247impl ToPure for syn::Generics {
248 type Output = PureGenerics;
249
250 fn to_pure(&self) -> PureGenerics {
251 PureGenerics {
252 params: self.params.iter().map(|p| p.to_pure()).collect(),
253 where_clause: self
254 .where_clause
255 .as_ref()
256 .map(|w| {
257 w.predicates
258 .iter()
259 .map(|p| p.to_token_stream().to_string())
260 .collect()
261 })
262 .unwrap_or_default(),
263 }
264 }
265}
266
267impl ToPure for syn::GenericParam {
268 type Output = PureGenericParam;
269
270 fn to_pure(&self) -> PureGenericParam {
271 match self {
272 syn::GenericParam::Type(t) => PureGenericParam::Type {
273 name: t.ident.to_string(),
274 bounds: t
275 .bounds
276 .iter()
277 .map(|b| b.to_token_stream().to_string())
278 .collect(),
279 },
280 syn::GenericParam::Lifetime(l) => PureGenericParam::Lifetime {
281 name: l.lifetime.to_string(),
282 bounds: l.bounds.iter().map(|b| b.to_string()).collect(),
283 },
284 syn::GenericParam::Const(c) => PureGenericParam::Const {
285 name: c.ident.to_string(),
286 ty: c.ty.to_token_stream().to_string(),
287 },
288 }
289 }
290}
291
292impl ToPure for syn::Block {
293 type Output = PureBlock;
294
295 fn to_pure(&self) -> PureBlock {
296 PureBlock {
297 stmts: self.stmts.iter().map(|s| s.to_pure()).collect(),
298 }
299 }
300}
301
302impl ToPure for syn::Stmt {
303 type Output = PureStmt;
304
305 fn to_pure(&self) -> PureStmt {
306 match self {
307 syn::Stmt::Local(l) => {
308 let (pattern, ty) = match &l.pat {
310 syn::Pat::Type(pt) => (pt.pat.to_pure(), Some(pt.ty.to_pure())),
311 other => (other.to_pure(), None),
312 };
313 PureStmt::Local {
314 pattern,
315 ty,
316 init: l.init.as_ref().map(|i| i.expr.to_pure()),
317 else_branch: l
318 .init
319 .as_ref()
320 .and_then(|i| i.diverge.as_ref())
321 .map(|(_else_kw, expr)| Box::new(expr.to_pure())),
322 }
323 }
324 syn::Stmt::Item(i) => PureStmt::Item(Box::new(i.to_pure())),
325 syn::Stmt::Expr(e, semi) => {
326 if semi.is_some() {
327 PureStmt::Semi(e.to_pure())
328 } else {
329 PureStmt::Expr(e.to_pure())
330 }
331 }
332 syn::Stmt::Macro(m) => PureStmt::Semi(PureExpr::Macro {
333 name: path_to_string(&m.mac.path),
334 delimiter: match m.mac.delimiter {
335 syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
336 syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
337 syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
338 },
339 tokens: m.mac.tokens.to_string(),
340 }),
341 }
342 }
343}
344
345impl ToPure for syn::Pat {
346 type Output = PurePattern;
347
348 fn to_pure(&self) -> PurePattern {
349 match self {
350 syn::Pat::Ident(i) => PurePattern::Ident {
351 name: i.ident.to_string(),
352 is_mut: i.mutability.is_some(),
353 by_ref: i.by_ref.is_some(),
354 },
355 syn::Pat::Wild(_) => PurePattern::Wild,
356 syn::Pat::Tuple(t) => PurePattern::Tuple(t.elems.iter().map(|p| p.to_pure()).collect()),
357 syn::Pat::TupleStruct(ts) => PurePattern::Struct {
358 path: path_to_string(&ts.path),
359 fields: ts
360 .elems
361 .iter()
362 .enumerate()
363 .map(|(i, p)| (i.to_string(), p.to_pure()))
364 .collect(),
365 rest: false,
366 },
367 syn::Pat::Struct(s) => PurePattern::Struct {
368 path: path_to_string(&s.path),
369 fields: s
370 .fields
371 .iter()
372 .map(|f| {
373 let name = f.member.to_token_stream().to_string();
374 (name, f.pat.to_pure())
375 })
376 .collect(),
377 rest: s.rest.is_some(),
378 },
379 syn::Pat::Reference(r) => PurePattern::Ref {
380 is_mut: r.mutability.is_some(),
381 pattern: Box::new(r.pat.to_pure()),
382 },
383 syn::Pat::Lit(l) => PurePattern::Lit(l.lit.to_token_stream().to_string()),
384 syn::Pat::Or(o) => PurePattern::Or(o.cases.iter().map(|p| p.to_pure()).collect()),
385 syn::Pat::Type(t) => t.pat.to_pure(),
386 syn::Pat::Path(p) => PurePattern::Path(path_to_string(&p.path)),
387 syn::Pat::Range(r) => PurePattern::Range {
388 start: r.start.as_ref().map(|e| e.to_token_stream().to_string()),
389 end: r.end.as_ref().map(|e| e.to_token_stream().to_string()),
390 inclusive: matches!(r.limits, syn::RangeLimits::Closed(_)),
391 },
392 syn::Pat::Slice(s) => PurePattern::Slice(s.elems.iter().map(|p| p.to_pure()).collect()),
393 syn::Pat::Rest(_) => PurePattern::Rest,
394 syn::Pat::Paren(p) => p.pat.to_pure(),
395 other => PurePattern::Other(other.to_token_stream().to_string()),
396 }
397 }
398}
399
400impl ToPure for syn::Expr {
401 type Output = PureExpr;
402
403 fn to_pure(&self) -> PureExpr {
404 match self {
405 syn::Expr::Lit(l) => PureExpr::Lit(l.lit.to_token_stream().to_string()),
406 syn::Expr::Path(p) => PureExpr::Path(path_to_string(&p.path)),
407 syn::Expr::Binary(b) => PureExpr::Binary {
408 op: b.op.to_token_stream().to_string(),
409 left: Box::new(b.left.to_pure()),
410 right: Box::new(b.right.to_pure()),
411 },
412 syn::Expr::Unary(u) => PureExpr::Unary {
413 op: u.op.to_token_stream().to_string(),
414 expr: Box::new(u.expr.to_pure()),
415 },
416 syn::Expr::Call(c) => PureExpr::Call {
417 func: Box::new(c.func.to_pure()),
418 args: c.args.iter().map(|a| a.to_pure()).collect(),
419 },
420 syn::Expr::MethodCall(m) => PureExpr::MethodCall {
421 receiver: Box::new(m.receiver.to_pure()),
422 method: m.method.to_string(),
423 turbofish: m
424 .turbofish
425 .as_ref()
426 .map(|t| t.args.to_token_stream().to_string()),
427 args: m.args.iter().map(|a| a.to_pure()).collect(),
428 },
429 syn::Expr::Field(f) => PureExpr::Field {
430 expr: Box::new(f.base.to_pure()),
431 field: f.member.to_token_stream().to_string(),
432 },
433 syn::Expr::Index(i) => PureExpr::Index {
434 expr: Box::new(i.expr.to_pure()),
435 index: Box::new(i.index.to_pure()),
436 },
437 syn::Expr::Block(b) => PureExpr::Block {
438 label: b.label.as_ref().map(label_to_string),
439 block: b.block.to_pure(),
440 },
441 syn::Expr::If(i) => PureExpr::If {
442 cond: Box::new(i.cond.to_pure()),
443 then_branch: i.then_branch.to_pure(),
444 else_branch: i.else_branch.as_ref().map(|(_, e)| Box::new(e.to_pure())),
445 },
446 syn::Expr::Match(m) => PureExpr::Match {
447 expr: Box::new(m.expr.to_pure()),
448 arms: m.arms.iter().map(|a| a.to_pure()).collect(),
449 },
450 syn::Expr::Loop(l) => PureExpr::Loop {
451 label: l.label.as_ref().map(label_to_string),
452 body: l.body.to_pure(),
453 },
454 syn::Expr::While(w) => PureExpr::While {
455 label: w.label.as_ref().map(label_to_string),
456 cond: Box::new(w.cond.to_pure()),
457 body: w.body.to_pure(),
458 },
459 syn::Expr::ForLoop(f) => PureExpr::For {
460 label: f.label.as_ref().map(label_to_string),
461 pat: f.pat.to_pure(),
462 expr: Box::new(f.expr.to_pure()),
463 body: f.body.to_pure(),
464 },
465 syn::Expr::Return(r) => {
466 PureExpr::Return(r.expr.as_ref().map(|e| Box::new(e.to_pure())))
467 }
468 syn::Expr::Break(b) => PureExpr::Break {
469 label: b.label.as_ref().map(|l| l.ident.to_string()),
470 expr: b.expr.as_ref().map(|e| Box::new(e.to_pure())),
471 },
472 syn::Expr::Continue(c) => PureExpr::Continue {
473 label: c.label.as_ref().map(|l| l.ident.to_string()),
474 },
475 syn::Expr::Closure(c) => PureExpr::Closure {
476 is_async: c.asyncness.is_some(),
477 is_move: c.capture.is_some(),
478 params: c
479 .inputs
480 .iter()
481 .map(|p| match p {
482 syn::Pat::Type(pt) => {
483 PureClosureParam::typed(pt.pat.to_pure(), pt.ty.to_pure())
484 }
485 other => PureClosureParam::untyped(other.to_pure()),
486 })
487 .collect(),
488 ret: match &c.output {
489 syn::ReturnType::Default => None,
490 syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
491 },
492 body: Box::new(c.body.to_pure()),
493 },
494 syn::Expr::Struct(s) => PureExpr::Struct {
495 path: path_to_string(&s.path),
496 fields: s
497 .fields
498 .iter()
499 .map(|f| {
500 let name = f.member.to_token_stream().to_string();
501 (name, f.expr.to_pure())
502 })
503 .collect(),
504 rest: s.rest.as_ref().map(|e| Box::new(e.to_pure())),
505 },
506 syn::Expr::Tuple(t) => PureExpr::Tuple(t.elems.iter().map(|e| e.to_pure()).collect()),
507 syn::Expr::Array(a) => PureExpr::Array(a.elems.iter().map(|e| e.to_pure()).collect()),
508 syn::Expr::Reference(r) => PureExpr::Ref {
509 is_mut: r.mutability.is_some(),
510 expr: Box::new(r.expr.to_pure()),
511 },
512 syn::Expr::Macro(m) => PureExpr::Macro {
513 name: path_to_string(&m.mac.path),
514 delimiter: match m.mac.delimiter {
515 syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
516 syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
517 syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
518 },
519 tokens: m.mac.tokens.to_string(),
520 },
521 syn::Expr::Await(a) => PureExpr::Await(Box::new(a.base.to_pure())),
522 syn::Expr::Try(t) => PureExpr::Try(Box::new(t.expr.to_pure())),
523 syn::Expr::Paren(p) => p.expr.to_pure(),
524 syn::Expr::Assign(a) => PureExpr::Binary {
526 op: "=".to_string(),
527 left: Box::new(a.left.to_pure()),
528 right: Box::new(a.right.to_pure()),
529 },
530 syn::Expr::Range(r) => PureExpr::Range {
532 start: r.start.as_ref().map(|e| Box::new(e.to_pure())),
533 end: r.end.as_ref().map(|e| Box::new(e.to_pure())),
534 inclusive: matches!(r.limits, syn::RangeLimits::Closed(_)),
535 },
536 syn::Expr::Cast(c) => PureExpr::Cast {
538 expr: Box::new(c.expr.to_pure()),
539 ty: c.ty.to_pure(),
540 },
541 syn::Expr::Let(l) => PureExpr::Let {
543 pattern: l.pat.to_pure(),
544 expr: Box::new(l.expr.to_pure()),
545 },
546 syn::Expr::Async(a) => PureExpr::Async {
548 is_move: a.capture.is_some(),
549 body: a.block.to_pure(),
550 },
551 syn::Expr::Unsafe(u) => PureExpr::Unsafe(u.block.to_pure()),
553 syn::Expr::Repeat(r) => PureExpr::Repeat {
555 expr: Box::new(r.expr.to_pure()),
556 len: Box::new(r.len.to_pure()),
557 },
558 other => PureExpr::Other(other.to_token_stream().to_string()),
559 }
560 }
561}
562
563impl ToPure for syn::Arm {
564 type Output = PureMatchArm;
565
566 fn to_pure(&self) -> PureMatchArm {
567 PureMatchArm {
568 pattern: self.pat.to_pure(),
569 guard: self.guard.as_ref().map(|(_, e)| e.to_pure()),
570 body: self.body.to_pure(),
571 }
572 }
573}
574
575impl ToPure for syn::Type {
576 type Output = PureType;
577
578 fn to_pure(&self) -> PureType {
579 match self {
580 syn::Type::Path(p) => PureType::Path(path_to_string(&p.path)),
581 syn::Type::Reference(r) => PureType::Ref {
582 lifetime: r.lifetime.as_ref().map(|l| l.to_string()),
583 is_mut: r.mutability.is_some(),
584 ty: Box::new(r.elem.to_pure()),
585 },
586 syn::Type::Tuple(t) => PureType::Tuple(t.elems.iter().map(|t| t.to_pure()).collect()),
587 syn::Type::Array(a) => PureType::Array {
588 ty: Box::new(a.elem.to_pure()),
589 len: a.len.to_token_stream().to_string(),
590 },
591 syn::Type::Slice(s) => PureType::Slice(Box::new(s.elem.to_pure())),
592 syn::Type::BareFn(f) => PureType::Fn {
593 params: f.inputs.iter().map(|i| i.ty.to_pure()).collect(),
594 ret: match &f.output {
595 syn::ReturnType::Default => None,
596 syn::ReturnType::Type(_, ty) => Some(Box::new(ty.to_pure())),
597 },
598 },
599 syn::Type::ImplTrait(i) => PureType::ImplTrait(
600 i.bounds
601 .iter()
602 .map(|b| b.to_token_stream().to_string())
603 .collect(),
604 ),
605 syn::Type::TraitObject(t) => PureType::TraitObject(
606 t.bounds
607 .iter()
608 .map(|b| b.to_token_stream().to_string())
609 .collect(),
610 ),
611 syn::Type::Infer(_) => PureType::Infer,
612 syn::Type::Never(_) => PureType::Never,
613 syn::Type::Paren(p) => p.elem.to_pure(),
614 other => PureType::Other(other.to_token_stream().to_string()),
615 }
616 }
617}
618
619impl ToPure for syn::ItemStruct {
620 type Output = PureStruct;
621
622 fn to_pure(&self) -> PureStruct {
623 PureStruct {
624 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
625 vis: self.vis.to_pure(),
626 name: self.ident.to_string(),
627 generics: self.generics.to_pure(),
628 fields: self.fields.to_pure(),
629 }
630 }
631}
632
633impl ToPure for syn::Fields {
634 type Output = PureFields;
635
636 fn to_pure(&self) -> PureFields {
637 match self {
638 syn::Fields::Named(n) => {
639 PureFields::Named(n.named.iter().map(|f| f.to_pure()).collect())
640 }
641 syn::Fields::Unnamed(u) => PureFields::Tuple(
642 u.unnamed
643 .iter()
644 .map(|f| PureTupleField {
645 attrs: f.attrs.iter().map(|a| a.to_pure()).collect(),
646 vis: f.vis.to_pure(),
647 ty: f.ty.to_pure(),
648 })
649 .collect(),
650 ),
651 syn::Fields::Unit => PureFields::Unit,
652 }
653 }
654}
655
656impl ToPure for syn::Field {
657 type Output = PureField;
658
659 fn to_pure(&self) -> PureField {
660 PureField {
661 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
662 vis: self.vis.to_pure(),
663 name: self
664 .ident
665 .as_ref()
666 .map(|i| i.to_string())
667 .unwrap_or_default(),
668 ty: self.ty.to_pure(),
669 }
670 }
671}
672
673impl ToPure for syn::ItemEnum {
674 type Output = PureEnum;
675
676 fn to_pure(&self) -> PureEnum {
677 PureEnum {
678 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
679 vis: self.vis.to_pure(),
680 name: self.ident.to_string(),
681 generics: self.generics.to_pure(),
682 variants: self.variants.iter().map(|v| v.to_pure()).collect(),
683 }
684 }
685}
686
687impl ToPure for syn::Variant {
688 type Output = PureVariant;
689
690 fn to_pure(&self) -> PureVariant {
691 PureVariant {
692 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
693 name: self.ident.to_string(),
694 fields: self.fields.to_pure(),
695 discriminant: self
696 .discriminant
697 .as_ref()
698 .map(|(_, e)| e.to_token_stream().to_string()),
699 }
700 }
701}
702
703impl ToPure for syn::ItemImpl {
704 type Output = PureImpl;
705
706 fn to_pure(&self) -> PureImpl {
707 PureImpl {
708 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
709 generics: self.generics.to_pure(),
710 is_unsafe: self.unsafety.is_some(),
711 trait_: self.trait_.as_ref().map(|(_, p, _)| path_to_string(p)),
712 self_ty: self.self_ty.to_token_stream().to_string(),
713 items: self.items.iter().map(|i| i.to_pure()).collect(),
714 }
715 }
716}
717
718impl ToPure for syn::ImplItem {
719 type Output = PureImplItem;
720
721 fn to_pure(&self) -> PureImplItem {
722 match self {
723 syn::ImplItem::Fn(f) => PureImplItem::Fn(f.to_pure()),
724 syn::ImplItem::Const(c) => PureImplItem::Const(c.to_pure()),
725 syn::ImplItem::Type(t) => PureImplItem::Type(t.to_pure()),
726 other => PureImplItem::Other(other.to_token_stream().to_string()),
727 }
728 }
729}
730
731impl ToPure for syn::ImplItemFn {
732 type Output = PureFn;
733
734 fn to_pure(&self) -> PureFn {
735 let is_async = self.sig.asyncness.is_some();
736 let is_async_inferred = !is_async && infer_async_from_return_type(&self.sig.output);
737
738 PureFn {
739 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
740 vis: self.vis.to_pure(),
741 is_async,
742 is_async_inferred,
743 is_const: self.sig.constness.is_some(),
744 is_unsafe: self.sig.unsafety.is_some(),
745 abi: self.sig.abi.as_ref().map(abi_to_string),
746 name: self.sig.ident.to_string(),
747 generics: self.sig.generics.to_pure(),
748 params: self.sig.inputs.iter().map(|p| p.to_pure()).collect(),
749 ret: match &self.sig.output {
750 syn::ReturnType::Default => None,
751 syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
752 },
753 body: self.block.to_pure(),
754 }
755 }
756}
757
758impl ToPure for syn::ImplItemConst {
759 type Output = PureConst;
760
761 fn to_pure(&self) -> PureConst {
762 PureConst {
763 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
764 vis: self.vis.to_pure(),
765 name: self.ident.to_string(),
766 ty: self.ty.to_pure(),
767 value: Some(self.expr.to_pure()),
768 }
769 }
770}
771
772impl ToPure for syn::ImplItemType {
773 type Output = PureTypeAlias;
774
775 fn to_pure(&self) -> PureTypeAlias {
776 PureTypeAlias {
777 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
778 vis: self.vis.to_pure(),
779 name: self.ident.to_string(),
780 generics: self.generics.to_pure(),
781 ty: self.ty.to_pure(),
782 }
783 }
784}
785
786impl ToPure for syn::ItemConst {
787 type Output = PureConst;
788
789 fn to_pure(&self) -> PureConst {
790 PureConst {
791 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
792 vis: self.vis.to_pure(),
793 name: self.ident.to_string(),
794 ty: self.ty.to_pure(),
795 value: Some(self.expr.to_pure()),
796 }
797 }
798}
799
800impl ToPure for syn::ItemStatic {
801 type Output = PureStatic;
802
803 fn to_pure(&self) -> PureStatic {
804 PureStatic {
805 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
806 vis: self.vis.to_pure(),
807 is_mut: matches!(self.mutability, syn::StaticMutability::Mut(_)),
808 name: self.ident.to_string(),
809 ty: self.ty.to_pure(),
810 value: self.expr.to_pure(),
811 }
812 }
813}
814
815impl ToPure for syn::ItemType {
816 type Output = PureTypeAlias;
817
818 fn to_pure(&self) -> PureTypeAlias {
819 PureTypeAlias {
820 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
821 vis: self.vis.to_pure(),
822 name: self.ident.to_string(),
823 generics: self.generics.to_pure(),
824 ty: self.ty.to_pure(),
825 }
826 }
827}
828
829fn is_cfg_test_attr(attr: &PureAttribute) -> bool {
839 attr.path == "cfg" && attr.meta == PureAttrMeta::List("test".to_string()) && !attr.is_inner
840}
841
842fn extract_cfg_test_scope(attrs: &mut Vec<PureAttribute>) -> ModScope {
848 if let Some(pos) = attrs.iter().position(is_cfg_test_attr) {
849 attrs.remove(pos);
850 ModScope::Test
851 } else {
852 ModScope::Prod
853 }
854}
855
856impl ToPure for syn::ItemMod {
857 type Output = PureMod;
858
859 fn to_pure(&self) -> PureMod {
860 let items = if let Some((_, items)) = &self.content {
862 items.iter().map(|item| item.to_pure()).collect()
863 } else {
864 Vec::new()
866 };
867
868 let mut attrs: Vec<PureAttribute> = self.attrs.iter().map(|a| a.to_pure()).collect();
869 let scope = extract_cfg_test_scope(&mut attrs);
870
871 PureMod {
872 attrs,
873 vis: self.vis.to_pure(),
874 name: self.ident.to_string(),
875 items,
876 scope,
877 }
878 }
879}
880
881impl ToPure for syn::ItemTrait {
882 type Output = PureTrait;
883
884 fn to_pure(&self) -> PureTrait {
885 PureTrait {
886 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
887 vis: self.vis.to_pure(),
888 is_unsafe: self.unsafety.is_some(),
889 is_auto: self.auto_token.is_some(),
890 name: self.ident.to_string(),
891 generics: self.generics.to_pure(),
892 supertraits: self
893 .supertraits
894 .iter()
895 .map(|b| b.to_token_stream().to_string())
896 .collect(),
897 items: self.items.iter().map(|i| i.to_pure()).collect(),
898 }
899 }
900}
901
902impl ToPure for syn::TraitItem {
903 type Output = PureTraitItem;
904
905 fn to_pure(&self) -> PureTraitItem {
906 match self {
907 syn::TraitItem::Fn(f) => PureTraitItem::Fn(f.to_pure()),
908 syn::TraitItem::Const(c) => PureTraitItem::Const(c.to_pure()),
909 syn::TraitItem::Type(t) => PureTraitItem::Type {
910 name: t.ident.to_string(),
911 bounds: t
912 .bounds
913 .iter()
914 .map(|b| b.to_token_stream().to_string())
915 .collect(),
916 default: t.default.as_ref().map(|(_, ty)| ty.to_pure()),
917 },
918 other => PureTraitItem::Other(other.to_token_stream().to_string()),
919 }
920 }
921}
922
923impl ToPure for syn::TraitItemFn {
924 type Output = PureFn;
925
926 fn to_pure(&self) -> PureFn {
927 let is_async = self.sig.asyncness.is_some();
928 let is_async_inferred = !is_async && infer_async_from_return_type(&self.sig.output);
929
930 PureFn {
931 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
932 vis: PureVis::Private, is_async,
934 is_async_inferred,
935 is_const: self.sig.constness.is_some(),
936 is_unsafe: self.sig.unsafety.is_some(),
937 abi: self.sig.abi.as_ref().map(abi_to_string),
938 name: self.sig.ident.to_string(),
939 generics: self.sig.generics.to_pure(),
940 params: self.sig.inputs.iter().map(|p| p.to_pure()).collect(),
941 ret: match &self.sig.output {
942 syn::ReturnType::Default => None,
943 syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
944 },
945 body: self
946 .default
947 .as_ref()
948 .map(|b| b.to_pure())
949 .unwrap_or_default(),
950 }
951 }
952}
953
954impl ToPure for syn::TraitItemConst {
955 type Output = PureConst;
956
957 fn to_pure(&self) -> PureConst {
958 PureConst {
959 attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
960 vis: PureVis::Private,
961 name: self.ident.to_string(),
962 ty: self.ty.to_pure(),
963 value: self.default.as_ref().map(|(_, e)| e.to_pure()),
964 }
965 }
966}
967
968impl ToPure for syn::ItemMacro {
969 type Output = PureMacro;
970
971 fn to_pure(&self) -> PureMacro {
972 PureMacro {
973 path: path_to_string(&self.mac.path),
974 name: self.ident.as_ref().map(|i| i.to_string()),
975 delimiter: match self.mac.delimiter {
976 syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
977 syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
978 syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
979 },
980 tokens: self.mac.tokens.to_string(),
981 }
982 }
983}
984
985fn path_to_string(path: &syn::Path) -> String {
988 path.segments
989 .iter()
990 .map(|s| {
991 let ident = s.ident.to_string();
992 match &s.arguments {
993 syn::PathArguments::None => ident,
994 syn::PathArguments::AngleBracketed(args) => {
995 format!("{}{}", ident, args.to_token_stream())
996 }
997 syn::PathArguments::Parenthesized(args) => {
998 format!("{}{}", ident, args.to_token_stream())
999 }
1000 }
1001 })
1002 .collect::<Vec<_>>()
1003 .join("::")
1004}
1005
1006fn pat_to_name(pat: &syn::Pat) -> String {
1007 match pat {
1008 syn::Pat::Ident(i) => i.ident.to_string(),
1009 syn::Pat::Type(t) => pat_to_name(&t.pat),
1010 syn::Pat::Tuple(t) => {
1011 format!("_tuple{}", t.elems.len())
1013 }
1014 syn::Pat::TupleStruct(ts) => {
1015 ts.path
1017 .segments
1018 .last()
1019 .map(|s| s.ident.to_string())
1020 .unwrap_or_else(|| "_pattern".to_string())
1021 }
1022 syn::Pat::Struct(s) => {
1023 s.path
1025 .segments
1026 .last()
1027 .map(|seg| seg.ident.to_string())
1028 .unwrap_or_else(|| "_pattern".to_string())
1029 }
1030 syn::Pat::Wild(_) => "_".to_string(),
1031 syn::Pat::Rest(_) => "_rest".to_string(),
1032 syn::Pat::Slice(_) => "_slice".to_string(),
1033 syn::Pat::Reference(r) => pat_to_name(&r.pat),
1034 syn::Pat::Or(o) => {
1035 o.cases
1037 .first()
1038 .map(pat_to_name)
1039 .unwrap_or_else(|| "_or".to_string())
1040 }
1041 _other => "_param".to_string(),
1042 }
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047 use super::*;
1048 use crate::pure::to_syn::ToSyn;
1049 use quote::ToTokens;
1050
1051 #[test]
1052 fn test_parse_simple_fn() {
1053 let pure = PureFile::from_source("fn main() {}").unwrap();
1054 assert_eq!(pure.functions().len(), 1);
1055 assert_eq!(pure.functions()[0].name, "main");
1056 }
1057
1058 #[test]
1059 fn test_parse_struct() {
1060 let pure = PureFile::from_source(
1061 r#"
1062 struct Point {
1063 x: i32,
1064 y: i32,
1065 }
1066 "#,
1067 )
1068 .unwrap();
1069
1070 assert_eq!(pure.structs().len(), 1);
1071 let s = &pure.structs()[0];
1072 assert_eq!(s.name, "Point");
1073
1074 if let PureFields::Named(fields) = &s.fields {
1075 assert_eq!(fields.len(), 2);
1076 assert_eq!(fields[0].name, "x");
1077 assert_eq!(fields[1].name, "y");
1078 } else {
1079 panic!("Expected named fields");
1080 }
1081 }
1082
1083 #[test]
1084 fn test_parse_use() {
1085 let pure = PureFile::from_source("use std::io;").unwrap();
1086 assert_eq!(pure.uses().len(), 1);
1087 }
1088
1089 #[test]
1090 fn test_thread_safe() {
1091 use std::sync::Arc;
1092 use std::thread;
1093
1094 let pure = PureFile::from_source(
1095 r#"
1096 fn foo() {}
1097 fn bar() {}
1098 "#,
1099 )
1100 .unwrap();
1101
1102 let shared = Arc::new(pure);
1103
1104 let s1 = Arc::clone(&shared);
1105 let s2 = Arc::clone(&shared);
1106
1107 let h1 = thread::spawn(move || s1.functions().len());
1108 let h2 = thread::spawn(move || s2.find_fn("foo").map(|f| f.name.clone()));
1109
1110 assert_eq!(h1.join().unwrap(), 2);
1111 assert_eq!(h2.join().unwrap(), Some("foo".to_string()));
1112 }
1113
1114 #[test]
1117 fn test_expr_binary() {
1118 let pure = PureFile::from_source("fn f() { let _ = 1 + 2; }").unwrap();
1119 let f = &pure.functions()[0];
1120 if let PureStmt::Local {
1121 init: Some(expr), ..
1122 } = &f.body.stmts[0]
1123 {
1124 if let PureExpr::Binary { op, .. } = expr {
1125 assert_eq!(op, "+");
1126 } else {
1127 panic!("Expected Binary expr");
1128 }
1129 } else {
1130 panic!("Expected Local stmt");
1131 }
1132 }
1133
1134 #[test]
1135 fn test_expr_unary() {
1136 let pure = PureFile::from_source("fn f() { let _ = -x; }").unwrap();
1137 let f = &pure.functions()[0];
1138 if let PureStmt::Local {
1139 init: Some(expr), ..
1140 } = &f.body.stmts[0]
1141 {
1142 if let PureExpr::Unary { op, .. } = expr {
1143 assert_eq!(op, "-");
1144 } else {
1145 panic!("Expected Unary expr");
1146 }
1147 } else {
1148 panic!("Expected Local stmt");
1149 }
1150 }
1151
1152 #[test]
1153 fn test_expr_method_call() {
1154 let pure = PureFile::from_source("fn f() { x.foo(1, 2); }").unwrap();
1155 let f = &pure.functions()[0];
1156 if let PureStmt::Semi(PureExpr::MethodCall { method, args, .. }) = &f.body.stmts[0] {
1157 assert_eq!(method, "foo");
1158 assert_eq!(args.len(), 2);
1159 } else {
1160 panic!("Expected MethodCall expr");
1161 }
1162 }
1163
1164 #[test]
1165 fn test_expr_method_call_with_turbofish() {
1166 let pure = PureFile::from_source("fn f() { x.collect::<Vec<_>>(); }").unwrap();
1167 let f = &pure.functions()[0];
1168 if let PureStmt::Semi(PureExpr::MethodCall {
1169 method, turbofish, ..
1170 }) = &f.body.stmts[0]
1171 {
1172 assert_eq!(method, "collect");
1173 assert!(turbofish.is_some());
1174 assert!(turbofish.as_ref().unwrap().contains("Vec"));
1175 } else {
1176 panic!("Expected MethodCall expr with turbofish");
1177 }
1178 }
1179
1180 #[test]
1181 fn test_expr_closure() {
1182 let pure = PureFile::from_source("fn f() { let _ = |x| x + 1; }").unwrap();
1183 let f = &pure.functions()[0];
1184 if let PureStmt::Local {
1185 init: Some(expr), ..
1186 } = &f.body.stmts[0]
1187 {
1188 if let PureExpr::Closure {
1189 params, is_move, ..
1190 } = expr
1191 {
1192 assert!(!is_move);
1193 assert_eq!(params.len(), 1);
1194 } else {
1195 panic!("Expected Closure expr");
1196 }
1197 } else {
1198 panic!("Expected Local stmt");
1199 }
1200 }
1201
1202 #[test]
1203 fn test_expr_closure_move() {
1204 let pure = PureFile::from_source("fn f() { let _ = move |x| x; }").unwrap();
1205 let f = &pure.functions()[0];
1206 if let PureStmt::Local {
1207 init: Some(PureExpr::Closure { is_move, .. }),
1208 ..
1209 } = &f.body.stmts[0]
1210 {
1211 assert!(is_move);
1212 } else {
1213 panic!("Expected move Closure");
1214 }
1215 }
1216
1217 #[test]
1218 fn test_expr_async_closure() {
1219 let pure = PureFile::from_source("fn f() { let _ = async || {}; }").unwrap();
1220 let f = &pure.functions()[0];
1221 if let PureStmt::Local {
1222 init: Some(PureExpr::Closure { is_async, .. }),
1223 ..
1224 } = &f.body.stmts[0]
1225 {
1226 assert!(is_async);
1227 } else {
1228 panic!("Expected async Closure");
1229 }
1230 }
1231
1232 #[test]
1233 fn test_expr_closure_typed_params() {
1234 let pure =
1235 PureFile::from_source("fn f() { let _ = |x: i32, y: String| -> bool { true }; }")
1236 .unwrap();
1237 let f = &pure.functions()[0];
1238 if let PureStmt::Local {
1239 init: Some(expr), ..
1240 } = &f.body.stmts[0]
1241 {
1242 if let PureExpr::Closure { params, ret, .. } = expr {
1243 assert_eq!(params.len(), 2);
1244 assert!(params[0].ty.is_some());
1246 assert_eq!(
1247 params[0].ty.as_ref().unwrap(),
1248 &PureType::Path("i32".to_string())
1249 );
1250 assert!(params[1].ty.is_some());
1252 assert_eq!(
1253 params[1].ty.as_ref().unwrap(),
1254 &PureType::Path("String".to_string())
1255 );
1256 assert!(ret.is_some());
1258 assert_eq!(ret.as_ref().unwrap(), &PureType::Path("bool".to_string()));
1259 } else {
1260 panic!("Expected Closure expr");
1261 }
1262 } else {
1263 panic!("Expected Local stmt");
1264 }
1265 }
1266
1267 #[test]
1268 fn test_expr_closure_untyped_params_no_ret() {
1269 let pure = PureFile::from_source("fn f() { let _ = |x| x + 1; }").unwrap();
1270 let f = &pure.functions()[0];
1271 if let PureStmt::Local {
1272 init: Some(PureExpr::Closure { params, ret, .. }),
1273 ..
1274 } = &f.body.stmts[0]
1275 {
1276 assert_eq!(params.len(), 1);
1277 assert!(params[0].ty.is_none());
1278 assert!(ret.is_none());
1279 } else {
1280 panic!("Expected Closure");
1281 }
1282 }
1283
1284 #[test]
1285 fn test_expr_if() {
1286 let pure = PureFile::from_source("fn f() { if true { 1 } else { 2 } }").unwrap();
1287 let f = &pure.functions()[0];
1288 if let PureStmt::Expr(PureExpr::If { else_branch, .. }) = &f.body.stmts[0] {
1289 assert!(else_branch.is_some());
1290 } else {
1291 panic!("Expected If expr");
1292 }
1293 }
1294
1295 #[test]
1296 fn test_expr_match() {
1297 let pure =
1298 PureFile::from_source(r#"fn f() { match x { 1 => "one", _ => "other" } }"#).unwrap();
1299 let f = &pure.functions()[0];
1300 if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1301 assert_eq!(arms.len(), 2);
1302 } else {
1303 panic!("Expected Match expr");
1304 }
1305 }
1306
1307 #[test]
1308 fn test_expr_for_loop() {
1309 let pure = PureFile::from_source("fn f() { for i in 0..10 {} }").unwrap();
1310 let f = &pure.functions()[0];
1311 match &f.body.stmts[0] {
1313 PureStmt::Expr(PureExpr::For { pat, .. })
1314 | PureStmt::Semi(PureExpr::For { pat, .. }) => {
1315 if let PurePattern::Ident { name, .. } = pat {
1316 assert_eq!(name, "i");
1317 } else {
1318 panic!("Expected Ident pattern");
1319 }
1320 }
1321 _ => panic!("Expected For expr"),
1322 }
1323 }
1324
1325 #[test]
1326 fn test_expr_while() {
1327 let pure = PureFile::from_source("fn f() { while x > 0 { x -= 1; } }").unwrap();
1328 let f = &pure.functions()[0];
1329 match &f.body.stmts[0] {
1330 PureStmt::Expr(PureExpr::While { .. }) | PureStmt::Semi(PureExpr::While { .. }) => {}
1331 _ => panic!("Expected While expr"),
1332 }
1333 }
1334
1335 #[test]
1336 fn test_expr_loop() {
1337 let pure = PureFile::from_source("fn f() { loop { break; } }").unwrap();
1338 let f = &pure.functions()[0];
1339 match &f.body.stmts[0] {
1340 PureStmt::Expr(PureExpr::Loop { .. }) | PureStmt::Semi(PureExpr::Loop { .. }) => {}
1341 _ => panic!("Expected Loop expr"),
1342 }
1343 }
1344
1345 #[test]
1346 fn test_expr_return() {
1347 let pure = PureFile::from_source("fn f() -> i32 { return 42; }").unwrap();
1348 let f = &pure.functions()[0];
1349 if let PureStmt::Semi(PureExpr::Return(Some(_))) = &f.body.stmts[0] {
1350 } else {
1352 panic!("Expected Return expr");
1353 }
1354 }
1355
1356 #[test]
1357 fn test_expr_struct() {
1358 let pure = PureFile::from_source("fn f() { Point { x: 1, y: 2 } }").unwrap();
1359 let f = &pure.functions()[0];
1360 if let PureStmt::Expr(PureExpr::Struct { path, fields, .. }) = &f.body.stmts[0] {
1361 assert_eq!(path, "Point");
1362 assert_eq!(fields.len(), 2);
1363 } else {
1364 panic!("Expected Struct expr");
1365 }
1366 }
1367
1368 #[test]
1369 fn test_expr_tuple() {
1370 let pure = PureFile::from_source("fn f() { (1, 2, 3) }").unwrap();
1371 let f = &pure.functions()[0];
1372 if let PureStmt::Expr(PureExpr::Tuple(elems)) = &f.body.stmts[0] {
1373 assert_eq!(elems.len(), 3);
1374 } else {
1375 panic!("Expected Tuple expr");
1376 }
1377 }
1378
1379 #[test]
1380 fn test_expr_array() {
1381 let pure = PureFile::from_source("fn f() { [1, 2, 3] }").unwrap();
1382 let f = &pure.functions()[0];
1383 if let PureStmt::Expr(PureExpr::Array(elems)) = &f.body.stmts[0] {
1384 assert_eq!(elems.len(), 3);
1385 } else {
1386 panic!("Expected Array expr");
1387 }
1388 }
1389
1390 #[test]
1391 fn test_expr_repeat() {
1392 let pure = PureFile::from_source("fn f() { [0; 10] }").unwrap();
1393 let f = &pure.functions()[0];
1394 if let PureStmt::Expr(PureExpr::Repeat { .. }) = &f.body.stmts[0] {
1395 } else {
1397 panic!("Expected Repeat expr");
1398 }
1399 }
1400
1401 #[test]
1402 fn test_expr_reference() {
1403 let pure = PureFile::from_source("fn f() { let _ = &x; }").unwrap();
1404 let f = &pure.functions()[0];
1405 if let PureStmt::Local {
1406 init: Some(PureExpr::Ref { is_mut, .. }),
1407 ..
1408 } = &f.body.stmts[0]
1409 {
1410 assert!(!is_mut);
1411 } else {
1412 panic!("Expected Ref expr");
1413 }
1414 }
1415
1416 #[test]
1417 fn test_expr_reference_mut() {
1418 let pure = PureFile::from_source("fn f() { let _ = &mut x; }").unwrap();
1419 let f = &pure.functions()[0];
1420 if let PureStmt::Local {
1421 init: Some(PureExpr::Ref { is_mut, .. }),
1422 ..
1423 } = &f.body.stmts[0]
1424 {
1425 assert!(is_mut);
1426 } else {
1427 panic!("Expected mut Ref expr");
1428 }
1429 }
1430
1431 #[test]
1432 fn test_expr_await() {
1433 let pure = PureFile::from_source("async fn f() { x.await }").unwrap();
1434 let f = &pure.functions()[0];
1435 if let PureStmt::Expr(PureExpr::Await(_)) = &f.body.stmts[0] {
1436 } else {
1438 panic!("Expected Await expr");
1439 }
1440 }
1441
1442 #[test]
1443 fn test_expr_try() {
1444 let pure = PureFile::from_source("fn f() -> Result<(), ()> { x?; Ok(()) }").unwrap();
1445 let f = &pure.functions()[0];
1446 if let PureStmt::Semi(PureExpr::Try(_)) = &f.body.stmts[0] {
1447 } else {
1449 panic!("Expected Try expr");
1450 }
1451 }
1452
1453 #[test]
1454 fn test_expr_range() {
1455 let pure = PureFile::from_source("fn f() { let _ = 0..10; }").unwrap();
1456 let f = &pure.functions()[0];
1457 if let PureStmt::Local {
1458 init:
1459 Some(PureExpr::Range {
1460 start,
1461 end,
1462 inclusive,
1463 }),
1464 ..
1465 } = &f.body.stmts[0]
1466 {
1467 assert!(start.is_some());
1468 assert!(end.is_some());
1469 assert!(!inclusive);
1470 } else {
1471 panic!("Expected Range expr");
1472 }
1473 }
1474
1475 #[test]
1476 fn test_expr_range_inclusive() {
1477 let pure = PureFile::from_source("fn f() { let _ = 0..=10; }").unwrap();
1478 let f = &pure.functions()[0];
1479 if let PureStmt::Local {
1480 init: Some(PureExpr::Range { inclusive, .. }),
1481 ..
1482 } = &f.body.stmts[0]
1483 {
1484 assert!(inclusive);
1485 } else {
1486 panic!("Expected inclusive Range expr");
1487 }
1488 }
1489
1490 #[test]
1491 fn test_expr_cast() {
1492 let pure = PureFile::from_source("fn f() { let _ = x as u32; }").unwrap();
1493 let f = &pure.functions()[0];
1494 if let PureStmt::Local {
1495 init: Some(PureExpr::Cast { ty, .. }),
1496 ..
1497 } = &f.body.stmts[0]
1498 {
1499 if let PureType::Path(p) = ty {
1500 assert_eq!(p, "u32");
1501 } else {
1502 panic!("Expected Path type");
1503 }
1504 } else {
1505 panic!("Expected Cast expr");
1506 }
1507 }
1508
1509 #[test]
1510 fn test_expr_let() {
1511 let pure = PureFile::from_source("fn f() { if let Some(x) = y { } }").unwrap();
1512 let f = &pure.functions()[0];
1513 let if_expr = match &f.body.stmts[0] {
1514 PureStmt::Expr(e) | PureStmt::Semi(e) => e,
1515 _ => panic!("Expected expression statement"),
1516 };
1517 if let PureExpr::If { cond, .. } = if_expr {
1518 if let PureExpr::Let { pattern, .. } = cond.as_ref() {
1519 if let PurePattern::Struct { path, .. } = pattern {
1520 assert_eq!(path, "Some");
1521 } else {
1522 panic!("Expected Struct pattern");
1523 }
1524 } else {
1525 panic!("Expected Let expr in condition");
1526 }
1527 } else {
1528 panic!("Expected If expr");
1529 }
1530 }
1531
1532 #[test]
1533 fn test_expr_async_block() {
1534 let pure = PureFile::from_source("fn f() { async { 42 } }").unwrap();
1535 let f = &pure.functions()[0];
1536 if let PureStmt::Expr(PureExpr::Async { is_move, .. }) = &f.body.stmts[0] {
1537 assert!(!is_move);
1538 } else {
1539 panic!("Expected Async block");
1540 }
1541 }
1542
1543 #[test]
1544 fn test_expr_unsafe_block() {
1545 let pure = PureFile::from_source("fn f() { unsafe { dangerous() } }").unwrap();
1546 let f = &pure.functions()[0];
1547 if let PureStmt::Expr(PureExpr::Unsafe(_)) = &f.body.stmts[0] {
1548 } else {
1550 panic!("Expected Unsafe block");
1551 }
1552 }
1553
1554 #[test]
1555 fn test_expr_macro() {
1556 let pure = PureFile::from_source("fn f() { println!(\"hello\"); }").unwrap();
1557 let f = &pure.functions()[0];
1558 if let PureStmt::Semi(PureExpr::Macro { name, .. }) = &f.body.stmts[0] {
1559 assert_eq!(name, "println");
1560 } else {
1561 panic!("Expected Macro expr");
1562 }
1563 }
1564
1565 #[test]
1568 fn test_pattern_ident() {
1569 let pure = PureFile::from_source("fn f() { let x = 1; }").unwrap();
1570 let f = &pure.functions()[0];
1571 if let PureStmt::Local {
1572 pattern: PurePattern::Ident { name, is_mut, .. },
1573 ..
1574 } = &f.body.stmts[0]
1575 {
1576 assert_eq!(name, "x");
1577 assert!(!is_mut);
1578 } else {
1579 panic!("Expected Ident pattern");
1580 }
1581 }
1582
1583 #[test]
1584 fn test_pattern_ident_mut() {
1585 let pure = PureFile::from_source("fn f() { let mut x = 1; }").unwrap();
1586 let f = &pure.functions()[0];
1587 if let PureStmt::Local {
1588 pattern: PurePattern::Ident { is_mut, .. },
1589 ..
1590 } = &f.body.stmts[0]
1591 {
1592 assert!(is_mut);
1593 } else {
1594 panic!("Expected mut Ident pattern");
1595 }
1596 }
1597
1598 #[test]
1599 fn test_pattern_wild() {
1600 let pure = PureFile::from_source("fn f() { let _ = 1; }").unwrap();
1601 let f = &pure.functions()[0];
1602 if let PureStmt::Local {
1603 pattern: PurePattern::Wild,
1604 ..
1605 } = &f.body.stmts[0]
1606 {
1607 } else {
1609 panic!("Expected Wild pattern");
1610 }
1611 }
1612
1613 #[test]
1614 fn test_pattern_tuple() {
1615 let pure = PureFile::from_source("fn f() { let (a, b) = (1, 2); }").unwrap();
1616 let f = &pure.functions()[0];
1617 if let PureStmt::Local {
1618 pattern: PurePattern::Tuple(elems),
1619 ..
1620 } = &f.body.stmts[0]
1621 {
1622 assert_eq!(elems.len(), 2);
1623 } else {
1624 panic!("Expected Tuple pattern");
1625 }
1626 }
1627
1628 #[test]
1629 fn test_pattern_struct() {
1630 let pure = PureFile::from_source("fn f() { let Point { x, y } = p; }").unwrap();
1631 let f = &pure.functions()[0];
1632 if let PureStmt::Local {
1633 pattern: PurePattern::Struct { path, fields, rest },
1634 ..
1635 } = &f.body.stmts[0]
1636 {
1637 assert_eq!(path, "Point");
1638 assert_eq!(fields.len(), 2);
1639 assert!(!rest);
1640 } else {
1641 panic!("Expected Struct pattern");
1642 }
1643 }
1644
1645 #[test]
1646 fn test_pattern_struct_with_rest() {
1647 let pure = PureFile::from_source("fn f() { let Point { x, .. } = p; }").unwrap();
1648 let f = &pure.functions()[0];
1649 if let PureStmt::Local {
1650 pattern: PurePattern::Struct { rest, .. },
1651 ..
1652 } = &f.body.stmts[0]
1653 {
1654 assert!(rest);
1655 } else {
1656 panic!("Expected Struct pattern with rest");
1657 }
1658 }
1659
1660 #[test]
1661 fn test_pattern_tuple_struct() {
1662 let pure = PureFile::from_source("fn f() { let Some(x) = opt; }").unwrap();
1663 let f = &pure.functions()[0];
1664 if let PureStmt::Local {
1665 pattern: PurePattern::Struct { path, .. },
1666 ..
1667 } = &f.body.stmts[0]
1668 {
1669 assert_eq!(path, "Some");
1670 } else {
1671 panic!("Expected TupleStruct pattern");
1672 }
1673 }
1674
1675 #[test]
1676 fn test_pattern_reference() {
1677 let pure = PureFile::from_source("fn f() { let &x = r; }").unwrap();
1678 let f = &pure.functions()[0];
1679 if let PureStmt::Local {
1680 pattern: PurePattern::Ref { is_mut, .. },
1681 ..
1682 } = &f.body.stmts[0]
1683 {
1684 assert!(!is_mut);
1685 } else {
1686 panic!("Expected Ref pattern");
1687 }
1688 }
1689
1690 #[test]
1691 fn test_pattern_or() {
1692 let pure =
1694 PureFile::from_source("fn f(x: i32) { match x { 1 | 2 | 3 => {} _ => {} } }").unwrap();
1695 let f = &pure.functions()[0];
1696 if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1697 if let PurePattern::Or(cases) = &arms[0].pattern {
1698 assert_eq!(cases.len(), 3);
1699 } else {
1700 panic!("Expected Or pattern");
1701 }
1702 } else {
1703 panic!("Expected Match expr");
1704 }
1705 }
1706
1707 #[test]
1708 fn test_pattern_range() {
1709 let pure =
1710 PureFile::from_source("fn f(x: i32) { match x { 0..=10 => {} _ => {} } }").unwrap();
1711 let f = &pure.functions()[0];
1712 if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1713 if let PurePattern::Range { inclusive, .. } = &arms[0].pattern {
1714 assert!(inclusive);
1715 } else {
1716 panic!("Expected Range pattern");
1717 }
1718 } else {
1719 panic!("Expected Match expr");
1720 }
1721 }
1722
1723 #[test]
1724 fn test_pattern_slice() {
1725 let pure = PureFile::from_source("fn f() { let [a, b, c] = arr; }").unwrap();
1726 let f = &pure.functions()[0];
1727 if let PureStmt::Local {
1728 pattern: PurePattern::Slice(elems),
1729 ..
1730 } = &f.body.stmts[0]
1731 {
1732 assert_eq!(elems.len(), 3);
1733 } else {
1734 panic!("Expected Slice pattern");
1735 }
1736 }
1737
1738 #[test]
1739 fn test_pattern_path() {
1740 let pure = PureFile::from_source(
1742 "fn f(x: std::cmp::Ordering) { match x { std::cmp::Ordering::Less => {} _ => {} } }",
1743 )
1744 .unwrap();
1745 let f = &pure.functions()[0];
1746 if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1747 if let PurePattern::Path(path) = &arms[0].pattern {
1748 assert!(path.contains("Ordering"));
1749 assert!(path.contains("Less"));
1750 } else {
1751 panic!("Expected Path pattern, got {:?}", arms[0].pattern);
1752 }
1753 } else {
1754 panic!("Expected Match expr");
1755 }
1756 }
1757
1758 #[test]
1759 fn test_pattern_ident_by_ref() {
1760 let pure = PureFile::from_source(
1762 "fn f(opt: Option<i32>) { match opt { Some(ref x) => {} _ => {} } }",
1763 )
1764 .unwrap();
1765 let f = &pure.functions()[0];
1766 if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1767 if let PurePattern::Struct { fields, .. } = &arms[0].pattern {
1768 if let PurePattern::Ident { by_ref, is_mut, .. } = &fields[0].1 {
1769 assert!(*by_ref, "Expected by_ref=true for `ref x`");
1770 assert!(!is_mut, "Expected is_mut=false for `ref x`");
1771 } else {
1772 panic!("Expected Ident sub-pattern");
1773 }
1774 } else {
1775 panic!("Expected Struct pattern for Some(ref x)");
1776 }
1777 } else {
1778 panic!("Expected Match expr");
1779 }
1780 }
1781
1782 #[test]
1783 fn test_pattern_ident_ref_mut() {
1784 let pure = PureFile::from_source(
1786 "fn f(opt: Option<i32>) { match opt { Some(ref mut x) => {} _ => {} } }",
1787 )
1788 .unwrap();
1789 let f = &pure.functions()[0];
1790 if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
1791 if let PurePattern::Struct { fields, .. } = &arms[0].pattern {
1792 if let PurePattern::Ident { by_ref, is_mut, .. } = &fields[0].1 {
1793 assert!(*by_ref, "Expected by_ref=true for `ref mut x`");
1794 assert!(*is_mut, "Expected is_mut=true for `ref mut x`");
1795 } else {
1796 panic!("Expected Ident sub-pattern");
1797 }
1798 } else {
1799 panic!("Expected Struct pattern for Some(ref mut x)");
1800 }
1801 } else {
1802 panic!("Expected Match expr");
1803 }
1804 }
1805
1806 #[test]
1807 fn test_pattern_ident_ref_round_trip() {
1808 use syn::token;
1809 let cases: &[(bool, bool, &str)] = &[
1812 (true, false, "x"),
1813 (true, true, "x"),
1814 (false, true, "x"),
1815 (false, false, "x"),
1816 ];
1817 for &(expected_by_ref, expected_is_mut, name) in cases {
1818 let syn_pat = syn::Pat::Ident(syn::PatIdent {
1819 attrs: vec![],
1820 by_ref: if expected_by_ref {
1821 Some(token::Ref::default())
1822 } else {
1823 None
1824 },
1825 mutability: if expected_is_mut {
1826 Some(token::Mut::default())
1827 } else {
1828 None
1829 },
1830 ident: syn::Ident::new(name, proc_macro2::Span::call_site()),
1831 subpat: None,
1832 });
1833 let pure = syn_pat.to_pure();
1834 if let PurePattern::Ident { by_ref, is_mut, .. } = &pure {
1835 assert_eq!(
1836 *by_ref, expected_by_ref,
1837 "by_ref mismatch (by_ref={}, is_mut={})",
1838 expected_by_ref, expected_is_mut
1839 );
1840 assert_eq!(
1841 *is_mut, expected_is_mut,
1842 "is_mut mismatch (by_ref={}, is_mut={})",
1843 expected_by_ref, expected_is_mut
1844 );
1845 } else {
1846 panic!(
1847 "Expected PurePattern::Ident (by_ref={}, is_mut={})",
1848 expected_by_ref, expected_is_mut
1849 );
1850 }
1851 let restored = pure.to_syn().unwrap();
1853 let restored_tokens = restored.to_token_stream().to_string();
1854 let original_tokens = syn_pat.to_token_stream().to_string();
1855 assert_eq!(
1856 restored_tokens, original_tokens,
1857 "Round-trip token mismatch (by_ref={}, is_mut={})",
1858 expected_by_ref, expected_is_mut
1859 );
1860 }
1861 }
1862
1863 #[test]
1866 fn test_type_path() {
1867 let pure = PureFile::from_source("fn f(x: i32) {}").unwrap();
1868 let f = &pure.functions()[0];
1869 if let PureParam::Typed {
1870 ty: PureType::Path(p),
1871 ..
1872 } = &f.params[0]
1873 {
1874 assert_eq!(p, "i32");
1875 } else {
1876 panic!("Expected Path type");
1877 }
1878 }
1879
1880 #[test]
1881 fn test_type_reference() {
1882 let pure = PureFile::from_source("fn f(x: &str) {}").unwrap();
1883 let f = &pure.functions()[0];
1884 if let PureParam::Typed {
1885 ty: PureType::Ref { is_mut, .. },
1886 ..
1887 } = &f.params[0]
1888 {
1889 assert!(!is_mut);
1890 } else {
1891 panic!("Expected Ref type");
1892 }
1893 }
1894
1895 #[test]
1896 fn test_type_reference_mut() {
1897 let pure = PureFile::from_source("fn f(x: &mut String) {}").unwrap();
1898 let f = &pure.functions()[0];
1899 if let PureParam::Typed {
1900 ty: PureType::Ref { is_mut, .. },
1901 ..
1902 } = &f.params[0]
1903 {
1904 assert!(is_mut);
1905 } else {
1906 panic!("Expected mut Ref type");
1907 }
1908 }
1909
1910 #[test]
1911 fn test_type_reference_with_lifetime() {
1912 let pure = PureFile::from_source("fn f<'a>(x: &'a str) {}").unwrap();
1913 let f = &pure.functions()[0];
1914 if let PureParam::Typed {
1915 ty: PureType::Ref { lifetime, .. },
1916 ..
1917 } = &f.params[0]
1918 {
1919 assert!(lifetime.is_some());
1920 assert!(lifetime.as_ref().unwrap().contains("'a"));
1921 } else {
1922 panic!("Expected Ref type with lifetime");
1923 }
1924 }
1925
1926 #[test]
1927 fn test_type_tuple() {
1928 let pure = PureFile::from_source("fn f(x: (i32, i32)) {}").unwrap();
1929 let f = &pure.functions()[0];
1930 if let PureParam::Typed {
1931 ty: PureType::Tuple(elems),
1932 ..
1933 } = &f.params[0]
1934 {
1935 assert_eq!(elems.len(), 2);
1936 } else {
1937 panic!("Expected Tuple type");
1938 }
1939 }
1940
1941 #[test]
1942 fn test_type_array() {
1943 let pure = PureFile::from_source("fn f(x: [i32; 10]) {}").unwrap();
1944 let f = &pure.functions()[0];
1945 if let PureParam::Typed {
1946 ty: PureType::Array { len, .. },
1947 ..
1948 } = &f.params[0]
1949 {
1950 assert_eq!(len, "10");
1951 } else {
1952 panic!("Expected Array type");
1953 }
1954 }
1955
1956 #[test]
1957 fn test_type_slice() {
1958 let pure = PureFile::from_source("fn f(x: &[i32]) {}").unwrap();
1959 let f = &pure.functions()[0];
1960 if let PureParam::Typed {
1961 ty: PureType::Ref { ty, .. },
1962 ..
1963 } = &f.params[0]
1964 {
1965 if let PureType::Slice(_) = ty.as_ref() {
1966 } else {
1968 panic!("Expected Slice type");
1969 }
1970 } else {
1971 panic!("Expected Ref to Slice type");
1972 }
1973 }
1974
1975 #[test]
1976 fn test_type_fn() {
1977 let pure = PureFile::from_source("fn f(x: fn(i32) -> i32) {}").unwrap();
1978 let f = &pure.functions()[0];
1979 if let PureParam::Typed {
1980 ty: PureType::Fn { params, ret },
1981 ..
1982 } = &f.params[0]
1983 {
1984 assert_eq!(params.len(), 1);
1985 assert!(ret.is_some());
1986 } else {
1987 panic!("Expected Fn type");
1988 }
1989 }
1990
1991 #[test]
1992 fn test_type_impl_trait() {
1993 let pure =
1994 PureFile::from_source("fn f() -> impl Iterator<Item = i32> { todo!() }").unwrap();
1995 let f = &pure.functions()[0];
1996 if let Some(PureType::ImplTrait(bounds)) = &f.ret {
1997 assert!(!bounds.is_empty());
1998 } else {
1999 panic!("Expected ImplTrait type");
2000 }
2001 }
2002
2003 #[test]
2004 fn test_type_never() {
2005 let pure = PureFile::from_source("fn f() -> ! { panic!() }").unwrap();
2006 let f = &pure.functions()[0];
2007 if let Some(PureType::Never) = &f.ret {
2008 } else {
2010 panic!("Expected Never type");
2011 }
2012 }
2013
2014 #[test]
2017 fn test_generics_type_param() {
2018 let pure = PureFile::from_source("fn f<T>(x: T) {}").unwrap();
2019 let f = &pure.functions()[0];
2020 assert_eq!(f.generics.params.len(), 1);
2021 if let PureGenericParam::Type { name, .. } = &f.generics.params[0] {
2022 assert_eq!(name, "T");
2023 } else {
2024 panic!("Expected Type param");
2025 }
2026 }
2027
2028 #[test]
2029 fn test_generics_type_param_with_bound() {
2030 let pure = PureFile::from_source("fn f<T: Clone>(x: T) {}").unwrap();
2031 let f = &pure.functions()[0];
2032 if let PureGenericParam::Type { bounds, .. } = &f.generics.params[0] {
2033 assert!(!bounds.is_empty());
2034 assert!(bounds[0].contains("Clone"));
2035 } else {
2036 panic!("Expected Type param with bound");
2037 }
2038 }
2039
2040 #[test]
2041 fn test_generics_lifetime_param() {
2042 let pure = PureFile::from_source("fn f<'a>(x: &'a str) {}").unwrap();
2043 let f = &pure.functions()[0];
2044 if let PureGenericParam::Lifetime { name, .. } = &f.generics.params[0] {
2045 assert!(name.contains("'a"));
2046 } else {
2047 panic!("Expected Lifetime param");
2048 }
2049 }
2050
2051 #[test]
2052 fn test_generics_const_param() {
2053 let pure = PureFile::from_source("fn f<const N: usize>() {}").unwrap();
2054 let f = &pure.functions()[0];
2055 if let PureGenericParam::Const { name, ty } = &f.generics.params[0] {
2056 assert_eq!(name, "N");
2057 assert!(ty.contains("usize"));
2058 } else {
2059 panic!("Expected Const param");
2060 }
2061 }
2062
2063 #[test]
2064 fn test_generics_where_clause() {
2065 let pure = PureFile::from_source("fn f<T>(x: T) where T: Clone {}").unwrap();
2066 let f = &pure.functions()[0];
2067 assert!(!f.generics.where_clause.is_empty());
2068 assert!(f.generics.where_clause[0].contains("Clone"));
2069 }
2070
2071 #[test]
2074 fn test_fn_async() {
2075 let pure = PureFile::from_source("async fn f() {}").unwrap();
2076 assert!(pure.functions()[0].is_async);
2077 }
2078
2079 #[test]
2080 fn test_fn_const() {
2081 let pure = PureFile::from_source("const fn f() {}").unwrap();
2082 assert!(pure.functions()[0].is_const);
2083 }
2084
2085 #[test]
2086 fn test_fn_unsafe() {
2087 let pure = PureFile::from_source("unsafe fn f() {}").unwrap();
2088 assert!(pure.functions()[0].is_unsafe);
2089 }
2090
2091 #[test]
2092 fn test_fn_self_param() {
2093 let pure = PureFile::from_source("impl Foo { fn f(&self) {} }").unwrap();
2094 let impls = pure.impls();
2095 if let PureImplItem::Fn(f) = &impls[0].items[0] {
2096 if let PureParam::SelfValue { is_ref, is_mut } = &f.params[0] {
2097 assert!(is_ref);
2098 assert!(!is_mut);
2099 } else {
2100 panic!("Expected SelfValue param");
2101 }
2102 } else {
2103 panic!("Expected Fn item");
2104 }
2105 }
2106
2107 #[test]
2108 fn test_fn_self_mut_param() {
2109 let pure = PureFile::from_source("impl Foo { fn f(&mut self) {} }").unwrap();
2110 let impls = pure.impls();
2111 if let PureImplItem::Fn(f) = &impls[0].items[0] {
2112 if let PureParam::SelfValue { is_ref, is_mut } = &f.params[0] {
2113 assert!(is_ref);
2114 assert!(is_mut);
2115 } else {
2116 panic!("Expected mut SelfValue param");
2117 }
2118 } else {
2119 panic!("Expected Fn item");
2120 }
2121 }
2122
2123 #[test]
2126 fn test_impl_inherent() {
2127 let pure = PureFile::from_source("impl Foo { fn new() -> Self { Foo } }").unwrap();
2128 let impls = pure.impls();
2129 assert_eq!(impls.len(), 1);
2130 assert!(impls[0].trait_.is_none());
2131 assert_eq!(impls[0].self_ty, "Foo");
2132 }
2133
2134 #[test]
2135 fn test_impl_trait() {
2136 let pure = PureFile::from_source("impl Clone for Foo { fn clone(&self) -> Self { Foo } }")
2137 .unwrap();
2138 let impls = pure.impls();
2139 assert_eq!(impls[0].trait_.as_ref().unwrap(), "Clone");
2140 }
2141
2142 #[test]
2143 fn test_impl_unsafe() {
2144 let pure = PureFile::from_source("unsafe impl Send for Foo {}").unwrap();
2145 let impls = pure.impls();
2146 assert!(impls[0].is_unsafe);
2147 }
2148
2149 #[test]
2150 fn test_impl_const() {
2151 let pure = PureFile::from_source("impl Foo { const X: i32 = 42; }").unwrap();
2152 let impls = pure.impls();
2153 if let PureImplItem::Const(c) = &impls[0].items[0] {
2154 assert_eq!(c.name, "X");
2155 } else {
2156 panic!("Expected Const item");
2157 }
2158 }
2159
2160 #[test]
2161 fn test_impl_type() {
2162 let pure = PureFile::from_source(
2163 "impl Iterator for Foo { type Item = i32; fn next(&mut self) -> Option<i32> { None } }",
2164 )
2165 .unwrap();
2166 let impls = pure.impls();
2167 if let PureImplItem::Type(t) = &impls[0].items[0] {
2168 assert_eq!(t.name, "Item");
2169 } else {
2170 panic!("Expected Type item");
2171 }
2172 }
2173
2174 #[test]
2177 fn test_trait_simple() {
2178 let pure = PureFile::from_source("trait Foo {}").unwrap();
2179 let traits = pure.traits();
2180 assert_eq!(traits.len(), 1);
2181 assert_eq!(traits[0].name, "Foo");
2182 }
2183
2184 #[test]
2185 fn test_trait_unsafe() {
2186 let pure = PureFile::from_source("unsafe trait Foo {}").unwrap();
2187 assert!(pure.traits()[0].is_unsafe);
2188 }
2189
2190 #[test]
2191 fn test_trait_with_supertraits() {
2192 let pure = PureFile::from_source("trait Foo: Clone + Send {}").unwrap();
2193 let traits = pure.traits();
2194 assert_eq!(traits[0].supertraits.len(), 2);
2195 }
2196
2197 #[test]
2198 fn test_trait_with_method() {
2199 let pure = PureFile::from_source("trait Foo { fn bar(&self); }").unwrap();
2200 let traits = pure.traits();
2201 if let PureTraitItem::Fn(f) = &traits[0].items[0] {
2202 assert_eq!(f.name, "bar");
2203 } else {
2204 panic!("Expected Fn item");
2205 }
2206 }
2207
2208 #[test]
2209 fn test_trait_with_default_method() {
2210 let pure = PureFile::from_source("trait Foo { fn bar(&self) { } }").unwrap();
2211 let traits = pure.traits();
2212 if let PureTraitItem::Fn(f) = &traits[0].items[0] {
2213 assert!(!f.body.stmts.is_empty() || f.body.stmts.is_empty()); } else {
2215 panic!("Expected Fn item");
2216 }
2217 }
2218
2219 #[test]
2220 fn test_trait_with_associated_type() {
2221 let pure = PureFile::from_source("trait Foo { type Item; }").unwrap();
2222 let traits = pure.traits();
2223 if let PureTraitItem::Type { name, .. } = &traits[0].items[0] {
2224 assert_eq!(name, "Item");
2225 } else {
2226 panic!("Expected Type item");
2227 }
2228 }
2229
2230 #[test]
2231 fn test_trait_with_associated_const() {
2232 let pure = PureFile::from_source("trait Foo { const X: i32; }").unwrap();
2233 let traits = pure.traits();
2234 if let PureTraitItem::Const(c) = &traits[0].items[0] {
2235 assert_eq!(c.name, "X");
2236 } else {
2237 panic!("Expected Const item");
2238 }
2239 }
2240
2241 #[test]
2244 fn test_enum_simple() {
2245 let pure = PureFile::from_source("enum Color { Red, Green, Blue }").unwrap();
2246 let enums: Vec<_> = pure
2247 .items
2248 .iter()
2249 .filter_map(|i| {
2250 if let PureItem::Enum(e) = i {
2251 Some(e)
2252 } else {
2253 None
2254 }
2255 })
2256 .collect();
2257 assert_eq!(enums.len(), 1);
2258 assert_eq!(enums[0].variants.len(), 3);
2259 }
2260
2261 #[test]
2262 fn test_enum_with_discriminant() {
2263 let pure = PureFile::from_source("enum Num { One = 1, Two = 2 }").unwrap();
2264 if let PureItem::Enum(e) = &pure.items[0] {
2265 assert!(e.variants[0].discriminant.is_some());
2266 } else {
2267 panic!("Expected Enum item");
2268 }
2269 }
2270
2271 #[test]
2272 fn test_enum_tuple_variant() {
2273 let pure = PureFile::from_source("enum Opt { Some(i32), None }").unwrap();
2274 if let PureItem::Enum(e) = &pure.items[0] {
2275 if let PureFields::Tuple(types) = &e.variants[0].fields {
2276 assert_eq!(types.len(), 1);
2277 } else {
2278 panic!("Expected Tuple fields");
2279 }
2280 } else {
2281 panic!("Expected Enum item");
2282 }
2283 }
2284
2285 #[test]
2286 fn test_enum_struct_variant() {
2287 let pure = PureFile::from_source("enum Msg { Move { x: i32, y: i32 } }").unwrap();
2288 if let PureItem::Enum(e) = &pure.items[0] {
2289 if let PureFields::Named(fields) = &e.variants[0].fields {
2290 assert_eq!(fields.len(), 2);
2291 } else {
2292 panic!("Expected Named fields");
2293 }
2294 } else {
2295 panic!("Expected Enum item");
2296 }
2297 }
2298
2299 #[test]
2302 fn test_struct_unit() {
2303 let pure = PureFile::from_source("struct Unit;").unwrap();
2304 let structs = pure.structs();
2305 assert!(matches!(structs[0].fields, PureFields::Unit));
2306 }
2307
2308 #[test]
2309 fn test_struct_tuple() {
2310 let pure = PureFile::from_source("struct Point(i32, i32);").unwrap();
2311 let structs = pure.structs();
2312 if let PureFields::Tuple(types) = &structs[0].fields {
2313 assert_eq!(types.len(), 2);
2314 } else {
2315 panic!("Expected Tuple fields");
2316 }
2317 }
2318
2319 #[test]
2322 fn test_vis_public() {
2323 let pure = PureFile::from_source("pub fn f() {}").unwrap();
2324 assert!(matches!(pure.functions()[0].vis, PureVis::Public));
2325 }
2326
2327 #[test]
2328 fn test_vis_private() {
2329 let pure = PureFile::from_source("fn f() {}").unwrap();
2330 assert!(matches!(pure.functions()[0].vis, PureVis::Private));
2331 }
2332
2333 #[test]
2334 fn test_vis_crate() {
2335 let pure = PureFile::from_source("pub(crate) fn f() {}").unwrap();
2336 assert!(matches!(pure.functions()[0].vis, PureVis::Crate));
2337 }
2338
2339 #[test]
2340 fn test_vis_super() {
2341 let pure = PureFile::from_source("pub(super) fn f() {}").unwrap();
2342 assert!(matches!(pure.functions()[0].vis, PureVis::Super));
2343 }
2344
2345 #[test]
2346 fn test_vis_in_path() {
2347 let pure = PureFile::from_source("pub(in crate::foo) fn f() {}").unwrap();
2348 if let PureVis::In(path) = &pure.functions()[0].vis {
2349 assert!(path.contains("crate"));
2350 } else {
2351 panic!("Expected In visibility");
2352 }
2353 }
2354
2355 #[test]
2358 fn test_attr_derive() {
2359 let pure = PureFile::from_source("#[derive(Clone, Debug)] struct Foo;").unwrap();
2360 let attrs = &pure.structs()[0].attrs;
2361 assert_eq!(attrs.len(), 1);
2362 assert_eq!(attrs[0].path, "derive");
2363 }
2364
2365 #[test]
2366 fn test_attr_simple() {
2367 let pure = PureFile::from_source("#[test] fn f() {}").unwrap();
2368 let attrs = &pure.functions()[0].attrs;
2369 assert_eq!(attrs.len(), 1);
2370 assert_eq!(attrs[0].path, "test");
2371 }
2372
2373 #[test]
2374 fn test_attr_inner() {
2375 let pure = PureFile::from_source("#![allow(unused)]").unwrap();
2376 assert!(pure.attrs[0].is_inner);
2377 }
2378
2379 #[test]
2380 fn test_attr_outer() {
2381 let pure = PureFile::from_source("#[allow(unused)] fn f() {}").unwrap();
2382 assert!(!pure.functions()[0].attrs[0].is_inner);
2383 }
2384
2385 #[test]
2388 fn test_use_path() {
2389 let pure = PureFile::from_source("use std::io::Read;").unwrap();
2390 let uses = pure.uses();
2391 if let PureUseTree::Path { path, tree } = &uses[0].tree {
2393 assert_eq!(path, "std");
2394 if let PureUseTree::Path { path, tree } = tree.as_ref() {
2395 assert_eq!(path, "io");
2396 if let PureUseTree::Name(name) = tree.as_ref() {
2397 assert_eq!(name, "Read");
2398 } else {
2399 panic!("Expected Name");
2400 }
2401 } else {
2402 panic!("Expected Path");
2403 }
2404 } else {
2405 panic!("Expected Path");
2406 }
2407 }
2408
2409 #[test]
2410 fn test_use_glob() {
2411 let pure = PureFile::from_source("use std::io::*;").unwrap();
2412 let uses = pure.uses();
2413 if let PureUseTree::Path { tree, .. } = &uses[0].tree {
2414 if let PureUseTree::Path { tree, .. } = tree.as_ref() {
2415 assert!(matches!(tree.as_ref(), PureUseTree::Glob));
2416 } else {
2417 panic!("Expected Path");
2418 }
2419 } else {
2420 panic!("Expected Path");
2421 }
2422 }
2423
2424 #[test]
2425 fn test_use_group() {
2426 let pure = PureFile::from_source("use std::{io, fs};").unwrap();
2427 let uses = pure.uses();
2428 if let PureUseTree::Path { tree, .. } = &uses[0].tree {
2429 if let PureUseTree::Group(items) = tree.as_ref() {
2430 assert_eq!(items.len(), 2);
2431 } else {
2432 panic!("Expected Group");
2433 }
2434 } else {
2435 panic!("Expected Path");
2436 }
2437 }
2438
2439 #[test]
2440 fn test_use_rename() {
2441 let pure = PureFile::from_source("use std::io::Result as IoResult;").unwrap();
2442 let uses = pure.uses();
2443 if let PureUseTree::Path { tree, .. } = &uses[0].tree {
2445 if let PureUseTree::Path { tree, .. } = tree.as_ref() {
2446 if let PureUseTree::Rename { name, rename } = tree.as_ref() {
2447 assert_eq!(name, "Result");
2448 assert_eq!(rename, "IoResult");
2449 } else {
2450 panic!("Expected Rename");
2451 }
2452 } else {
2453 panic!("Expected Path");
2454 }
2455 } else {
2456 panic!("Expected Path");
2457 }
2458 }
2459
2460 #[test]
2463 fn test_const_item() {
2464 let pure = PureFile::from_source("const MAX: i32 = 100;").unwrap();
2465 if let PureItem::Const(c) = &pure.items[0] {
2466 assert_eq!(c.name, "MAX");
2467 } else {
2468 panic!("Expected Const item");
2469 }
2470 }
2471
2472 #[test]
2473 fn test_static_item() {
2474 let pure = PureFile::from_source("static mut COUNTER: i32 = 0;").unwrap();
2475 if let PureItem::Static(s) = &pure.items[0] {
2476 assert!(s.is_mut);
2477 } else {
2478 panic!("Expected Static item");
2479 }
2480 }
2481
2482 #[test]
2485 fn test_type_alias() {
2486 let pure =
2487 PureFile::from_source("type Result<T> = std::result::Result<T, Error>;").unwrap();
2488 if let PureItem::Type(t) = &pure.items[0] {
2489 assert_eq!(t.name, "Result");
2490 } else {
2491 panic!("Expected Type item");
2492 }
2493 }
2494
2495 #[test]
2498 fn test_mod_declaration() {
2499 let pure = PureFile::from_source("mod foo;").unwrap();
2500 if let PureItem::Mod(m) = &pure.items[0] {
2501 assert!(m.items.is_empty());
2502 } else {
2503 panic!("Expected Mod item");
2504 }
2505 }
2506
2507 #[test]
2508 fn test_mod_inline() {
2509 let pure = PureFile::from_source("mod foo { fn bar() {} }").unwrap();
2510 if let PureItem::Mod(m) = &pure.items[0] {
2511 assert!(!m.items.is_empty());
2512 assert_eq!(m.items.len(), 1);
2513 } else {
2514 panic!("Expected Mod item");
2515 }
2516 }
2517
2518 #[test]
2521 fn test_macro_invocation() {
2522 let pure = PureFile::from_source("include!(\"foo.rs\");").unwrap();
2523 if let PureItem::Macro(m) = &pure.items[0] {
2524 assert_eq!(m.path, "include");
2525 } else {
2526 panic!("Expected Macro item");
2527 }
2528 }
2529
2530 #[test]
2533 fn test_match_arm_guard() {
2534 let pure =
2535 PureFile::from_source("fn f(x: i32) { match x { n if n > 0 => {} _ => {} } }").unwrap();
2536 let f = &pure.functions()[0];
2537 if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
2538 assert!(arms[0].guard.is_some());
2539 } else {
2540 panic!("Expected Match expr");
2541 }
2542 }
2543
2544 #[test]
2547 fn test_path_to_string_simple() {
2548 assert_eq!(
2549 path_to_string(&syn::parse_str::<syn::Path>("std").unwrap()),
2550 "std"
2551 );
2552 }
2553
2554 #[test]
2555 fn test_path_to_string_nested() {
2556 assert_eq!(
2557 path_to_string(&syn::parse_str::<syn::Path>("std::io::Read").unwrap()),
2558 "std::io::Read"
2559 );
2560 }
2561
2562 #[test]
2563 fn test_path_to_string_with_generics() {
2564 let path = syn::parse_str::<syn::Path>("Vec<i32>").unwrap();
2565 let result = path_to_string(&path);
2566 assert!(result.contains("Vec"));
2567 assert!(result.contains("i32"));
2568 }
2569
2570 #[test]
2573 fn test_is_pinned_boxed_future_basic() {
2574 assert!(is_pinned_boxed_future("Pin<Box<dyn Future<Output = ()>>>"));
2575 assert!(is_pinned_boxed_future(
2576 "Pin<Box<dyn Future<Output = Result<T, E>>>>"
2577 ));
2578 assert!(is_pinned_boxed_future(
2579 "Pin<Box<dyn Future<Output = ()> + Send>>"
2580 ));
2581 assert!(is_pinned_boxed_future(
2582 "Pin<Box<dyn Future<Output = ()> + Send + 'static>>"
2583 ));
2584 }
2585
2586 #[test]
2587 fn test_is_pinned_boxed_future_with_spaces() {
2588 assert!(is_pinned_boxed_future(
2590 "Pin < Box < dyn Future < Output = () > > >"
2591 ));
2592 assert!(is_pinned_boxed_future(
2593 "Pin < Box < dyn Future < Output = Result < T , E > > + Send > >"
2594 ));
2595 }
2596
2597 #[test]
2598 fn test_is_pinned_boxed_future_with_path_prefix() {
2599 assert!(is_pinned_boxed_future(
2600 "::core::pin::Pin<Box<dyn Future<Output = ()>>>"
2601 ));
2602 assert!(is_pinned_boxed_future(
2603 "core::pin::Pin<Box<dyn Future<Output = ()>>>"
2604 ));
2605 assert!(is_pinned_boxed_future(
2606 "std::pin::Pin<Box<dyn Future<Output = ()>>>"
2607 ));
2608 assert!(is_pinned_boxed_future(
2609 "::std::pin::Pin<Box<dyn Future<Output = ()>>>"
2610 ));
2611 }
2612
2613 #[test]
2614 fn test_is_pinned_boxed_future_negative() {
2615 assert!(!is_pinned_boxed_future("Result<T, E>"));
2616 assert!(!is_pinned_boxed_future("Option<T>"));
2617 assert!(!is_pinned_boxed_future("Box<dyn Future<Output = ()>>"));
2618 assert!(!is_pinned_boxed_future("Pin<Box<T>>"));
2619 assert!(!is_pinned_boxed_future("Future<Output = ()>"));
2620 }
2621
2622 #[test]
2623 fn test_async_fn_is_async() {
2624 let pure = PureFile::from_source("async fn foo() {}").unwrap();
2625 let f = &pure.functions()[0];
2626 assert!(f.is_async);
2627 assert!(!f.is_async_inferred);
2628 assert!(f.is_effectively_async());
2629 }
2630
2631 #[test]
2632 fn test_sync_fn_not_async() {
2633 let pure = PureFile::from_source("fn foo() {}").unwrap();
2634 let f = &pure.functions()[0];
2635 assert!(!f.is_async);
2636 assert!(!f.is_async_inferred);
2637 assert!(!f.is_effectively_async());
2638 }
2639
2640 #[test]
2641 fn test_async_trait_style_fn_inferred() {
2642 let code = r#"
2644 fn foo(&self) -> Pin<Box<dyn Future<Output = ()> + Send>> {
2645 Box::pin(async { })
2646 }
2647 "#;
2648 let pure = PureFile::from_source(code).unwrap();
2649 let f = &pure.functions()[0];
2650 assert!(!f.is_async, "Should not be explicitly async");
2651 assert!(
2652 f.is_async_inferred,
2653 "Should be inferred as async from return type"
2654 );
2655 assert!(f.is_effectively_async());
2656 }
2657
2658 #[test]
2659 fn test_async_trait_in_impl() {
2660 let code = r#"
2661 struct Foo;
2662 impl Foo {
2663 fn bar(&self) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send>> {
2664 Box::pin(async { Ok(()) })
2665 }
2666 }
2667 "#;
2668 let pure = PureFile::from_source(code).unwrap();
2669 let impls = pure.impls();
2670 assert_eq!(impls.len(), 1);
2671
2672 if let PureImplItem::Fn(method) = &impls[0].items[0] {
2673 assert!(!method.is_async);
2674 assert!(method.is_async_inferred);
2675 assert!(method.is_effectively_async());
2676 } else {
2677 panic!("Expected Fn in impl");
2678 }
2679 }
2680
2681 #[test]
2684 fn test_to_pure_cfg_test_promotes_to_modscope_test() {
2685 let item_mod = syn::parse_str::<syn::ItemMod>("#[cfg(test)] mod tests;").unwrap();
2687 let pure_mod = item_mod.to_pure();
2688 assert_eq!(pure_mod.scope, ModScope::Test, "scope should be Test");
2689 assert!(
2690 pure_mod.attrs.is_empty(),
2691 "cfg(test) should be removed from attrs, got: {:?}",
2692 pure_mod.attrs
2693 );
2694 assert_eq!(pure_mod.name, "tests");
2695 }
2696
2697 #[test]
2698 fn test_to_pure_cfg_not_test_stays_in_attrs() {
2699 let item_mod = syn::parse_str::<syn::ItemMod>("#[cfg(not(test))] mod foo;").unwrap();
2701 let pure_mod = item_mod.to_pure();
2702 assert_eq!(pure_mod.scope, ModScope::Prod, "scope should be Prod");
2703 assert_eq!(
2704 pure_mod.attrs.len(),
2705 1,
2706 "cfg(not(test)) should remain in attrs"
2707 );
2708 assert_eq!(pure_mod.attrs[0].path, "cfg");
2709 assert_eq!(
2710 pure_mod.attrs[0].meta,
2711 PureAttrMeta::List("not (test)".to_string())
2712 );
2713 }
2714
2715 #[test]
2716 fn test_to_pure_cfg_feature_stays_in_attrs() {
2717 let item_mod =
2719 syn::parse_str::<syn::ItemMod>("#[cfg(feature = \"bar\")] mod foo;").unwrap();
2720 let pure_mod = item_mod.to_pure();
2721 assert_eq!(pure_mod.scope, ModScope::Prod, "scope should be Prod");
2722 assert_eq!(
2723 pure_mod.attrs.len(),
2724 1,
2725 "cfg(feature=...) should remain in attrs"
2726 );
2727 assert_eq!(pure_mod.attrs[0].path, "cfg");
2728 }
2729
2730 #[test]
2731 fn test_to_pure_cfg_test_inline_mod_promotes() {
2732 let item_mod =
2734 syn::parse_str::<syn::ItemMod>("#[cfg(test)] mod tests { fn helper() {} }").unwrap();
2735 let pure_mod = item_mod.to_pure();
2736 assert_eq!(pure_mod.scope, ModScope::Test, "scope should be Test");
2737 assert!(
2738 pure_mod.attrs.is_empty(),
2739 "cfg(test) should be removed from attrs"
2740 );
2741 assert_eq!(pure_mod.items.len(), 1);
2742 }
2743
2744 fn is_syn_cfg_test_attr(attr: &syn::Attribute) -> bool {
2745 use quote::ToTokens;
2746 if !matches!(attr.style, syn::AttrStyle::Outer) {
2747 return false;
2748 }
2749 let path_str = attr.path().to_token_stream().to_string();
2750 if path_str != "cfg" {
2751 return false;
2752 }
2753 let meta_str = attr.to_token_stream().to_string();
2754 meta_str.contains("cfg") && meta_str.contains("test")
2755 }
2756
2757 #[test]
2758 fn test_to_pure_cfg_test_roundtrip() {
2759 let item_mod = syn::parse_str::<syn::ItemMod>("#[cfg(test)] mod tests {}").unwrap();
2761 let pure_mod = item_mod.to_pure();
2762 assert_eq!(pure_mod.scope, ModScope::Test);
2763 assert!(
2764 pure_mod.attrs.is_empty(),
2765 "cfg(test) should be removed from attrs at parse"
2766 );
2767
2768 let syn_out = pure_mod.to_syn().unwrap();
2769 let cfg_test_count = syn_out
2770 .attrs
2771 .iter()
2772 .filter(|a| is_syn_cfg_test_attr(a))
2773 .count();
2774 assert_eq!(
2775 cfg_test_count, 1,
2776 "#[cfg(test)] should appear exactly once after round-trip, got {} times",
2777 cfg_test_count
2778 );
2779 }
2780}