Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions compiler/rustc_lint/src/unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -937,21 +937,41 @@ impl EarlyLintPass for UnusedParens {
&& !dyn2015_exception
{
let s = poly_trait_ref.span;
let spans = (!s.from_expansion()).then(|| {
(
// `poly_trait_ref.parens == Yes` only promises that the *parser*
// saw parentheses around this bound. It does not guarantee that
// the source text covered by `s` is literally wrapped in ASCII
// parentheses, which is what the byte arithmetic below assumes:
//
// - A proc-macro can synthesize the parentheses while reusing an
// unrelated span from its input (issue #144378), so `s` may not
// point at parentheses at all.
// - The parser recovers from Unicode parens such as `( )`, so
// the first and last *characters* of `s` may be parens, but
// multibyte ones.
//
// In both cases trimming a single byte off each end would corrupt
// the suggestion or even ICE by slicing through a multibyte char.
// So only lint when the source really is wrapped in ASCII parens;
// those are exactly one byte each, which makes the trimming sound.
if !s.from_expansion()
&& let Ok(snippet) = cx.sess().source_map().span_to_snippet(s)
&& snippet.starts_with('(')
&& snippet.ends_with(')')
{
let spans = Some((
s.with_hi(s.lo() + rustc_span::BytePos(1)),
s.with_lo(s.hi() - rustc_span::BytePos(1)),
)
});

self.emit_unused_delims(
cx,
poly_trait_ref.span,
spans,
"type",
(false, false),
false,
);
));

self.emit_unused_delims(
cx,
poly_trait_ref.span,
spans,
"type",
(false, false),
false,
);
}
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions tests/ui/lint/unused/auxiliary/unused-parens-bound-proc-macro.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
extern crate proc_macro;

use proc_macro::{Group, Span, TokenStream, TokenTree};

// Recursively overwrite the span of every token (including group delimiters)
// with `span`.
fn respan(span: Span, stream: TokenStream) -> TokenStream {
stream
.into_iter()
.map(|tt| match tt {
TokenTree::Group(group) => {
let mut group = Group::new(group.delimiter(), respan(span, group.stream()));
group.set_span(span);
TokenTree::Group(group)
}
mut tt => {
tt.set_span(span);
tt
}
})
.collect()
}

/// Emits `const _: &dyn (Send) = &();` with every token carrying the span of the
/// macro's first input token. The parenthesized trait-object bound is therefore
/// reported at a span that does not actually contain parentheses in the source.
#[proc_macro]
pub fn emit_parenthesized_bound(input: TokenStream) -> TokenStream {
let span = input.into_iter().next().unwrap().span();
let code: TokenStream = "const _: &dyn (Send) = &();".parse().unwrap();
respan(span, code)
}
25 changes: 25 additions & 0 deletions tests/ui/lint/unused/unused-parens-trait-obj-proc-macro-144378.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//@ check-pass
//@ edition: 2021
//@ proc-macro: unused-parens-bound-proc-macro.rs

// Regression test for #144378.
//
// A proc-macro can synthesize parentheses around a trait-object bound while
// reusing a span from its input. That span does not actually point at the
// parentheses, so the `unused_parens` lint must not blindly trim its first and
// last byte: doing so produced an invalid suggestion (e.g. turning a field
// `val: u8` into `al: u`) and, when the reused span started or ended on a
// multibyte character, ICEd by slicing through that character.

#![deny(unused_parens)]
#![allow(uncommon_codepoints)]

extern crate unused_parens_bound_proc_macro;

// The generated `&dyn (Send)` reuses the span of the identifier `é`, whose
// first byte is the start of a two-byte character. Before the fix, trimming one
// byte off the front of that span sliced through `é` and ICEd; now the lint is
// skipped because the source span is not wrapped in parentheses.
unused_parens_bound_proc_macro::emit_parenthesized_bound!(é);

fn main() {}
Loading