use core::fmt;
use std::borrow::Cow;
use std::fmt::Write;
use std::mem;
use std::str::FromStr;
use parser::node::{Call, Macro, MacroArg, Ws};
use parser::{Expr, Span, WithSpan};
use proc_macro2::TokenStream;
use quote::quote_spanned;
use crate::generator::node::AstLevel;
use crate::generator::{Generator, LocalMeta, RenderFor, is_copyable};
use crate::heritage::Context;
use crate::integration::Buffer;
use crate::{CompileError, HashMap, SizeHint, field_new, quote_into};
pub(crate) struct MacroInvocation<'a, 'b> {
pub callsite_ctx: &'b Context<'a>,
pub callsite_span: Span,
pub callsite_ws: Ws,
pub call_args: &'b [WithSpan<Box<Expr<'a>>>],
pub call: Option<&'a WithSpan<Call<'a>>>,
pub macro_def: &'a Macro<'a>,
pub macro_ctx: &'b Context<'a>,
}
impl<'a, 'b> MacroInvocation<'a, 'b> {
pub(crate) fn write<'h>(
&self,
buf: &'b mut Buffer,
generator: &mut Generator<'a, 'h>,
render_for: RenderFor,
) -> Result<SizeHint, CompileError> {
if generator
.seen_callers
.iter()
.any(|(s, _)| std::ptr::eq(*s, self.macro_def))
{
let mut message = "Found recursion in macro calls:".to_owned();
for (m, f) in &generator.seen_callers {
if let Some(f) = f {
write!(message, "{f}").unwrap();
} else {
write!(message, "\n`{}`", m.name.escape_debug()).unwrap();
}
}
return Err(self
.callsite_ctx
.generate_error(message, self.callsite_span));
} else {
generator.seen_callers.push((
self.macro_def,
self.callsite_ctx.file_info_of(self.callsite_span),
));
}
generator.push_locals(|this| {
if let Some(call) = self.call {
this.locals.insert(
"caller".into(),
LocalMeta::caller(call, self.callsite_ctx.clone()),
);
}
self.ensure_arg_count()?;
this.flush_ws(self.callsite_ws); let mut content = Buffer::new();
let mut size_hint = this.write_buf_writable(self.callsite_ctx, &mut content)?;
this.prepare_ws(self.macro_def.ws1);
self.write_preamble(&mut content, this)?;
size_hint += this.handle(
self.macro_ctx,
&self.macro_def.nodes,
&mut content,
AstLevel::Nested,
render_for,
)?;
this.flush_ws(self.macro_def.ws2);
size_hint += this.write_buf_writable(self.callsite_ctx, &mut content)?;
let content = content.into_token_stream();
quote_into!(buf, self.callsite_ctx.span_for_node(self.callsite_span), {{ #content }});
this.prepare_ws(self.callsite_ws);
this.seen_callers.pop();
Ok(size_hint)
})
}
fn handle_macro_arg<'h>(
&self,
expr: &WithSpan<Box<Expr<'a>>>,
arg: &MacroArg<'a>,
buf: &mut Buffer,
generator: &mut Generator<'a, 'h>,
) -> Result<(), CompileError> {
let mut ty_buf = Buffer::new();
let span = self.callsite_ctx.span_for_node(arg.name.span());
if let Some(ref ty) = arg.ty {
ty_buf.write_token(syn::Token![:], span);
ty_buf.write_token(syn::Token![&], span);
generator.visit_ty_generic(self.callsite_ctx, &mut ty_buf, ty, span);
}
match &***expr {
&Expr::Var(name) if name != "self" => {
let var = generator.locals.resolve_or_self(name);
if arg.ty.is_none() {
generator
.locals
.insert(Cow::Borrowed(*arg.name), LocalMeta::var_with_ref(var));
} else {
let id = field_new(&arg.name, span);
let var = TokenStream::from_str(&var).expect("invalid variable name");
buf.write_tokens(quote_spanned! { span => let #id #ty_buf = &#var; });
generator
.locals
.insert_with_default(Cow::Borrowed(*arg.name));
}
}
Expr::AssociatedItem(obj, associated_item) => {
let mut associated_item_buf = Buffer::new();
generator.visit_associated_item(
self.callsite_ctx,
&mut associated_item_buf,
obj,
associated_item,
)?;
let associated_item = associated_item_buf.into_token_stream().to_string();
let var = generator
.locals
.resolve(&associated_item)
.unwrap_or(associated_item);
if arg.ty.is_none() {
generator
.locals
.insert(Cow::Borrowed(*arg.name), LocalMeta::var_with_ref(var));
} else {
let id = field_new(&arg.name, span);
let var = TokenStream::from_str(&var).expect("invalid variable name");
buf.write_tokens(quote_spanned! { span => let #id #ty_buf = &#var; });
generator
.locals
.insert_with_default(Cow::Borrowed(*arg.name));
}
}
_ => {
let mut value = Buffer::new();
value.write_tokens(generator.visit_expr_root(self.callsite_ctx, expr)?);
let id = field_new(&arg.name, span);
buf.write_tokens(if !is_copyable(expr) || arg.ty.is_some() {
quote_spanned! { span => let #id #ty_buf = &(#value); }
} else {
quote_spanned! { span => let #id #ty_buf = #value; }
});
generator
.locals
.insert_with_default(Cow::Borrowed(*arg.name));
}
}
Ok(())
}
fn write_preamble<'h>(
&self,
buf: &'b mut Buffer,
generator: &mut Generator<'a, 'h>,
) -> Result<(), CompileError> {
let mut named_arguments = HashMap::default();
if let Some(Expr::NamedArgument(_, _)) = self.call_args.last().map(|expr| &***expr) {
for arg in self.call_args.iter().rev() {
let &Expr::NamedArgument(arg_name, _) = &***arg else {
break;
};
let Some(index) = self
.macro_def
.args
.iter()
.position(|arg| arg.name == arg_name)
else {
return Err(self.callsite_ctx.generate_error(
format_args!(
"no argument named `{}` in macro `{}`",
arg_name.escape_debug(),
self.macro_def.name.escape_debug(),
),
self.callsite_span,
));
};
named_arguments.insert(&**arg_name, (index, arg));
}
}
let mut used_args = vec![None; self.macro_def.args.len()];
for (index, call_arg) in self.call_args.iter().enumerate() {
let (index, expr) = match &***call_arg {
Expr::NamedArgument(arg_name, _) => {
let Some((index, expr)) = named_arguments.get(&**arg_name) else {
unreachable!()
};
(*index, *expr)
}
_ => (index, call_arg),
};
used_args[index] = Some(expr);
}
for (index, used_arg) in used_args.into_iter().enumerate() {
let arg = &self.macro_def.args[index];
let expr = match &used_arg {
Some(expr) => expr,
None => {
if let Some(default_value) = &arg.default {
default_value
} else {
return Err(self.callsite_ctx.generate_error(
format_args!("missing `{}` argument", arg.name.escape_debug()),
self.callsite_span,
));
}
}
};
self.handle_macro_arg(expr, arg, buf, generator)?;
}
Ok(())
}
fn ensure_arg_count(&self) -> Result<(), CompileError> {
if self.call_args.len() > self.macro_def.args.len() {
return Err(self.callsite_ctx.generate_error(
format_args!(
"macro `{}` expected {} argument{}, found {}",
self.macro_def.name.escape_debug(),
self.macro_def.args.len(),
if self.macro_def.args.len() > 1 {
"s"
} else {
""
},
self.call_args.len(),
),
self.callsite_span,
));
}
let mut args: Vec<_> = self
.macro_def
.args
.iter()
.map(|arg| Some(arg.name))
.collect();
for (pos, arg) in self.call_args.iter().enumerate() {
let pos = match ***arg {
Expr::NamedArgument(name, ..) => {
self.macro_def.args.iter().position(|arg| arg.name == name)
}
_ => Some(pos),
};
if let Some(pos) = pos
&& mem::take(&mut args[pos]).is_none()
{
return Err(self.callsite_ctx.generate_error(
format_args!(
"argument `{}` was passed more than once when calling macro `{}`",
self.macro_def.args[pos].name.escape_debug(),
self.macro_def.name.escape_debug(),
),
arg.span(),
));
}
}
for (pos, arg) in self.macro_def.args.iter().enumerate() {
if arg.default.is_some() {
args[pos] = None;
}
}
struct FmtMissing<'a, I> {
count: usize,
missing: I,
name: WithSpan<&'a str>,
}
impl<'a, I: Iterator<Item = WithSpan<&'a str>> + Clone> fmt::Display for FmtMissing<'a, I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut iter = self.missing.clone();
if self.count == 1 {
let a = iter.next().unwrap();
write!(
f,
"missing argument when calling macro `{}`: `{}`",
self.name.escape_debug(),
a.escape_debug(),
)
} else {
write!(
f,
"missing arguments when calling macro `{}`: ",
self.name.escape_debug(),
)?;
for (idx, a) in iter.enumerate() {
if idx == self.count - 1 {
write!(f, " and ")?;
} else if idx > 0 {
write!(f, ", ")?;
}
write!(f, "`{}`", a.escape_debug())?;
}
Ok(())
}
}
}
let missing = args.iter().filter_map(|o| *o);
let count = missing.clone().count();
if count == 0 {
return Ok(());
}
let fmt_missing = FmtMissing {
count,
missing,
name: self.macro_def.name,
};
Err(self
.callsite_ctx
.generate_error(fmt_missing, self.callsite_span))
}
}