1use proc_macro2::Span;
4use syn::token;
5
6use super::helpers::{ident, try_parse_path};
7use super::{ToSyn, ToSynError};
8use crate::pure::ast::{MacroDelimiter, PureExpr, PureMatchArm, PurePattern};
9
10fn make_label(name: &str) -> syn::Label {
12 syn::Label {
13 name: syn::Lifetime {
14 apostrophe: Span::call_site(),
15 ident: proc_macro2::Ident::new(name, Span::call_site()),
16 },
17 colon_token: token::Colon::default(),
18 }
19}
20
21fn make_lifetime(name: &str) -> syn::Lifetime {
23 syn::Lifetime {
24 apostrophe: Span::call_site(),
25 ident: proc_macro2::Ident::new(name, Span::call_site()),
26 }
27}
28
29impl ToSyn for PurePattern {
30 type Output = syn::Pat;
31
32 fn to_syn(&self) -> Result<syn::Pat, ToSynError> {
33 match self {
34 PurePattern::Ident { name, is_mut } => Ok(syn::Pat::Ident(syn::PatIdent {
35 attrs: vec![],
36 by_ref: None,
37 mutability: if *is_mut {
38 Some(token::Mut::default())
39 } else {
40 None
41 },
42 ident: ident(name),
43 subpat: None,
44 })),
45 PurePattern::Wild => Ok(syn::Pat::Wild(syn::PatWild {
46 attrs: vec![],
47 underscore_token: token::Underscore::default(),
48 })),
49 PurePattern::Tuple(pats) => Ok(syn::Pat::Tuple(syn::PatTuple {
50 attrs: vec![],
51 paren_token: token::Paren::default(),
52 elems: pats.iter().map(|p| p.to_syn()).collect::<Result<_, _>>()?,
53 })),
54 PurePattern::Struct { path, fields, rest } => {
55 let is_tuple_struct = !*rest
61 && !fields.is_empty()
62 && fields.iter().all(|(name, _)| name.parse::<u32>().is_ok());
63 if is_tuple_struct {
64 Ok(syn::Pat::TupleStruct(syn::PatTupleStruct {
65 attrs: vec![],
66 qself: None,
67 path: try_parse_path(path)?,
68 paren_token: token::Paren::default(),
69 elems: fields
70 .iter()
71 .map(|(_, pat)| pat.to_syn())
72 .collect::<Result<_, _>>()?,
73 }))
74 } else {
75 let syn_fields = fields
76 .iter()
77 .map(|(name, pat)| {
78 Ok(syn::FieldPat {
79 attrs: vec![],
80 member: syn::Member::Named(ident(name)),
81 colon_token: Some(token::Colon::default()),
82 pat: Box::new(pat.to_syn()?),
83 })
84 })
85 .collect::<Result<_, ToSynError>>()?;
86 Ok(syn::Pat::Struct(syn::PatStruct {
87 attrs: vec![],
88 qself: None,
89 path: try_parse_path(path)?,
90 brace_token: token::Brace::default(),
91 fields: syn_fields,
92 rest: if *rest {
93 Some(syn::PatRest {
94 attrs: vec![],
95 dot2_token: token::DotDot::default(),
96 })
97 } else {
98 None
99 },
100 }))
101 }
102 }
103 PurePattern::Ref { is_mut, pattern } => Ok(syn::Pat::Reference(syn::PatReference {
104 attrs: vec![],
105 and_token: token::And::default(),
106 mutability: if *is_mut {
107 Some(token::Mut::default())
108 } else {
109 None
110 },
111 pat: Box::new(pattern.to_syn()?),
112 })),
113 PurePattern::Lit(lit) => match syn::parse_str::<syn::Expr>(lit) {
114 Ok(syn::Expr::Lit(lit_expr)) => Ok(syn::Pat::Lit(lit_expr)),
115 Ok(_) => Err(ToSynError::ParsePattern {
116 input: lit.clone(),
117 message: "Expected literal pattern but got non-literal expression".to_string(),
118 }),
119 Err(e) => Err(ToSynError::ParsePattern {
120 input: lit.clone(),
121 message: e.to_string(),
122 }),
123 },
124 PurePattern::Or(pats) => Ok(syn::Pat::Or(syn::PatOr {
125 attrs: vec![],
126 leading_vert: None,
127 cases: pats.iter().map(|p| p.to_syn()).collect::<Result<_, _>>()?,
128 })),
129 PurePattern::Path(path) => {
130 debug_assert!(
133 !path.contains('(') && !path.contains('{') && !path.contains(".."),
134 "PurePattern::Path received a complex pattern '{}'. Use PurePattern::Other instead.",
135 path
136 );
137 Ok(syn::Pat::Path(syn::ExprPath {
138 attrs: vec![],
139 qself: None,
140 path: try_parse_path(path)?,
141 }))
142 }
143 PurePattern::Range {
144 start,
145 end,
146 inclusive,
147 } => {
148 let start_expr = start
149 .as_ref()
150 .map(|s| {
151 syn::parse_str::<syn::Expr>(s).map_err(|e| ToSynError::ParseExpr {
152 input: s.clone(),
153 message: e.to_string(),
154 })
155 })
156 .transpose()?;
157 let end_expr = end
158 .as_ref()
159 .map(|s| {
160 syn::parse_str::<syn::Expr>(s).map_err(|e| ToSynError::ParseExpr {
161 input: s.clone(),
162 message: e.to_string(),
163 })
164 })
165 .transpose()?;
166 let limits = if *inclusive {
167 syn::RangeLimits::Closed(token::DotDotEq::default())
168 } else {
169 syn::RangeLimits::HalfOpen(token::DotDot::default())
170 };
171 Ok(syn::Pat::Range(syn::ExprRange {
172 attrs: vec![],
173 start: start_expr.map(Box::new),
174 limits,
175 end: end_expr.map(Box::new),
176 }))
177 }
178 PurePattern::Slice(pats) => Ok(syn::Pat::Slice(syn::PatSlice {
179 attrs: vec![],
180 bracket_token: token::Bracket::default(),
181 elems: pats.iter().map(|p| p.to_syn()).collect::<Result<_, _>>()?,
182 })),
183 PurePattern::Rest => Ok(syn::Pat::Rest(syn::PatRest {
184 attrs: vec![],
185 dot2_token: token::DotDot::default(),
186 })),
187 PurePattern::Other(s) => {
188 let tokens: proc_macro2::TokenStream =
189 s.parse()
190 .map_err(|e: proc_macro2::LexError| ToSynError::ParsePattern {
191 input: s.clone(),
192 message: format!("{}", e),
193 })?;
194 Ok(syn::Pat::Verbatim(tokens))
195 }
196 }
197 }
198}
199
200impl ToSyn for PureExpr {
201 type Output = syn::Expr;
202
203 fn to_syn(&self) -> Result<syn::Expr, ToSynError> {
204 match self {
205 PureExpr::Lit(lit) => syn::parse_str(lit).map_err(|e| ToSynError::ParseExpr {
206 input: lit.clone(),
207 message: e.to_string(),
208 }),
209 PureExpr::Path(path) => Ok(syn::Expr::Path(syn::ExprPath {
210 attrs: vec![],
211 qself: None,
212 path: try_parse_path(path)?,
213 })),
214 PureExpr::Binary { op, left, right } => {
215 if op == "=" {
217 Ok(syn::Expr::Assign(syn::ExprAssign {
218 attrs: vec![],
219 left: Box::new(left.to_syn()?),
220 eq_token: token::Eq::default(),
221 right: Box::new(right.to_syn()?),
222 }))
223 } else {
224 Ok(syn::Expr::Binary(syn::ExprBinary {
225 attrs: vec![],
226 left: Box::new(left.to_syn()?),
227 op: syn::parse_str(op).map_err(|e| ToSynError::ParseExpr {
228 input: op.clone(),
229 message: format!("Failed to parse binary operator: {}", e),
230 })?,
231 right: Box::new(right.to_syn()?),
232 }))
233 }
234 }
235 PureExpr::Unary { op, expr } => Ok(syn::Expr::Unary(syn::ExprUnary {
236 attrs: vec![],
237 op: syn::parse_str(op).map_err(|e| ToSynError::ParseExpr {
238 input: op.clone(),
239 message: format!("Failed to parse unary operator: {}", e),
240 })?,
241 expr: Box::new(expr.to_syn()?),
242 })),
243 PureExpr::Call { func, args } => Ok(syn::Expr::Call(syn::ExprCall {
244 attrs: vec![],
245 func: Box::new(func.to_syn()?),
246 paren_token: token::Paren::default(),
247 args: args.iter().map(|a| a.to_syn()).collect::<Result<_, _>>()?,
248 })),
249 PureExpr::MethodCall {
250 receiver,
251 method,
252 turbofish,
253 args,
254 } => Ok(syn::Expr::MethodCall(syn::ExprMethodCall {
255 attrs: vec![],
256 receiver: Box::new(receiver.to_syn()?),
257 dot_token: token::Dot::default(),
258 method: ident(method),
259 turbofish: turbofish.as_ref().and_then(|t| {
260 let args_str = format!("::<{}>", t);
262 syn::parse_str::<syn::AngleBracketedGenericArguments>(&args_str).ok()
263 }),
264 paren_token: token::Paren::default(),
265 args: args.iter().map(|a| a.to_syn()).collect::<Result<_, _>>()?,
266 })),
267 PureExpr::Field { expr, field } => Ok(syn::Expr::Field(syn::ExprField {
268 attrs: vec![],
269 base: Box::new(expr.to_syn()?),
270 dot_token: token::Dot::default(),
271 member: if let Ok(index) = field.parse::<u32>() {
272 syn::Member::Unnamed(syn::Index {
273 index,
274 span: Span::call_site(),
275 })
276 } else {
277 syn::Member::Named(ident(field))
278 },
279 })),
280 PureExpr::Index { expr, index } => Ok(syn::Expr::Index(syn::ExprIndex {
281 attrs: vec![],
282 expr: Box::new(expr.to_syn()?),
283 bracket_token: token::Bracket::default(),
284 index: Box::new(index.to_syn()?),
285 })),
286 PureExpr::Block { label, block } => Ok(syn::Expr::Block(syn::ExprBlock {
287 attrs: vec![],
288 label: label.as_ref().map(|l| make_label(l)),
289 block: block.to_syn()?,
290 })),
291 PureExpr::If {
292 cond,
293 then_branch,
294 else_branch,
295 } => {
296 let else_b = else_branch
297 .as_ref()
298 .map(|e| Ok((token::Else::default(), Box::new(e.to_syn()?))))
299 .transpose()?;
300 Ok(syn::Expr::If(syn::ExprIf {
301 attrs: vec![],
302 if_token: token::If::default(),
303 cond: Box::new(cond.to_syn()?),
304 then_branch: then_branch.to_syn()?,
305 else_branch: else_b,
306 }))
307 }
308 PureExpr::Match { expr, arms } => Ok(syn::Expr::Match(syn::ExprMatch {
309 attrs: vec![],
310 match_token: token::Match::default(),
311 expr: Box::new(expr.to_syn()?),
312 brace_token: token::Brace::default(),
313 arms: arms
314 .iter()
315 .map(|a| a.to_syn())
316 .collect::<Result<Vec<_>, _>>()?,
317 })),
318 PureExpr::Loop { label, body } => Ok(syn::Expr::Loop(syn::ExprLoop {
319 attrs: vec![],
320 label: label.as_ref().map(|l| make_label(l)),
321 loop_token: token::Loop::default(),
322 body: body.to_syn()?,
323 })),
324 PureExpr::While { label, cond, body } => Ok(syn::Expr::While(syn::ExprWhile {
325 attrs: vec![],
326 label: label.as_ref().map(|l| make_label(l)),
327 while_token: token::While::default(),
328 cond: Box::new(cond.to_syn()?),
329 body: body.to_syn()?,
330 })),
331 PureExpr::For {
332 label,
333 pat,
334 expr,
335 body,
336 } => Ok(syn::Expr::ForLoop(syn::ExprForLoop {
337 attrs: vec![],
338 label: label.as_ref().map(|l| make_label(l)),
339 for_token: token::For::default(),
340 pat: Box::new(pat.to_syn()?),
341 in_token: token::In::default(),
342 expr: Box::new(expr.to_syn()?),
343 body: body.to_syn()?,
344 })),
345 PureExpr::Return(expr) => {
346 let ret_expr = expr
347 .as_ref()
348 .map(|e| Ok(Box::new(e.to_syn()?)))
349 .transpose()?;
350 Ok(syn::Expr::Return(syn::ExprReturn {
351 attrs: vec![],
352 return_token: token::Return::default(),
353 expr: ret_expr,
354 }))
355 }
356 PureExpr::Break { label, expr } => {
357 let break_expr = expr
358 .as_ref()
359 .map(|e| Ok(Box::new(e.to_syn()?)))
360 .transpose()?;
361 Ok(syn::Expr::Break(syn::ExprBreak {
362 attrs: vec![],
363 break_token: token::Break::default(),
364 label: label.as_ref().map(|l| make_lifetime(l)),
365 expr: break_expr,
366 }))
367 }
368 PureExpr::Continue { label } => Ok(syn::Expr::Continue(syn::ExprContinue {
369 attrs: vec![],
370 continue_token: token::Continue::default(),
371 label: label.as_ref().map(|l| make_lifetime(l)),
372 })),
373 PureExpr::Closure {
374 is_async,
375 is_move,
376 params,
377 ret,
378 body,
379 } => {
380 let inputs = params
381 .iter()
382 .map(|cp| {
383 let pat = cp.pattern.to_syn()?;
384 match &cp.ty {
385 Some(ty) => Ok(syn::Pat::Type(syn::PatType {
386 attrs: vec![],
387 pat: Box::new(pat),
388 colon_token: token::Colon::default(),
389 ty: Box::new(ty.to_syn()?),
390 })),
391 None => Ok(pat),
392 }
393 })
394 .collect::<Result<_, ToSynError>>()?;
395 let output = match ret {
396 Some(ty) => {
397 syn::ReturnType::Type(token::RArrow::default(), Box::new(ty.to_syn()?))
398 }
399 None => syn::ReturnType::Default,
400 };
401 Ok(syn::Expr::Closure(syn::ExprClosure {
402 attrs: vec![],
403 lifetimes: None,
404 constness: None,
405 movability: None,
406 asyncness: if *is_async {
407 Some(token::Async::default())
408 } else {
409 None
410 },
411 capture: if *is_move {
412 Some(token::Move::default())
413 } else {
414 None
415 },
416 or1_token: token::Or::default(),
417 inputs,
418 or2_token: token::Or::default(),
419 output,
420 body: Box::new(body.to_syn()?),
421 }))
422 }
423 PureExpr::Struct { path, fields } => {
424 let syn_fields = fields
425 .iter()
426 .map(|(name, expr)| {
427 Ok(syn::FieldValue {
428 attrs: vec![],
429 member: syn::Member::Named(ident(name)),
430 colon_token: Some(token::Colon::default()),
431 expr: expr.to_syn()?,
432 })
433 })
434 .collect::<Result<_, ToSynError>>()?;
435 Ok(syn::Expr::Struct(syn::ExprStruct {
436 attrs: vec![],
437 qself: None,
438 path: try_parse_path(path)?,
439 brace_token: token::Brace::default(),
440 fields: syn_fields,
441 dot2_token: None,
442 rest: None,
443 }))
444 }
445 PureExpr::Tuple(elems) => Ok(syn::Expr::Tuple(syn::ExprTuple {
446 attrs: vec![],
447 paren_token: token::Paren::default(),
448 elems: elems.iter().map(|e| e.to_syn()).collect::<Result<_, _>>()?,
449 })),
450 PureExpr::Array(elems) => Ok(syn::Expr::Array(syn::ExprArray {
451 attrs: vec![],
452 bracket_token: token::Bracket::default(),
453 elems: elems.iter().map(|e| e.to_syn()).collect::<Result<_, _>>()?,
454 })),
455 PureExpr::Ref { is_mut, expr } => Ok(syn::Expr::Reference(syn::ExprReference {
456 attrs: vec![],
457 and_token: token::And::default(),
458 mutability: if *is_mut {
459 Some(token::Mut::default())
460 } else {
461 None
462 },
463 expr: Box::new(expr.to_syn()?),
464 })),
465 PureExpr::Macro {
466 name,
467 delimiter,
468 tokens,
469 } => {
470 let parsed_tokens: proc_macro2::TokenStream =
471 tokens
472 .parse()
473 .map_err(|e: proc_macro2::LexError| ToSynError::Other {
474 message: format!("Failed to parse macro tokens '{}': {}", tokens, e),
475 })?;
476 Ok(syn::Expr::Macro(syn::ExprMacro {
477 attrs: vec![],
478 mac: syn::Macro {
479 path: try_parse_path(name)?,
480 bang_token: token::Not::default(),
481 delimiter: match delimiter {
482 MacroDelimiter::Paren => {
483 syn::MacroDelimiter::Paren(token::Paren::default())
484 }
485 MacroDelimiter::Brace => {
486 syn::MacroDelimiter::Brace(token::Brace::default())
487 }
488 MacroDelimiter::Bracket => {
489 syn::MacroDelimiter::Bracket(token::Bracket::default())
490 }
491 },
492 tokens: parsed_tokens,
493 },
494 }))
495 }
496 PureExpr::Await(expr) => Ok(syn::Expr::Await(syn::ExprAwait {
497 attrs: vec![],
498 base: Box::new(expr.to_syn()?),
499 dot_token: token::Dot::default(),
500 await_token: token::Await::default(),
501 })),
502 PureExpr::Try(expr) => Ok(syn::Expr::Try(syn::ExprTry {
503 attrs: vec![],
504 expr: Box::new(expr.to_syn()?),
505 question_token: token::Question::default(),
506 })),
507 PureExpr::Range {
508 start,
509 end,
510 inclusive,
511 } => {
512 let start_expr = start
513 .as_ref()
514 .map(|e| Ok(Box::new(e.to_syn()?)))
515 .transpose()?;
516 let end_expr = end
517 .as_ref()
518 .map(|e| Ok(Box::new(e.to_syn()?)))
519 .transpose()?;
520 Ok(syn::Expr::Range(syn::ExprRange {
521 attrs: vec![],
522 start: start_expr,
523 limits: if *inclusive {
524 syn::RangeLimits::Closed(token::DotDotEq::default())
525 } else {
526 syn::RangeLimits::HalfOpen(token::DotDot::default())
527 },
528 end: end_expr,
529 }))
530 }
531 PureExpr::Cast { expr, ty } => Ok(syn::Expr::Cast(syn::ExprCast {
532 attrs: vec![],
533 expr: Box::new(expr.to_syn()?),
534 as_token: token::As::default(),
535 ty: Box::new(ty.to_syn()?),
536 })),
537 PureExpr::Let { pattern, expr } => Ok(syn::Expr::Let(syn::ExprLet {
538 attrs: vec![],
539 let_token: token::Let::default(),
540 pat: Box::new(pattern.to_syn()?),
541 eq_token: token::Eq::default(),
542 expr: Box::new(expr.to_syn()?),
543 })),
544 PureExpr::Async { is_move, body } => Ok(syn::Expr::Async(syn::ExprAsync {
545 attrs: vec![],
546 async_token: token::Async::default(),
547 capture: if *is_move {
548 Some(token::Move::default())
549 } else {
550 None
551 },
552 block: body.to_syn()?,
553 })),
554 PureExpr::Unsafe(body) => Ok(syn::Expr::Unsafe(syn::ExprUnsafe {
555 attrs: vec![],
556 unsafe_token: token::Unsafe::default(),
557 block: body.to_syn()?,
558 })),
559 PureExpr::Repeat { expr, len } => Ok(syn::Expr::Repeat(syn::ExprRepeat {
560 attrs: vec![],
561 bracket_token: token::Bracket::default(),
562 expr: Box::new(expr.to_syn()?),
563 semi_token: token::Semi::default(),
564 len: Box::new(len.to_syn()?),
565 })),
566 PureExpr::Other(s) => syn::parse_str(s).map_err(|e| ToSynError::ParseExpr {
567 input: s.clone(),
568 message: e.to_string(),
569 }),
570 }
571 }
572}
573
574impl ToSyn for PureMatchArm {
575 type Output = syn::Arm;
576
577 fn to_syn(&self) -> Result<syn::Arm, ToSynError> {
578 let guard = self
579 .guard
580 .as_ref()
581 .map(|g| Ok((token::If::default(), Box::new(g.to_syn()?))))
582 .transpose()?;
583 Ok(syn::Arm {
584 attrs: vec![],
585 pat: self.pattern.to_syn()?,
586 guard,
587 fat_arrow_token: token::FatArrow::default(),
588 body: Box::new(self.body.to_syn()?),
589 comma: Some(token::Comma::default()),
590 })
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597 use crate::pure::ast::{PureBlock, PureClosureParam, PureStmt};
598 use quote::ToTokens;
599
600 #[test]
601 fn test_pure_pattern_ident() {
602 let pat = PurePattern::Ident {
603 name: "x".to_string(),
604 is_mut: false,
605 };
606 let syn_pat = pat.to_syn().unwrap();
607 let output = syn_pat.to_token_stream().to_string();
608 assert_eq!(output.trim(), "x");
609 }
610
611 #[test]
612 fn test_pure_pattern_mut_ident() {
613 let pat = PurePattern::Ident {
614 name: "x".to_string(),
615 is_mut: true,
616 };
617 let syn_pat = pat.to_syn().unwrap();
618 let output = syn_pat.to_token_stream().to_string();
619 assert!(output.contains("mut"), "Output: {}", output);
620 }
621
622 #[test]
623 fn test_pure_expr_binary() {
624 let expr = PureExpr::Binary {
625 op: "+".to_string(),
626 left: Box::new(PureExpr::Path("a".to_string())),
627 right: Box::new(PureExpr::Path("b".to_string())),
628 };
629 let syn_expr = expr.to_syn().unwrap();
630 let output = syn_expr.to_token_stream().to_string();
631 assert!(output.contains("+"), "Output: {}", output);
632 }
633
634 #[test]
635 fn test_pure_expr_closure() {
636 let expr = PureExpr::Closure {
637 is_async: false,
638 is_move: true,
639 params: vec![PureClosureParam::untyped(PurePattern::Ident {
640 name: "x".to_string(),
641 is_mut: false,
642 })],
643 ret: None,
644 body: Box::new(PureExpr::Path("x".to_string())),
645 };
646 let syn_expr = expr.to_syn().unwrap();
647 let output = syn_expr.to_token_stream().to_string();
648 assert!(output.contains("move"), "Output: {}", output);
649 }
650
651 #[test]
652 fn test_pure_match_arm() {
653 let arm = PureMatchArm {
654 pattern: PurePattern::Ident {
655 name: "x".to_string(),
656 is_mut: false,
657 },
658 guard: None,
659 body: PureExpr::Path("x".to_string()),
660 };
661 let syn_arm = arm.to_syn().unwrap();
662 let output = syn_arm.to_token_stream().to_string();
663 assert!(output.contains("=>"), "Output: {}", output);
664 }
665
666 #[test]
667 fn test_pure_expr_labeled_loop() {
668 let expr = PureExpr::Loop {
669 label: Some("outer".to_string()),
670 body: PureBlock { stmts: vec![] },
671 };
672 let syn_expr = expr.to_syn().unwrap();
673 let output = syn_expr.to_token_stream().to_string();
674 assert!(
675 output.contains("'outer") || output.contains("' outer"),
676 "Output should contain label: {}",
677 output
678 );
679 assert!(
680 output.contains("loop"),
681 "Output should contain 'loop': {}",
682 output
683 );
684 }
685
686 #[test]
687 fn test_pure_expr_labeled_while() {
688 let expr = PureExpr::While {
689 label: Some("my_loop".to_string()),
690 cond: Box::new(PureExpr::Lit("true".to_string())),
691 body: PureBlock { stmts: vec![] },
692 };
693 let syn_expr = expr.to_syn().unwrap();
694 let output = syn_expr.to_token_stream().to_string();
695 assert!(
696 output.contains("'my_loop") || output.contains("' my_loop"),
697 "Output should contain label: {}",
698 output
699 );
700 assert!(
701 output.contains("while"),
702 "Output should contain 'while': {}",
703 output
704 );
705 }
706
707 #[test]
708 fn test_pure_expr_labeled_for() {
709 let expr = PureExpr::For {
710 label: Some("iter_loop".to_string()),
711 pat: PurePattern::Ident {
712 name: "i".to_string(),
713 is_mut: false,
714 },
715 expr: Box::new(PureExpr::Path("items".to_string())),
716 body: PureBlock { stmts: vec![] },
717 };
718 let syn_expr = expr.to_syn().unwrap();
719 let output = syn_expr.to_token_stream().to_string();
720 assert!(
721 output.contains("'iter_loop") || output.contains("' iter_loop"),
722 "Output should contain label: {}",
723 output
724 );
725 assert!(
726 output.contains("for"),
727 "Output should contain 'for': {}",
728 output
729 );
730 }
731
732 #[test]
733 fn test_pure_expr_labeled_block() {
734 let expr = PureExpr::Block {
735 label: Some("my_block".to_string()),
736 block: PureBlock {
737 stmts: vec![PureStmt::Expr(PureExpr::Lit("42".to_string()))],
738 },
739 };
740 let syn_expr = expr.to_syn().unwrap();
741 let output = syn_expr.to_token_stream().to_string();
742 assert!(
743 output.contains("'my_block") || output.contains("' my_block"),
744 "Output should contain label: {}",
745 output
746 );
747 }
748
749 #[test]
750 fn test_pure_expr_break_with_label() {
751 let expr = PureExpr::Break {
752 label: Some("outer".to_string()),
753 expr: None,
754 };
755 let syn_expr = expr.to_syn().unwrap();
756 let output = syn_expr.to_token_stream().to_string();
757 assert!(
758 output.contains("break 'outer") || output.contains("break ' outer"),
759 "Output should contain 'break 'outer': {}",
760 output
761 );
762 }
763
764 #[test]
765 fn test_pure_expr_break_with_label_and_value() {
766 let expr = PureExpr::Break {
767 label: Some("block".to_string()),
768 expr: Some(Box::new(PureExpr::Lit("42".to_string()))),
769 };
770 let syn_expr = expr.to_syn().unwrap();
771 let output = syn_expr.to_token_stream().to_string();
772 assert!(
773 output.contains("break 'block 42") || output.contains("break ' block 42"),
774 "Output should contain 'break 'block 42': {}",
775 output
776 );
777 }
778
779 #[test]
780 fn test_pure_expr_continue_with_label() {
781 let expr = PureExpr::Continue {
782 label: Some("loop_label".to_string()),
783 };
784 let syn_expr = expr.to_syn().unwrap();
785 let output = syn_expr.to_token_stream().to_string();
786 assert!(
787 output.contains("continue 'loop_label") || output.contains("continue ' loop_label"),
788 "Output should contain 'continue 'loop_label': {}",
789 output
790 );
791 }
792
793 #[test]
794 fn test_pure_expr_continue_without_label() {
795 let expr = PureExpr::Continue { label: None };
796 let syn_expr = expr.to_syn().unwrap();
797 let output = syn_expr.to_token_stream().to_string();
798 assert_eq!(output.trim(), "continue");
799 }
800
801 #[test]
802 fn test_pure_expr_break_without_label() {
803 let expr = PureExpr::Break {
804 label: None,
805 expr: None,
806 };
807 let syn_expr = expr.to_syn().unwrap();
808 let output = syn_expr.to_token_stream().to_string();
809 assert_eq!(output.trim(), "break");
810 }
811
812 #[test]
813 fn test_pure_expr_loop_without_label() {
814 let expr = PureExpr::Loop {
815 label: None,
816 body: PureBlock { stmts: vec![] },
817 };
818 let syn_expr = expr.to_syn().unwrap();
819 let output = syn_expr.to_token_stream().to_string();
820 assert!(output.contains("loop"), "Output: {}", output);
821 assert!(
822 !output.contains("'"),
823 "Output should not contain label quote: {}",
824 output
825 );
826 }
827}