use {
proc_macro_warning::FormattedWarning,
proc_macro2::{
Span,
TokenStream,
},
quote::{
ToTokens,
quote,
},
std::sync::atomic::{
AtomicUsize,
Ordering,
},
};
static GLOBAL_WARNING_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub struct WarningEmitter {
warnings: Vec<TokenStream>,
}
impl WarningEmitter {
pub fn new() -> Self {
Self {
warnings: Vec::new(),
}
}
pub fn warn(
&mut self,
span: Span,
message: impl Into<String>,
) {
let id = GLOBAL_WARNING_COUNTER.fetch_add(1, Ordering::Relaxed);
let name = format!("_fp_macros_warning_{id}");
let warning = FormattedWarning::new_deprecated(&name, message, span);
let warning_tokens = warning.into_token_stream();
self.warnings.push(quote! {
#[expect(clippy::let_unit_value, reason = "proc-macro-warning emits `let _ = _w` where _w: ()")]
#warning_tokens
});
}
#[allow(dead_code, reason = "API kept for completeness")]
pub fn is_empty(&self) -> bool {
self.warnings.is_empty()
}
pub fn into_tokens(self) -> Vec<TokenStream> {
self.warnings
}
}
impl Default for WarningEmitter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[expect(
clippy::indexing_slicing,
reason = "Tests use panicking operations for brevity and clarity"
)]
mod tests {
use super::*;
#[test]
fn test_new_is_empty() {
let emitter = WarningEmitter::new();
assert!(emitter.is_empty());
}
#[test]
fn test_warn_makes_nonempty() {
let mut emitter = WarningEmitter::new();
emitter.warn(Span::call_site(), "test warning");
assert!(!emitter.is_empty());
}
#[test]
fn test_into_tokens_empty() {
let emitter = WarningEmitter::new();
assert!(emitter.into_tokens().is_empty());
}
#[test]
fn test_into_tokens_count() {
let mut emitter = WarningEmitter::new();
emitter.warn(Span::call_site(), "warning 1");
emitter.warn(Span::call_site(), "warning 2");
emitter.warn(Span::call_site(), "warning 3");
assert_eq!(emitter.into_tokens().len(), 3);
}
#[test]
fn test_unique_names() {
let mut emitter = WarningEmitter::new();
emitter.warn(Span::call_site(), "first");
emitter.warn(Span::call_site(), "second");
emitter.warn(Span::call_site(), "third");
let tokens = emitter.into_tokens();
let token_strings: Vec<String> = tokens.iter().map(|t| t.to_string()).collect();
assert!(token_strings[0].contains("_fp_macros_warning_"));
assert!(token_strings[1].contains("_fp_macros_warning_"));
assert!(token_strings[2].contains("_fp_macros_warning_"));
assert_ne!(token_strings[0], token_strings[1]);
assert_ne!(token_strings[1], token_strings[2]);
assert_ne!(token_strings[0], token_strings[2]);
}
}