#![allow(rustdoc::redundant_explicit_links)]
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{punctuated::Punctuated, Lifetime, Token};
use syntax::CbitForExpr;
mod syntax;
#[proc_macro]
pub fn cbit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = syn::parse_macro_input!(input as CbitForExpr);
let core_ = quote! { ::core };
let ops_ = quote! { #core_::ops };
let pin_ = quote! { #core_::pin };
let task_ = quote! { #core_::task };
let future_ = quote! { #core_::future };
let option_ = quote! { #core_::option::Option };
let hg_did_run = Ident::new("__cbit_internal_did_run", Span::mixed_site());
let hg_body_input = Ident::new("__cbit_internal_body_input", Span::mixed_site());
let hg_how_to_resolve_pending =
Ident::new("__cbit_internal_how_to_resolve_pending", Span::mixed_site());
let hg_break_result = Ident::new("__cbit_internal_break_result", Span::mixed_site());
let hg_body = Ident::new("__cbit_internal_body", Span::mixed_site());
let hg_end_result = Ident::new("__cbit_internal_end_result", Span::mixed_site());
let hg_result = Ident::new("__cbit_internal_result", Span::mixed_site());
#[expect(non_snake_case)]
let hg_OurControlFlowResult =
Ident::new("__CbitInternalOurControlFlowResult", Span::mixed_site());
#[expect(non_snake_case)]
let hg_OurControlFlow = Ident::new("__CbitInternalOurControlFlow", Span::mixed_site());
let hg_absorber_magic_innermost = Lifetime::new(
"'__cbit_internal_absorber_magic_innermost",
Span::mixed_site(),
);
let empty_punct_list = Punctuated::new();
let in_break_labels = input
.breaks
.as_ref()
.map_or(&empty_punct_list, |breaks| &breaks.lt);
let derive_early_break_variant_name =
|lt: &Lifetime| Ident::new(&format!("EarlyBreakTo_{}", lt.ident), lt.span());
let derive_early_continue_variant_name =
|lt: &Lifetime| Ident::new(&format!("EarlyContinueTo_{}", lt.ident), lt.span());
let control_flow_enum_def;
let control_flow_ty_decl;
let control_flow_ty_use;
{
let break_variant_names = in_break_labels
.iter()
.map(|v| derive_early_break_variant_name(&v.lt))
.collect::<Vec<_>>();
let continue_variant_names = in_break_labels
.iter()
.filter(|&v| v.kw_loop.is_some())
.map(|v| derive_early_continue_variant_name(&v.lt));
control_flow_enum_def = quote! {
#[allow(non_camel_case_types)]
#[allow(clippy::enum_variant_names)]
enum #hg_OurControlFlowResult<EarlyReturn, EarlyBreak #(, #break_variant_names)*> {
EarlyReturn(EarlyReturn),
EarlyBreak(EarlyBreak),
#(#break_variant_names (#break_variant_names),)*
#(#continue_variant_names,)*
}
};
control_flow_ty_decl = quote! {
#[allow(non_camel_case_types)]
type #hg_OurControlFlow<EarlyReturn, EarlyBreak #(, #break_variant_names)*> = #ops_::ControlFlow<
#hg_OurControlFlowResult<EarlyReturn, EarlyBreak #(, #break_variant_names)*>,
EarlyBreak,
>;
};
let underscores =
(0..(break_variant_names.len() + 2)).map(|_| Token));
control_flow_ty_use = quote! { #hg_OurControlFlow<#(#underscores),*> };
}
let aborter = |resolution: TokenStream| {
quote! {
#hg_how_to_resolve_pending = #option_::Some(#resolution);
#future_::pending::<()>().await;
#core_::unreachable!();
}
};
let for_body = input.body.body;
let for_body = {
let body_input_pat = &input.body_pattern;
let optional_label = &input.label;
let break_aborter = aborter(quote! {
#ops_::ControlFlow::Break(#hg_OurControlFlowResult::EarlyBreak(#hg_break_result))
});
quote! {
#hg_absorber_magic_innermost: {
let mut #hg_did_run = false;
let #hg_break_result = #optional_label loop {
if #hg_did_run {
break #hg_absorber_magic_innermost #core_::default::Default::default();
}
#hg_did_run = true;
let #body_input_pat = #hg_body_input.take().unwrap();
let #hg_break_result = { #for_body };
#[allow(unreachable_code)]
break #hg_absorber_magic_innermost #hg_break_result;
};
#[allow(unreachable_code)]
{
#break_aborter
}
}
}
};
let for_body = {
let mut for_body = for_body;
for break_label_entry in in_break_labels {
let break_label = &break_label_entry.lt;
let break_aborter = {
let variant_name = derive_early_break_variant_name(break_label);
aborter(quote! {
#ops_::ControlFlow::Break(#hg_OurControlFlowResult::#variant_name(#hg_break_result))
})
};
let outer_label = Lifetime::new(
&format!("'__cbit_internal_absorber_magic_for_{}", break_label.ident),
Span::mixed_site(),
);
if break_label_entry.kw_loop.is_some() {
let continue_aborter = {
let variant_name = derive_early_continue_variant_name(break_label);
aborter(quote! {
#ops_::ControlFlow::Break(#hg_OurControlFlowResult::#variant_name)
})
};
for_body = quote! {#outer_label: {
let mut #hg_did_run = false;
let #hg_break_result = #break_label: loop {
if #hg_did_run {
#continue_aborter
}
#hg_did_run = true;
let #hg_break_result = { #for_body };
#[allow(unreachable_code)]
break #outer_label #hg_break_result;
};
#[allow(unreachable_code)]
{
#break_aborter
}
}};
} else {
for_body = quote! {#outer_label: {
let #hg_break_result = #break_label: {
let #hg_break_result = { #for_body };
#[allow(unreachable_code)]
break #outer_label #hg_break_result;
};
#[allow(unreachable_code)]
{
#break_aborter
}
}};
}
}
for_body
};
let for_body = {
let termination_aborter = aborter(quote! { #ops_::ControlFlow::Continue(#hg_end_result) });
quote! {
|#hg_body_input| {
let mut #hg_body_input = #option_::Some(#hg_body_input);
let mut #hg_how_to_resolve_pending = #option_::None;
let #hg_body = #pin_::pin!(async {
let #hg_end_result = { #for_body };
#[allow(unreachable_code)] { #termination_aborter }
});
match #future_::Future::poll(
#hg_body,
&mut #task_::Context::from_waker(&#task_::Waker::noop())
) {
#task_::Poll::Ready(early_return) => #ops_::ControlFlow::Break(
#hg_OurControlFlowResult::EarlyReturn(early_return),
),
#task_::Poll::Pending => #hg_how_to_resolve_pending.expect(
"the async block in a cbit iterator is an implementation detail; do not \
`.await` in it!"
),
}
}
}
};
let break_out_matchers = in_break_labels.iter().map(|v| {
let lt = &v.lt;
let variant_name = derive_early_break_variant_name(lt);
quote! {
#hg_OurControlFlowResult::#variant_name(break_out) => break #lt break_out,
}
});
let continue_out_matchers = in_break_labels
.iter()
.filter(|v| v.kw_loop.is_some())
.map(|v| {
let lt = &v.lt;
let variant_name = derive_early_continue_variant_name(lt);
quote! {
#hg_OurControlFlowResult::#variant_name => continue #lt,
}
});
let driver_call_site = match &input.call {
syntax::AnyCallExpr::Function(call) => {
let driver_attrs = &call.attrs;
let driver_fn_expr = &call.func;
let driver_fn_args = call.args.iter();
quote! {
#(#driver_attrs)*
let #hg_result: #control_flow_ty_use = #driver_fn_expr (#(#driver_fn_args,)* #for_body);
}
}
syntax::AnyCallExpr::Method(call) => {
let driver_attrs = &call.attrs;
let driver_receiver_expr = &call.receiver;
let driver_method = &call.method;
let driver_turbo = &call.turbofish;
let driver_fn_args = call.args.iter();
quote! {
#(#driver_attrs)*
let #hg_result: #control_flow_ty_use =
#driver_receiver_expr.#driver_method #driver_turbo (
#(#driver_fn_args,)*
#for_body
);
}
}
};
quote! {{
#control_flow_enum_def
#control_flow_ty_decl
#driver_call_site
match #hg_result {
#ops_::ControlFlow::Break(result) => match result {
#hg_OurControlFlowResult::EarlyReturn(early_result) => return early_result,
#hg_OurControlFlowResult::EarlyBreak(result) => result,
#(#break_out_matchers)*
#(#continue_out_matchers)*
},
#ops_::ControlFlow::Continue(result) => result,
}
}}
.into()
}