macros: Remove 'r#' prefix from raw identifiers in field names (#3130)

* macros: Add test involving raw identifier

* macros: Remove 'r#' prefix from raw identifiers in field names
This commit is contained in:
David Tolnay 2025-05-13 03:51:29 -07:00 committed by Hayden Stainsby
parent cffdc830d6
commit 6f1ac9d791
3 changed files with 80 additions and 4 deletions

View File

@ -987,10 +987,11 @@ pub mod subscriber;
pub mod __macro_support {
pub use crate::callsite::Callsite;
use crate::{subscriber::Interest, Metadata};
use core::{fmt, str};
// Re-export the `core` functions that are used in macros. This allows
// a crate to be named `core` and avoid name clashes.
// See here: https://github.com/tokio-rs/tracing/issues/2761
pub use core::{concat, file, format_args, iter::Iterator, line, option::Option};
pub use core::{concat, file, format_args, iter::Iterator, line, option::Option, stringify};
/// Callsite implementation used by macro-generated code.
///
@ -1065,6 +1066,66 @@ pub mod __macro_support {
.build(),
);
}
/// Implementation detail used for constructing FieldSet names from raw
/// identifiers. In `info!(..., r#type = "...")` the macro would end up
/// constructing a name equivalent to `FieldName(*b"type")`.
pub struct FieldName<const N: usize>([u8; N]);
impl<const N: usize> FieldName<N> {
/// Convert `"prefix.r#keyword.suffix"` to `b"prefix.keyword.suffix"`.
pub const fn new(input: &str) -> Self {
let input = input.as_bytes();
let mut output = [0u8; N];
let mut read = 0;
let mut write = 0;
while read < input.len() {
if read + 1 < input.len() && input[read] == b'r' && input[read + 1] == b'#' {
read += 2;
}
output[write] = input[read];
read += 1;
write += 1;
}
assert!(write == N);
Self(output)
}
pub const fn as_str(&self) -> &str {
// SAFETY: Because of the private visibility of self.0, it must have
// been computed by Self::new. So these bytes are all of the bytes
// of some original valid UTF-8 string, but with "r#" substrings
// removed, which cannot have produced invalid UTF-8.
unsafe { str::from_utf8_unchecked(self.0.as_slice()) }
}
}
impl FieldName<0> {
/// For `"prefix.r#keyword.suffix"` compute `"prefix.keyword.suffix".len()`.
pub const fn len(input: &str) -> usize {
// Count occurrences of "r#"
let mut raw = 0;
let mut i = 0;
while i < input.len() {
if input.as_bytes()[i] == b'#' {
raw += 1;
}
i += 1;
}
input.len() - 2 * raw
}
}
impl<const N: usize> fmt::Debug for FieldName<N> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("FieldName")
.field(&self.as_str())
.finish()
}
}
}
#[cfg(feature = "log")]

View File

@ -3096,9 +3096,12 @@ macro_rules! level_to_log {
#[doc(hidden)]
#[macro_export]
macro_rules! __tracing_stringify {
($($t:tt)*) => {
stringify!($($t)*)
};
($($k:ident).+) => {{
const NAME: $crate::__macro_support::FieldName<{
$crate::__macro_support::FieldName::len($crate::__macro_support::stringify!($($k).+))
}> = $crate::__macro_support::FieldName::new($crate::__macro_support::stringify!($($k).+));
NAME.as_str()
}};
}
#[cfg(not(feature = "log"))]

View File

@ -618,3 +618,15 @@ fn keyword_ident_in_field_name() {
with_default(subscriber, || error!(crate = "tracing", "message"));
handle.assert_finished();
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn raw_ident_in_field_name() {
let (subscriber, handle) = subscriber::mock()
.event(expect::event().with_fields(expect::field("this.type").with_value(&"Value")))
.only()
.run_with_handle();
with_default(subscriber, || error!(this.r#type = "Value"));
handle.assert_finished();
}