summaryrefslogtreecommitdiffstats
path: root/third_party/rust/tracing-attributes/src/attr.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/tracing-attributes/src/attr.rs
parentInitial commit. (diff)
downloadfirefox-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.rs413
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);
+}