use {
super::input::{
DoInput,
DoStatement,
},
proc_macro2::TokenStream,
quote::quote,
syn::{
Expr,
ExprCall,
ExprPath,
Type,
visit_mut::VisitMut,
},
};
pub fn m_do_worker(input: DoInput) -> syn::Result<TokenStream> {
let brand = input.brand.as_ref();
let ref_mode = input.ref_mode;
let mut result = rewrite_pure(brand, &input.final_expr, ref_mode);
for stmt in input.statements.iter().rev() {
result = match stmt {
DoStatement::Bind {
pattern,
ty,
expr,
} => {
let expr = rewrite_pure(brand, expr, ref_mode);
let closure_param = match (ref_mode, ty) {
(true, None) => quote! { #pattern: &_ },
(true, Some(ty)) => quote! { #pattern: #ty },
(false, Some(ty)) => quote! { #pattern: #ty },
(false, None) => quote! { #pattern },
};
let container = if ref_mode {
quote! { &(#expr) }
} else {
quote! { #expr }
};
if let Some(brand) = brand {
quote! { explicit::bind::<#brand, _, _, _, _>(#container, move |#closure_param| { #result }) }
} else {
quote! { bind(#container, move |#closure_param| { #result }) }
}
}
DoStatement::Let {
pattern,
ty,
expr,
} => {
let binding = match ty {
Some(ty) => quote! { let #pattern: #ty = #expr; },
None => quote! { let #pattern = #expr; },
};
quote! { { #binding #result } }
}
DoStatement::Sequence {
expr,
} => {
let expr = rewrite_pure(brand, expr, ref_mode);
let discard = if ref_mode {
quote! { _: &_ }
} else {
quote! { _ }
};
let container = if ref_mode {
quote! { &(#expr) }
} else {
quote! { #expr }
};
if let Some(brand) = brand {
quote! { explicit::bind::<#brand, _, _, _, _>(#container, move |#discard| { #result }) }
} else {
quote! { bind(#container, move |#discard| { #result }) }
}
}
};
}
Ok(result)
}
pub(crate) fn rewrite_pure(
brand: Option<&Type>,
expr: &Expr,
ref_mode: bool,
) -> TokenStream {
let mut expr = expr.clone();
let mut rewriter = PureRewriter {
brand,
ref_mode,
};
rewriter.visit_expr_mut(&mut expr);
quote! { #expr }
}
struct PureRewriter<'a> {
brand: Option<&'a Type>,
ref_mode: bool,
}
impl VisitMut for PureRewriter<'_> {
fn visit_expr_mut(
&mut self,
expr: &mut Expr,
) {
syn::visit_mut::visit_expr_mut(self, expr);
if let Expr::Call(call) = expr
&& is_bare_pure_call(call)
{
if let Some(brand) = self.brand {
let args = &call.args;
if self.ref_mode {
*expr = syn::parse_quote! { ref_pure::<#brand, _>(&(#args)) };
} else {
*expr = syn::parse_quote! { pure::<#brand, _>(#args) };
}
} else {
*expr = syn::parse_quote! {
compile_error!("pure() requires an explicit brand; use m_do!(Brand { ... }) or write the concrete constructor (e.g., Some(x) instead of pure(x))")
};
}
}
}
}
fn is_bare_pure_call(call: &ExprCall) -> bool {
if let Expr::Path(ExprPath {
qself: None,
path,
..
}) = call.func.as_ref()
{
path.leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments.first().is_some_and(|s| s.ident == "pure" && s.arguments.is_none())
} else {
false
}
}