Skip to content

Commit

Permalink
macros: Remove 'r#' prefix from raw identifiers in field names
Browse files Browse the repository at this point in the history
  • Loading branch information
dtolnay committed Dec 13, 2024
1 parent e9f1d67 commit cc24c65
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 6 deletions.
64 changes: 62 additions & 2 deletions tracing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1002,12 +1002,12 @@ pub mod span;
pub mod __macro_support {
pub use crate::callsite::{Callsite, Registration};
use crate::{collect::Interest, Metadata};
use core::fmt;
use core::sync::atomic::{AtomicU8, Ordering};
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.
///
Expand Down Expand Up @@ -1195,6 +1195,66 @@ pub mod __macro_support {
.finish()
}
}

/// 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")]
Expand Down
9 changes: 6 additions & 3 deletions tracing/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3069,9 +3069,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"))]
Expand Down
2 changes: 1 addition & 1 deletion tracing/tests/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ fn keyword_ident_in_field_name() {
#[test]
fn raw_ident_in_field_name() {
let (collector, handle) = collector::mock()
.event(expect::event().with_fields(expect::field("this.r#type").with_value(&"Value")))
.event(expect::event().with_fields(expect::field("this.type").with_value(&"Value")))
.only()
.run_with_handle();

Expand Down

0 comments on commit cc24c65

Please sign in to comment.