Skip to content

Commit 097251d

Browse files
authored
Merge pull request #286 from nyurik/litstr
Optimize performance for string literals in Display::fmt
2 parents 0717de3 + cd79876 commit 097251d

File tree

3 files changed

+79
-4
lines changed

3 files changed

+79
-4
lines changed

impl/src/attr.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub struct Attrs<'a> {
1818
#[derive(Clone)]
1919
pub struct Display<'a> {
2020
pub original: &'a Attribute,
21+
pub use_write_str: bool,
2122
pub fmt: LitStr,
2223
pub args: TokenStream,
2324
pub has_bonus_display: bool,
@@ -103,10 +104,14 @@ fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Resu
103104
return Ok(());
104105
}
105106

107+
let fmt = input.parse()?;
108+
let args = parse_token_expr(input, false)?;
106109
let display = Display {
107110
original: attr,
108-
fmt: input.parse()?,
109-
args: parse_token_expr(input, false)?,
111+
// This will be updated later if format_args are still required (i.e. has braces)
112+
use_write_str: args.is_empty(),
113+
fmt,
114+
args,
110115
has_bonus_display: false,
111116
implied_bounds: Set::new(),
112117
};
@@ -196,8 +201,18 @@ impl ToTokens for Display<'_> {
196201
fn to_tokens(&self, tokens: &mut TokenStream) {
197202
let fmt = &self.fmt;
198203
let args = &self.args;
199-
tokens.extend(quote! {
200-
::core::write!(__formatter, #fmt #args)
204+
205+
// Currently compiler is unable to generate as efficient code for
206+
// write!(f, "text") as it does for f.write_str("text"),
207+
// so handle it here when the literal string has no braces/no args.
208+
tokens.extend(if self.use_write_str {
209+
quote! {
210+
__formatter.write_str(#fmt)
211+
}
212+
} else {
213+
quote! {
214+
::core::write!(__formatter, #fmt #args)
215+
}
201216
});
202217
}
203218
}

impl/src/fmt.rs

+5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ impl Display<'_> {
3232
}
3333
}
3434

35+
if self.use_write_str && fmt.contains('}') {
36+
self.use_write_str = false;
37+
}
38+
3539
while let Some(brace) = read.find('{') {
40+
self.use_write_str = false;
3641
out += &read[..brace + 1];
3742
read = &read[brace + 1..];
3843
if read.starts_with('{') {

tests/test_display.rs

+55
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,58 @@ fn test_keyword() {
301301

302302
assert("error: 1", Error);
303303
}
304+
305+
#[test]
306+
fn test_str_special_chars() {
307+
#[derive(Error, Debug)]
308+
pub enum Error {
309+
#[error("brace left {{")]
310+
BraceLeft,
311+
#[error("brace left 2 \x7B\x7B")]
312+
BraceLeft2,
313+
#[error("brace left 3 \u{7B}\u{7B}")]
314+
BraceLeft3,
315+
#[error("brace right }}")]
316+
BraceRight,
317+
#[error("brace right 2 \x7D\x7D")]
318+
BraceRight2,
319+
#[error("brace right 3 \u{7D}\u{7D}")]
320+
BraceRight3,
321+
#[error(
322+
"new_\
323+
line"
324+
)]
325+
NewLine,
326+
#[error("escape24 \u{78}")]
327+
Escape24,
328+
}
329+
330+
assert("brace left {", Error::BraceLeft);
331+
assert("brace left 2 {", Error::BraceLeft2);
332+
assert("brace left 3 {", Error::BraceLeft3);
333+
assert("brace right }", Error::BraceRight);
334+
assert("brace right 2 }", Error::BraceRight2);
335+
assert("brace right 3 }", Error::BraceRight3);
336+
assert("new_line", Error::NewLine);
337+
assert("escape24 x", Error::Escape24);
338+
}
339+
340+
#[test]
341+
fn test_raw_str() {
342+
#[derive(Error, Debug)]
343+
pub enum Error {
344+
#[error(r#"raw brace left {{"#)]
345+
BraceLeft,
346+
#[error(r#"raw brace left 2 \x7B"#)]
347+
BraceLeft2,
348+
#[error(r#"raw brace right }}"#)]
349+
BraceRight,
350+
#[error(r#"raw brace right 2 \x7D"#)]
351+
BraceRight2,
352+
}
353+
354+
assert(r#"raw brace left {"#, Error::BraceLeft);
355+
assert(r#"raw brace left 2 \x7B"#, Error::BraceLeft2);
356+
assert(r#"raw brace right }"#, Error::BraceRight);
357+
assert(r#"raw brace right 2 \x7D"#, Error::BraceRight2);
358+
}

0 commit comments

Comments
 (0)