diff --git a/mono-fmt-macro/src/to_tokens.rs b/mono-fmt-macro/src/to_tokens.rs index 2a8606d..cbe5e7d 100644 --- a/mono-fmt-macro/src/to_tokens.rs +++ b/mono-fmt-macro/src/to_tokens.rs @@ -1,6 +1,6 @@ use std::cell::Cell; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use crate::{ @@ -17,6 +17,14 @@ pub(crate) struct Scoped<'a, T> { inner: &'a T, } +fn pos_arg_ident(idx: usize) -> Ident { + Ident::new(&format!("__pos_arg_{idx}"), Span::mixed_site()) +} + +fn named_arg_ident(name: impl std::fmt::Display) -> Ident { + Ident::new(&format!("__named_arg_{name}"), Span::mixed_site()) +} + impl<'a, T> Scoped<'a, T> { pub fn new(input: &'a Input, inner: &'a T, current_position: &'a Cell) -> Self { Self { @@ -46,10 +54,33 @@ impl ToTokens for Scoped<'_, Format<'_>> { fn to_tokens(&self, tokens: &mut TokenStream) { let parts = self.inner.pieces.iter().map(|piece| self.scope(piece)); + let input = &self.input; + + let pos_args = input.positional_args.iter(); + let named_args = input.named_args.iter().map(|(_, expr)| expr); + + let args = pos_args.chain(named_args); + + let pos_idents = input + .positional_args + .iter() + .enumerate() + .map(|(idx, _)| pos_arg_ident(idx)); + + let named_idents = input + .named_args + .iter() + .map(|(name, _)| named_arg_ident(name)); + + let idents = pos_idents.chain(named_idents); + tokens.extend(quote! { - ( - #(#parts),* - ) + #[allow(unused_parens)] + match (#(#args),*) { + (#(#idents),*) => ( + #(#parts),* + ) + } }) } } @@ -80,20 +111,16 @@ impl ToTokens for Scoped<'_, FormatArg<'_>> { let current_position = self.current_position.get(); self.current_position.set(current_position + 1); - self.input.positional_args[current_position].to_token_stream() - } - Some(FormatArgRef::Positional(idx)) => { - self.input.positional_args[idx].to_token_stream() + pos_arg_ident(current_position) } + Some(FormatArgRef::Positional(idx)) => pos_arg_ident(idx), Some(FormatArgRef::Named(name)) => self .input .named_args .iter() .find(|(arg, _)| arg == name) - .map(|(_, expr)| expr.to_token_stream()) - .unwrap_or_else(|| { - Ident::new(name, self.input.format_str.span()).to_token_stream() - }), + .map(|(name, _)| named_arg_ident(name)) + .unwrap_or_else(|| Ident::new(name, self.input.format_str.span())), }; let opt_ty = opt_ty_tokens(self.scope(&self.inner.format_spec.formatter_args)); diff --git a/src/lib.rs b/src/lib.rs index 1b7e111..7ab9949 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,8 +116,19 @@ mod tests { } pub mod uwu { - fn test_format_debug_hex() { - assert_eq!(format!("{:02x?}", b"Foo\0"), "[46, 6f, 6f, 00]"); - assert_eq!(format!("{:02X?}", b"Foo\0"), "[46, 6F, 6F, 00]"); + use std::cell::Cell; + + fn test_expansion() { + let evil = Cell::new(0); + format!( + "{0}{0}{1}{owo}", + { + evil.set(evil.get() + 1); + 0 + }, + 5, + owo = "owo", + ); + // assert_eq!(evil.get(), 1); } } diff --git a/tests/base.rs b/tests/base.rs index 8d3daef..2c52691 100644 --- a/tests/base.rs +++ b/tests/base.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; + #[macro_use] extern crate mono_fmt; @@ -17,3 +19,13 @@ fn test_pointer_formats_data_pointer() { assert_eq!(format!("{s:p}"), format!("{:p}", s.as_ptr())); assert_eq!(format!("{b:p}"), format!("{:p}", b.as_ptr())); } + +#[test] +fn only_eval_once() { + let evil = Cell::new(0); + let _ = format!("{0} {0}", { + evil.set(evil.get() + 1); + 0 + }); + assert_eq!(evil.get(), 1); +}