diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/tracing-attributes/src/attr.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/tracing-attributes/src/attr.rs')
-rw-r--r-- | third_party/rust/tracing-attributes/src/attr.rs | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/third_party/rust/tracing-attributes/src/attr.rs b/third_party/rust/tracing-attributes/src/attr.rs new file mode 100644 index 0000000000..ff875e1797 --- /dev/null +++ b/third_party/rust/tracing-attributes/src/attr.rs @@ -0,0 +1,413 @@ +use std::collections::HashSet; +use syn::{punctuated::Punctuated, Expr, Ident, LitInt, LitStr, Path, Token}; + +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned, ToTokens}; +use syn::ext::IdentExt as _; +use syn::parse::{Parse, ParseStream}; + +#[derive(Clone, Default, Debug)] +pub(crate) struct InstrumentArgs { + level: Option<Level>, + pub(crate) name: Option<LitStr>, + target: Option<LitStr>, + pub(crate) parent: Option<Expr>, + pub(crate) follows_from: Option<Expr>, + pub(crate) skips: HashSet<Ident>, + pub(crate) skip_all: bool, + pub(crate) fields: Option<Fields>, + pub(crate) err_mode: Option<FormatMode>, + pub(crate) ret_mode: Option<FormatMode>, + /// Errors describing any unrecognized parse inputs that we skipped. + parse_warnings: Vec<syn::Error>, +} + +impl InstrumentArgs { + pub(crate) fn level(&self) -> impl ToTokens { + fn is_level(lit: &LitInt, expected: u64) -> bool { + match lit.base10_parse::<u64>() { + Ok(value) => value == expected, + Err(_) => false, + } + } + + match &self.level { + Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("trace") => { + quote!(tracing::Level::TRACE) + } + Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("debug") => { + quote!(tracing::Level::DEBUG) + } + Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("info") => { + quote!(tracing::Level::INFO) + } + Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("warn") => { + quote!(tracing::Level::WARN) + } + Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("error") => { + quote!(tracing::Level::ERROR) + } + Some(Level::Int(ref lit)) if is_level(lit, 1) => quote!(tracing::Level::TRACE), + Some(Level::Int(ref lit)) if is_level(lit, 2) => quote!(tracing::Level::DEBUG), + Some(Level::Int(ref lit)) if is_level(lit, 3) => quote!(tracing::Level::INFO), + Some(Level::Int(ref lit)) if is_level(lit, 4) => quote!(tracing::Level::WARN), + Some(Level::Int(ref lit)) if is_level(lit, 5) => quote!(tracing::Level::ERROR), + Some(Level::Path(ref pat)) => quote!(#pat), + Some(_) => quote! { + compile_error!( + "unknown verbosity level, expected one of \"trace\", \ + \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5" + ) + }, + None => quote!(tracing::Level::INFO), + } + } + + pub(crate) fn target(&self) -> impl ToTokens { + if let Some(ref target) = self.target { + quote!(#target) + } else { + quote!(module_path!()) + } + } + + /// Generate "deprecation" warnings for any unrecognized attribute inputs + /// that we skipped. + /// + /// For backwards compatibility, we need to emit compiler warnings rather + /// than errors for unrecognized inputs. Generating a fake deprecation is + /// the only way to do this on stable Rust right now. + pub(crate) fn warnings(&self) -> impl ToTokens { + let warnings = self.parse_warnings.iter().map(|err| { + let msg = format!("found unrecognized input, {}", err); + let msg = LitStr::new(&msg, err.span()); + // TODO(eliza): This is a bit of a hack, but it's just about the + // only way to emit warnings from a proc macro on stable Rust. + // Eventually, when the `proc_macro::Diagnostic` API stabilizes, we + // should definitely use that instead. + quote_spanned! {err.span()=> + #[warn(deprecated)] + { + #[deprecated(since = "not actually deprecated", note = #msg)] + const TRACING_INSTRUMENT_WARNING: () = (); + let _ = TRACING_INSTRUMENT_WARNING; + } + } + }); + quote! { + { #(#warnings)* } + } + } +} + +impl Parse for InstrumentArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let mut args = Self::default(); + while !input.is_empty() { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::name) { + if args.name.is_some() { + return Err(input.error("expected only a single `name` argument")); + } + let name = input.parse::<StrArg<kw::name>>()?.value; + args.name = Some(name); + } else if lookahead.peek(LitStr) { + // XXX: apparently we support names as either named args with an + // sign, _or_ as unnamed string literals. That's weird, but + // changing it is apparently breaking. + if args.name.is_some() { + return Err(input.error("expected only a single `name` argument")); + } + args.name = Some(input.parse()?); + } else if lookahead.peek(kw::target) { + if args.target.is_some() { + return Err(input.error("expected only a single `target` argument")); + } + let target = input.parse::<StrArg<kw::target>>()?.value; + args.target = Some(target); + } else if lookahead.peek(kw::parent) { + if args.target.is_some() { + return Err(input.error("expected only a single `parent` argument")); + } + let parent = input.parse::<ExprArg<kw::parent>>()?; + args.parent = Some(parent.value); + } else if lookahead.peek(kw::follows_from) { + if args.target.is_some() { + return Err(input.error("expected only a single `follows_from` argument")); + } + let follows_from = input.parse::<ExprArg<kw::follows_from>>()?; + args.follows_from = Some(follows_from.value); + } else if lookahead.peek(kw::level) { + if args.level.is_some() { + return Err(input.error("expected only a single `level` argument")); + } + args.level = Some(input.parse()?); + } else if lookahead.peek(kw::skip) { + if !args.skips.is_empty() { + return Err(input.error("expected only a single `skip` argument")); + } + if args.skip_all { + return Err(input.error("expected either `skip` or `skip_all` argument")); + } + let Skips(skips) = input.parse()?; + args.skips = skips; + } else if lookahead.peek(kw::skip_all) { + if args.skip_all { + return Err(input.error("expected only a single `skip_all` argument")); + } + if !args.skips.is_empty() { + return Err(input.error("expected either `skip` or `skip_all` argument")); + } + let _ = input.parse::<kw::skip_all>()?; + args.skip_all = true; + } else if lookahead.peek(kw::fields) { + if args.fields.is_some() { + return Err(input.error("expected only a single `fields` argument")); + } + args.fields = Some(input.parse()?); + } else if lookahead.peek(kw::err) { + let _ = input.parse::<kw::err>(); + let mode = FormatMode::parse(input)?; + args.err_mode = Some(mode); + } else if lookahead.peek(kw::ret) { + let _ = input.parse::<kw::ret>()?; + let mode = FormatMode::parse(input)?; + args.ret_mode = Some(mode); + } else if lookahead.peek(Token![,]) { + let _ = input.parse::<Token![,]>()?; + } else { + // We found a token that we didn't expect! + // We want to emit warnings for these, rather than errors, so + // we'll add it to the list of unrecognized inputs we've seen so + // far and keep going. + args.parse_warnings.push(lookahead.error()); + // Parse the unrecognized token tree to advance the parse + // stream, and throw it away so we can keep parsing. + let _ = input.parse::<proc_macro2::TokenTree>(); + } + } + Ok(args) + } +} + +struct StrArg<T> { + value: LitStr, + _p: std::marker::PhantomData<T>, +} + +impl<T: Parse> Parse for StrArg<T> { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let _ = input.parse::<T>()?; + let _ = input.parse::<Token![=]>()?; + let value = input.parse()?; + Ok(Self { + value, + _p: std::marker::PhantomData, + }) + } +} + +struct ExprArg<T> { + value: Expr, + _p: std::marker::PhantomData<T>, +} + +impl<T: Parse> Parse for ExprArg<T> { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let _ = input.parse::<T>()?; + let _ = input.parse::<Token![=]>()?; + let value = input.parse()?; + Ok(Self { + value, + _p: std::marker::PhantomData, + }) + } +} + +struct Skips(HashSet<Ident>); + +impl Parse for Skips { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let _ = input.parse::<kw::skip>(); + let content; + let _ = syn::parenthesized!(content in input); + let names: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse_any)?; + let mut skips = HashSet::new(); + for name in names { + if skips.contains(&name) { + return Err(syn::Error::new( + name.span(), + "tried to skip the same field twice", + )); + } else { + skips.insert(name); + } + } + Ok(Self(skips)) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub(crate) enum FormatMode { + Default, + Display, + Debug, +} + +impl Default for FormatMode { + fn default() -> Self { + FormatMode::Default + } +} + +impl Parse for FormatMode { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + if !input.peek(syn::token::Paren) { + return Ok(FormatMode::default()); + } + let content; + let _ = syn::parenthesized!(content in input); + let maybe_mode: Option<Ident> = content.parse()?; + maybe_mode.map_or(Ok(FormatMode::default()), |ident| { + match ident.to_string().as_str() { + "Debug" => Ok(FormatMode::Debug), + "Display" => Ok(FormatMode::Display), + _ => Err(syn::Error::new( + ident.span(), + "unknown error mode, must be Debug or Display", + )), + } + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct Fields(pub(crate) Punctuated<Field, Token![,]>); + +#[derive(Clone, Debug)] +pub(crate) struct Field { + pub(crate) name: Punctuated<Ident, Token![.]>, + pub(crate) value: Option<Expr>, + pub(crate) kind: FieldKind, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum FieldKind { + Debug, + Display, + Value, +} + +impl Parse for Fields { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let _ = input.parse::<kw::fields>(); + let content; + let _ = syn::parenthesized!(content in input); + let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?; + Ok(Self(fields)) + } +} + +impl ToTokens for Fields { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0.to_tokens(tokens) + } +} + +impl Parse for Field { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let mut kind = FieldKind::Value; + if input.peek(Token![%]) { + input.parse::<Token![%]>()?; + kind = FieldKind::Display; + } else if input.peek(Token![?]) { + input.parse::<Token![?]>()?; + kind = FieldKind::Debug; + }; + let name = Punctuated::parse_separated_nonempty_with(input, Ident::parse_any)?; + let value = if input.peek(Token![=]) { + input.parse::<Token![=]>()?; + if input.peek(Token![%]) { + input.parse::<Token![%]>()?; + kind = FieldKind::Display; + } else if input.peek(Token![?]) { + input.parse::<Token![?]>()?; + kind = FieldKind::Debug; + }; + Some(input.parse()?) + } else { + None + }; + Ok(Self { name, value, kind }) + } +} + +impl ToTokens for Field { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(ref value) = self.value { + let name = &self.name; + let kind = &self.kind; + tokens.extend(quote! { + #name = #kind#value + }) + } else if self.kind == FieldKind::Value { + // XXX(eliza): I don't like that fields without values produce + // empty fields rather than local variable shorthand...but, + // we've released a version where field names without values in + // `instrument` produce empty field values, so changing it now + // is a breaking change. agh. + let name = &self.name; + tokens.extend(quote!(#name = tracing::field::Empty)) + } else { + self.kind.to_tokens(tokens); + self.name.to_tokens(tokens); + } + } +} + +impl ToTokens for FieldKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + FieldKind::Debug => tokens.extend(quote! { ? }), + FieldKind::Display => tokens.extend(quote! { % }), + _ => {} + } + } +} + +#[derive(Clone, Debug)] +enum Level { + Str(LitStr), + Int(LitInt), + Path(Path), +} + +impl Parse for Level { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let _ = input.parse::<kw::level>()?; + let _ = input.parse::<Token![=]>()?; + let lookahead = input.lookahead1(); + if lookahead.peek(LitStr) { + Ok(Self::Str(input.parse()?)) + } else if lookahead.peek(LitInt) { + Ok(Self::Int(input.parse()?)) + } else if lookahead.peek(Ident) { + Ok(Self::Path(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + +mod kw { + syn::custom_keyword!(fields); + syn::custom_keyword!(skip); + syn::custom_keyword!(skip_all); + syn::custom_keyword!(level); + syn::custom_keyword!(target); + syn::custom_keyword!(parent); + syn::custom_keyword!(follows_from); + syn::custom_keyword!(name); + syn::custom_keyword!(err); + syn::custom_keyword!(ret); +} |