use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use quote::{quote, ToTokens, TokenStreamExt as _};
use syn::{parse::ParseStream, spanned::Spanned as _, Token};
use crate::{
delegate_entry::DelegateEntry,
expr::NeedsCommaAsArmBody as _,
util::{debug_trace, SynErrorContext as _},
};
#[derive(Clone)]
pub struct DelegateArm {
pub attrs: Vec<syn::Attribute>,
pub path: Option<syn::Path>,
pub path_sep: Option<Token![::]>,
pub _brace_token: syn::token::Brace,
pub entries: Vec<DelegateEntry>,
pub pat: Option<TokenStream2>,
pub guard: Option<(Token![if], TokenStream2)>,
pub fat_arrow_token: Token![=>],
pub body: TokenStream2,
pub comma: Option<Token![,]>,
}
impl ToTokens for DelegateArm {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self.build_arms() {
Ok(arms) => tokens.append_all(&arms),
Err(e) => {
let err = e.to_compile_error();
tokens.append_all(quote! {
_ => { #err; unreachable!("compile error in delegate arm") }
});
}
}
}
}
impl syn::parse::Parse for DelegateArm {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
debug_trace!("parsing arm");
let attrs = input.call(syn::Attribute::parse_outer)?;
debug_trace!("parsing path");
let path = Self::parse_path(input)?;
let path_sep = Self::parse_path_sep(input, path.as_ref())?;
debug_trace!("parsing entries");
let (brace_token, entries) = Self::parse_entries(input)?;
let pat = Self::parse_pat(input)?;
let guard = Self::parse_guard(input)?;
let fat_arrow_token = input.parse()?;
debug_trace!("parsing body tokens");
let body = crate::expr::parse_tokens(input)
.wrap_err(input.error("failed to parse body tokens"))?;
debug_trace!("parsed body tokens: {body}");
let comma = input.parse()?;
Ok(Self {
attrs,
path,
path_sep,
_brace_token: brace_token,
entries,
pat,
guard,
fat_arrow_token,
body,
comma,
})
}
}
impl DelegateArm {
fn parse_path(input: ParseStream<'_>) -> syn::Result<Option<syn::Path>> {
if input.peek(syn::token::Brace) {
return Ok(None);
}
let mut tokens: Vec<TokenTree> = vec![];
while !input.is_empty() {
if input.peek(Token![::]) && input.peek3(syn::token::Brace) {
if tokens.is_empty() {
return Err(syn::Error::new(
input.span(),
"found leading path separator, expected non-crate path",
));
}
break;
}
let tt = input.parse()?;
tokens.push(tt);
}
let ts: TokenStream2 = tokens.into_iter().collect();
debug_trace!("parsed path: {ts}");
syn::parse2(ts)
.map(Some)
.wrap_err(input.error("failed to parse delegate arm path"))
}
fn parse_entries(
input: ParseStream<'_>,
) -> syn::Result<(syn::token::Brace, Vec<DelegateEntry>)> {
let content;
let brace_token = syn::braced!(content in input);
let entries = DelegateEntry::parse_multiple(&content).wrap_err(syn::Error::new(
content.span(),
"failed to parse delegate arm entry",
))?;
Ok((brace_token, entries))
}
fn parse_tokens_until<F>(input: ParseStream<'_>, mut f: F) -> syn::Result<TokenStream2>
where
F: FnMut(ParseStream<'_>, &TokenTree) -> bool,
{
let mut tokens = Vec::<TokenTree>::new();
while !input.is_empty() {
let tt: TokenTree = input.parse()?;
let is_end = f(input, &tt);
tokens.push(tt);
if is_end {
break;
}
}
Ok(tokens.into_iter().collect())
}
fn parse_tokens_with_depth<F>(input: ParseStream<'_>, mut f: F) -> syn::Result<TokenStream2>
where
F: FnMut(ParseStream<'_>, &TokenTree, i32) -> bool,
{
let mut depth: i32 = 0;
Self::parse_tokens_until(input, |input, tt| {
if let TokenTree::Punct(p) = tt {
match p.as_char() {
'(' | '[' | '{' => depth += 1,
')' | ']' | '}' => depth -= 1,
_ => {}
}
}
f(input, tt, depth)
})
}
fn parse_pat(input: ParseStream<'_>) -> syn::Result<Option<TokenStream2>> {
if input.peek(Token![if]) || input.peek(Token![=>]) {
return Ok(None);
}
let tokens = Self::parse_tokens_with_depth(input, |input, _tt, depth| {
depth == 0 && (input.peek(Token![if]) || input.peek(Token![=>]))
})?;
Ok(Some(tokens))
}
fn parse_guard(input: ParseStream<'_>) -> syn::Result<Option<(Token![if], TokenStream2)>> {
if !input.peek(Token![if]) {
return Ok(None);
}
let if_token: Token![if] = input.parse()?;
let tokens = Self::parse_tokens_with_depth(input, |input, _tt, depth| {
depth == 0 && input.peek(Token![=>])
})?;
Ok(Some((if_token, tokens.into_iter().collect())))
}
fn parse_path_sep(
input: ParseStream<'_>,
outer_path: Option<&syn::Path>,
) -> syn::Result<Option<Token![::]>> {
if outer_path.is_some() {
Ok(Some(input.parse()?))
} else {
Ok(None)
}
}
fn build_arms(&self) -> syn::Result<Vec<syn::Arm>> {
let len = self.entries.len();
let is_last = |i: usize| i == len - 1;
self.entries
.iter()
.enumerate()
.map(|(i, entry)| self.build_arm_with(entry, is_last(i)))
.collect()
}
fn build_guard_with(
&self,
entry: &DelegateEntry,
) -> syn::Result<Option<(Token![if], Box<syn::Expr>)>> {
match self.guard {
Some((if_tok, ref guard_ts)) => {
let guard_expr = Self::build_substituted_expr_with(
guard_ts,
guard_ts.span(),
syn::Expr::parse_with_earlier_boundary_rule,
entry,
)
.wrap_err(syn::Error::new(
guard_ts.span(),
"failed to parse guard expression",
))?;
let guard = (if_tok, Box::new(guard_expr));
Ok(Some(guard))
}
None => Ok(None),
}
}
fn build_body_expr_with(&self, entry: &DelegateEntry) -> syn::Result<Box<syn::Expr>> {
let expr = Self::build_substituted_expr_with(
&self.body,
self.body.span(),
syn::Expr::parse_with_earlier_boundary_rule,
entry,
)
.wrap_err_with(|| syn::Error::new(self.body.span(), "failed to parse delegate arm body"))?;
Ok(Box::new(expr))
}
fn build_arm_comma_with(
&self,
body: &syn::Expr,
is_last_entry: bool,
) -> Option<syn::token::Comma> {
self.comma.or_else(|| {
let condition = body.needs_comma() && !is_last_entry;
condition.then(|| {
syn::token::Comma {
spans: [self.body.span(); 1],
}
})
})
}
fn build_arm_with(&self, entry: &DelegateEntry, is_last_entry: bool) -> syn::Result<syn::Arm> {
let attrs = self.attrs.clone();
let pat = self.build_pattern_with(entry)?;
let body = self.build_body_expr_with(entry)?;
let guard = self.build_guard_with(entry)?;
let comma = self.build_arm_comma_with(&body, is_last_entry);
Ok(syn::Arm {
attrs,
pat,
guard,
fat_arrow_token: self.fat_arrow_token,
body,
comma,
})
}
fn build_pattern_with(&self, entry: &DelegateEntry) -> syn::Result<syn::Pat> {
let arm_pat_ts = self.pat.as_ref().map(|ts| {
crate::substitute::substitute(ts, &entry.pat, entry.associated_tokens().as_ref())
});
Self::build_final_pattern(
self.path.as_ref(),
self.path_sep.as_ref(),
&entry.pat,
arm_pat_ts.as_ref(),
)
}
fn build_final_pattern(
path: Option<&syn::Path>,
path_sep: Option<&Token![::]>,
entry_pat: &syn::Pat,
arm_pat_ts: Option<&TokenStream2>,
) -> syn::Result<syn::Pat> {
let verbatim_join = || syn::Pat::Verbatim(quote!(#path #path_sep #entry_pat #arm_pat_ts));
#[allow(
clippy::match_same_arms,
reason = "loses semantic distinction between cases"
)]
match (&entry_pat, &arm_pat_ts) {
(syn::Pat::Ident(_) | syn::Pat::Path(_), _) => Ok(verbatim_join()),
(syn::Pat::TupleStruct(_) | syn::Pat::Struct(_), None) => Ok(verbatim_join()),
(_, Some(_)) => Err(syn::Error::new(
entry_pat.span(),
"entry pattern incompatible with arm pattern",
)),
(_, None) => Ok(entry_pat.clone()),
}
}
fn build_substituted_expr_with<F>(
ts: &TokenStream2,
span: proc_macro2::Span,
f: F,
entry: &DelegateEntry,
) -> syn::Result<syn::Expr>
where
F: FnOnce(ParseStream<'_>) -> syn::Result<syn::Expr>,
{
debug_trace!("tokenizing substituted expr");
debug_trace!("input: {ts}");
let tokens =
crate::substitute::substitute(ts, &entry.pat, entry.associated_tokens().as_ref());
debug_trace!("substituted tokens: {tokens}");
let expr = syn::parse::Parser::parse2(f, tokens).wrap_err(syn::Error::new(
span,
"failed to parse expr after substitution",
))?;
Ok(expr)
}
}