Skip to content

Commit d8da513

Browse files
committed
Auto merge of rust-lang#106916 - lukas-code:overlapping-substs, r=estebank
Remove overlapping parts of multipart suggestions This PR adds a debug assertion that the parts of a single substitution cannot overlap, fixes a overlapping substitution from the testsuite, and fixes rust-lang#106870. Note that a single suggestion can still have multiple overlapping substitutions / possible edits, we just don't suggest overlapping replacements in a single edit anymore. I've also included a fix for an unrelated bug where rustfix for `explicit_outlives_requirements` would produce multiple trailing commas for a where clause.
2 parents 226b249 + 228ddf0 commit d8da513

File tree

13 files changed

+214
-62
lines changed

13 files changed

+214
-62
lines changed

Diff for: compiler/rustc_errors/src/diagnostic.rs

+44-27
Original file line numberDiff line numberDiff line change
@@ -629,19 +629,27 @@ impl Diagnostic {
629629
applicability: Applicability,
630630
style: SuggestionStyle,
631631
) -> &mut Self {
632-
assert!(!suggestion.is_empty());
633-
debug_assert!(
634-
!(suggestion.iter().any(|(sp, text)| sp.is_empty() && text.is_empty())),
635-
"Span must not be empty and have no suggestion"
632+
let mut parts = suggestion
633+
.into_iter()
634+
.map(|(span, snippet)| SubstitutionPart { snippet, span })
635+
.collect::<Vec<_>>();
636+
637+
parts.sort_unstable_by_key(|part| part.span);
638+
639+
assert!(!parts.is_empty());
640+
debug_assert_eq!(
641+
parts.iter().find(|part| part.span.is_empty() && part.snippet.is_empty()),
642+
None,
643+
"Span must not be empty and have no suggestion",
644+
);
645+
debug_assert_eq!(
646+
parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)),
647+
None,
648+
"suggestion must not have overlapping parts",
636649
);
637650

638651
self.push_suggestion(CodeSuggestion {
639-
substitutions: vec![Substitution {
640-
parts: suggestion
641-
.into_iter()
642-
.map(|(span, snippet)| SubstitutionPart { snippet, span })
643-
.collect(),
644-
}],
652+
substitutions: vec![Substitution { parts }],
645653
msg: self.subdiagnostic_message_to_diagnostic_message(msg),
646654
style,
647655
applicability,
@@ -802,25 +810,34 @@ impl Diagnostic {
802810
suggestions: impl IntoIterator<Item = Vec<(Span, String)>>,
803811
applicability: Applicability,
804812
) -> &mut Self {
805-
let suggestions: Vec<_> = suggestions.into_iter().collect();
806-
debug_assert!(
807-
!(suggestions
808-
.iter()
809-
.flatten()
810-
.any(|(sp, suggestion)| sp.is_empty() && suggestion.is_empty())),
811-
"Span must not be empty and have no suggestion"
812-
);
813+
let substitutions = suggestions
814+
.into_iter()
815+
.map(|sugg| {
816+
let mut parts = sugg
817+
.into_iter()
818+
.map(|(span, snippet)| SubstitutionPart { snippet, span })
819+
.collect::<Vec<_>>();
820+
821+
parts.sort_unstable_by_key(|part| part.span);
822+
823+
assert!(!parts.is_empty());
824+
debug_assert_eq!(
825+
parts.iter().find(|part| part.span.is_empty() && part.snippet.is_empty()),
826+
None,
827+
"Span must not be empty and have no suggestion",
828+
);
829+
debug_assert_eq!(
830+
parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)),
831+
None,
832+
"suggestion must not have overlapping parts",
833+
);
834+
835+
Substitution { parts }
836+
})
837+
.collect();
813838

814839
self.push_suggestion(CodeSuggestion {
815-
substitutions: suggestions
816-
.into_iter()
817-
.map(|sugg| Substitution {
818-
parts: sugg
819-
.into_iter()
820-
.map(|(span, snippet)| SubstitutionPart { snippet, span })
821-
.collect(),
822-
})
823-
.collect(),
840+
substitutions,
824841
msg: self.subdiagnostic_message_to_diagnostic_message(msg),
825842
style: SuggestionStyle::ShowCode,
826843
applicability,

Diff for: compiler/rustc_errors/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! This module contains the code for creating and emitting diagnostics.
44
55
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
6+
#![feature(array_windows)]
67
#![feature(drain_filter)]
78
#![feature(if_let_guard)]
89
#![feature(is_terminal)]

Diff for: compiler/rustc_expand/src/mbe/macro_rules.rs

+18-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::mbe::transcribe::transcribe;
1010

1111
use rustc_ast as ast;
1212
use rustc_ast::token::{self, Delimiter, NonterminalKind, Token, TokenKind, TokenKind::*};
13-
use rustc_ast::tokenstream::{DelimSpan, TokenStream};
13+
use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree};
1414
use rustc_ast::{NodeId, DUMMY_NODE_ID};
1515
use rustc_ast_pretty::pprust;
1616
use rustc_attr::{self as attr, TransparencyError};
@@ -212,7 +212,6 @@ fn expand_macro<'cx>(
212212
};
213213
let arm_span = rhses[i].span();
214214

215-
let rhs_spans = rhs.tts.iter().map(|t| t.span()).collect::<Vec<_>>();
216215
// rhs has holes ( `$id` and `$(...)` that need filled)
217216
let mut tts = match transcribe(cx, &named_matches, &rhs, rhs_span, transparency) {
218217
Ok(tts) => tts,
@@ -224,12 +223,25 @@ fn expand_macro<'cx>(
224223

225224
// Replace all the tokens for the corresponding positions in the macro, to maintain
226225
// proper positions in error reporting, while maintaining the macro_backtrace.
227-
if rhs_spans.len() == tts.len() {
226+
if tts.len() == rhs.tts.len() {
228227
tts = tts.map_enumerated(|i, tt| {
229228
let mut tt = tt.clone();
230-
let mut sp = rhs_spans[i];
231-
sp = sp.with_ctxt(tt.span().ctxt());
232-
tt.set_span(sp);
229+
let rhs_tt = &rhs.tts[i];
230+
let ctxt = tt.span().ctxt();
231+
match (&mut tt, rhs_tt) {
232+
// preserve the delim spans if able
233+
(
234+
TokenTree::Delimited(target_sp, ..),
235+
mbe::TokenTree::Delimited(source_sp, ..),
236+
) => {
237+
target_sp.open = source_sp.open.with_ctxt(ctxt);
238+
target_sp.close = source_sp.close.with_ctxt(ctxt);
239+
}
240+
_ => {
241+
let sp = rhs_tt.span().with_ctxt(ctxt);
242+
tt.set_span(sp);
243+
}
244+
}
233245
tt
234246
});
235247
}

Diff for: compiler/rustc_lint/src/builtin.rs

+30-7
Original file line numberDiff line numberDiff line change
@@ -2175,13 +2175,31 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
21752175
dropped_predicate_count += 1;
21762176
}
21772177

2178-
if drop_predicate && !in_where_clause {
2179-
lint_spans.push(predicate_span);
2180-
} else if drop_predicate && i + 1 < num_predicates {
2181-
// If all the bounds on a predicate were inferable and there are
2182-
// further predicates, we want to eat the trailing comma.
2183-
let next_predicate_span = hir_generics.predicates[i + 1].span();
2184-
where_lint_spans.push(predicate_span.to(next_predicate_span.shrink_to_lo()));
2178+
if drop_predicate {
2179+
if !in_where_clause {
2180+
lint_spans.push(predicate_span);
2181+
} else if predicate_span.from_expansion() {
2182+
// Don't try to extend the span if it comes from a macro expansion.
2183+
where_lint_spans.push(predicate_span);
2184+
} else if i + 1 < num_predicates {
2185+
// If all the bounds on a predicate were inferable and there are
2186+
// further predicates, we want to eat the trailing comma.
2187+
let next_predicate_span = hir_generics.predicates[i + 1].span();
2188+
if next_predicate_span.from_expansion() {
2189+
where_lint_spans.push(predicate_span);
2190+
} else {
2191+
where_lint_spans
2192+
.push(predicate_span.to(next_predicate_span.shrink_to_lo()));
2193+
}
2194+
} else {
2195+
// Eat the optional trailing comma after the last predicate.
2196+
let where_span = hir_generics.where_clause_span;
2197+
if where_span.from_expansion() {
2198+
where_lint_spans.push(predicate_span);
2199+
} else {
2200+
where_lint_spans.push(predicate_span.to(where_span.shrink_to_hi()));
2201+
}
2202+
}
21852203
} else {
21862204
where_lint_spans.extend(self.consolidate_outlives_bound_spans(
21872205
predicate_span.shrink_to_lo(),
@@ -2225,6 +2243,11 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
22252243
Applicability::MaybeIncorrect
22262244
};
22272245

2246+
// Due to macros, there might be several predicates with the same span
2247+
// and we only want to suggest removing them once.
2248+
lint_spans.sort_unstable();
2249+
lint_spans.dedup();
2250+
22282251
cx.emit_spanned_lint(
22292252
EXPLICIT_OUTLIVES_REQUIREMENTS,
22302253
lint_spans.clone(),

Diff for: src/tools/clippy/clippy_lints/src/format_args.rs

+4
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,10 @@ fn check_uninlined_args(
311311
// in those cases, make the code suggestion hidden
312312
let multiline_fix = fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span));
313313

314+
// Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`.
315+
fixes.sort_unstable_by_key(|(span, _)| *span);
316+
fixes.dedup_by_key(|(span, _)| *span);
317+
314318
span_lint_and_then(
315319
cx,
316320
UNINLINED_FORMAT_ARGS,

Diff for: tests/ui/const-generics/min_const_generics/macro-fail.stderr

+9-14
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ LL | fn make_marker() -> impl Marker<gimme_a_const!(marker)> {
88
| in this macro invocation
99
...
1010
LL | ($rusty: ident) => {{ let $rusty = 3; *&$rusty }}
11-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type
11+
| ^ expected type
1212
|
1313
= note: this error originates in the macro `gimme_a_const` (in Nightly builds, run with -Z macro-backtrace for more info)
1414

@@ -22,26 +22,21 @@ LL | Example::<gimme_a_const!(marker)>
2222
| in this macro invocation
2323
...
2424
LL | ($rusty: ident) => {{ let $rusty = 3; *&$rusty }}
25-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type
25+
| ^ expected type
2626
|
2727
= note: this error originates in the macro `gimme_a_const` (in Nightly builds, run with -Z macro-backtrace for more info)
2828

2929
error: expected type, found `{`
3030
--> $DIR/macro-fail.rs:4:10
3131
|
32-
LL | () => {{
33-
| __________^
34-
LL | |
35-
LL | | const X: usize = 1337;
36-
LL | | X
37-
LL | | }}
38-
| |___^ expected type
32+
LL | () => {{
33+
| ^ expected type
3934
...
40-
LL | let _fail = Example::<external_macro!()>;
41-
| -----------------
42-
| |
43-
| this macro call doesn't expand to a type
44-
| in this macro invocation
35+
LL | let _fail = Example::<external_macro!()>;
36+
| -----------------
37+
| |
38+
| this macro call doesn't expand to a type
39+
| in this macro invocation
4540
|
4641
= note: this error originates in the macro `external_macro` (in Nightly builds, run with -Z macro-backtrace for more info)
4742

Diff for: tests/ui/imports/import-prefix-macro-1.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ error: expected one of `::`, `;`, or `as`, found `{`
22
--> $DIR/import-prefix-macro-1.rs:11:27
33
|
44
LL | ($p: path) => (use $p {S, Z});
5-
| ^^^^^^ expected one of `::`, `;`, or `as`
5+
| ^ expected one of `::`, `;`, or `as`
66
...
77
LL | import! { a::b::c }
88
| ------------------- in this macro invocation

Diff for: tests/ui/parser/issues/issue-44406.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ LL | foo!(true);
2121
= note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
2222
help: if `bar` is a struct, use braces as delimiters
2323
|
24-
LL | bar { }
25-
| ~
24+
LL | bar { baz: $rest }
25+
| ~ ~
2626
help: if `bar` is a function, use the arguments directly
2727
|
2828
LL - bar(baz: $rest)

Diff for: tests/ui/rust-2018/edition-lint-infer-outlives-multispan.rs

+20
Original file line numberDiff line numberDiff line change
@@ -365,4 +365,24 @@ mod unions {
365365
}
366366
}
367367

368+
// https://github.com/rust-lang/rust/issues/106870
369+
mod multiple_predicates_with_same_span {
370+
macro_rules! m {
371+
($($name:ident)+) => {
372+
struct Inline<'a, $($name: 'a,)+>(&'a ($($name,)+));
373+
//~^ ERROR: outlives requirements can be inferred
374+
struct FullWhere<'a, $($name,)+>(&'a ($($name,)+)) where $($name: 'a,)+;
375+
//~^ ERROR: outlives requirements can be inferred
376+
struct PartialWhere<'a, $($name,)+>(&'a ($($name,)+)) where (): Sized, $($name: 'a,)+;
377+
//~^ ERROR: outlives requirements can be inferred
378+
struct Interleaved<'a, $($name,)+>(&'a ($($name,)+))
379+
where
380+
(): Sized,
381+
$($name: 'a, $name: 'a, )+ //~ ERROR: outlives requirements can be inferred
382+
$($name: 'a, $name: 'a, )+;
383+
}
384+
}
385+
m!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15);
386+
}
387+
368388
fn main() {}

Diff for: tests/ui/rust-2018/edition-lint-infer-outlives-multispan.stderr

+57-1
Original file line numberDiff line numberDiff line change
@@ -819,5 +819,61 @@ LL - union BeeWhereAyTeeYooWhereOutlivesAyIsDebugBee<'a, 'b, T, U> where U:
819819
LL + union BeeWhereAyTeeYooWhereOutlivesAyIsDebugBee<'a, 'b, T, U> where U: Debug, {
820820
|
821821

822-
error: aborting due to 68 previous errors
822+
error: outlives requirements can be inferred
823+
--> $DIR/edition-lint-infer-outlives-multispan.rs:372:38
824+
|
825+
LL | struct Inline<'a, $($name: 'a,)+>(&'a ($($name,)+));
826+
| ^^^^ help: remove these bounds
827+
...
828+
LL | m!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15);
829+
| --------------------------------------------------------- in this macro invocation
830+
|
831+
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
832+
833+
error: outlives requirements can be inferred
834+
--> $DIR/edition-lint-infer-outlives-multispan.rs:374:64
835+
|
836+
LL | struct FullWhere<'a, $($name,)+>(&'a ($($name,)+)) where $($name: 'a,)+;
837+
| ^^^^^^^^^^^^^^^^^^ help: remove these bounds
838+
...
839+
LL | m!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15);
840+
| --------------------------------------------------------- in this macro invocation
841+
|
842+
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
843+
844+
error: outlives requirements can be inferred
845+
--> $DIR/edition-lint-infer-outlives-multispan.rs:376:86
846+
|
847+
LL | struct PartialWhere<'a, $($name,)+>(&'a ($($name,)+)) where (): Sized, $($name: 'a,)+;
848+
| ^^^^^^^^^ help: remove these bounds
849+
...
850+
LL | m!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15);
851+
| --------------------------------------------------------- in this macro invocation
852+
|
853+
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
854+
855+
error: outlives requirements can be inferred
856+
--> $DIR/edition-lint-infer-outlives-multispan.rs:381:19
857+
|
858+
LL | $($name: 'a, $name: 'a, )+
859+
| ^^^^^^^^^ ^^^^^^^^^
860+
LL | $($name: 'a, $name: 'a, )+;
861+
| ^^^^^^^^^ ^^^^^^^^^
862+
...
863+
LL | m!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15);
864+
| ---------------------------------------------------------
865+
| |
866+
| in this macro invocation
867+
| in this macro invocation
868+
| in this macro invocation
869+
| in this macro invocation
870+
|
871+
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
872+
help: remove these bounds
873+
|
874+
LL ~ $(, , )+
875+
LL ~ $(, , )+;
876+
|
877+
878+
error: aborting due to 72 previous errors
823879

Diff for: tests/ui/rust-2018/edition-lint-infer-outlives.fixed

+9
Original file line numberDiff line numberDiff line change
@@ -791,5 +791,14 @@ struct StaticRef<T: 'static> {
791791
field: &'static T
792792
}
793793

794+
struct TrailingCommaInWhereClause<'a, T, U>
795+
where
796+
T: 'a,
797+
798+
//~^ ERROR outlives requirements can be inferred
799+
{
800+
tee: T,
801+
yoo: &'a U
802+
}
794803

795804
fn main() {}

Diff for: tests/ui/rust-2018/edition-lint-infer-outlives.rs

+9
Original file line numberDiff line numberDiff line change
@@ -791,5 +791,14 @@ struct StaticRef<T: 'static> {
791791
field: &'static T
792792
}
793793

794+
struct TrailingCommaInWhereClause<'a, T, U>
795+
where
796+
T: 'a,
797+
U: 'a,
798+
//~^ ERROR outlives requirements can be inferred
799+
{
800+
tee: T,
801+
yoo: &'a U
802+
}
794803

795804
fn main() {}

0 commit comments

Comments
 (0)