Skip to content

Commit b35f383

Browse files
authored
Rollup merge of #157692 - sanidhyasin:fix-unused-parens-proc-macro-span, r=mejrs
Don't emit `unused_parens` suggestion for proc-macro-synthesized parens around bounds Supersedes #157662, which got auto closed after I force pushed the branch and github won't let me reopen it. Fixes #144378. When the unused_parens lint removes the parentheses around a trait bound, it assumed the bound's span starts and ends with those parentheses, so it just trimmed one byte off each end to build the suggestion. That isn't always true. A proc macro can give the bound a span that doesn't point at parentheses at all, and then trimming a byte produces a broken suggestion, or crashes when that byte lands in the middle of a multibyte character. So now we only remove the parens when the source text at that span actually starts with `(` and ends with `)`. That also handles the unicode parens case you mentioned, since `(` isn't an ASCII `(` and just gets skipped. I added a test that reproduces the crash with a proc macro. r? @mejrs
2 parents 2ad3bd4 + c4c61c0 commit b35f383

3 files changed

Lines changed: 77 additions & 13 deletions

File tree

compiler/rustc_lint/src/unused.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -937,21 +937,28 @@ impl EarlyLintPass for UnusedParens {
937937
&& !dyn2015_exception
938938
{
939939
let s = poly_trait_ref.span;
940-
let spans = (!s.from_expansion()).then(|| {
941-
(
940+
// Check that the span really is wrapped in single-byte ASCII parens
941+
// before trimming a byte off each end, in case a macro does weird
942+
// things with spans or parser recovery produced multibyte parens.
943+
if !s.from_expansion()
944+
&& let Ok(snippet) = cx.sess().source_map().span_to_snippet(s)
945+
&& snippet.starts_with('(')
946+
&& snippet.ends_with(')')
947+
{
948+
let spans = Some((
942949
s.with_hi(s.lo() + rustc_span::BytePos(1)),
943950
s.with_lo(s.hi() - rustc_span::BytePos(1)),
944-
)
945-
});
946-
947-
self.emit_unused_delims(
948-
cx,
949-
poly_trait_ref.span,
950-
spans,
951-
"type",
952-
(false, false),
953-
false,
954-
);
951+
));
952+
953+
self.emit_unused_delims(
954+
cx,
955+
poly_trait_ref.span,
956+
spans,
957+
"type",
958+
(false, false),
959+
false,
960+
);
961+
}
955962
}
956963
}
957964
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
extern crate proc_macro;
2+
3+
use proc_macro::{Group, Span, TokenStream, TokenTree};
4+
5+
// Recursively overwrite the span of every token (including group delimiters)
6+
// with `span`.
7+
fn respan(span: Span, stream: TokenStream) -> TokenStream {
8+
stream
9+
.into_iter()
10+
.map(|tt| match tt {
11+
TokenTree::Group(group) => {
12+
let mut group = Group::new(group.delimiter(), respan(span, group.stream()));
13+
group.set_span(span);
14+
TokenTree::Group(group)
15+
}
16+
mut tt => {
17+
tt.set_span(span);
18+
tt
19+
}
20+
})
21+
.collect()
22+
}
23+
24+
/// Emits `const _: &dyn (Send) = &();` with every token carrying the span of the
25+
/// macro's first input token. The parenthesized trait-object bound is therefore
26+
/// reported at a span that does not actually contain parentheses in the source.
27+
#[proc_macro]
28+
pub fn emit_parenthesized_bound(input: TokenStream) -> TokenStream {
29+
let span = input.into_iter().next().unwrap().span();
30+
let code: TokenStream = "const _: &dyn (Send) = &();".parse().unwrap();
31+
respan(span, code)
32+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@ check-pass
2+
//@ edition: 2021
3+
//@ proc-macro: unused-parens-bound-proc-macro.rs
4+
5+
// Regression test for #144378.
6+
//
7+
// A proc-macro can synthesize parentheses around a trait-object bound while
8+
// reusing a span from its input. That span does not actually point at the
9+
// parentheses, so the `unused_parens` lint must not blindly trim its first and
10+
// last byte: doing so produced an invalid suggestion (e.g. turning a field
11+
// `val: u8` into `al: u`) and, when the reused span started or ended on a
12+
// multibyte character, ICEd by slicing through that character.
13+
14+
#![deny(unused_parens)]
15+
#![allow(uncommon_codepoints)]
16+
17+
extern crate unused_parens_bound_proc_macro;
18+
19+
// The generated `&dyn (Send)` reuses the span of the identifier `é`, whose
20+
// first byte is the start of a two-byte character. Before the fix, trimming one
21+
// byte off the front of that span sliced through `é` and ICEd; now the lint is
22+
// skipped because the source span is not wrapped in parentheses.
23+
unused_parens_bound_proc_macro::emit_parenthesized_bound!(é);
24+
25+
fn main() {}

0 commit comments

Comments
 (0)