Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion rust/kernel/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,89 @@
//!
//! This module is intended to be used in place of `core::fmt` in kernel code.

pub use core::fmt::{Arguments, Debug, Display, Error, Formatter, Result, Write};
pub use core::fmt::{Arguments, Debug, Error, Formatter, Result, Write};

/// Internal adapter used to route allow implementations of formatting traits for foreign types.
///
/// It is inserted automatically by the [`fmt!`] macro and is not meant to be used directly.
///
/// [`fmt!`]: crate::prelude::fmt!
#[doc(hidden)]
pub struct Adapter<T>(pub T);

macro_rules! impl_fmt_adapter_forward {
($($trait:ident),* $(,)?) => {
$(
impl<T: $trait> $trait for Adapter<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let Self(t) = self;
$trait::fmt(t, f)
}
}
)*
};
}

use core::fmt::{Binary, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex};
impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, Pointer, LowerExp, UpperExp);

/// A copy of [`core::fmt::Display`] that allows us to implement it for foreign types.
///
/// Types should implement this trait rather than [`core::fmt::Display`]. Together with the
/// [`Adapter`] type and [`fmt!`] macro, it allows for formatting foreign types (e.g. types from
/// core) which do not implement [`core::fmt::Display`] directly.
///
/// [`fmt!`]: crate::prelude::fmt!
pub trait Display {
/// Same as [`core::fmt::Display::fmt`].
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}

impl<T: ?Sized + Display> Display for &T {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Display::fmt(*self, f)
}
}

impl<T: ?Sized + Display> core::fmt::Display for Adapter<&T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let Self(t) = self;
Display::fmt(t, f)
}
}

macro_rules! impl_display_forward {
($(
$( { $($generics:tt)* } )? $ty:ty $( { where $($where:tt)* } )?
),* $(,)?) => {
$(
impl$($($generics)*)? Display for $ty $(where $($where)*)? {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
core::fmt::Display::fmt(self, f)
}
}
)*
};
}

impl_display_forward!(
bool,
char,
core::panic::PanicInfo<'_>,
Arguments<'_>,
i128,
i16,
i32,
i64,
i8,
isize,
str,
u128,
u16,
u32,
u64,
u8,
usize,
{<T: ?Sized>} crate::sync::Arc<T> {where crate::sync::Arc<T>: core::fmt::Display},
{<T: ?Sized>} crate::sync::UniqueArc<T> {where crate::sync::UniqueArc<T>: core::fmt::Display},
);
3 changes: 1 addition & 2 deletions rust/kernel/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use ::ffi::{
pub use crate::alloc::{flags::*, Box, KBox, KVBox, KVVec, KVec, VBox, VVec, Vec};

#[doc(no_inline)]
pub use macros::{export, kunit_tests, module, vtable};
pub use macros::{export, fmt, kunit_tests, module, vtable};

pub use pin_init::{init, pin_data, pin_init, pinned_drop, InPlaceWrite, Init, PinInit, Zeroable};

Expand All @@ -33,7 +33,6 @@ pub use super::{build_assert, build_error};
pub use super::dbg;
pub use super::{dev_alert, dev_crit, dev_dbg, dev_emerg, dev_err, dev_info, dev_notice, dev_warn};
pub use super::{pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
pub use core::format_args as fmt;

pub use super::{try_init, try_pin_init};

Expand Down
94 changes: 94 additions & 0 deletions rust/macros/fmt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0

use proc_macro::{Ident, TokenStream, TokenTree};
use std::collections::BTreeSet;

/// Please see [`crate::fmt`] for documentation.
pub(crate) fn fmt(input: TokenStream) -> TokenStream {
let mut input = input.into_iter();

let first_opt = input.next();
let first_owned_str;
let mut names = BTreeSet::new();
let first_span = {
let Some((mut first_str, first_span)) = (match first_opt.as_ref() {
Some(TokenTree::Literal(first_lit)) => {
first_owned_str = first_lit.to_string();
Some(first_owned_str.as_str()).and_then(|first| {
let first = first.strip_prefix('"')?;
let first = first.strip_suffix('"')?;
Some((first, first_lit.span()))
})
}
_ => None,
}) else {
return first_opt.into_iter().chain(input).collect();
};

// Parse `identifier`s from the format string.
//
// See https://doc.rust-lang.org/std/fmt/index.html#syntax.
while let Some((_, rest)) = first_str.split_once('{') {
first_str = rest;
if let Some(rest) = first_str.strip_prefix('{') {
first_str = rest;
continue;
}
if let Some((name, rest)) = first_str.split_once('}') {
first_str = rest;
let name = name.split_once(':').map_or(name, |(name, _)| name);
if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
names.insert(name);
}
}
}
first_span
};

let adapter = quote_spanned!(first_span => ::kernel::fmt::Adapter);

let mut args = TokenStream::from_iter(first_opt);
{
let mut flush = |args: &mut TokenStream, current: &mut TokenStream| {
let current = std::mem::take(current);
if !current.is_empty() {
let (lhs, rhs) = (|| {
let mut current = current.into_iter();
let mut acc = TokenStream::new();
while let Some(tt) = current.next() {
// Split on `=` only once to handle cases like `a = b = c`.
if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '=') {
names.remove(acc.to_string().as_str());
// Include the `=` itself to keep the handling below uniform.
acc.extend([tt]);
return (Some(acc), current.collect::<TokenStream>());
}
acc.extend([tt]);
}
(None, acc)
})();
args.extend(quote_spanned!(first_span => #lhs #adapter(&#rhs)));
}
};

let mut current = TokenStream::new();
for tt in input {
match &tt {
TokenTree::Punct(p) if p.as_char() == ',' => {
flush(&mut args, &mut current);
&mut args
}
_ => &mut current,
}
.extend([tt]);
}
flush(&mut args, &mut current);
}

for name in names {
let name = Ident::new(name, first_span);
args.extend(quote_spanned!(first_span => , #name = #adapter(&#name)));
}

quote_spanned!(first_span => ::core::format_args!(#args))
}
19 changes: 19 additions & 0 deletions rust/macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
mod quote;
mod concat_idents;
mod export;
mod fmt;
mod helpers;
mod kunit;
mod module;
Expand Down Expand Up @@ -201,6 +202,24 @@ pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
export::export(attr, ts)
}

/// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`].
///
/// This macro allows generating `fmt::Arguments` while ensuring that each argument is wrapped with
/// `::kernel::fmt::Adapter`, which customizes formatting behavior for kernel logging.
///
/// Named arguments used in the format string (e.g. `{foo}`) are detected and resolved from local
/// bindings. All positional and named arguments are automatically wrapped.
///
/// This macro is an implementation detail of other kernel logging macros like [`pr_info!`] and
/// should not typically be used directly.
///
/// [`kernel::fmt::Adapter`]: ../kernel/fmt/struct.Adapter.html
/// [`pr_info!`]: ../kernel/macro.pr_info.html
#[proc_macro]
pub fn fmt(input: TokenStream) -> TokenStream {
fmt::fmt(input)
}

/// Concatenate two identifiers.
///
/// This is useful in macros that need to declare or reference items with names
Expand Down
Loading