loop match: run exhaustiveness check

This commit is contained in:
Folkert de Vries
2025-07-01 15:29:09 +02:00
parent f46ce66fcc
commit aa7cc5d2f4
9 changed files with 241 additions and 152 deletions

View File

@@ -380,11 +380,11 @@ pub enum ExprKind<'tcx> {
}, },
/// A `#[loop_match] loop { state = 'blk: { match state { ... } } }` expression. /// A `#[loop_match] loop { state = 'blk: { match state { ... } } }` expression.
LoopMatch { LoopMatch {
/// The state variable that is updated, and also the scrutinee of the match. /// The state variable that is updated.
/// The `match_data.scrutinee` is the same variable, but with a different span.
state: ExprId, state: ExprId,
region_scope: region::Scope, region_scope: region::Scope,
arms: Box<[ArmId]>, match_data: Box<LoopMatchMatchData>,
match_span: Span,
}, },
/// Special expression representing the `let` part of an `if let` or similar construct /// Special expression representing the `let` part of an `if let` or similar construct
/// (including `if let` guards in match arms, and let-chains formed by `&&`). /// (including `if let` guards in match arms, and let-chains formed by `&&`).
@@ -599,6 +599,14 @@ pub struct Arm<'tcx> {
pub span: Span, pub span: Span,
} }
/// The `match` part of a `#[loop_match]`
#[derive(Clone, Debug, HashStable)]
pub struct LoopMatchMatchData {
pub scrutinee: ExprId,
pub arms: Box<[ArmId]>,
pub span: Span,
}
#[derive(Copy, Clone, Debug, HashStable)] #[derive(Copy, Clone, Debug, HashStable)]
pub enum LogicalOp { pub enum LogicalOp {
/// The `&&` operator. /// The `&&` operator.

View File

@@ -2,6 +2,7 @@ use super::{
AdtExpr, AdtExprBase, Arm, Block, ClosureExpr, Expr, ExprKind, InlineAsmExpr, InlineAsmOperand, AdtExpr, AdtExprBase, Arm, Block, ClosureExpr, Expr, ExprKind, InlineAsmExpr, InlineAsmOperand,
Pat, PatKind, Stmt, StmtKind, Thir, Pat, PatKind, Stmt, StmtKind, Thir,
}; };
use crate::thir::LoopMatchMatchData;
/// Every `walk_*` method uses deconstruction to access fields of structs and /// Every `walk_*` method uses deconstruction to access fields of structs and
/// enums. This will result in a compile error if a field is added, which makes /// enums. This will result in a compile error if a field is added, which makes
@@ -83,7 +84,8 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>(
visitor.visit_pat(pat); visitor.visit_pat(pat);
} }
Loop { body } => visitor.visit_expr(&visitor.thir()[body]), Loop { body } => visitor.visit_expr(&visitor.thir()[body]),
LoopMatch { state: scrutinee, ref arms, .. } | Match { scrutinee, ref arms, .. } => { LoopMatch { match_data: box LoopMatchMatchData { scrutinee, ref arms, .. }, .. }
| Match { scrutinee, ref arms, .. } => {
visitor.visit_expr(&visitor.thir()[scrutinee]); visitor.visit_expr(&visitor.thir()[scrutinee]);
for &arm in &**arms { for &arm in &**arms {
visitor.visit_arm(&visitor.thir()[arm]); visitor.visit_arm(&visitor.thir()[arm]);

View File

@@ -245,7 +245,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
None None
}) })
} }
ExprKind::LoopMatch { state, region_scope, match_span, ref arms } => { ExprKind::LoopMatch {
state,
region_scope,
match_data: box LoopMatchMatchData { box ref arms, span: match_span, scrutinee },
} => {
// Intuitively, this is a combination of a loop containing a labeled block // Intuitively, this is a combination of a loop containing a labeled block
// containing a match. // containing a match.
// //
@@ -292,8 +296,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// Logic for `match`. // Logic for `match`.
let scrutinee_place_builder = let scrutinee_place_builder =
unpack!(body_block = this.as_place_builder(body_block, state)); unpack!(body_block = this.as_place_builder(body_block, scrutinee));
let scrutinee_span = this.thir.exprs[state].span; let scrutinee_span = this.thir.exprs[scrutinee].span;
let match_start_span = match_span.shrink_to_lo().to(scrutinee_span); let match_start_span = match_span.shrink_to_lo().to(scrutinee_span);
let mut patterns = Vec::with_capacity(arms.len()); let mut patterns = Vec::with_capacity(arms.len());
@@ -335,7 +339,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
move |this| { move |this| {
this.in_breakable_scope(None, state_place, expr_span, |this| { this.in_breakable_scope(None, state_place, expr_span, |this| {
Some(this.in_const_continuable_scope( Some(this.in_const_continuable_scope(
arms.clone(), Box::from(arms),
built_tree.clone(), built_tree.clone(),
state_place, state_place,
expr_span, expr_span,

View File

@@ -983,8 +983,11 @@ impl<'tcx> ThirBuildCx<'tcx> {
data: region::ScopeData::Node, data: region::ScopeData::Node,
}, },
arms: arms.iter().map(|a| self.convert_arm(a)).collect(), match_data: Box::new(LoopMatchMatchData {
match_span: block_body_expr.span, scrutinee: self.mirror_expr(scrutinee),
arms: arms.iter().map(|a| self.convert_arm(a)).collect(),
span: block_body_expr.span,
}),
} }
} else { } else {
let block_ty = self.typeck_results.node_type(body.hir_id); let block_ty = self.typeck_results.node_type(body.hir_id);

View File

@@ -6,7 +6,7 @@ use rustc_errors::codes::*;
use rustc_errors::{Applicability, ErrorGuaranteed, MultiSpan, struct_span_code_err}; use rustc_errors::{Applicability, ErrorGuaranteed, MultiSpan, struct_span_code_err};
use rustc_hir::def::*; use rustc_hir::def::*;
use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::LocalDefId;
use rustc_hir::{self as hir, BindingMode, ByRef, HirId}; use rustc_hir::{self as hir, BindingMode, ByRef, HirId, MatchSource};
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::Level; use rustc_lint::Level;
use rustc_middle::bug; use rustc_middle::bug;
@@ -154,6 +154,12 @@ impl<'p, 'tcx> Visitor<'p, 'tcx> for MatchVisitor<'p, 'tcx> {
ExprKind::Match { scrutinee, box ref arms, match_source } => { ExprKind::Match { scrutinee, box ref arms, match_source } => {
self.check_match(scrutinee, arms, match_source, ex.span); self.check_match(scrutinee, arms, match_source, ex.span);
} }
ExprKind::LoopMatch {
match_data: box LoopMatchMatchData { scrutinee, box ref arms, span },
..
} => {
self.check_match(scrutinee, arms, MatchSource::Normal, span);
}
ExprKind::Let { box ref pat, expr } => { ExprKind::Let { box ref pat, expr } => {
self.check_let(pat, Some(expr), ex.span); self.check_let(pat, Some(expr), ex.span);
} }

View File

@@ -318,18 +318,23 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> {
self.print_expr(*body, depth_lvl + 2); self.print_expr(*body, depth_lvl + 2);
print_indented!(self, ")", depth_lvl); print_indented!(self, ")", depth_lvl);
} }
LoopMatch { state, region_scope, match_span, arms } => { LoopMatch { state, region_scope, match_data } => {
print_indented!(self, "LoopMatch {", depth_lvl); print_indented!(self, "LoopMatch {", depth_lvl);
print_indented!(self, "state:", depth_lvl + 1); print_indented!(self, "state:", depth_lvl + 1);
self.print_expr(*state, depth_lvl + 2); self.print_expr(*state, depth_lvl + 2);
print_indented!(self, format!("region_scope: {:?}", region_scope), depth_lvl + 1); print_indented!(self, format!("region_scope: {:?}", region_scope), depth_lvl + 1);
print_indented!(self, format!("match_span: {:?}", match_span), depth_lvl + 1); print_indented!(self, "match_data:", depth_lvl + 1);
print_indented!(self, "LoopMatchMatchData {", depth_lvl + 2);
print_indented!(self, format!("span: {:?}", match_data.span), depth_lvl + 3);
print_indented!(self, "scrutinee:", depth_lvl + 3);
self.print_expr(match_data.scrutinee, depth_lvl + 4);
print_indented!(self, "arms: [", depth_lvl + 1); print_indented!(self, "arms: [", depth_lvl + 3);
for arm_id in arms.iter() { for arm_id in match_data.arms.iter() {
self.print_arm(*arm_id, depth_lvl + 2); self.print_arm(*arm_id, depth_lvl + 4);
} }
print_indented!(self, "]", depth_lvl + 1); print_indented!(self, "]", depth_lvl + 3);
print_indented!(self, "}", depth_lvl + 2);
print_indented!(self, "}", depth_lvl); print_indented!(self, "}", depth_lvl);
} }
Let { expr, pat } => { Let { expr, pat } => {

View File

@@ -159,3 +159,16 @@ fn arm_has_guard(cond: bool) {
} }
} }
} }
fn non_exhaustive() {
let mut state = State::A;
#[loop_match]
loop {
state = 'blk: {
match state {
//~^ ERROR non-exhaustive patterns: `State::B` and `State::C` not covered
State::A => State::B,
}
}
}
}

View File

@@ -86,6 +86,30 @@ error: match arms that are part of a `#[loop_match]` cannot have guards
LL | State::B if cond => break 'a, LL | State::B if cond => break 'a,
| ^^^^ | ^^^^
error: aborting due to 12 previous errors error[E0004]: non-exhaustive patterns: `State::B` and `State::C` not covered
--> $DIR/invalid.rs:168:19
|
LL | match state {
| ^^^^^ patterns `State::B` and `State::C` not covered
|
note: `State` defined here
--> $DIR/invalid.rs:7:6
|
LL | enum State {
| ^^^^^
LL | A,
LL | B,
| - not covered
LL | C,
| - not covered
= note: the matched value is of type `State`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
|
LL ~ State::A => State::B,
LL ~ State::B | State::C => todo!(),
|
For more information about this error, try `rustc --explain E0308`. error: aborting due to 13 previous errors
Some errors have detailed explanations: E0004, E0308.
For more information about an error, try `rustc --explain E0004`.

View File

@@ -89,158 +89,182 @@ body:
} }
} }
region_scope: Node(10) region_scope: Node(10)
match_span: $DIR/thir-tree-loop-match.rs:11:13: 17:14 (#0) match_data:
arms: [ LoopMatchMatchData {
Arm { span: $DIR/thir-tree-loop-match.rs:11:13: 17:14 (#0)
pattern: scrutinee:
Pat: {
ty: bool
span: $DIR/thir-tree-loop-match.rs:12:17: 12:21 (#0)
kind: PatKind {
Constant {
value: Ty(bool, true)
}
}
}
guard: None
body:
Expr { Expr {
ty: bool ty: bool
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None } temp_lifetime: TempLifetime { temp_lifetime: Some(Node(5)), backwards_incompatible: None }
span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0) span: $DIR/thir-tree-loop-match.rs:11:19: 11:24 (#0)
kind: kind:
Scope { Scope {
region_scope: Node(17) region_scope: Node(12)
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).17)) lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).12))
value: value:
Expr { Expr {
ty: bool ty: bool
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None } temp_lifetime: TempLifetime { temp_lifetime: Some(Node(5)), backwards_incompatible: None }
span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0) span: $DIR/thir-tree-loop-match.rs:11:19: 11:24 (#0)
kind: kind:
NeverToAny { VarRef {
source: id: LocalVarId(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).2))
Expr { }
ty: ! }
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None } }
span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0) }
kind: arms: [
Block { Arm {
targeted_by_break: false pattern:
Pat: {
ty: bool
span: $DIR/thir-tree-loop-match.rs:12:17: 12:21 (#0)
kind: PatKind {
Constant {
value: Ty(bool, true)
}
}
}
guard: None
body:
Expr {
ty: bool
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None }
span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0)
kind:
Scope {
region_scope: Node(17)
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).17))
value:
Expr {
ty: bool
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None }
span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0)
kind:
NeverToAny {
source:
Expr {
ty: !
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None }
span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0) span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0)
region_scope: Node(18) kind:
safety_mode: Safe Block {
stmts: [ targeted_by_break: false
Stmt { span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0)
kind: Expr { region_scope: Node(18)
scope: Node(21) safety_mode: Safe
expr: stmts: [
Expr { Stmt {
ty: ! kind: Expr {
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None } scope: Node(21)
span: $DIR/thir-tree-loop-match.rs:14:21: 14:37 (#0) expr:
kind: Expr {
Scope { ty: !
region_scope: Node(19) temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None }
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).19)) span: $DIR/thir-tree-loop-match.rs:14:21: 14:37 (#0)
value: kind:
Expr { Scope {
ty: ! region_scope: Node(19)
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None } lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).19))
span: $DIR/thir-tree-loop-match.rs:14:21: 14:37 (#0) value:
kind: Expr {
ConstContinue ( ty: !
label: Node(10) temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None }
value: span: $DIR/thir-tree-loop-match.rs:14:21: 14:37 (#0)
Expr { kind:
ty: bool ConstContinue (
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None } label: Node(10)
span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0) value:
kind: Expr {
Scope { ty: bool
region_scope: Node(20) temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None }
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).20)) span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0)
value: kind:
Expr { Scope {
ty: bool region_scope: Node(20)
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None } lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).20))
span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0) value:
kind: Expr {
Literal( lit: Spanned { node: Bool(false), span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0) }, neg: false) ty: bool
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None }
span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0)
kind:
Literal( lit: Spanned { node: Bool(false), span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0) }, neg: false)
}
} }
} }
} )
) }
} }
} }
} }
} }
]
expr: []
} }
]
expr: []
} }
} }
} }
} }
} }
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).16))
scope: Node(16)
span: $DIR/thir-tree-loop-match.rs:12:17: 15:18 (#0)
} }
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).16)) Arm {
scope: Node(16) pattern:
span: $DIR/thir-tree-loop-match.rs:12:17: 15:18 (#0) Pat: {
} ty: bool
Arm { span: $DIR/thir-tree-loop-match.rs:16:17: 16:22 (#0)
pattern: kind: PatKind {
Pat: { Constant {
ty: bool value: Ty(bool, false)
span: $DIR/thir-tree-loop-match.rs:16:17: 16:22 (#0) }
kind: PatKind { }
Constant {
value: Ty(bool, false)
} }
} guard: None
} body:
guard: None Expr {
body: ty: bool
Expr { temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
ty: bool span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0)
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } kind:
span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0) Scope {
kind: region_scope: Node(25)
Scope { lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).25))
region_scope: Node(25) value:
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).25)) Expr {
value: ty: bool
Expr { temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
ty: bool span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0)
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } kind:
span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0) NeverToAny {
kind: source:
NeverToAny { Expr {
source: ty: !
Expr { temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
ty: ! span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0)
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } kind:
span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0) Return {
kind: value:
Return { Expr {
value: ty: bool
Expr { temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
ty: bool span: $DIR/thir-tree-loop-match.rs:16:33: 16:38 (#0)
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } kind:
span: $DIR/thir-tree-loop-match.rs:16:33: 16:38 (#0) Scope {
kind: region_scope: Node(26)
Scope { lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).26))
region_scope: Node(26) value:
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).26)) Expr {
value: ty: bool
Expr { temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
ty: bool span: $DIR/thir-tree-loop-match.rs:16:33: 16:38 (#0)
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } kind:
span: $DIR/thir-tree-loop-match.rs:16:33: 16:38 (#0) VarRef {
kind: id: LocalVarId(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).2))
VarRef { }
id: LocalVarId(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).2))
} }
} }
} }
@@ -250,12 +274,12 @@ body:
} }
} }
} }
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).24))
scope: Node(24)
span: $DIR/thir-tree-loop-match.rs:16:17: 16:38 (#0)
} }
lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).24)) ]
scope: Node(24)
span: $DIR/thir-tree-loop-match.rs:16:17: 16:38 (#0)
} }
]
} }
} }
} }