Skip to content

Commit 851f694

Browse files
authored
Merge pull request #337 from dtolnay/scan
More robust scanning for fmt argument expressions
2 parents 751dc63 + 45e18f5 commit 851f694

File tree

5 files changed

+301
-5
lines changed

5 files changed

+301
-5
lines changed

impl/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ proc-macro = true
1414
[dependencies]
1515
proc-macro2 = "1.0.74"
1616
quote = "1.0.35"
17-
syn = "2.0.86"
17+
syn = "2.0.87"
1818

1919
[package.metadata.docs.rs]
2020
targets = ["x86_64-unknown-linux-gnu"]

impl/src/fmt.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::ast::Field;
22
use crate::attr::{Display, Trait};
3+
use crate::scan_expr::scan_expr;
34
use proc_macro2::TokenTree;
45
use quote::{format_ident, quote_spanned};
56
use std::collections::{BTreeSet as Set, HashMap as Map};
@@ -121,14 +122,16 @@ fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
121122
let mut named_args = Set::new();
122123

123124
while !input.is_empty() {
124-
if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
125-
input.parse::<Token![,]>()?;
125+
input.parse::<Token![,]>()?;
126+
if input.is_empty() {
127+
break;
128+
}
129+
if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) {
126130
let ident = input.call(Ident::parse_any)?;
127131
input.parse::<Token![=]>()?;
128132
named_args.insert(ident);
129-
} else {
130-
input.parse::<TokenTree>()?;
131133
}
134+
scan_expr(input)?;
132135
}
133136

134137
Ok(named_args)

impl/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
clippy::blocks_in_conditions,
33
clippy::cast_lossless,
44
clippy::cast_possible_truncation,
5+
clippy::enum_glob_use,
56
clippy::manual_find,
67
clippy::manual_let_else,
78
clippy::manual_map,
@@ -23,6 +24,7 @@ mod expand;
2324
mod fmt;
2425
mod generics;
2526
mod prop;
27+
mod scan_expr;
2628
mod span;
2729
mod valid;
2830

impl/src/scan_expr.rs

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
use self::{Action::*, Input::*};
2+
use proc_macro2::{Delimiter, Ident, Spacing, TokenTree};
3+
use syn::parse::{ParseStream, Result};
4+
use syn::{AngleBracketedGenericArguments, BinOp, Expr, ExprPath, Lifetime, Lit, Token, Type};
5+
6+
enum Input {
7+
Keyword(&'static str),
8+
Punct(&'static str),
9+
ConsumeAny,
10+
ConsumeBinOp,
11+
ConsumeBrace,
12+
ConsumeDelimiter,
13+
ConsumeIdent,
14+
ConsumeLifetime,
15+
ConsumeLiteral,
16+
ConsumeNestedBrace,
17+
ExpectPath,
18+
ExpectTurbofish,
19+
ExpectType,
20+
CanBeginExpr,
21+
Otherwise,
22+
Empty,
23+
}
24+
25+
enum Action {
26+
SetState(&'static [(Input, Action)]),
27+
IncDepth,
28+
DecDepth,
29+
Finish,
30+
}
31+
32+
static INIT: [(Input, Action); 28] = [
33+
(ConsumeDelimiter, SetState(&POSTFIX)),
34+
(Keyword("async"), SetState(&ASYNC)),
35+
(Keyword("break"), SetState(&BREAK_LABEL)),
36+
(Keyword("const"), SetState(&CONST)),
37+
(Keyword("continue"), SetState(&CONTINUE)),
38+
(Keyword("for"), SetState(&FOR)),
39+
(Keyword("if"), IncDepth),
40+
(Keyword("let"), SetState(&PATTERN)),
41+
(Keyword("loop"), SetState(&BLOCK)),
42+
(Keyword("match"), IncDepth),
43+
(Keyword("move"), SetState(&CLOSURE)),
44+
(Keyword("return"), SetState(&RETURN)),
45+
(Keyword("static"), SetState(&CLOSURE)),
46+
(Keyword("unsafe"), SetState(&BLOCK)),
47+
(Keyword("while"), IncDepth),
48+
(Keyword("yield"), SetState(&RETURN)),
49+
(Keyword("_"), SetState(&POSTFIX)),
50+
(Punct("!"), SetState(&INIT)),
51+
(Punct("#"), SetState(&[(ConsumeDelimiter, SetState(&INIT))])),
52+
(Punct("&"), SetState(&REFERENCE)),
53+
(Punct("*"), SetState(&INIT)),
54+
(Punct("-"), SetState(&INIT)),
55+
(Punct("..="), SetState(&INIT)),
56+
(Punct(".."), SetState(&RANGE)),
57+
(Punct("|"), SetState(&CLOSURE_ARGS)),
58+
(ConsumeLifetime, SetState(&[(Punct(":"), SetState(&INIT))])),
59+
(ConsumeLiteral, SetState(&POSTFIX)),
60+
(ExpectPath, SetState(&PATH)),
61+
];
62+
63+
static POSTFIX: [(Input, Action); 10] = [
64+
(Keyword("as"), SetState(&[(ExpectType, SetState(&POSTFIX))])),
65+
(Punct("..="), SetState(&INIT)),
66+
(Punct(".."), SetState(&RANGE)),
67+
(Punct("."), SetState(&DOT)),
68+
(Punct("?"), SetState(&POSTFIX)),
69+
(ConsumeBinOp, SetState(&INIT)),
70+
(Punct("="), SetState(&INIT)),
71+
(ConsumeNestedBrace, SetState(&IF_THEN)),
72+
(ConsumeDelimiter, SetState(&POSTFIX)),
73+
(Empty, Finish),
74+
];
75+
76+
static ASYNC: [(Input, Action); 3] = [
77+
(Keyword("move"), SetState(&ASYNC)),
78+
(Punct("|"), SetState(&CLOSURE_ARGS)),
79+
(ConsumeBrace, SetState(&POSTFIX)),
80+
];
81+
82+
static BLOCK: [(Input, Action); 1] = [(ConsumeBrace, SetState(&POSTFIX))];
83+
84+
static BREAK_LABEL: [(Input, Action); 2] = [
85+
(ConsumeLifetime, SetState(&BREAK_VALUE)),
86+
(Otherwise, SetState(&BREAK_VALUE)),
87+
];
88+
89+
static BREAK_VALUE: [(Input, Action); 3] = [
90+
(ConsumeNestedBrace, SetState(&IF_THEN)),
91+
(CanBeginExpr, SetState(&INIT)),
92+
(Otherwise, SetState(&POSTFIX)),
93+
];
94+
95+
static CLOSURE: [(Input, Action); 6] = [
96+
(Keyword("async"), SetState(&CLOSURE)),
97+
(Keyword("move"), SetState(&CLOSURE)),
98+
(Punct(","), SetState(&CLOSURE)),
99+
(Punct(">"), SetState(&CLOSURE)),
100+
(Punct("|"), SetState(&CLOSURE_ARGS)),
101+
(ConsumeLifetime, SetState(&CLOSURE)),
102+
];
103+
104+
static CLOSURE_ARGS: [(Input, Action); 2] = [
105+
(Punct("|"), SetState(&CLOSURE_RET)),
106+
(ConsumeAny, SetState(&CLOSURE_ARGS)),
107+
];
108+
109+
static CLOSURE_RET: [(Input, Action); 2] = [
110+
(Punct("->"), SetState(&[(ExpectType, SetState(&BLOCK))])),
111+
(Otherwise, SetState(&INIT)),
112+
];
113+
114+
static CONST: [(Input, Action); 2] = [
115+
(Punct("|"), SetState(&CLOSURE_ARGS)),
116+
(ConsumeBrace, SetState(&POSTFIX)),
117+
];
118+
119+
static CONTINUE: [(Input, Action); 2] = [
120+
(ConsumeLifetime, SetState(&POSTFIX)),
121+
(Otherwise, SetState(&POSTFIX)),
122+
];
123+
124+
static DOT: [(Input, Action); 3] = [
125+
(Keyword("await"), SetState(&POSTFIX)),
126+
(ConsumeIdent, SetState(&METHOD)),
127+
(ConsumeLiteral, SetState(&POSTFIX)),
128+
];
129+
130+
static FOR: [(Input, Action); 2] = [
131+
(Punct("<"), SetState(&CLOSURE)),
132+
(Otherwise, SetState(&PATTERN)),
133+
];
134+
135+
static IF_ELSE: [(Input, Action); 2] = [(Keyword("if"), SetState(&INIT)), (ConsumeBrace, DecDepth)];
136+
static IF_THEN: [(Input, Action); 2] =
137+
[(Keyword("else"), SetState(&IF_ELSE)), (Otherwise, DecDepth)];
138+
139+
static METHOD: [(Input, Action); 1] = [(ExpectTurbofish, SetState(&POSTFIX))];
140+
141+
static PATH: [(Input, Action); 4] = [
142+
(Punct("!="), SetState(&INIT)),
143+
(Punct("!"), SetState(&INIT)),
144+
(ConsumeNestedBrace, SetState(&IF_THEN)),
145+
(Otherwise, SetState(&POSTFIX)),
146+
];
147+
148+
static PATTERN: [(Input, Action); 15] = [
149+
(ConsumeDelimiter, SetState(&PATTERN)),
150+
(Keyword("box"), SetState(&PATTERN)),
151+
(Keyword("in"), IncDepth),
152+
(Keyword("mut"), SetState(&PATTERN)),
153+
(Keyword("ref"), SetState(&PATTERN)),
154+
(Keyword("_"), SetState(&PATTERN)),
155+
(Punct("!"), SetState(&PATTERN)),
156+
(Punct("&"), SetState(&PATTERN)),
157+
(Punct("..="), SetState(&PATTERN)),
158+
(Punct(".."), SetState(&PATTERN)),
159+
(Punct("="), SetState(&INIT)),
160+
(Punct("@"), SetState(&PATTERN)),
161+
(Punct("|"), SetState(&PATTERN)),
162+
(ConsumeLiteral, SetState(&PATTERN)),
163+
(ExpectPath, SetState(&PATTERN)),
164+
];
165+
166+
static RANGE: [(Input, Action); 6] = [
167+
(Punct("..="), SetState(&INIT)),
168+
(Punct(".."), SetState(&RANGE)),
169+
(Punct("."), SetState(&DOT)),
170+
(ConsumeNestedBrace, SetState(&IF_THEN)),
171+
(Empty, Finish),
172+
(Otherwise, SetState(&INIT)),
173+
];
174+
175+
static RAW: [(Input, Action); 3] = [
176+
(Keyword("const"), SetState(&INIT)),
177+
(Keyword("mut"), SetState(&INIT)),
178+
(Otherwise, SetState(&POSTFIX)),
179+
];
180+
181+
static REFERENCE: [(Input, Action); 3] = [
182+
(Keyword("mut"), SetState(&INIT)),
183+
(Keyword("raw"), SetState(&RAW)),
184+
(Otherwise, SetState(&INIT)),
185+
];
186+
187+
static RETURN: [(Input, Action); 2] = [
188+
(CanBeginExpr, SetState(&INIT)),
189+
(Otherwise, SetState(&POSTFIX)),
190+
];
191+
192+
pub(crate) fn scan_expr(input: ParseStream) -> Result<()> {
193+
let mut state = INIT.as_slice();
194+
let mut depth = 0usize;
195+
'table: loop {
196+
for rule in state {
197+
if match rule.0 {
198+
Input::Keyword(expected) => input.step(|cursor| match cursor.ident() {
199+
Some((ident, rest)) if ident == expected => Ok((true, rest)),
200+
_ => Ok((false, *cursor)),
201+
})?,
202+
Input::Punct(expected) => input.step(|cursor| {
203+
let begin = *cursor;
204+
let mut cursor = begin;
205+
for (i, ch) in expected.chars().enumerate() {
206+
match cursor.punct() {
207+
Some((punct, _)) if punct.as_char() != ch => break,
208+
Some((_, rest)) if i == expected.len() - 1 => {
209+
return Ok((true, rest));
210+
}
211+
Some((punct, rest)) if punct.spacing() == Spacing::Joint => {
212+
cursor = rest;
213+
}
214+
_ => break,
215+
}
216+
}
217+
Ok((false, begin))
218+
})?,
219+
Input::ConsumeAny => input.parse::<Option<TokenTree>>()?.is_some(),
220+
Input::ConsumeBinOp => input.parse::<BinOp>().is_ok(),
221+
Input::ConsumeBrace | Input::ConsumeNestedBrace => {
222+
(matches!(rule.0, Input::ConsumeBrace) || depth > 0)
223+
&& input.step(|cursor| match cursor.group(Delimiter::Brace) {
224+
Some((_inside, _span, rest)) => Ok((true, rest)),
225+
None => Ok((false, *cursor)),
226+
})?
227+
}
228+
Input::ConsumeDelimiter => input.step(|cursor| match cursor.any_group() {
229+
Some((_inside, _delimiter, _span, rest)) => Ok((true, rest)),
230+
None => Ok((false, *cursor)),
231+
})?,
232+
Input::ConsumeIdent => input.parse::<Option<Ident>>()?.is_some(),
233+
Input::ConsumeLifetime => input.parse::<Option<Lifetime>>()?.is_some(),
234+
Input::ConsumeLiteral => input.parse::<Option<Lit>>()?.is_some(),
235+
Input::ExpectPath => {
236+
input.parse::<ExprPath>()?;
237+
true
238+
}
239+
Input::ExpectTurbofish => {
240+
if input.peek(Token![::]) {
241+
input.parse::<AngleBracketedGenericArguments>()?;
242+
}
243+
true
244+
}
245+
Input::ExpectType => {
246+
Type::without_plus(input)?;
247+
true
248+
}
249+
Input::CanBeginExpr => Expr::peek(input),
250+
Input::Otherwise => true,
251+
Input::Empty => input.is_empty() || input.peek(Token![,]),
252+
} {
253+
state = match rule.1 {
254+
Action::SetState(next) => next,
255+
Action::IncDepth => (depth += 1, &INIT).1,
256+
Action::DecDepth => (depth -= 1, &POSTFIX).1,
257+
Action::Finish => return if depth == 0 { Ok(()) } else { break },
258+
};
259+
continue 'table;
260+
}
261+
}
262+
return Err(input.error("unsupported expression"));
263+
}
264+
}

tests/test_expr.rs

+27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![allow(clippy::iter_cloned_collect, clippy::uninlined_format_args)]
22

33
use core::fmt::Display;
4+
use std::path::PathBuf;
45
use thiserror::Error;
56

67
// Some of the elaborate cases from the rcc codebase, which is a C compiler in
@@ -87,3 +88,29 @@ fn test_rustup() {
8788
},
8889
);
8990
}
91+
92+
// Regression test for https://github.com/dtolnay/thiserror/issues/335
93+
#[test]
94+
#[allow(non_snake_case)]
95+
fn test_assoc_type_equality_constraint() {
96+
pub trait Trait<T>: Display {
97+
type A;
98+
}
99+
100+
impl<T> Trait<T> for i32 {
101+
type A = i32;
102+
}
103+
104+
#[derive(Error, Debug)]
105+
#[error("{A} {b}", b = &0 as &dyn Trait<i32, A = i32>)]
106+
pub struct Error {
107+
pub A: PathBuf,
108+
}
109+
110+
assert(
111+
"... 0",
112+
Error {
113+
A: PathBuf::from("..."),
114+
},
115+
);
116+
}

0 commit comments

Comments
 (0)