referrerpolicy=no-referrer-when-downgrade

tracing_gum_proc_macro/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17#![deny(unused_crate_dependencies)]
18#![deny(missing_docs)]
19#![deny(clippy::dbg_macro)]
20
21//! Generative part of `tracing-gum`. See `tracing-gum` for usage documentation.
22
23use proc_macro2::{Ident, Span, TokenStream};
24use quote::{quote, ToTokens};
25use syn::{parse2, parse_quote, punctuated::Punctuated, Result, Token};
26
27mod types;
28
29use self::types::*;
30
31#[cfg(test)]
32mod tests;
33
34/// Print an error message.
35#[proc_macro]
36pub fn error(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
37	gum(item, Level::Error)
38}
39
40/// Print a warning level message.
41#[proc_macro]
42pub fn warn(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
43	gum(item, Level::Warn)
44}
45
46/// Print a warning or debug level message depending on their frequency
47#[proc_macro]
48pub fn warn_if_frequent(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
49	let ArgsIfFrequent { freq, max_rate, rest } = parse2(item.into()).unwrap();
50
51	let freq_expr = freq.expr;
52	let max_rate_expr = max_rate.expr;
53	let debug: proc_macro2::TokenStream = gum(rest.clone().into(), Level::Debug).into();
54	let warn: proc_macro2::TokenStream = gum(rest.into(), Level::Warn).into();
55
56	let stream = quote! {
57		if #freq_expr .is_frequent(#max_rate_expr) {
58			#warn
59		} else {
60			#debug
61		}
62	};
63
64	stream.into()
65}
66
67/// Print a info level message.
68#[proc_macro]
69pub fn info(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
70	gum(item, Level::Info)
71}
72
73/// Print a debug level message.
74#[proc_macro]
75pub fn debug(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
76	gum(item, Level::Debug)
77}
78
79/// Print a trace level message.
80#[proc_macro]
81pub fn trace(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
82	gum(item, Level::Trace)
83}
84
85/// One-size-fits all internal implementation that produces the actual code.
86pub(crate) fn gum(item: proc_macro::TokenStream, level: Level) -> proc_macro::TokenStream {
87	let item: TokenStream = item.into();
88
89	let res = expander::Expander::new("gum")
90		.add_comment("Generated overseer code by `gum::warn!(..)`".to_owned())
91		// `dry=true` until rust-analyzer can selectively disable features so it's
92		// not all red squiggles. Originally: `!cfg!(feature = "expand")`
93		// ISSUE: https://github.com/rust-lang/rust-analyzer/issues/11777
94		.dry(true)
95		.verbose(false)
96		.fmt(expander::Edition::_2021)
97		.maybe_write_to_out_dir(impl_gum2(item, level))
98		.expect("Expander does not fail due to IO in OUT_DIR. qed");
99
100	res.unwrap_or_else(|err| err.to_compile_error()).into()
101}
102
103/// Does the actual parsing and token generation based on `proc_macro2` types.
104///
105/// Required for unit tests.
106pub(crate) fn impl_gum2(orig: TokenStream, level: Level) -> Result<TokenStream> {
107	let args: Args = parse2(orig)?;
108
109	let krate = support_crate();
110	let span = Span::call_site();
111
112	let Args { target, comma, mut values, fmt } = args;
113
114	// find a value or alias called `candidate_hash`.
115	let maybe_candidate_hash = values.iter_mut().find(|value| value.as_ident() == "candidate_hash");
116
117	if let Some(kv) = maybe_candidate_hash {
118		let (ident, rhs_expr, replace_with) = match kv {
119			Value::Alias(alias) => {
120				let ValueWithAliasIdent { alias, marker, expr, .. } = alias.clone();
121				(
122					alias.clone(),
123					expr.to_token_stream(),
124					Some(Value::Value(ValueWithFormatMarker {
125						marker,
126						ident: alias,
127						dot: None,
128						inner: Punctuated::new(),
129					})),
130				)
131			},
132			Value::Value(value) => (value.ident.clone(), value.ident.to_token_stream(), None),
133		};
134
135		// we generate a local value with the same alias name
136		// so replace the expr with just a value
137		if let Some(replace_with) = replace_with {
138			let _old = std::mem::replace(kv, replace_with);
139		};
140
141		// Inject the addition `traceID = % trace_id` identifier
142		// while maintaining trailing comma semantics.
143		let had_trailing_comma = values.trailing_punct();
144		if !had_trailing_comma {
145			values.push_punct(Token![,](span));
146		}
147
148		values.push_value(parse_quote! {
149			traceID = % trace_id
150		});
151		if had_trailing_comma {
152			values.push_punct(Token![,](span));
153		}
154
155		Ok(quote! {
156			if #krate :: enabled!(#target #comma #level) {
157				use ::std::ops::Deref;
158
159				// create a scoped let binding of something that `deref`s to
160				// `Hash`.
161				let value = #rhs_expr;
162				let value = &value;
163				let value: & #krate:: Hash = value.deref();
164				// Do the `deref` to `Hash` and convert to a `TraceIdentifier`.
165				let #ident: #krate:: Hash = * value;
166				let trace_id = #krate:: hash_to_trace_identifier ( #ident );
167				#krate :: event!(
168					#target #comma #level, #values #fmt
169				)
170			}
171		})
172	} else {
173		Ok(quote! {
174				#krate :: event!(
175					#target #comma #level, #values #fmt
176				)
177		})
178	}
179}
180
181/// Extract the support crate path.
182fn support_crate() -> TokenStream {
183	let support_crate_name = if cfg!(test) {
184		quote! {crate}
185	} else {
186		use proc_macro_crate::{crate_name, FoundCrate};
187		let crate_name = crate_name("tracing-gum")
188			.expect("Support crate `tracing-gum` is present in `Cargo.toml`. qed");
189		match crate_name {
190			FoundCrate::Itself => quote! {crate},
191			FoundCrate::Name(name) => Ident::new(&name, Span::call_site()).to_token_stream(),
192		}
193	};
194	support_crate_name
195}