Skip to content

Commit

Permalink
Rollup merge of rust-lang#74056 - fusion-engineering-forks:fmt-argume…
Browse files Browse the repository at this point in the history
…nts-as-str, r=Amanieu

Add Arguments::as_str().

There exist quite a few macros in the Rust ecosystem which use `format_args!()` for formatting, but special case the one-argument case for optimization:

```rust
#[macro_export]
macro_rules! some_macro {
    ($s:expr) => { /* print &str directly, no formatting, no buffers */ };
    ($s:expr, $($tt:tt)*) => { /* use format_args to write to a buffer first */ }
}
```

E.g. [here](https://github.com/rust-embedded/cortex-m-semihosting/blob/7a961f0fbe6eb1b29a7ebde4bad4b9cf5f842b31/src/macros.rs#L48-L58), [here](https://github.com/rust-lang-nursery/failure/blob/20f9a9e223b7cd71aed541d050cc73a747fc00c4/src/macros.rs#L9-L17), and [here](https://github.com/fusion-engineering/px4-rust/blob/7b679cd6da9ffd95f36f6526d88345f8b36121da/px4/src/logging.rs#L45-L52).

The problem with these is that a forgotten argument such as in `some_macro!("{}")` will not be diagnosed, but just prints `"{}"`.

With this PR, it is possible to handle the no-arguments case separately *after* `format_args!()`, while simplifying the macro. Then these macros can give the proper error about a missing argument, just like `print!("{}")` does, while still using the same optimized implementation as before.

This is even more important with [RFC 2795](rust-lang/rfcs#2795), to make sure `some_macro!("{some_variable}")` works as expected.
  • Loading branch information
Manishearth committed Jul 17, 2020
2 parents db3e572 + 9c3353b commit 41c73e4
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 290 deletions.
47 changes: 44 additions & 3 deletions src/libcore/fmt/mod.rs
Expand Up @@ -324,7 +324,7 @@ impl<'a> Arguments<'a> {
#[doc(hidden)]
#[inline]
#[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")]
pub fn new_v1(pieces: &'a [&'a str], args: &'a [ArgumentV1<'a>]) -> Arguments<'a> {
pub fn new_v1(pieces: &'a [&'static str], args: &'a [ArgumentV1<'a>]) -> Arguments<'a> {
Arguments { pieces, fmt: None, args }
}

Expand All @@ -338,7 +338,7 @@ impl<'a> Arguments<'a> {
#[inline]
#[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")]
pub fn new_v1_formatted(
pieces: &'a [&'a str],
pieces: &'a [&'static str],
args: &'a [ArgumentV1<'a>],
fmt: &'a [rt::v1::Argument],
) -> Arguments<'a> {
Expand Down Expand Up @@ -399,7 +399,7 @@ impl<'a> Arguments<'a> {
#[derive(Copy, Clone)]
pub struct Arguments<'a> {
// Format string pieces to print.
pieces: &'a [&'a str],
pieces: &'a [&'static str],

// Placeholder specs, or `None` if all specs are default (as in "{}{}").
fmt: Option<&'a [rt::v1::Argument]>,
Expand All @@ -409,6 +409,47 @@ pub struct Arguments<'a> {
args: &'a [ArgumentV1<'a>],
}

impl<'a> Arguments<'a> {
/// Get the formatted string, if it has no arguments to be formatted.
///
/// This can be used to avoid allocations in the most trivial case.
///
/// # Examples
///
/// ```rust
/// #![feature(fmt_as_str)]
///
/// use core::fmt::Arguments;
///
/// fn write_str(_: &str) { /* ... */ }
///
/// fn write_fmt(args: &Arguments) {
/// if let Some(s) = args.as_str() {
/// write_str(s)
/// } else {
/// write_str(&args.to_string());
/// }
/// }
/// ```
///
/// ```rust
/// #![feature(fmt_as_str)]
///
/// assert_eq!(format_args!("hello").as_str(), Some("hello"));
/// assert_eq!(format_args!("").as_str(), Some(""));
/// assert_eq!(format_args!("{}", 1).as_str(), None);
/// ```
#[unstable(feature = "fmt_as_str", issue = "74442")]
#[inline]
pub fn as_str(&self) -> Option<&'static str> {
match (self.pieces, self.args) {
([], []) => Some(""),
([s], []) => Some(s),
_ => None,
}
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl Debug for Arguments<'_> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
Expand Down
5 changes: 4 additions & 1 deletion src/libcore/macros/mod.rs
Expand Up @@ -6,9 +6,12 @@ macro_rules! panic {
() => (
$crate::panic!("explicit panic")
);
($msg:expr) => (
($msg:literal) => (
$crate::panicking::panic($msg)
);
($msg:expr) => (
$crate::panic!("{}", $crate::convert::identity::<&str>($msg))
);
($msg:expr,) => (
$crate::panic!($msg)
);
Expand Down
2 changes: 1 addition & 1 deletion src/libcore/panicking.rs
Expand Up @@ -36,7 +36,7 @@ use crate::panic::{Location, PanicInfo};
#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))]
#[track_caller]
#[lang = "panic"] // needed by codegen for panic on overflow and other `Assert` MIR terminators
pub fn panic(expr: &str) -> ! {
pub fn panic(expr: &'static str) -> ! {
if cfg!(feature = "panic_immediate_abort") {
super::intrinsics::abort()
}
Expand Down

0 comments on commit 41c73e4

Please sign in to comment.