From 2b16098823352dee5e91323ec057f0a1853d6851 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 31 Oct 2024 21:02:21 -0700 Subject: [PATCH 01/12] Preserve None-delimited groups inside format args --- impl/src/attr.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/impl/src/attr.rs b/impl/src/attr.rs index a3746b0..c28761c 100644 --- a/impl/src/attr.rs +++ b/impl/src/attr.rs @@ -142,6 +142,13 @@ fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Resu fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result { let mut tokens = Vec::new(); while !input.is_empty() { + if input.peek(token::Group) { + let group: TokenTree = input.parse()?; + tokens.push(group); + begin_expr = false; + continue; + } + if begin_expr && input.peek(Token![.]) { if input.peek2(Ident) { input.parse::()?; From 747ce20cc28d62d564ba0a6f0b01e5fef22e93de Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 31 Oct 2024 21:23:45 -0700 Subject: [PATCH 02/12] Remove format var parsing workaround that targeted rustc 1.40 and older --- impl/src/fmt.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs index b38b7bf..c036536 100644 --- a/impl/src/fmt.rs +++ b/impl/src/fmt.rs @@ -93,11 +93,6 @@ impl Display<'_> { if formatvar.to_string().starts_with("r#") { formatvar = format_ident!("r_{}", formatvar); } - if formatvar.to_string().starts_with('_') { - // Work around leading underscore being rejected by 1.40 and - // older compilers. https://github.com/rust-lang/rust/pull/66847 - formatvar = format_ident!("field_{}", formatvar); - } out += &formatvar.to_string(); if !named_args.insert(formatvar.clone()) { // Already specified in the format argument list. From 751dc63a8ee66102a0b967d189e3d1ab860d29d1 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 2 Nov 2024 09:41:40 -0700 Subject: [PATCH 03/12] Track caller of the assertion helper in test_expr --- tests/test_expr.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_expr.rs b/tests/test_expr.rs index c5e3b4b..e369ba0 100644 --- a/tests/test_expr.rs +++ b/tests/test_expr.rs @@ -50,6 +50,7 @@ pub enum RustupError { }, } +#[track_caller] fn assert(expected: &str, value: T) { assert_eq!(expected, value.to_string()); } From 561e29eb809d78f918b90d624e9617931c4194dd Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 2 Nov 2024 09:39:37 -0700 Subject: [PATCH 04/12] Add regression test for issue 335 error[E0277]: `PathBuf` doesn't implement `std::fmt::Display` --> tests/test_expr.rs:105:14 | 104 | #[derive(Error, Debug)] | ----- in this derive macro expansion 105 | #[error("{A} {b}", b = &0 as &dyn Trait)] | ^^^ `PathBuf` cannot be formatted with the default formatter; call `.display()` on it | = help: the trait `std::fmt::Display` is not implemented for `PathBuf` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: call `.display()` or `.to_string_lossy()` to safely print paths, as they may contain non-Unicode data = note: this error originates in the macro `$crate::format_args` which comes from the expansion of the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info) --- tests/test_expr.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_expr.rs b/tests/test_expr.rs index e369ba0..4252280 100644 --- a/tests/test_expr.rs +++ b/tests/test_expr.rs @@ -1,6 +1,7 @@ #![allow(clippy::iter_cloned_collect, clippy::uninlined_format_args)] use core::fmt::Display; +use std::path::PathBuf; use thiserror::Error; // Some of the elaborate cases from the rcc codebase, which is a C compiler in @@ -87,3 +88,29 @@ fn test_rustup() { }, ); } + +// Regression test for https://github.com/dtolnay/thiserror/issues/335 +#[test] +#[allow(non_snake_case)] +fn test_assoc_type_equality_constraint() { + pub trait Trait: Display { + type A; + } + + impl Trait for i32 { + type A = i32; + } + + #[derive(Error, Debug)] + #[error("{A} {b}", b = &0 as &dyn Trait)] + pub struct Error { + pub A: PathBuf, + } + + assert( + "... 0", + Error { + A: PathBuf::from("..."), + }, + ); +} From c0050558f7638e1a5c23f2997b5bd6dcaac88a76 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 2 Nov 2024 09:31:56 -0700 Subject: [PATCH 05/12] Import expr scanner from syn 2.0.87 --- impl/Cargo.toml | 2 +- impl/src/lib.rs | 1 + impl/src/scan_expr.rs | 264 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 impl/src/scan_expr.rs diff --git a/impl/Cargo.toml b/impl/Cargo.toml index 5acbe8f..1d65e9c 100644 --- a/impl/Cargo.toml +++ b/impl/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.74" quote = "1.0.35" -syn = "2.0.86" +syn = "2.0.87" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 58f4bb5..48075eb 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -23,6 +23,7 @@ mod expand; mod fmt; mod generics; mod prop; +mod scan_expr; mod span; mod valid; diff --git a/impl/src/scan_expr.rs b/impl/src/scan_expr.rs new file mode 100644 index 0000000..155b5b6 --- /dev/null +++ b/impl/src/scan_expr.rs @@ -0,0 +1,264 @@ +use self::{Action::*, Input::*}; +use proc_macro2::{Delimiter, Ident, Spacing, TokenTree}; +use syn::parse::{ParseStream, Result}; +use syn::{AngleBracketedGenericArguments, BinOp, Expr, ExprPath, Lifetime, Lit, Token, Type}; + +enum Input { + Keyword(&'static str), + Punct(&'static str), + ConsumeAny, + ConsumeBinOp, + ConsumeBrace, + ConsumeDelimiter, + ConsumeIdent, + ConsumeLifetime, + ConsumeLiteral, + ConsumeNestedBrace, + ExpectPath, + ExpectTurbofish, + ExpectType, + CanBeginExpr, + Otherwise, + Empty, +} + +enum Action { + SetState(&'static [(Input, Action)]), + IncDepth, + DecDepth, + Finish, +} + +static INIT: [(Input, Action); 28] = [ + (ConsumeDelimiter, SetState(&POSTFIX)), + (Keyword("async"), SetState(&ASYNC)), + (Keyword("break"), SetState(&BREAK_LABEL)), + (Keyword("const"), SetState(&CONST)), + (Keyword("continue"), SetState(&CONTINUE)), + (Keyword("for"), SetState(&FOR)), + (Keyword("if"), IncDepth), + (Keyword("let"), SetState(&PATTERN)), + (Keyword("loop"), SetState(&BLOCK)), + (Keyword("match"), IncDepth), + (Keyword("move"), SetState(&CLOSURE)), + (Keyword("return"), SetState(&RETURN)), + (Keyword("static"), SetState(&CLOSURE)), + (Keyword("unsafe"), SetState(&BLOCK)), + (Keyword("while"), IncDepth), + (Keyword("yield"), SetState(&RETURN)), + (Keyword("_"), SetState(&POSTFIX)), + (Punct("!"), SetState(&INIT)), + (Punct("#"), SetState(&[(ConsumeDelimiter, SetState(&INIT))])), + (Punct("&"), SetState(&REFERENCE)), + (Punct("*"), SetState(&INIT)), + (Punct("-"), SetState(&INIT)), + (Punct("..="), SetState(&INIT)), + (Punct(".."), SetState(&RANGE)), + (Punct("|"), SetState(&CLOSURE_ARGS)), + (ConsumeLifetime, SetState(&[(Punct(":"), SetState(&INIT))])), + (ConsumeLiteral, SetState(&POSTFIX)), + (ExpectPath, SetState(&PATH)), +]; + +static POSTFIX: [(Input, Action); 10] = [ + (Keyword("as"), SetState(&[(ExpectType, SetState(&POSTFIX))])), + (Punct("..="), SetState(&INIT)), + (Punct(".."), SetState(&RANGE)), + (Punct("."), SetState(&DOT)), + (Punct("?"), SetState(&POSTFIX)), + (ConsumeBinOp, SetState(&INIT)), + (Punct("="), SetState(&INIT)), + (ConsumeNestedBrace, SetState(&IF_THEN)), + (ConsumeDelimiter, SetState(&POSTFIX)), + (Empty, Finish), +]; + +static ASYNC: [(Input, Action); 3] = [ + (Keyword("move"), SetState(&ASYNC)), + (Punct("|"), SetState(&CLOSURE_ARGS)), + (ConsumeBrace, SetState(&POSTFIX)), +]; + +static BLOCK: [(Input, Action); 1] = [(ConsumeBrace, SetState(&POSTFIX))]; + +static BREAK_LABEL: [(Input, Action); 2] = [ + (ConsumeLifetime, SetState(&BREAK_VALUE)), + (Otherwise, SetState(&BREAK_VALUE)), +]; + +static BREAK_VALUE: [(Input, Action); 3] = [ + (ConsumeNestedBrace, SetState(&IF_THEN)), + (CanBeginExpr, SetState(&INIT)), + (Otherwise, SetState(&POSTFIX)), +]; + +static CLOSURE: [(Input, Action); 6] = [ + (Keyword("async"), SetState(&CLOSURE)), + (Keyword("move"), SetState(&CLOSURE)), + (Punct(","), SetState(&CLOSURE)), + (Punct(">"), SetState(&CLOSURE)), + (Punct("|"), SetState(&CLOSURE_ARGS)), + (ConsumeLifetime, SetState(&CLOSURE)), +]; + +static CLOSURE_ARGS: [(Input, Action); 2] = [ + (Punct("|"), SetState(&CLOSURE_RET)), + (ConsumeAny, SetState(&CLOSURE_ARGS)), +]; + +static CLOSURE_RET: [(Input, Action); 2] = [ + (Punct("->"), SetState(&[(ExpectType, SetState(&BLOCK))])), + (Otherwise, SetState(&INIT)), +]; + +static CONST: [(Input, Action); 2] = [ + (Punct("|"), SetState(&CLOSURE_ARGS)), + (ConsumeBrace, SetState(&POSTFIX)), +]; + +static CONTINUE: [(Input, Action); 2] = [ + (ConsumeLifetime, SetState(&POSTFIX)), + (Otherwise, SetState(&POSTFIX)), +]; + +static DOT: [(Input, Action); 3] = [ + (Keyword("await"), SetState(&POSTFIX)), + (ConsumeIdent, SetState(&METHOD)), + (ConsumeLiteral, SetState(&POSTFIX)), +]; + +static FOR: [(Input, Action); 2] = [ + (Punct("<"), SetState(&CLOSURE)), + (Otherwise, SetState(&PATTERN)), +]; + +static IF_ELSE: [(Input, Action); 2] = [(Keyword("if"), SetState(&INIT)), (ConsumeBrace, DecDepth)]; +static IF_THEN: [(Input, Action); 2] = + [(Keyword("else"), SetState(&IF_ELSE)), (Otherwise, DecDepth)]; + +static METHOD: [(Input, Action); 1] = [(ExpectTurbofish, SetState(&POSTFIX))]; + +static PATH: [(Input, Action); 4] = [ + (Punct("!="), SetState(&INIT)), + (Punct("!"), SetState(&INIT)), + (ConsumeNestedBrace, SetState(&IF_THEN)), + (Otherwise, SetState(&POSTFIX)), +]; + +static PATTERN: [(Input, Action); 15] = [ + (ConsumeDelimiter, SetState(&PATTERN)), + (Keyword("box"), SetState(&PATTERN)), + (Keyword("in"), IncDepth), + (Keyword("mut"), SetState(&PATTERN)), + (Keyword("ref"), SetState(&PATTERN)), + (Keyword("_"), SetState(&PATTERN)), + (Punct("!"), SetState(&PATTERN)), + (Punct("&"), SetState(&PATTERN)), + (Punct("..="), SetState(&PATTERN)), + (Punct(".."), SetState(&PATTERN)), + (Punct("="), SetState(&INIT)), + (Punct("@"), SetState(&PATTERN)), + (Punct("|"), SetState(&PATTERN)), + (ConsumeLiteral, SetState(&PATTERN)), + (ExpectPath, SetState(&PATTERN)), +]; + +static RANGE: [(Input, Action); 6] = [ + (Punct("..="), SetState(&INIT)), + (Punct(".."), SetState(&RANGE)), + (Punct("."), SetState(&DOT)), + (ConsumeNestedBrace, SetState(&IF_THEN)), + (Empty, Finish), + (Otherwise, SetState(&INIT)), +]; + +static RAW: [(Input, Action); 3] = [ + (Keyword("const"), SetState(&INIT)), + (Keyword("mut"), SetState(&INIT)), + (Otherwise, SetState(&POSTFIX)), +]; + +static REFERENCE: [(Input, Action); 3] = [ + (Keyword("mut"), SetState(&INIT)), + (Keyword("raw"), SetState(&RAW)), + (Otherwise, SetState(&INIT)), +]; + +static RETURN: [(Input, Action); 2] = [ + (CanBeginExpr, SetState(&INIT)), + (Otherwise, SetState(&POSTFIX)), +]; + +pub(crate) fn scan_expr(input: ParseStream) -> Result<()> { + let mut state = INIT.as_slice(); + let mut depth = 0usize; + 'table: loop { + for rule in state { + if match rule.0 { + Input::Keyword(expected) => input.step(|cursor| match cursor.ident() { + Some((ident, rest)) if ident == expected => Ok((true, rest)), + _ => Ok((false, *cursor)), + })?, + Input::Punct(expected) => input.step(|cursor| { + let begin = *cursor; + let mut cursor = begin; + for (i, ch) in expected.chars().enumerate() { + match cursor.punct() { + Some((punct, _)) if punct.as_char() != ch => break, + Some((_, rest)) if i == expected.len() - 1 => { + return Ok((true, rest)); + } + Some((punct, rest)) if punct.spacing() == Spacing::Joint => { + cursor = rest; + } + _ => break, + } + } + Ok((false, begin)) + })?, + Input::ConsumeAny => input.parse::>()?.is_some(), + Input::ConsumeBinOp => input.parse::().is_ok(), + Input::ConsumeBrace | Input::ConsumeNestedBrace => { + (matches!(rule.0, Input::ConsumeBrace) || depth > 0) + && input.step(|cursor| match cursor.group(Delimiter::Brace) { + Some((_inside, _span, rest)) => Ok((true, rest)), + None => Ok((false, *cursor)), + })? + } + Input::ConsumeDelimiter => input.step(|cursor| match cursor.any_group() { + Some((_inside, _delimiter, _span, rest)) => Ok((true, rest)), + None => Ok((false, *cursor)), + })?, + Input::ConsumeIdent => input.parse::>()?.is_some(), + Input::ConsumeLifetime => input.parse::>()?.is_some(), + Input::ConsumeLiteral => input.parse::>()?.is_some(), + Input::ExpectPath => { + input.parse::()?; + true + } + Input::ExpectTurbofish => { + if input.peek(Token![::]) { + input.parse::()?; + } + true + } + Input::ExpectType => { + Type::without_plus(input)?; + true + } + Input::CanBeginExpr => Expr::peek(input), + Input::Otherwise => true, + Input::Empty => input.is_empty() || input.peek(Token![,]), + } { + state = match rule.1 { + Action::SetState(next) => next, + Action::IncDepth => (depth += 1, &INIT).1, + Action::DecDepth => (depth -= 1, &POSTFIX).1, + Action::Finish => return if depth == 0 { Ok(()) } else { break }, + }; + continue 'table; + } + } + return Err(input.error("unsupported expression")); + } +} From 2585669fa1a44c2ce1456e91fdcd38c2f1e747a5 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 2 Nov 2024 09:36:05 -0700 Subject: [PATCH 06/12] More robust scanning for fmt argument expressions --- impl/src/fmt.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs index c036536..029870b 100644 --- a/impl/src/fmt.rs +++ b/impl/src/fmt.rs @@ -1,5 +1,6 @@ use crate::ast::Field; use crate::attr::{Display, Trait}; +use crate::scan_expr::scan_expr; use proc_macro2::TokenTree; use quote::{format_ident, quote_spanned}; use std::collections::{BTreeSet as Set, HashMap as Map}; @@ -121,14 +122,16 @@ fn explicit_named_args(input: ParseStream) -> Result> { let mut named_args = Set::new(); while !input.is_empty() { - if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) { - input.parse::()?; + input.parse::()?; + if input.is_empty() { + break; + } + if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) { let ident = input.call(Ident::parse_any)?; input.parse::()?; named_args.insert(ident); - } else { - input.parse::()?; } + scan_expr(input)?; } Ok(named_args) From 45e18f53dfc8ff742e16bff0b0818b615b317cf0 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 2 Nov 2024 09:45:25 -0700 Subject: [PATCH 07/12] Ignore enum_glob_use pedantic clippy lint warning: usage of wildcard import for enum variants --> impl/src/scan_expr.rs:1:12 | 1 | use self::{Action::*, Input::*}; | ^^^^^^^^^ help: try: `Action::{DecDepth, Finish, IncDepth, SetState}` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use = note: `-W clippy::enum-glob-use` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::enum_glob_use)]` warning: usage of wildcard import for enum variants --> impl/src/scan_expr.rs:1:23 | 1 | use self::{Action::*, Input::*}; | ^^^^^^^^ help: try: `Input::{CanBeginExpr, ConsumeAny, ConsumeBinOp, ConsumeBrace, ConsumeDelimiter, ConsumeIdent, ConsumeLifetime, ConsumeLiteral, ConsumeNestedBrace, Empty, ExpectPath, ExpectTurbofish, ExpectType, Keyword, Otherwise, Punct}` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use --- impl/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 48075eb..7d7c6e3 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -2,6 +2,7 @@ clippy::blocks_in_conditions, clippy::cast_lossless, clippy::cast_possible_truncation, + clippy::enum_glob_use, clippy::manual_find, clippy::manual_let_else, clippy::manual_map, From 144b3b690b3dcc9338cf5c61dede46c9bbebe4f8 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Fri, 1 Nov 2024 13:47:37 +0000 Subject: [PATCH 08/12] Remove `#[allow]` for fixed clippy bug --- impl/src/generics.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/impl/src/generics.rs b/impl/src/generics.rs index 95592a7..254c2ed 100644 --- a/impl/src/generics.rs +++ b/impl/src/generics.rs @@ -57,7 +57,6 @@ impl InferredBounds { } } - #[allow(clippy::type_repetition_in_bounds, clippy::trait_duplication_in_bounds)] // clippy bug: https://github.com/rust-lang/rust-clippy/issues/8771 pub fn insert(&mut self, ty: impl ToTokens, bound: impl ToTokens) { let ty = ty.to_token_stream(); let bound = bound.to_token_stream(); From dabb96fdaf9a76bf7315bc0a5c8cb0a3974670ad Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 2 Nov 2024 20:41:34 -0700 Subject: [PATCH 09/12] Use syn's real expression parser if it has full syntax support --- impl/src/fmt.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs index 029870b..df75044 100644 --- a/impl/src/fmt.rs +++ b/impl/src/fmt.rs @@ -1,12 +1,12 @@ use crate::ast::Field; use crate::attr::{Display, Trait}; -use crate::scan_expr::scan_expr; +use crate::scan_expr; use proc_macro2::TokenTree; -use quote::{format_ident, quote_spanned}; +use quote::{format_ident, quote, quote_spanned}; use std::collections::{BTreeSet as Set, HashMap as Map}; use syn::ext::IdentExt; use syn::parse::{ParseStream, Parser}; -use syn::{Ident, Index, LitStr, Member, Result, Token}; +use syn::{Expr, Ident, Index, LitStr, Member, Result, Token}; impl Display<'_> { // Transform `"error {var}"` to `"error {}", var`. @@ -119,6 +119,12 @@ impl Display<'_> { } fn explicit_named_args(input: ParseStream) -> Result> { + let scan_expr = if is_syn_full() { + |input: ParseStream| input.parse::().map(drop) + } else { + scan_expr::scan_expr + }; + let mut named_args = Set::new(); while !input.is_empty() { @@ -137,6 +143,24 @@ fn explicit_named_args(input: ParseStream) -> Result> { Ok(named_args) } +fn is_syn_full() -> bool { + // Expr::Block contains syn::Block which contains Vec. In the + // current version of Syn, syn::Stmt is exhaustive and could only plausibly + // represent `trait Trait {}` in Stmt::Item which contains syn::Item. Most + // of the point of syn's non-"full" mode is to avoid compiling Item and the + // entire expansive syntax tree it comprises. So the following expression + // being parsed to Expr::Block is a reliable indication that "full" is + // enabled. + let test = quote!({ + trait Trait {} + }); + match syn::parse2(test) { + Ok(Expr::Verbatim(_)) | Err(_) => false, + Ok(Expr::Block(_)) => true, + Ok(_) => unreachable!(), + } +} + fn take_int(read: &mut &str) -> String { let mut int = String::new(); for (i, ch) in read.char_indices() { From c357f9728e4c5497dec5128d8ed258b82bbd163a Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 2 Nov 2024 21:05:30 -0700 Subject: [PATCH 10/12] Add infallible expr scanner fallback for scanning invalid code --- impl/src/fmt.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs index df75044..a15aeb6 100644 --- a/impl/src/fmt.rs +++ b/impl/src/fmt.rs @@ -1,10 +1,11 @@ use crate::ast::Field; use crate::attr::{Display, Trait}; use crate::scan_expr; -use proc_macro2::TokenTree; +use proc_macro2::{TokenStream, TokenTree}; use quote::{format_ident, quote, quote_spanned}; use std::collections::{BTreeSet as Set, HashMap as Map}; use syn::ext::IdentExt; +use syn::parse::discouraged::Speculative; use syn::parse::{ParseStream, Parser}; use syn::{Expr, Ident, Index, LitStr, Member, Result, Token}; @@ -119,6 +120,23 @@ impl Display<'_> { } fn explicit_named_args(input: ParseStream) -> Result> { + let ahead = input.fork(); + if let Ok(set) = try_explicit_named_args(&ahead) { + input.advance_to(&ahead); + return Ok(set); + } + + let ahead = input.fork(); + if let Ok(set) = fallback_explicit_named_args(&ahead) { + input.advance_to(&ahead); + return Ok(set); + } + + input.parse::().unwrap(); + Ok(Set::new()) +} + +fn try_explicit_named_args(input: ParseStream) -> Result> { let scan_expr = if is_syn_full() { |input: ParseStream| input.parse::().map(drop) } else { @@ -143,6 +161,21 @@ fn explicit_named_args(input: ParseStream) -> Result> { Ok(named_args) } +fn fallback_explicit_named_args(input: ParseStream) -> Result> { + let mut named_args = Set::new(); + + while !input.is_empty() { + if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) { + input.parse::()?; + let ident = input.call(Ident::parse_any)?; + input.parse::()?; + named_args.insert(ident); + } + } + + Ok(named_args) +} + fn is_syn_full() -> bool { // Expr::Block contains syn::Block which contains Vec. In the // current version of Syn, syn::Stmt is exhaustive and could only plausibly From 0ab908aab0dc12415ce34241fdc23bb722bd0eba Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 2 Nov 2024 21:12:28 -0700 Subject: [PATCH 11/12] Ignore expected unnecessary_wraps pedantic clippy lint warning: this function's return value is unnecessarily wrapped by `Result` --> impl/src/fmt.rs:122:1 | 122 | / fn explicit_named_args(input: ParseStream) -> Result> { 123 | | let ahead = input.fork(); 124 | | if let Ok(set) = try_explicit_named_args(&ahead) { 125 | | input.advance_to(&ahead); ... | 136 | | Ok(Set::new()) 137 | | } | |_^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps = note: `-W clippy::unnecessary-wraps` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::unnecessary_wraps)]` help: remove `Result` from the return type... | 122 | fn explicit_named_args(input: ParseStream) -> std::collections::BTreeSet { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ help: ...and then change returning expressions | 126 ~ return set; 127 | } ... 131 | input.advance_to(&ahead); 132 ~ return set; 133 | } 134 | 135 | input.parse::().unwrap(); 136 ~ Set::new() | --- impl/src/fmt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs index a15aeb6..153a423 100644 --- a/impl/src/fmt.rs +++ b/impl/src/fmt.rs @@ -119,6 +119,7 @@ impl Display<'_> { } } +#[allow(clippy::unnecessary_wraps)] fn explicit_named_args(input: ParseStream) -> Result> { let ahead = input.fork(); if let Ok(set) = try_explicit_named_args(&ahead) { From 925f2dde771de0be96f9e6402f8bf5d06f523ebc Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 3 Nov 2024 10:17:51 -0500 Subject: [PATCH 12/12] Release 1.0.67 --- Cargo.toml | 4 ++-- impl/Cargo.toml | 2 +- src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 96a87e8..e107eef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thiserror" -version = "1.0.66" +version = "1.0.67" authors = ["David Tolnay "] categories = ["rust-patterns"] description = "derive(Error)" @@ -12,7 +12,7 @@ repository = "https://github.com/dtolnay/thiserror" rust-version = "1.61" [dependencies] -thiserror-impl = { version = "=1.0.66", path = "impl" } +thiserror-impl = { version = "=1.0.67", path = "impl" } [dev-dependencies] anyhow = "1.0.73" diff --git a/impl/Cargo.toml b/impl/Cargo.toml index 1d65e9c..1b474cb 100644 --- a/impl/Cargo.toml +++ b/impl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thiserror-impl" -version = "1.0.66" +version = "1.0.67" authors = ["David Tolnay "] description = "Implementation detail of the `thiserror` crate" edition = "2021" diff --git a/src/lib.rs b/src/lib.rs index b7afdb7..8437394 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -258,7 +258,7 @@ //! //! [`anyhow`]: https://github.com/dtolnay/anyhow -#![doc(html_root_url = "https://docs.rs/thiserror/1.0.66")] +#![doc(html_root_url = "https://docs.rs/thiserror/1.0.67")] #![allow( clippy::module_name_repetitions, clippy::needless_lifetimes,