diff --git a/Cargo.toml b/Cargo.toml index fd6cf03e..d3697541 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thiserror" -version = "1.0.56" +version = "1.0.57" authors = ["David Tolnay "] categories = ["rust-patterns"] description = "derive(Error)" @@ -12,7 +12,7 @@ repository = "https://github.com/dtolnay/thiserror" rust-version = "1.56" [dependencies] -thiserror-impl = { version = "=1.0.56", path = "impl" } +thiserror-impl = { version = "=1.0.57", path = "impl" } [dev-dependencies] anyhow = "1.0.73" diff --git a/impl/Cargo.toml b/impl/Cargo.toml index 1fd1975a..026611c6 100644 --- a/impl/Cargo.toml +++ b/impl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" authors = ["David Tolnay "] description = "Implementation detail of the `thiserror` crate" edition = "2021" diff --git a/impl/src/attr.rs b/impl/src/attr.rs index 4beb8c96..269c69ec 100644 --- a/impl/src/attr.rs +++ b/impl/src/attr.rs @@ -1,6 +1,7 @@ use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree}; use quote::{format_ident, quote, ToTokens}; use std::collections::BTreeSet as Set; +use syn::parse::discouraged::Speculative; use syn::parse::ParseStream; use syn::{ braced, bracketed, parenthesized, token, Attribute, Error, Ident, Index, LitInt, LitStr, Meta, @@ -20,6 +21,7 @@ pub struct Display<'a> { pub original: &'a Attribute, pub fmt: LitStr, pub args: TokenStream, + pub requires_fmt_machinery: bool, pub has_bonus_display: bool, pub implied_bounds: Set<(usize, Trait)>, } @@ -103,10 +105,24 @@ fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Resu return Ok(()); } + let fmt: LitStr = input.parse()?; + + let ahead = input.fork(); + ahead.parse::>()?; + let args = if ahead.is_empty() { + input.advance_to(&ahead); + TokenStream::new() + } else { + parse_token_expr(input, false)? + }; + + let requires_fmt_machinery = !args.is_empty(); + let display = Display { original: attr, - fmt: input.parse()?, - args: parse_token_expr(input, false)?, + fmt, + args, + requires_fmt_machinery, has_bonus_display: false, implied_bounds: Set::new(), }; @@ -196,8 +212,18 @@ impl ToTokens for Display<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let fmt = &self.fmt; let args = &self.args; - tokens.extend(quote! { - ::core::write!(__formatter, #fmt #args) + + // Currently `write!(f, "text")` produces less efficient code than + // `f.write_str("text")`. We recognize the case when the format string + // has no braces and no interpolated values, and generate simpler code. + tokens.extend(if self.requires_fmt_machinery { + quote! { + ::core::write!(__formatter, #fmt #args) + } + } else { + quote! { + __formatter.write_str(#fmt) + } }); } } diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs index 807dfb96..b38b7bf1 100644 --- a/impl/src/fmt.rs +++ b/impl/src/fmt.rs @@ -32,7 +32,10 @@ impl Display<'_> { } } + self.requires_fmt_machinery = self.requires_fmt_machinery || fmt.contains('}'); + while let Some(brace) = read.find('{') { + self.requires_fmt_machinery = true; out += &read[..brace + 1]; read = &read[brace + 1..]; if read.starts_with('{') { diff --git a/src/lib.rs b/src/lib.rs index 73e6e217..717cdc6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -228,7 +228,7 @@ //! //! [`anyhow`]: https://github.com/dtolnay/anyhow -#![doc(html_root_url = "https://docs.rs/thiserror/1.0.56")] +#![doc(html_root_url = "https://docs.rs/thiserror/1.0.57")] #![allow( clippy::module_name_repetitions, clippy::needless_lifetimes, diff --git a/tests/test_display.rs b/tests/test_display.rs index 6f603882..95a210f0 100644 --- a/tests/test_display.rs +++ b/tests/test_display.rs @@ -1,4 +1,4 @@ -#![allow(clippy::uninlined_format_args)] +#![allow(clippy::needless_raw_string_hashes, clippy::uninlined_format_args)] use std::fmt::{self, Display}; use thiserror::Error; @@ -301,3 +301,58 @@ fn test_keyword() { assert("error: 1", Error); } + +#[test] +fn test_str_special_chars() { + #[derive(Error, Debug)] + pub enum Error { + #[error("brace left {{")] + BraceLeft, + #[error("brace left 2 \x7B\x7B")] + BraceLeft2, + #[error("brace left 3 \u{7B}\u{7B}")] + BraceLeft3, + #[error("brace right }}")] + BraceRight, + #[error("brace right 2 \x7D\x7D")] + BraceRight2, + #[error("brace right 3 \u{7D}\u{7D}")] + BraceRight3, + #[error( + "new_\ +line" + )] + NewLine, + #[error("escape24 \u{78}")] + Escape24, + } + + assert("brace left {", Error::BraceLeft); + assert("brace left 2 {", Error::BraceLeft2); + assert("brace left 3 {", Error::BraceLeft3); + assert("brace right }", Error::BraceRight); + assert("brace right 2 }", Error::BraceRight2); + assert("brace right 3 }", Error::BraceRight3); + assert("new_line", Error::NewLine); + assert("escape24 x", Error::Escape24); +} + +#[test] +fn test_raw_str() { + #[derive(Error, Debug)] + pub enum Error { + #[error(r#"raw brace left {{"#)] + BraceLeft, + #[error(r#"raw brace left 2 \x7B"#)] + BraceLeft2, + #[error(r#"raw brace right }}"#)] + BraceRight, + #[error(r#"raw brace right 2 \x7D"#)] + BraceRight2, + } + + assert(r#"raw brace left {"#, Error::BraceLeft); + assert(r#"raw brace left 2 \x7B"#, Error::BraceLeft2); + assert(r#"raw brace right }"#, Error::BraceRight); + assert(r#"raw brace right 2 \x7D"#, Error::BraceRight2); +} diff --git a/tests/ui/no-display.stderr b/tests/ui/no-display.stderr index 0f47c24b..88d00926 100644 --- a/tests/ui/no-display.stderr +++ b/tests/ui/no-display.stderr @@ -15,3 +15,6 @@ note: the trait `std::fmt::Display` must be implemented | | pub trait Display { | ^^^^^^^^^^^^^^^^^ + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `as_display`, perhaps you need to implement it: + candidate #1: `AsDisplay` diff --git a/tests/ui/source-enum-not-error.stderr b/tests/ui/source-enum-not-error.stderr index 4c44742d..649d77df 100644 --- a/tests/ui/source-enum-not-error.stderr +++ b/tests/ui/source-enum-not-error.stderr @@ -2,10 +2,7 @@ error[E0599]: the method `as_dyn_error` exists for reference `&NotError`, but it --> tests/ui/source-enum-not-error.rs:9:14 | 4 | pub struct NotError; - | ------------------- - | | - | doesn't satisfy `NotError: AsDynError<'_>` - | doesn't satisfy `NotError: std::error::Error` + | ------------------- doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error` ... 9 | Broken { source: NotError }, | ^^^^^^ method cannot be called on `&NotError` due to unsatisfied trait bounds @@ -20,3 +17,6 @@ note: the trait `std::error::Error` must be implemented | | pub trait Error: Debug + Display { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it: + candidate #1: `AsDynError` diff --git a/tests/ui/source-enum-unnamed-field-not-error.stderr b/tests/ui/source-enum-unnamed-field-not-error.stderr index da6d225f..a1fe2b5b 100644 --- a/tests/ui/source-enum-unnamed-field-not-error.stderr +++ b/tests/ui/source-enum-unnamed-field-not-error.stderr @@ -2,10 +2,7 @@ error[E0599]: the method `as_dyn_error` exists for reference `&NotError`, but it --> tests/ui/source-enum-unnamed-field-not-error.rs:9:14 | 4 | pub struct NotError; - | ------------------- - | | - | doesn't satisfy `NotError: AsDynError<'_>` - | doesn't satisfy `NotError: std::error::Error` + | ------------------- doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error` ... 9 | Broken(#[source] NotError), | ^^^^^^ method cannot be called on `&NotError` due to unsatisfied trait bounds @@ -20,3 +17,6 @@ note: the trait `std::error::Error` must be implemented | | pub trait Error: Debug + Display { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it: + candidate #1: `AsDynError` diff --git a/tests/ui/source-struct-not-error.stderr b/tests/ui/source-struct-not-error.stderr index b98460fc..07cd67ac 100644 --- a/tests/ui/source-struct-not-error.stderr +++ b/tests/ui/source-struct-not-error.stderr @@ -2,11 +2,7 @@ error[E0599]: the method `as_dyn_error` exists for struct `NotError`, but its tr --> tests/ui/source-struct-not-error.rs:9:5 | 4 | struct NotError; - | --------------- - | | - | method `as_dyn_error` not found for this struct - | doesn't satisfy `NotError: AsDynError<'_>` - | doesn't satisfy `NotError: std::error::Error` + | --------------- method `as_dyn_error` not found for this struct because it doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error` ... 9 | source: NotError, | ^^^^^^ method cannot be called on `NotError` due to unsatisfied trait bounds @@ -19,3 +15,6 @@ note: the trait `std::error::Error` must be implemented | | pub trait Error: Debug + Display { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it: + candidate #1: `AsDynError` diff --git a/tests/ui/source-struct-unnamed-field-not-error.stderr b/tests/ui/source-struct-unnamed-field-not-error.stderr index a23f2682..2022ea67 100644 --- a/tests/ui/source-struct-unnamed-field-not-error.stderr +++ b/tests/ui/source-struct-unnamed-field-not-error.stderr @@ -2,11 +2,7 @@ error[E0599]: the method `as_dyn_error` exists for struct `NotError`, but its tr --> tests/ui/source-struct-unnamed-field-not-error.rs:8:26 | 4 | struct NotError; - | --------------- - | | - | method `as_dyn_error` not found for this struct - | doesn't satisfy `NotError: AsDynError<'_>` - | doesn't satisfy `NotError: std::error::Error` + | --------------- method `as_dyn_error` not found for this struct because it doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error` ... 8 | pub struct ErrorStruct(#[source] NotError); | ^^^^^^ method cannot be called on `NotError` due to unsatisfied trait bounds @@ -19,3 +15,6 @@ note: the trait `std::error::Error` must be implemented | | pub trait Error: Debug + Display { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it: + candidate #1: `AsDynError` diff --git a/tests/ui/transparent-enum-not-error.stderr b/tests/ui/transparent-enum-not-error.stderr index 9be51434..bb836d4e 100644 --- a/tests/ui/transparent-enum-not-error.stderr +++ b/tests/ui/transparent-enum-not-error.stderr @@ -7,10 +7,7 @@ error[E0599]: the method `as_dyn_error` exists for reference `&String`, but its ::: $RUST/alloc/src/string.rs | | pub struct String { - | ----------------- - | | - | doesn't satisfy `String: AsDynError<'_>` - | doesn't satisfy `String: std::error::Error` + | ----------------- doesn't satisfy `String: AsDynError<'_>` or `String: std::error::Error` | = note: the following trait bounds were not satisfied: `String: std::error::Error` diff --git a/tests/ui/transparent-enum-unnamed-field-not-error.stderr b/tests/ui/transparent-enum-unnamed-field-not-error.stderr index 3d23c3a0..f337c592 100644 --- a/tests/ui/transparent-enum-unnamed-field-not-error.stderr +++ b/tests/ui/transparent-enum-unnamed-field-not-error.stderr @@ -7,10 +7,7 @@ error[E0599]: the method `as_dyn_error` exists for reference `&String`, but its ::: $RUST/alloc/src/string.rs | | pub struct String { - | ----------------- - | | - | doesn't satisfy `String: AsDynError<'_>` - | doesn't satisfy `String: std::error::Error` + | ----------------- doesn't satisfy `String: AsDynError<'_>` or `String: std::error::Error` | = note: the following trait bounds were not satisfied: `String: std::error::Error` diff --git a/tests/ui/transparent-struct-not-error.stderr b/tests/ui/transparent-struct-not-error.stderr index d67a6944..ee50d03a 100644 --- a/tests/ui/transparent-struct-not-error.stderr +++ b/tests/ui/transparent-struct-not-error.stderr @@ -7,10 +7,7 @@ error[E0599]: the method `as_dyn_error` exists for struct `String`, but its trai ::: $RUST/alloc/src/string.rs | | pub struct String { - | ----------------- - | | - | doesn't satisfy `String: AsDynError<'_>` - | doesn't satisfy `String: std::error::Error` + | ----------------- doesn't satisfy `String: AsDynError<'_>` or `String: std::error::Error` | = note: the following trait bounds were not satisfied: `String: std::error::Error` diff --git a/tests/ui/transparent-struct-unnamed-field-not-error.stderr b/tests/ui/transparent-struct-unnamed-field-not-error.stderr index f715a151..c3d6c002 100644 --- a/tests/ui/transparent-struct-unnamed-field-not-error.stderr +++ b/tests/ui/transparent-struct-unnamed-field-not-error.stderr @@ -7,10 +7,7 @@ error[E0599]: the method `as_dyn_error` exists for struct `String`, but its trai ::: $RUST/alloc/src/string.rs | | pub struct String { - | ----------------- - | | - | doesn't satisfy `String: AsDynError<'_>` - | doesn't satisfy `String: std::error::Error` + | ----------------- doesn't satisfy `String: AsDynError<'_>` or `String: std::error::Error` | = note: the following trait bounds were not satisfied: `String: std::error::Error`