Skip to content

Commit 8538562

Browse files
committed
Auto merge of #13567 - y21:span_debug_assertions, r=flip1995
Add debug assertions for empty replacements and overlapping spans rustc has debug assertions [^1] [^2] that check that a substitution doesn't have an empty suggestion string and an empty span at the same time, as well as that spans in multipart suggestions don't overlap. However, since we link to the rustc-dev distributed compiler, these debug assertions are always disabled and so we never actually run them. This leads to the problem that the debug ICE is not necessarily caught in the PR and only triggered in the rust repo sync, and in one of the last syncs this was a blocker and delayed the sync by several weeks because the fix was not obvious. So this PR essentially copies the checks over and runs them in clippy debug builds as well, so that we can catch these errors in PRs directly. ----- As for the second commit, this also *did* cause an ICE in a sync before and was fixed in the sync PR (see rust-lang/rust#120345 (comment)), but it seems like that commit didn't make it back into the clippy repo (cc `@flip1995),` so the fixed code is in the rust repo but not in the clippy repo. changelog: none [^1]: https://doc.rust-lang.org/1.82.0/nightly-rustc/src/rustc_errors/diagnostic.rs.html#1019 [^2]: https://doc.rust-lang.org/1.82.0/nightly-rustc/src/rustc_errors/diagnostic.rs.html#932
2 parents d00ab2e + 65eb1ec commit 8538562

File tree

7 files changed

+127
-20
lines changed

7 files changed

+127
-20
lines changed

clippy_lints/src/from_over_into.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,6 @@ fn convert_to_from(
181181
let from = self_ty.span.get_source_text(cx)?;
182182
let into = target_ty.span.get_source_text(cx)?;
183183

184-
let return_type = matches!(sig.decl.output, FnRetTy::Return(_))
185-
.then_some(String::from("Self"))
186-
.unwrap_or_default();
187184
let mut suggestions = vec![
188185
// impl Into<T> for U -> impl From<T> for U
189186
// ~~~~ ~~~~
@@ -200,10 +197,13 @@ fn convert_to_from(
200197
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
201198
// ~~~~ ~~~~
202199
(self_ident.span, format!("val: {from}")),
200+
];
201+
202+
if let FnRetTy::Return(_) = sig.decl.output {
203203
// fn into(self) -> T -> fn into(self) -> Self
204204
// ~ ~~~~
205-
(sig.decl.output.span(), return_type),
206-
];
205+
suggestions.push((sig.decl.output.span(), String::from("Self")));
206+
}
207207

208208
let mut finder = SelfFinder {
209209
cx,

clippy_lints/src/semicolon_block.rs

+15-12
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,21 @@ impl SemicolonBlock {
101101
);
102102
}
103103

104-
fn semicolon_outside_block(
105-
&self,
106-
cx: &LateContext<'_>,
107-
block: &Block<'_>,
108-
tail_stmt_expr: &Expr<'_>,
109-
semi_span: Span,
110-
) {
104+
fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) {
111105
let insert_span = block.span.with_lo(block.span.hi());
112-
// account for macro calls
113-
let semi_span = cx.sess().source_map().stmt_span(semi_span, block.span);
114-
let remove_span = semi_span.with_lo(tail_stmt_expr.span.source_callsite().hi());
106+
107+
// For macro call semicolon statements (`mac!();`), the statement's span does not actually
108+
// include the semicolon itself, so use `mac_call_stmt_semi_span`, which finds the semicolon
109+
// based on a source snippet.
110+
// (Does not use `stmt_span` as that requires `.from_expansion()` to return true,
111+
// which is not the case for e.g. `line!();` and `asm!();`)
112+
let Some(remove_span) = cx
113+
.sess()
114+
.source_map()
115+
.mac_call_stmt_semi_span(tail_stmt_expr.span.source_callsite())
116+
else {
117+
return;
118+
};
115119

116120
if self.semicolon_outside_block_ignore_multiline && get_line(cx, remove_span) != get_line(cx, insert_span) {
117121
return;
@@ -150,13 +154,12 @@ impl LateLintPass<'_> for SemicolonBlock {
150154
};
151155
let &Stmt {
152156
kind: StmtKind::Semi(expr),
153-
span,
154157
..
155158
} = stmt
156159
else {
157160
return;
158161
};
159-
self.semicolon_outside_block(cx, block, expr, span);
162+
self.semicolon_outside_block(cx, block, expr);
160163
},
161164
StmtKind::Semi(Expr {
162165
kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _),

clippy_utils/src/diagnostics.rs

+61-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
//! Thank you!
99
//! ~The `INTERNAL_METADATA_COLLECTOR` lint
1010
11-
use rustc_errors::{Applicability, Diag, DiagMessage, MultiSpan, SubdiagMessage};
11+
use rustc_errors::{
12+
Applicability, Diag, DiagMessage, EmissionGuarantee, MultiSpan, SubdiagMessage, SubstitutionPart, Suggestions,
13+
};
1214
use rustc_hir::HirId;
1315
use rustc_lint::{LateContext, Lint, LintContext};
1416
use rustc_span::Span;
@@ -28,6 +30,42 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) {
2830
}
2931
}
3032

33+
/// Makes sure that a diagnostic is well formed.
34+
///
35+
/// rustc debug asserts a few properties about spans,
36+
/// but the clippy repo uses a distributed rustc build with debug assertions disabled,
37+
/// so this has historically led to problems during subtree syncs where those debug assertions
38+
/// only started triggered there.
39+
///
40+
/// This function makes sure we also validate them in debug clippy builds.
41+
fn validate_diag(diag: &Diag<'_, impl EmissionGuarantee>) {
42+
let suggestions = match &diag.suggestions {
43+
Suggestions::Enabled(suggs) => &**suggs,
44+
Suggestions::Sealed(suggs) => &**suggs,
45+
Suggestions::Disabled => return,
46+
};
47+
48+
for substitution in suggestions.iter().flat_map(|s| &s.substitutions) {
49+
assert_eq!(
50+
substitution
51+
.parts
52+
.iter()
53+
.find(|SubstitutionPart { snippet, span }| snippet.is_empty() && span.is_empty()),
54+
None,
55+
"span must not be empty and have no suggestion"
56+
);
57+
58+
assert_eq!(
59+
substitution
60+
.parts
61+
.array_windows()
62+
.find(|[a, b]| a.span.overlaps(b.span)),
63+
None,
64+
"suggestion must not have overlapping parts"
65+
);
66+
}
67+
}
68+
3169
/// Emit a basic lint message with a `msg` and a `span`.
3270
///
3371
/// This is the most primitive of our lint emission methods and can
@@ -64,6 +102,9 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
64102
cx.span_lint(lint, sp, |diag| {
65103
diag.primary_message(msg);
66104
docs_link(diag, lint);
105+
106+
#[cfg(debug_assertions)]
107+
validate_diag(diag);
67108
});
68109
}
69110

@@ -118,6 +159,9 @@ pub fn span_lint_and_help<T: LintContext>(
118159
diag.help(help.into());
119160
}
120161
docs_link(diag, lint);
162+
163+
#[cfg(debug_assertions)]
164+
validate_diag(diag);
121165
});
122166
}
123167

@@ -175,6 +219,9 @@ pub fn span_lint_and_note<T: LintContext>(
175219
diag.note(note.into());
176220
}
177221
docs_link(diag, lint);
222+
223+
#[cfg(debug_assertions)]
224+
validate_diag(diag);
178225
});
179226
}
180227

@@ -208,6 +255,9 @@ where
208255
diag.primary_message(msg);
209256
f(diag);
210257
docs_link(diag, lint);
258+
259+
#[cfg(debug_assertions)]
260+
validate_diag(diag);
211261
});
212262
}
213263

@@ -240,6 +290,9 @@ pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, s
240290
cx.tcx.node_span_lint(lint, hir_id, sp, |diag| {
241291
diag.primary_message(msg);
242292
docs_link(diag, lint);
293+
294+
#[cfg(debug_assertions)]
295+
validate_diag(diag);
243296
});
244297
}
245298

@@ -280,6 +333,9 @@ pub fn span_lint_hir_and_then(
280333
diag.primary_message(msg);
281334
f(diag);
282335
docs_link(diag, lint);
336+
337+
#[cfg(debug_assertions)]
338+
validate_diag(diag);
283339
});
284340
}
285341

@@ -316,7 +372,7 @@ pub fn span_lint_hir_and_then(
316372
/// |
317373
/// = note: `-D fold-any` implied by `-D warnings`
318374
/// ```
319-
#[expect(clippy::collapsible_span_lint_calls)]
375+
#[cfg_attr(not(debug_assertions), expect(clippy::collapsible_span_lint_calls))]
320376
pub fn span_lint_and_sugg<T: LintContext>(
321377
cx: &T,
322378
lint: &'static Lint,
@@ -328,5 +384,8 @@ pub fn span_lint_and_sugg<T: LintContext>(
328384
) {
329385
span_lint_and_then(cx, lint, sp, msg.into(), |diag| {
330386
diag.span_suggestion(sp, help.into(), sugg, applicability);
387+
388+
#[cfg(debug_assertions)]
389+
validate_diag(diag);
331390
});
332391
}

clippy_utils/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#![feature(rustc_private)]
1010
#![feature(assert_matches)]
1111
#![feature(unwrap_infallible)]
12+
#![feature(array_windows)]
1213
#![recursion_limit = "512"]
1314
#![allow(
1415
clippy::missing_errors_doc,

tests/ui/semicolon_outside_block.fixed

+8
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,13 @@ fn main() {
8080

8181
{ unit_fn_block(); };
8282

83+
unsafe {
84+
std::arch::asm!("")
85+
};
86+
87+
{
88+
line!()
89+
};
90+
8391
unit_fn_block()
8492
}

tests/ui/semicolon_outside_block.rs

+8
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,13 @@ fn main() {
8080

8181
{ unit_fn_block(); };
8282

83+
unsafe {
84+
std::arch::asm!("");
85+
}
86+
87+
{
88+
line!();
89+
}
90+
8391
unit_fn_block()
8492
}

tests/ui/semicolon_outside_block.stderr

+29-1
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,33 @@ LL - { m!(()); }
5151
LL + { m!(()) };
5252
|
5353

54-
error: aborting due to 4 previous errors
54+
error: consider moving the `;` outside the block for consistent formatting
55+
--> tests/ui/semicolon_outside_block.rs:83:5
56+
|
57+
LL | / unsafe {
58+
LL | | std::arch::asm!("");
59+
LL | | }
60+
| |_____^
61+
|
62+
help: put the `;` here
63+
|
64+
LL ~ std::arch::asm!("")
65+
LL ~ };
66+
|
67+
68+
error: consider moving the `;` outside the block for consistent formatting
69+
--> tests/ui/semicolon_outside_block.rs:87:5
70+
|
71+
LL | / {
72+
LL | | line!();
73+
LL | | }
74+
| |_____^
75+
|
76+
help: put the `;` here
77+
|
78+
LL ~ line!()
79+
LL ~ };
80+
|
81+
82+
error: aborting due to 6 previous errors
5583

0 commit comments

Comments
 (0)