use proc_macro2::Span;
use syn::token;
use super::helpers::{ident, try_parse_path};
use super::{ToSyn, ToSynError};
use crate::pure::ast::{MacroDelimiter, PureExpr, PureMatchArm, PurePattern};
fn make_label(name: &str) -> syn::Label {
syn::Label {
name: syn::Lifetime {
apostrophe: Span::call_site(),
ident: proc_macro2::Ident::new(name, Span::call_site()),
},
colon_token: token::Colon::default(),
}
}
fn make_lifetime(name: &str) -> syn::Lifetime {
syn::Lifetime {
apostrophe: Span::call_site(),
ident: proc_macro2::Ident::new(name, Span::call_site()),
}
}
impl ToSyn for PurePattern {
type Output = syn::Pat;
fn to_syn(&self) -> Result<syn::Pat, ToSynError> {
match self {
PurePattern::Ident { name, is_mut } => Ok(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: if *is_mut {
Some(token::Mut::default())
} else {
None
},
ident: ident(name),
subpat: None,
})),
PurePattern::Wild => Ok(syn::Pat::Wild(syn::PatWild {
attrs: vec![],
underscore_token: token::Underscore::default(),
})),
PurePattern::Tuple(pats) => Ok(syn::Pat::Tuple(syn::PatTuple {
attrs: vec![],
paren_token: token::Paren::default(),
elems: pats.iter().map(|p| p.to_syn()).collect::<Result<_, _>>()?,
})),
PurePattern::Struct { path, fields, rest } => {
let is_tuple_struct = !*rest
&& !fields.is_empty()
&& fields.iter().all(|(name, _)| name.parse::<u32>().is_ok());
if is_tuple_struct {
Ok(syn::Pat::TupleStruct(syn::PatTupleStruct {
attrs: vec![],
qself: None,
path: try_parse_path(path)?,
paren_token: token::Paren::default(),
elems: fields
.iter()
.map(|(_, pat)| pat.to_syn())
.collect::<Result<_, _>>()?,
}))
} else {
let syn_fields = fields
.iter()
.map(|(name, pat)| {
Ok(syn::FieldPat {
attrs: vec![],
member: syn::Member::Named(ident(name)),
colon_token: Some(token::Colon::default()),
pat: Box::new(pat.to_syn()?),
})
})
.collect::<Result<_, ToSynError>>()?;
Ok(syn::Pat::Struct(syn::PatStruct {
attrs: vec![],
qself: None,
path: try_parse_path(path)?,
brace_token: token::Brace::default(),
fields: syn_fields,
rest: if *rest {
Some(syn::PatRest {
attrs: vec![],
dot2_token: token::DotDot::default(),
})
} else {
None
},
}))
}
}
PurePattern::Ref { is_mut, pattern } => Ok(syn::Pat::Reference(syn::PatReference {
attrs: vec![],
and_token: token::And::default(),
mutability: if *is_mut {
Some(token::Mut::default())
} else {
None
},
pat: Box::new(pattern.to_syn()?),
})),
PurePattern::Lit(lit) => match syn::parse_str::<syn::Expr>(lit) {
Ok(syn::Expr::Lit(lit_expr)) => Ok(syn::Pat::Lit(lit_expr)),
Ok(_) => Err(ToSynError::ParsePattern {
input: lit.clone(),
message: "Expected literal pattern but got non-literal expression".to_string(),
}),
Err(e) => Err(ToSynError::ParsePattern {
input: lit.clone(),
message: e.to_string(),
}),
},
PurePattern::Or(pats) => Ok(syn::Pat::Or(syn::PatOr {
attrs: vec![],
leading_vert: None,
cases: pats.iter().map(|p| p.to_syn()).collect::<Result<_, _>>()?,
})),
PurePattern::Path(path) => {
debug_assert!(
!path.contains('(') && !path.contains('{') && !path.contains(".."),
"PurePattern::Path received a complex pattern '{}'. Use PurePattern::Other instead.",
path
);
Ok(syn::Pat::Path(syn::ExprPath {
attrs: vec![],
qself: None,
path: try_parse_path(path)?,
}))
}
PurePattern::Range {
start,
end,
inclusive,
} => {
let start_expr = start
.as_ref()
.map(|s| {
syn::parse_str::<syn::Expr>(s).map_err(|e| ToSynError::ParseExpr {
input: s.clone(),
message: e.to_string(),
})
})
.transpose()?;
let end_expr = end
.as_ref()
.map(|s| {
syn::parse_str::<syn::Expr>(s).map_err(|e| ToSynError::ParseExpr {
input: s.clone(),
message: e.to_string(),
})
})
.transpose()?;
let limits = if *inclusive {
syn::RangeLimits::Closed(token::DotDotEq::default())
} else {
syn::RangeLimits::HalfOpen(token::DotDot::default())
};
Ok(syn::Pat::Range(syn::ExprRange {
attrs: vec![],
start: start_expr.map(Box::new),
limits,
end: end_expr.map(Box::new),
}))
}
PurePattern::Slice(pats) => Ok(syn::Pat::Slice(syn::PatSlice {
attrs: vec![],
bracket_token: token::Bracket::default(),
elems: pats.iter().map(|p| p.to_syn()).collect::<Result<_, _>>()?,
})),
PurePattern::Rest => Ok(syn::Pat::Rest(syn::PatRest {
attrs: vec![],
dot2_token: token::DotDot::default(),
})),
PurePattern::Other(s) => {
let tokens: proc_macro2::TokenStream =
s.parse()
.map_err(|e: proc_macro2::LexError| ToSynError::ParsePattern {
input: s.clone(),
message: format!("{}", e),
})?;
Ok(syn::Pat::Verbatim(tokens))
}
}
}
}
impl ToSyn for PureExpr {
type Output = syn::Expr;
fn to_syn(&self) -> Result<syn::Expr, ToSynError> {
match self {
PureExpr::Lit(lit) => syn::parse_str(lit).map_err(|e| ToSynError::ParseExpr {
input: lit.clone(),
message: e.to_string(),
}),
PureExpr::Path(path) => Ok(syn::Expr::Path(syn::ExprPath {
attrs: vec![],
qself: None,
path: try_parse_path(path)?,
})),
PureExpr::Binary { op, left, right } => {
if op == "=" {
Ok(syn::Expr::Assign(syn::ExprAssign {
attrs: vec![],
left: Box::new(left.to_syn()?),
eq_token: token::Eq::default(),
right: Box::new(right.to_syn()?),
}))
} else {
Ok(syn::Expr::Binary(syn::ExprBinary {
attrs: vec![],
left: Box::new(left.to_syn()?),
op: syn::parse_str(op).map_err(|e| ToSynError::ParseExpr {
input: op.clone(),
message: format!("Failed to parse binary operator: {}", e),
})?,
right: Box::new(right.to_syn()?),
}))
}
}
PureExpr::Unary { op, expr } => Ok(syn::Expr::Unary(syn::ExprUnary {
attrs: vec![],
op: syn::parse_str(op).map_err(|e| ToSynError::ParseExpr {
input: op.clone(),
message: format!("Failed to parse unary operator: {}", e),
})?,
expr: Box::new(expr.to_syn()?),
})),
PureExpr::Call { func, args } => Ok(syn::Expr::Call(syn::ExprCall {
attrs: vec![],
func: Box::new(func.to_syn()?),
paren_token: token::Paren::default(),
args: args.iter().map(|a| a.to_syn()).collect::<Result<_, _>>()?,
})),
PureExpr::MethodCall {
receiver,
method,
turbofish,
args,
} => Ok(syn::Expr::MethodCall(syn::ExprMethodCall {
attrs: vec![],
receiver: Box::new(receiver.to_syn()?),
dot_token: token::Dot::default(),
method: ident(method),
turbofish: turbofish.as_ref().and_then(|t| {
let args_str = format!("::<{}>", t);
syn::parse_str::<syn::AngleBracketedGenericArguments>(&args_str).ok()
}),
paren_token: token::Paren::default(),
args: args.iter().map(|a| a.to_syn()).collect::<Result<_, _>>()?,
})),
PureExpr::Field { expr, field } => Ok(syn::Expr::Field(syn::ExprField {
attrs: vec![],
base: Box::new(expr.to_syn()?),
dot_token: token::Dot::default(),
member: if let Ok(index) = field.parse::<u32>() {
syn::Member::Unnamed(syn::Index {
index,
span: Span::call_site(),
})
} else {
syn::Member::Named(ident(field))
},
})),
PureExpr::Index { expr, index } => Ok(syn::Expr::Index(syn::ExprIndex {
attrs: vec![],
expr: Box::new(expr.to_syn()?),
bracket_token: token::Bracket::default(),
index: Box::new(index.to_syn()?),
})),
PureExpr::Block { label, block } => Ok(syn::Expr::Block(syn::ExprBlock {
attrs: vec![],
label: label.as_ref().map(|l| make_label(l)),
block: block.to_syn()?,
})),
PureExpr::If {
cond,
then_branch,
else_branch,
} => {
let else_b = else_branch
.as_ref()
.map(|e| Ok((token::Else::default(), Box::new(e.to_syn()?))))
.transpose()?;
Ok(syn::Expr::If(syn::ExprIf {
attrs: vec![],
if_token: token::If::default(),
cond: Box::new(cond.to_syn()?),
then_branch: then_branch.to_syn()?,
else_branch: else_b,
}))
}
PureExpr::Match { expr, arms } => Ok(syn::Expr::Match(syn::ExprMatch {
attrs: vec![],
match_token: token::Match::default(),
expr: Box::new(expr.to_syn()?),
brace_token: token::Brace::default(),
arms: arms
.iter()
.map(|a| a.to_syn())
.collect::<Result<Vec<_>, _>>()?,
})),
PureExpr::Loop { label, body } => Ok(syn::Expr::Loop(syn::ExprLoop {
attrs: vec![],
label: label.as_ref().map(|l| make_label(l)),
loop_token: token::Loop::default(),
body: body.to_syn()?,
})),
PureExpr::While { label, cond, body } => Ok(syn::Expr::While(syn::ExprWhile {
attrs: vec![],
label: label.as_ref().map(|l| make_label(l)),
while_token: token::While::default(),
cond: Box::new(cond.to_syn()?),
body: body.to_syn()?,
})),
PureExpr::For {
label,
pat,
expr,
body,
} => Ok(syn::Expr::ForLoop(syn::ExprForLoop {
attrs: vec![],
label: label.as_ref().map(|l| make_label(l)),
for_token: token::For::default(),
pat: Box::new(pat.to_syn()?),
in_token: token::In::default(),
expr: Box::new(expr.to_syn()?),
body: body.to_syn()?,
})),
PureExpr::Return(expr) => {
let ret_expr = expr
.as_ref()
.map(|e| Ok(Box::new(e.to_syn()?)))
.transpose()?;
Ok(syn::Expr::Return(syn::ExprReturn {
attrs: vec![],
return_token: token::Return::default(),
expr: ret_expr,
}))
}
PureExpr::Break { label, expr } => {
let break_expr = expr
.as_ref()
.map(|e| Ok(Box::new(e.to_syn()?)))
.transpose()?;
Ok(syn::Expr::Break(syn::ExprBreak {
attrs: vec![],
break_token: token::Break::default(),
label: label.as_ref().map(|l| make_lifetime(l)),
expr: break_expr,
}))
}
PureExpr::Continue { label } => Ok(syn::Expr::Continue(syn::ExprContinue {
attrs: vec![],
continue_token: token::Continue::default(),
label: label.as_ref().map(|l| make_lifetime(l)),
})),
PureExpr::Closure {
is_async,
is_move,
params,
ret,
body,
} => {
let inputs = params
.iter()
.map(|cp| {
let pat = cp.pattern.to_syn()?;
match &cp.ty {
Some(ty) => Ok(syn::Pat::Type(syn::PatType {
attrs: vec![],
pat: Box::new(pat),
colon_token: token::Colon::default(),
ty: Box::new(ty.to_syn()?),
})),
None => Ok(pat),
}
})
.collect::<Result<_, ToSynError>>()?;
let output = match ret {
Some(ty) => {
syn::ReturnType::Type(token::RArrow::default(), Box::new(ty.to_syn()?))
}
None => syn::ReturnType::Default,
};
Ok(syn::Expr::Closure(syn::ExprClosure {
attrs: vec![],
lifetimes: None,
constness: None,
movability: None,
asyncness: if *is_async {
Some(token::Async::default())
} else {
None
},
capture: if *is_move {
Some(token::Move::default())
} else {
None
},
or1_token: token::Or::default(),
inputs,
or2_token: token::Or::default(),
output,
body: Box::new(body.to_syn()?),
}))
}
PureExpr::Struct { path, fields } => {
let syn_fields = fields
.iter()
.map(|(name, expr)| {
Ok(syn::FieldValue {
attrs: vec![],
member: syn::Member::Named(ident(name)),
colon_token: Some(token::Colon::default()),
expr: expr.to_syn()?,
})
})
.collect::<Result<_, ToSynError>>()?;
Ok(syn::Expr::Struct(syn::ExprStruct {
attrs: vec![],
qself: None,
path: try_parse_path(path)?,
brace_token: token::Brace::default(),
fields: syn_fields,
dot2_token: None,
rest: None,
}))
}
PureExpr::Tuple(elems) => Ok(syn::Expr::Tuple(syn::ExprTuple {
attrs: vec![],
paren_token: token::Paren::default(),
elems: elems.iter().map(|e| e.to_syn()).collect::<Result<_, _>>()?,
})),
PureExpr::Array(elems) => Ok(syn::Expr::Array(syn::ExprArray {
attrs: vec![],
bracket_token: token::Bracket::default(),
elems: elems.iter().map(|e| e.to_syn()).collect::<Result<_, _>>()?,
})),
PureExpr::Ref { is_mut, expr } => Ok(syn::Expr::Reference(syn::ExprReference {
attrs: vec![],
and_token: token::And::default(),
mutability: if *is_mut {
Some(token::Mut::default())
} else {
None
},
expr: Box::new(expr.to_syn()?),
})),
PureExpr::Macro {
name,
delimiter,
tokens,
} => {
let parsed_tokens: proc_macro2::TokenStream =
tokens
.parse()
.map_err(|e: proc_macro2::LexError| ToSynError::Other {
message: format!("Failed to parse macro tokens '{}': {}", tokens, e),
})?;
Ok(syn::Expr::Macro(syn::ExprMacro {
attrs: vec![],
mac: syn::Macro {
path: try_parse_path(name)?,
bang_token: token::Not::default(),
delimiter: match delimiter {
MacroDelimiter::Paren => {
syn::MacroDelimiter::Paren(token::Paren::default())
}
MacroDelimiter::Brace => {
syn::MacroDelimiter::Brace(token::Brace::default())
}
MacroDelimiter::Bracket => {
syn::MacroDelimiter::Bracket(token::Bracket::default())
}
},
tokens: parsed_tokens,
},
}))
}
PureExpr::Await(expr) => Ok(syn::Expr::Await(syn::ExprAwait {
attrs: vec![],
base: Box::new(expr.to_syn()?),
dot_token: token::Dot::default(),
await_token: token::Await::default(),
})),
PureExpr::Try(expr) => Ok(syn::Expr::Try(syn::ExprTry {
attrs: vec![],
expr: Box::new(expr.to_syn()?),
question_token: token::Question::default(),
})),
PureExpr::Range {
start,
end,
inclusive,
} => {
let start_expr = start
.as_ref()
.map(|e| Ok(Box::new(e.to_syn()?)))
.transpose()?;
let end_expr = end
.as_ref()
.map(|e| Ok(Box::new(e.to_syn()?)))
.transpose()?;
Ok(syn::Expr::Range(syn::ExprRange {
attrs: vec![],
start: start_expr,
limits: if *inclusive {
syn::RangeLimits::Closed(token::DotDotEq::default())
} else {
syn::RangeLimits::HalfOpen(token::DotDot::default())
},
end: end_expr,
}))
}
PureExpr::Cast { expr, ty } => Ok(syn::Expr::Cast(syn::ExprCast {
attrs: vec![],
expr: Box::new(expr.to_syn()?),
as_token: token::As::default(),
ty: Box::new(ty.to_syn()?),
})),
PureExpr::Let { pattern, expr } => Ok(syn::Expr::Let(syn::ExprLet {
attrs: vec![],
let_token: token::Let::default(),
pat: Box::new(pattern.to_syn()?),
eq_token: token::Eq::default(),
expr: Box::new(expr.to_syn()?),
})),
PureExpr::Async { is_move, body } => Ok(syn::Expr::Async(syn::ExprAsync {
attrs: vec![],
async_token: token::Async::default(),
capture: if *is_move {
Some(token::Move::default())
} else {
None
},
block: body.to_syn()?,
})),
PureExpr::Unsafe(body) => Ok(syn::Expr::Unsafe(syn::ExprUnsafe {
attrs: vec![],
unsafe_token: token::Unsafe::default(),
block: body.to_syn()?,
})),
PureExpr::Repeat { expr, len } => Ok(syn::Expr::Repeat(syn::ExprRepeat {
attrs: vec![],
bracket_token: token::Bracket::default(),
expr: Box::new(expr.to_syn()?),
semi_token: token::Semi::default(),
len: Box::new(len.to_syn()?),
})),
PureExpr::Other(s) => syn::parse_str(s).map_err(|e| ToSynError::ParseExpr {
input: s.clone(),
message: e.to_string(),
}),
}
}
}
impl ToSyn for PureMatchArm {
type Output = syn::Arm;
fn to_syn(&self) -> Result<syn::Arm, ToSynError> {
let guard = self
.guard
.as_ref()
.map(|g| Ok((token::If::default(), Box::new(g.to_syn()?))))
.transpose()?;
Ok(syn::Arm {
attrs: vec![],
pat: self.pattern.to_syn()?,
guard,
fat_arrow_token: token::FatArrow::default(),
body: Box::new(self.body.to_syn()?),
comma: Some(token::Comma::default()),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pure::ast::{PureBlock, PureClosureParam, PureStmt};
use quote::ToTokens;
#[test]
fn test_pure_pattern_ident() {
let pat = PurePattern::Ident {
name: "x".to_string(),
is_mut: false,
};
let syn_pat = pat.to_syn().unwrap();
let output = syn_pat.to_token_stream().to_string();
assert_eq!(output.trim(), "x");
}
#[test]
fn test_pure_pattern_mut_ident() {
let pat = PurePattern::Ident {
name: "x".to_string(),
is_mut: true,
};
let syn_pat = pat.to_syn().unwrap();
let output = syn_pat.to_token_stream().to_string();
assert!(output.contains("mut"), "Output: {}", output);
}
#[test]
fn test_pure_expr_binary() {
let expr = PureExpr::Binary {
op: "+".to_string(),
left: Box::new(PureExpr::Path("a".to_string())),
right: Box::new(PureExpr::Path("b".to_string())),
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(output.contains("+"), "Output: {}", output);
}
#[test]
fn test_pure_expr_closure() {
let expr = PureExpr::Closure {
is_async: false,
is_move: true,
params: vec![PureClosureParam::untyped(PurePattern::Ident {
name: "x".to_string(),
is_mut: false,
})],
ret: None,
body: Box::new(PureExpr::Path("x".to_string())),
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(output.contains("move"), "Output: {}", output);
}
#[test]
fn test_pure_match_arm() {
let arm = PureMatchArm {
pattern: PurePattern::Ident {
name: "x".to_string(),
is_mut: false,
},
guard: None,
body: PureExpr::Path("x".to_string()),
};
let syn_arm = arm.to_syn().unwrap();
let output = syn_arm.to_token_stream().to_string();
assert!(output.contains("=>"), "Output: {}", output);
}
#[test]
fn test_pure_expr_labeled_loop() {
let expr = PureExpr::Loop {
label: Some("outer".to_string()),
body: PureBlock { stmts: vec![] },
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(
output.contains("'outer") || output.contains("' outer"),
"Output should contain label: {}",
output
);
assert!(
output.contains("loop"),
"Output should contain 'loop': {}",
output
);
}
#[test]
fn test_pure_expr_labeled_while() {
let expr = PureExpr::While {
label: Some("my_loop".to_string()),
cond: Box::new(PureExpr::Lit("true".to_string())),
body: PureBlock { stmts: vec![] },
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(
output.contains("'my_loop") || output.contains("' my_loop"),
"Output should contain label: {}",
output
);
assert!(
output.contains("while"),
"Output should contain 'while': {}",
output
);
}
#[test]
fn test_pure_expr_labeled_for() {
let expr = PureExpr::For {
label: Some("iter_loop".to_string()),
pat: PurePattern::Ident {
name: "i".to_string(),
is_mut: false,
},
expr: Box::new(PureExpr::Path("items".to_string())),
body: PureBlock { stmts: vec![] },
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(
output.contains("'iter_loop") || output.contains("' iter_loop"),
"Output should contain label: {}",
output
);
assert!(
output.contains("for"),
"Output should contain 'for': {}",
output
);
}
#[test]
fn test_pure_expr_labeled_block() {
let expr = PureExpr::Block {
label: Some("my_block".to_string()),
block: PureBlock {
stmts: vec![PureStmt::Expr(PureExpr::Lit("42".to_string()))],
},
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(
output.contains("'my_block") || output.contains("' my_block"),
"Output should contain label: {}",
output
);
}
#[test]
fn test_pure_expr_break_with_label() {
let expr = PureExpr::Break {
label: Some("outer".to_string()),
expr: None,
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(
output.contains("break 'outer") || output.contains("break ' outer"),
"Output should contain 'break 'outer': {}",
output
);
}
#[test]
fn test_pure_expr_break_with_label_and_value() {
let expr = PureExpr::Break {
label: Some("block".to_string()),
expr: Some(Box::new(PureExpr::Lit("42".to_string()))),
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(
output.contains("break 'block 42") || output.contains("break ' block 42"),
"Output should contain 'break 'block 42': {}",
output
);
}
#[test]
fn test_pure_expr_continue_with_label() {
let expr = PureExpr::Continue {
label: Some("loop_label".to_string()),
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(
output.contains("continue 'loop_label") || output.contains("continue ' loop_label"),
"Output should contain 'continue 'loop_label': {}",
output
);
}
#[test]
fn test_pure_expr_continue_without_label() {
let expr = PureExpr::Continue { label: None };
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert_eq!(output.trim(), "continue");
}
#[test]
fn test_pure_expr_break_without_label() {
let expr = PureExpr::Break {
label: None,
expr: None,
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert_eq!(output.trim(), "break");
}
#[test]
fn test_pure_expr_loop_without_label() {
let expr = PureExpr::Loop {
label: None,
body: PureBlock { stmts: vec![] },
};
let syn_expr = expr.to_syn().unwrap();
let output = syn_expr.to_token_stream().to_string();
assert!(output.contains("loop"), "Output: {}", output);
assert!(
!output.contains("'"),
"Output should not contain label quote: {}",
output
);
}
}