Skip to content

Commit a2af9cf

Browse files
committed
Auto merge of rust-lang#94987 - Dylan-DPC:rollup-5tssuhi, r=Dylan-DPC
Rollup of 5 pull requests Successful merges: - rust-lang#94868 (Format core and std macro rules, removing needless surrounding blocks) - rust-lang#94951 (Extend the irrefutable_let_patterns lint to let chains) - rust-lang#94955 (Refactor: Use `format_args_capture` in some parts of `rustc_parse`) - rust-lang#94957 (Improve the explanation about the behaviour of read_line) - rust-lang#94974 (Ensure that `let_else` does not interact with `let_chains`) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
2 parents af446e1 + aaf2255 commit a2af9cf

19 files changed

+650
-151
lines changed

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+180-35
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
158158
self.check_patterns(pat, Refutable);
159159
let mut cx = self.new_cx(scrutinee.hir_id);
160160
let tpat = self.lower_pattern(&mut cx, pat, &mut false);
161-
check_let_reachability(&mut cx, pat.hir_id, tpat, span);
161+
self.check_let_reachability(&mut cx, pat.hir_id, tpat, span);
162162
}
163163

164164
fn check_match(
@@ -176,7 +176,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
176176
if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard {
177177
self.check_patterns(pat, Refutable);
178178
let tpat = self.lower_pattern(&mut cx, pat, &mut false);
179-
check_let_reachability(&mut cx, pat.hir_id, tpat, tpat.span());
179+
self.check_let_reachability(&mut cx, pat.hir_id, tpat, tpat.span());
180180
}
181181
}
182182

@@ -224,6 +224,157 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
224224
}
225225
}
226226

227+
fn check_let_reachability(
228+
&mut self,
229+
cx: &mut MatchCheckCtxt<'p, 'tcx>,
230+
pat_id: HirId,
231+
pat: &'p DeconstructedPat<'p, 'tcx>,
232+
span: Span,
233+
) {
234+
if self.check_let_chain(cx, pat_id) {
235+
return;
236+
}
237+
238+
if is_let_irrefutable(cx, pat_id, pat) {
239+
irrefutable_let_pattern(cx.tcx, pat_id, span);
240+
}
241+
}
242+
243+
fn check_let_chain(&mut self, cx: &mut MatchCheckCtxt<'p, 'tcx>, pat_id: HirId) -> bool {
244+
let hir = self.tcx.hir();
245+
let parent = hir.get_parent_node(pat_id);
246+
247+
// First, figure out if the given pattern is part of a let chain,
248+
// and if so, obtain the top node of the chain.
249+
let mut top = parent;
250+
let mut part_of_chain = false;
251+
loop {
252+
let new_top = hir.get_parent_node(top);
253+
if let hir::Node::Expr(
254+
hir::Expr {
255+
kind: hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, lhs, rhs),
256+
..
257+
},
258+
..,
259+
) = hir.get(new_top)
260+
{
261+
// If this isn't the first iteration, we need to check
262+
// if there is a let expr before us in the chain, so
263+
// that we avoid doubly checking the let chain.
264+
265+
// The way a chain of &&s is encoded is ((let ... && let ...) && let ...) && let ...
266+
// as && is left-to-right associative. Thus, we need to check rhs.
267+
if part_of_chain && matches!(rhs.kind, hir::ExprKind::Let(..)) {
268+
return true;
269+
}
270+
// If there is a let at the lhs, and we provide the rhs, we don't do any checking either.
271+
if !part_of_chain && matches!(lhs.kind, hir::ExprKind::Let(..)) && rhs.hir_id == top
272+
{
273+
return true;
274+
}
275+
} else {
276+
// We've reached the top.
277+
break;
278+
}
279+
280+
// Since this function is called within a let context, it is reasonable to assume that any parent
281+
// `&&` infers a let chain
282+
part_of_chain = true;
283+
top = new_top;
284+
}
285+
if !part_of_chain {
286+
return false;
287+
}
288+
289+
// Second, obtain the refutabilities of all exprs in the chain,
290+
// and record chain members that aren't let exprs.
291+
let mut chain_refutabilities = Vec::new();
292+
let hir::Node::Expr(top_expr) = hir.get(top) else {
293+
// We ensure right above that it's an Expr
294+
unreachable!()
295+
};
296+
let mut cur_expr = top_expr;
297+
loop {
298+
let mut add = |expr: &hir::Expr<'tcx>| {
299+
let refutability = match expr.kind {
300+
hir::ExprKind::Let(hir::Let { pat, init, span, .. }) => {
301+
let mut ncx = self.new_cx(init.hir_id);
302+
let tpat = self.lower_pattern(&mut ncx, pat, &mut false);
303+
304+
let refutable = !is_let_irrefutable(&mut ncx, pat.hir_id, tpat);
305+
Some((*span, refutable))
306+
}
307+
_ => None,
308+
};
309+
chain_refutabilities.push(refutability);
310+
};
311+
if let hir::Expr {
312+
kind: hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, lhs, rhs),
313+
..
314+
} = cur_expr
315+
{
316+
add(rhs);
317+
cur_expr = lhs;
318+
} else {
319+
add(cur_expr);
320+
break;
321+
}
322+
}
323+
chain_refutabilities.reverse();
324+
325+
// Third, emit the actual warnings.
326+
327+
if chain_refutabilities.iter().all(|r| matches!(*r, Some((_, false)))) {
328+
// The entire chain is made up of irrefutable `let` statements
329+
let let_source = let_source_parent(self.tcx, top, None);
330+
irrefutable_let_patterns(
331+
cx.tcx,
332+
top,
333+
let_source,
334+
chain_refutabilities.len(),
335+
top_expr.span,
336+
);
337+
return true;
338+
}
339+
let lint_affix = |affix: &[Option<(Span, bool)>], kind, suggestion| {
340+
let span_start = affix[0].unwrap().0;
341+
let span_end = affix.last().unwrap().unwrap().0;
342+
let span = span_start.to(span_end);
343+
let cnt = affix.len();
344+
cx.tcx.struct_span_lint_hir(IRREFUTABLE_LET_PATTERNS, top, span, |lint| {
345+
let s = pluralize!(cnt);
346+
let mut diag = lint.build(&format!("{kind} irrefutable pattern{s} in let chain"));
347+
diag.note(&format!(
348+
"{these} pattern{s} will always match",
349+
these = pluralize!("this", cnt),
350+
));
351+
diag.help(&format!(
352+
"consider moving {} {suggestion}",
353+
if cnt > 1 { "them" } else { "it" }
354+
));
355+
diag.emit()
356+
});
357+
};
358+
if let Some(until) = chain_refutabilities.iter().position(|r| !matches!(*r, Some((_, false)))) && until > 0 {
359+
// The chain has a non-zero prefix of irrefutable `let` statements.
360+
361+
// Check if the let source is while, for there is no alternative place to put a prefix,
362+
// and we shouldn't lint.
363+
let let_source = let_source_parent(self.tcx, top, None);
364+
if !matches!(let_source, LetSource::WhileLet) {
365+
// Emit the lint
366+
let prefix = &chain_refutabilities[..until];
367+
lint_affix(prefix, "leading", "outside of the construct");
368+
}
369+
}
370+
if let Some(from) = chain_refutabilities.iter().rposition(|r| !matches!(*r, Some((_, false)))) && from != (chain_refutabilities.len() - 1) {
371+
// The chain has a non-empty suffix of irrefutable `let` statements
372+
let suffix = &chain_refutabilities[from + 1..];
373+
lint_affix(suffix, "trailing", "into the body");
374+
}
375+
true
376+
}
377+
227378
fn check_irrefutable(&self, pat: &'tcx Pat<'tcx>, origin: &str, sp: Option<Span>) {
228379
let mut cx = self.new_cx(pat.hir_id);
229380

@@ -453,21 +604,33 @@ fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option<
453604
}
454605

455606
fn irrefutable_let_pattern(tcx: TyCtxt<'_>, id: HirId, span: Span) {
607+
let source = let_source(tcx, id);
608+
irrefutable_let_patterns(tcx, id, source, 1, span);
609+
}
610+
611+
fn irrefutable_let_patterns(
612+
tcx: TyCtxt<'_>,
613+
id: HirId,
614+
source: LetSource,
615+
count: usize,
616+
span: Span,
617+
) {
456618
macro_rules! emit_diag {
457619
(
458620
$lint:expr,
459621
$source_name:expr,
460622
$note_sufix:expr,
461623
$help_sufix:expr
462624
) => {{
463-
let mut diag = $lint.build(concat!("irrefutable ", $source_name, " pattern"));
464-
diag.note(concat!("this pattern will always match, so the ", $note_sufix));
625+
let s = pluralize!(count);
626+
let these = pluralize!("this", count);
627+
let mut diag = $lint.build(&format!("irrefutable {} pattern{s}", $source_name));
628+
diag.note(&format!("{these} pattern{s} will always match, so the {}", $note_sufix));
465629
diag.help(concat!("consider ", $help_sufix));
466630
diag.emit()
467631
}};
468632
}
469633

470-
let source = let_source(tcx, id);
471634
let span = match source {
472635
LetSource::LetElse(span) => span,
473636
_ => span,
@@ -511,16 +674,11 @@ fn irrefutable_let_pattern(tcx: TyCtxt<'_>, id: HirId, span: Span) {
511674
});
512675
}
513676

514-
fn check_let_reachability<'p, 'tcx>(
677+
fn is_let_irrefutable<'p, 'tcx>(
515678
cx: &mut MatchCheckCtxt<'p, 'tcx>,
516679
pat_id: HirId,
517680
pat: &'p DeconstructedPat<'p, 'tcx>,
518-
span: Span,
519-
) {
520-
if is_let_chain(cx.tcx, pat_id) {
521-
return;
522-
}
523-
681+
) -> bool {
524682
let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }];
525683
let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty());
526684

@@ -529,10 +687,9 @@ fn check_let_reachability<'p, 'tcx>(
529687
// `is_uninhabited` check.
530688
report_arm_reachability(&cx, &report);
531689

532-
if report.non_exhaustiveness_witnesses.is_empty() {
533-
// The match is exhaustive, i.e. the `if let` pattern is irrefutable.
534-
irrefutable_let_pattern(cx.tcx, pat_id, span);
535-
}
690+
// If the list of witnesses is empty, the match is exhaustive,
691+
// i.e. the `if let` pattern is irrefutable.
692+
report.non_exhaustiveness_witnesses.is_empty()
536693
}
537694

538695
/// Report unreachable arms, if any.
@@ -941,13 +1098,19 @@ fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource {
9411098
let hir = tcx.hir();
9421099

9431100
let parent = hir.get_parent_node(pat_id);
1101+
let_source_parent(tcx, parent, Some(pat_id))
1102+
}
1103+
1104+
fn let_source_parent(tcx: TyCtxt<'_>, parent: HirId, pat_id: Option<HirId>) -> LetSource {
1105+
let hir = tcx.hir();
1106+
9441107
let parent_node = hir.get(parent);
9451108

9461109
match parent_node {
9471110
hir::Node::Arm(hir::Arm {
9481111
guard: Some(hir::Guard::IfLet(&hir::Pat { hir_id, .. }, _)),
9491112
..
950-
}) if hir_id == pat_id => {
1113+
}) if Some(hir_id) == pat_id => {
9511114
return LetSource::IfLetGuard;
9521115
}
9531116
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Let(..), span, .. }) => {
@@ -980,21 +1143,3 @@ fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource {
9801143

9811144
LetSource::GenericLet
9821145
}
983-
984-
// Since this function is called within a let context, it is reasonable to assume that any parent
985-
// `&&` infers a let chain
986-
fn is_let_chain(tcx: TyCtxt<'_>, pat_id: HirId) -> bool {
987-
let hir = tcx.hir();
988-
let parent = hir.get_parent_node(pat_id);
989-
let parent_parent = hir.get_parent_node(parent);
990-
matches!(
991-
hir.get(parent_parent),
992-
hir::Node::Expr(
993-
hir::Expr {
994-
kind: hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, ..),
995-
..
996-
},
997-
..
998-
)
999-
)
1000-
}

compiler/rustc_parse/src/parser/attr.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ impl<'a> Parser<'a> {
139139
Ok(attr::mk_attr_from_item(item, None, style, attr_sp))
140140
} else {
141141
let token_str = pprust::token_to_string(&this.token);
142-
let msg = &format!("expected `#`, found `{}`", token_str);
142+
let msg = &format!("expected `#`, found `{token_str}`");
143143
Err(this.struct_span_err(this.token.span, msg))
144144
}
145145
})
@@ -421,7 +421,7 @@ impl<'a> Parser<'a> {
421421
}
422422

423423
let found = pprust::token_to_string(&self.token);
424-
let msg = format!("expected unsuffixed literal or identifier, found `{}`", found);
424+
let msg = format!("expected unsuffixed literal or identifier, found `{found}`");
425425
Err(self.struct_span_err(self.token.span, &msg))
426426
}
427427
}

0 commit comments

Comments
 (0)