Skip to content

loop match: handle opaque patterns #143276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 3, 2025
Merged
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
14 changes: 11 additions & 3 deletions compiler/rustc_middle/src/thir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,11 @@ pub enum ExprKind<'tcx> {
},
/// A `#[loop_match] loop { state = 'blk: { match state { ... } } }` expression.
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,
region_scope: region::Scope,
arms: Box<[ArmId]>,
match_span: Span,
match_data: Box<LoopMatchMatchData>,
},
/// 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 `&&`).
Expand Down Expand Up @@ -599,6 +599,14 @@ pub struct Arm<'tcx> {
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)]
pub enum LogicalOp {
/// The `&&` operator.
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_middle/src/thir/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::{
AdtExpr, AdtExprBase, Arm, Block, ClosureExpr, Expr, ExprKind, InlineAsmExpr, InlineAsmOperand,
Pat, PatKind, Stmt, StmtKind, Thir,
};
use crate::thir::LoopMatchMatchData;

/// 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
Expand Down Expand Up @@ -83,7 +84,8 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>(
visitor.visit_pat(pat);
}
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]);
for &arm in &**arms {
visitor.visit_arm(&visitor.thir()[arm]);
Expand Down
12 changes: 8 additions & 4 deletions compiler/rustc_mir_build/src/builder/expr/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
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
// containing a match.
//
Expand Down Expand Up @@ -292,8 +296,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

// Logic for `match`.
let scrutinee_place_builder =
unpack!(body_block = this.as_place_builder(body_block, state));
let scrutinee_span = this.thir.exprs[state].span;
unpack!(body_block = this.as_place_builder(body_block, scrutinee));
let scrutinee_span = this.thir.exprs[scrutinee].span;
let match_start_span = match_span.shrink_to_lo().to(scrutinee_span);

let mut patterns = Vec::with_capacity(arms.len());
Expand Down Expand Up @@ -335,7 +339,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
move |this| {
this.in_breakable_scope(None, state_place, expr_span, |this| {
Some(this.in_const_continuable_scope(
arms.clone(),
Box::from(arms),
built_tree.clone(),
state_place,
expr_span,
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_mir_build/src/builder/matches/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2970,6 +2970,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
Constructor::Wildcard => true,

// Opaque patterns must not be matched on structurally.
Constructor::Opaque(_) => false,

// These we may eventually support:
Constructor::Struct
| Constructor::Ref
Expand All @@ -2980,8 +2983,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
| Constructor::Str(_) => bug!("unsupported pattern constructor {:?}", pat.ctor()),

// These should never occur here:
Constructor::Opaque(_)
| Constructor::Never
Constructor::Never
| Constructor::NonExhaustive
| Constructor::Hidden
| Constructor::Missing
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_mir_build/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1230,7 +1230,6 @@ pub(crate) struct ConstContinueMissingValue {

#[derive(Diagnostic)]
#[diag(mir_build_const_continue_unknown_jump_target)]
#[note]
pub(crate) struct ConstContinueUnknownJumpTarget {
#[primary_span]
pub span: Span,
Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_mir_build/src/thir/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -983,8 +983,11 @@ impl<'tcx> ThirBuildCx<'tcx> {
data: region::ScopeData::Node,
},

arms: arms.iter().map(|a| self.convert_arm(a)).collect(),
match_span: block_body_expr.span,
match_data: Box::new(LoopMatchMatchData {
scrutinee: self.mirror_expr(scrutinee),
arms: arms.iter().map(|a| self.convert_arm(a)).collect(),
span: block_body_expr.span,
}),
}
} else {
let block_ty = self.typeck_results.node_type(body.hir_id);
Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rustc_errors::codes::*;
use rustc_errors::{Applicability, ErrorGuaranteed, MultiSpan, struct_span_code_err};
use rustc_hir::def::*;
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_lint::Level;
use rustc_middle::bug;
Expand Down Expand Up @@ -154,6 +154,12 @@ impl<'p, 'tcx> Visitor<'p, 'tcx> for MatchVisitor<'p, 'tcx> {
ExprKind::Match { scrutinee, box ref arms, match_source } => {
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 } => {
self.check_let(pat, Some(expr), ex.span);
}
Expand Down
19 changes: 12 additions & 7 deletions compiler/rustc_mir_build/src/thir/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,18 +318,23 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> {
self.print_expr(*body, depth_lvl + 2);
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, "state:", depth_lvl + 1);
self.print_expr(*state, depth_lvl + 2);
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, "arms: [", depth_lvl + 1);
for arm_id in arms.iter() {
self.print_arm(*arm_id, depth_lvl + 2);
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 + 3);
for arm_id in match_data.arms.iter() {
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);
}
Let { expr, pat } => {
Expand Down
31 changes: 31 additions & 0 deletions tests/ui/loop-match/invalid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,34 @@ 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,
}
}
}
}

fn invalid_range_pattern(state: f32) {
#[loop_match]
loop {
state = 'blk: {
match state {
1.0 => {
#[const_continue]
break 'blk 2.5;
}
4.0..3.0 => {
//~^ ERROR lower range bound must be less than upper
todo!()
}
}
}
}
}
34 changes: 32 additions & 2 deletions tests/ui/loop-match/invalid.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,36 @@ error: match arms that are part of a `#[loop_match]` cannot have guards
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!(),
|

error[E0579]: lower range bound must be less than upper
--> $DIR/invalid.rs:185:17
|
LL | 4.0..3.0 => {
| ^^^^^^^^

error: aborting due to 14 previous errors

For more information about this error, try `rustc --explain E0308`.
Some errors have detailed explanations: E0004, E0308, E0579.
For more information about an error, try `rustc --explain E0004`.
Loading
Loading