From 72d0703fee5351b65a022a30b98f869a85f07628 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 18 Feb 2025 14:16:57 +0100 Subject: [PATCH 01/35] Add `#[loop_match]` for improved DFA codegen Co-authored-by: Folkert de Vries --- compiler/rustc_feature/src/builtin_attrs.rs | 10 + compiler/rustc_feature/src/unstable.rs | 1 + compiler/rustc_middle/src/thir.rs | 12 + compiler/rustc_middle/src/thir/visit.rs | 7 + compiler/rustc_mir_build/messages.ftl | 18 ++ .../src/builder/expr/as_place.rs | 2 + .../src/builder/expr/as_rvalue.rs | 2 + .../src/builder/expr/category.rs | 2 + .../rustc_mir_build/src/builder/expr/into.rs | 238 ++++++++++++++++++ .../rustc_mir_build/src/builder/expr/stmt.rs | 6 + compiler/rustc_mir_build/src/builder/scope.rs | 202 +++++++++++++++ .../rustc_mir_build/src/check_unsafety.rs | 2 + compiler/rustc_mir_build/src/errors.rs | 45 ++++ compiler/rustc_mir_build/src/thir/cx/expr.rs | 148 +++++++++-- .../src/thir/pattern/check_match.rs | 7 +- compiler/rustc_mir_build/src/thir/print.rs | 20 ++ compiler/rustc_passes/messages.ftl | 9 + compiler/rustc_passes/src/check_attr.rs | 28 +++ compiler/rustc_passes/src/errors.rs | 18 ++ compiler/rustc_span/src/symbol.rs | 2 + compiler/rustc_ty_utils/src/consts.rs | 8 +- .../feature-gates/feature-gate-loop-match.rs | 27 ++ .../feature-gate-loop-match.stderr | 33 +++ tests/ui/loop-match/break-to-block.rs | 20 ++ tests/ui/loop-match/drop-in-match-arm.rs | 32 +++ tests/ui/loop-match/integer-patterns.rs | 30 +++ tests/ui/loop-match/invalid-attribute.rs | 42 ++++ tests/ui/loop-match/invalid-attribute.stderr | 129 ++++++++++ tests/ui/loop-match/invalid.rs | 142 +++++++++++ tests/ui/loop-match/invalid.stderr | 85 +++++++ tests/ui/loop-match/loop-match.rs | 35 +++ tests/ui/loop-match/nested.rs | 78 ++++++ tests/ui/loop-match/or-patterns.rs | 41 +++ 33 files changed, 1457 insertions(+), 24 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-loop-match.rs create mode 100644 tests/ui/feature-gates/feature-gate-loop-match.stderr create mode 100644 tests/ui/loop-match/break-to-block.rs create mode 100644 tests/ui/loop-match/drop-in-match-arm.rs create mode 100644 tests/ui/loop-match/integer-patterns.rs create mode 100644 tests/ui/loop-match/invalid-attribute.rs create mode 100644 tests/ui/loop-match/invalid-attribute.stderr create mode 100644 tests/ui/loop-match/invalid.rs create mode 100644 tests/ui/loop-match/invalid.stderr create mode 100644 tests/ui/loop-match/loop-match.rs create mode 100644 tests/ui/loop-match/nested.rs create mode 100644 tests/ui/loop-match/or-patterns.rs diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 1e33e2e9393f7..3047b6f77b690 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -583,6 +583,16 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ EncodeCrossCrate::Yes, min_generic_const_args, experimental!(type_const), ), + // #[loop_match] and #[const_continue] + gated!( + const_continue, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::No, loop_match, experimental!(const_continue) + ), + gated!( + loop_match, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::No, loop_match, experimental!(loop_match) + ), + // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 87b88bb4223ed..79cf05fe16882 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -545,6 +545,7 @@ declare_features! ( /// Allows using `#[link(kind = "link-arg", name = "...")]` /// to pass custom arguments to the linker. (unstable, link_arg_attribute, "1.76.0", Some(99427)), + (unstable, loop_match, "CURRENT_RUSTC_VERSION", Some(138777)), /// Give access to additional metadata about declarative macro meta-variables. (unstable, macro_metavar_expr, "1.61.0", Some(83527)), /// Provides a way to concatenate identifiers using metavariable expressions. diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 6783bbf8bf42f..be156e70c0af7 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -375,6 +375,13 @@ pub enum ExprKind<'tcx> { Loop { body: ExprId, }, + /// A `#[loop_match] loop { state = 'blk: { match state { ... } } }` expression. + LoopMatch { + state: ExprId, + + region_scope: region::Scope, + arms: Box<[ArmId]>, + }, /// 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 `&&`). /// @@ -451,6 +458,11 @@ pub enum ExprKind<'tcx> { Continue { label: region::Scope, }, + /// A `#[const_continue] break` expression. + ConstContinue { + label: region::Scope, + value: ExprId, + }, /// A `return` expression. Return { value: Option, diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index 7d62ab7970d01..cc23c9c40eadc 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -79,6 +79,12 @@ 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, ref arms, .. } => { + visitor.visit_expr(&visitor.thir()[state]); + for &arm in &**arms { + visitor.visit_arm(&visitor.thir()[arm]); + } + } Match { scrutinee, ref arms, .. } => { visitor.visit_expr(&visitor.thir()[scrutinee]); for &arm in &**arms { @@ -104,6 +110,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( } } Continue { label: _ } => {} + ConstContinue { value, label: _ } => visitor.visit_expr(&visitor.thir()[value]), Return { value } => { if let Some(value) = value { visitor.visit_expr(&visitor.thir()[value]) diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index fae159103e70d..a0b6bd1781272 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -84,6 +84,8 @@ mir_build_call_to_unsafe_fn_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = mir_build_confused = missing patterns are not covered because `{$variable}` is interpreted as a constant pattern, not a new variable +mir_build_const_continue_missing_value = a `const_continue` must break to a label with a value + mir_build_const_defined_here = constant defined here mir_build_const_param_in_pattern = constant parameters cannot be referenced in patterns @@ -212,6 +214,22 @@ mir_build_literal_in_range_out_of_bounds = literal out of range for `{$ty}` .label = this value does not fit into the type `{$ty}` whose range is `{$min}..={$max}` +mir_build_loop_match_bad_rhs = + this expression must be a single `match` wrapped in a labelled block + +mir_build_loop_match_bad_statements = + statements are not allowed in this position within a `loop_match` + +mir_build_loop_match_invalid_match = + invalid match on `loop_match` state + .note = only matches on local variables are valid +mir_build_loop_match_invalid_update = + invalid update of the `loop_match` state + .label = the assignment must update this variable + +mir_build_loop_match_missing_assignment = + expected a single assignment expression + mir_build_lower_range_bound_must_be_less_than_or_equal_to_upper = lower range bound must be less than or equal to upper .label = lower bound larger than upper bound diff --git a/compiler/rustc_mir_build/src/builder/expr/as_place.rs b/compiler/rustc_mir_build/src/builder/expr/as_place.rs index 90e27c85f749a..6657c0fe3bb59 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_place.rs @@ -562,12 +562,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Match { .. } | ExprKind::If { .. } | ExprKind::Loop { .. } + | ExprKind::LoopMatch { .. } | ExprKind::Block { .. } | ExprKind::Let { .. } | ExprKind::Assign { .. } | ExprKind::AssignOp { .. } | ExprKind::Break { .. } | ExprKind::Continue { .. } + | ExprKind::ConstContinue { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } | ExprKind::Literal { .. } diff --git a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs index f9791776f71e5..e501f1e862f6e 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs @@ -538,6 +538,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::RawBorrow { .. } | ExprKind::Adt { .. } | ExprKind::Loop { .. } + | ExprKind::LoopMatch { .. } | ExprKind::LogicalOp { .. } | ExprKind::Call { .. } | ExprKind::Field { .. } @@ -548,6 +549,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::UpvarRef { .. } | ExprKind::Break { .. } | ExprKind::Continue { .. } + | ExprKind::ConstContinue { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } | ExprKind::InlineAsm { .. } diff --git a/compiler/rustc_mir_build/src/builder/expr/category.rs b/compiler/rustc_mir_build/src/builder/expr/category.rs index 34524aed40678..5e4219dbf5bc9 100644 --- a/compiler/rustc_mir_build/src/builder/expr/category.rs +++ b/compiler/rustc_mir_build/src/builder/expr/category.rs @@ -83,9 +83,11 @@ impl Category { | ExprKind::NamedConst { .. } => Some(Category::Constant), ExprKind::Loop { .. } + | ExprKind::LoopMatch { .. } | ExprKind::Block { .. } | ExprKind::Break { .. } | ExprKind::Continue { .. } + | ExprKind::ConstContinue { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } => // FIXME(#27840) these probably want their own diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 333e69475c508..9090e994d0201 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -1,5 +1,6 @@ //! See docs in build/expr/mod.rs +use rustc_abi::VariantIdx; use rustc_ast::{AsmMacro, InlineAsmOptions}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -8,7 +9,10 @@ use rustc_hir::lang_items::LangItem; use rustc_middle::mir::*; use rustc_middle::span_bug; use rustc_middle::thir::*; +use rustc_middle::ty::util::Discr; use rustc_middle::ty::{CanonicalUserTypeAnnotation, Ty}; +use rustc_pattern_analysis::constructor::Constructor; +use rustc_pattern_analysis::rustc::{DeconstructedPat, RustcPatCtxt}; use rustc_span::DUMMY_SP; use rustc_span::source_map::Spanned; use rustc_trait_selection::infer::InferCtxtExt; @@ -244,6 +248,188 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { None }) } + ExprKind::LoopMatch { state, region_scope, ref arms, .. } => { + // FIXME add diagram + + let dropless_arena = rustc_arena::DroplessArena::default(); + let typeck_results = this.tcx.typeck(this.def_id); + + // the PatCtxt is normally used in pattern exhaustiveness checking, but reused here + // because it performs normalization and const evaluation. + let cx = RustcPatCtxt { + tcx: this.tcx, + typeck_results, + module: this.tcx.parent_module(this.hir_id).to_def_id(), + // FIXME(#132279): We're in a body, should handle opaques. + typing_env: rustc_middle::ty::TypingEnv::non_body_analysis( + this.tcx, + this.def_id, + ), + dropless_arena: &dropless_arena, + match_lint_level: this.hir_id, + whole_match_span: Some(rustc_span::Span::default()), + scrut_span: rustc_span::Span::default(), + refutable: true, + known_valid_scrutinee: true, + }; + + let loop_block = this.cfg.start_new_block(); + + // Start the loop. + this.cfg.goto(block, source_info, loop_block); + + // FIXME do we need the breakable scope? + this.in_breakable_scope(Some(loop_block), destination, expr_span, |this| { + // conduct the test, if necessary + let mut body_block = this.cfg.start_new_block(); + this.cfg.terminate( + loop_block, + source_info, + TerminatorKind::FalseUnwind { + real_target: body_block, + unwind: UnwindAction::Continue, + }, + ); + this.diverge_from(loop_block); + + let state_place = unpack!(body_block = this.as_place(body_block, state)); + let state_ty = this.thir.exprs[state].ty; + + // the type of the value that is switched on by the `SwitchInt` + let discr_ty = match state_ty { + ty if ty.is_enum() => ty.discriminant_ty(this.tcx), + ty if ty.is_integral() => ty, + other => todo!("{other:?}"), + }; + + let rvalue = match state_ty { + ty if ty.is_enum() => Rvalue::Discriminant(state_place), + ty if ty.is_integral() => Rvalue::Use(Operand::Copy(state_place)), + _ => todo!(), + }; + + // block and arm of the wildcard pattern (if any) + let mut otherwise = None; + + unpack!( + body_block = this.in_scope( + (region_scope, source_info), + LintLevel::Inherited, + move |this| { + let mut arm_blocks = Vec::with_capacity(arms.len()); + for &arm in arms { + let pat = &this.thir[arm].pattern; + + this.loop_match_patterns( + arm, + &cx.lower_pat(pat), + None, + &mut arm_blocks, + &mut otherwise, + ); + } + + // handle patterns like `None | None` or two different arms that + // have the same pattern. + // + // NOTE: why this works is a bit subtle: we always want to pick the + // first arm for a pattern, and because this is a stable sort that + // works out. + arm_blocks.sort_by_key(|(_, discr, _, _)| discr.val); + arm_blocks.dedup_by_key(|(_, discr, _, _)| discr.val); + + // if we're matching on an enum, the discriminant order in the `SwitchInt` + // targets should match the order yielded by `AdtDef::discriminants`. + if state_ty.is_enum() { + arm_blocks.sort_by_key(|(variant_idx, ..)| *variant_idx); + } + + let targets = SwitchTargets::new( + arm_blocks + .iter() + .map(|&(_, discr, block, _arm)| (discr.val, block)), + if let Some((block, _)) = otherwise { + block + } else { + let unreachable_block = this.cfg.start_new_block(); + this.cfg.terminate( + unreachable_block, + source_info, + TerminatorKind::Unreachable, + ); + unreachable_block + }, + ); + + this.in_breakable_scope(None, state_place, expr_span, |this| { + Some(this.in_const_continuable_scope( + targets.clone(), + state_place, + expr_span, + |this| { + let discr = this.temp(discr_ty, source_info.span); + this.cfg.push_assign( + body_block, + source_info, + discr, + rvalue, + ); + let discr = Operand::Copy(discr); + this.cfg.terminate( + body_block, + source_info, + TerminatorKind::SwitchInt { discr, targets }, + ); + + let it = arm_blocks + .into_iter() + .map(|(_, _, block, arm)| (block, arm)) + .chain(otherwise); + + for (mut block, arm_id) in it { + if this.cfg.block_data(block).terminator.is_some() { + continue; // this can occur with or-patterns + } + + let arm = &this.thir[arm_id]; + let arm_source_info = this.source_info(arm.span); + let arm_scope = (arm.scope, arm_source_info); + + let empty_place = this.get_unit_temp(); + unpack!( + block = { + this.in_scope( + arm_scope, + arm.lint_level, + |this| { + this.expr_into_dest( + empty_place, + block, + arm.body, + ) + }, + ) + } + ); + this.cfg.terminate( + block, + source_info, + TerminatorKind::Unreachable, + ); + } + }, + )) + }) + } + ) + ); + + this.cfg.goto(body_block, source_info, loop_block); + + // Loops are only exited by `break` expressions. + None + }) + } ExprKind::Call { ty: _, fun, ref args, from_hir_call, fn_span } => { let fun = unpack!(block = this.as_local_operand(block, fun)); let args: Box<[_]> = args @@ -601,6 +787,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } ExprKind::Continue { .. } + | ExprKind::ConstContinue { .. } | ExprKind::Break { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } => { @@ -708,4 +895,55 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { _ => false, } } + + fn loop_match_patterns( + &mut self, + arm_id: ArmId, + pat: &DeconstructedPat<'_, 'tcx>, + current_block: Option, + result: &mut Vec<(VariantIdx, Discr<'tcx>, BasicBlock, ArmId)>, + otherwise: &mut Option<(BasicBlock, ArmId)>, + ) { + match pat.ctor() { + Constructor::Variant(variant_index) => { + let PatKind::Variant { adt_def, .. } = pat.data().kind else { unreachable!() }; + + let discr = adt_def.discriminant_for_variant(self.tcx, *variant_index); + + let block = current_block.unwrap_or_else(|| self.cfg.start_new_block()); + result.push((*variant_index, discr, block, arm_id)); + } + Constructor::IntRange(int_range) => { + assert!(int_range.is_singleton()); + + let bits = pat.ty().primitive_size(self.tcx).bits(); + + let value = if pat.ty().is_signed() { + int_range.lo.as_finite_int(bits).unwrap() + } else { + int_range.lo.as_finite_uint().unwrap() + }; + + let discr = Discr { val: value, ty: **pat.ty() }; + + let block = current_block.unwrap_or_else(|| self.cfg.start_new_block()); + result.push((VariantIdx::ZERO, discr, block, arm_id)); + } + Constructor::Wildcard => { + // the first wildcard wins + if otherwise.is_none() { + let block = current_block.unwrap_or_else(|| self.cfg.start_new_block()); + *otherwise = Some((block, arm_id)) + } + } + Constructor::Or => { + let block = current_block.unwrap_or_else(|| self.cfg.start_new_block()); + + for indexed in pat.iter_fields() { + self.loop_match_patterns(arm_id, &indexed.pat, Some(block), result, otherwise); + } + } + other => todo!("{:?}", other), + } + } } diff --git a/compiler/rustc_mir_build/src/builder/expr/stmt.rs b/compiler/rustc_mir_build/src/builder/expr/stmt.rs index 7f8a0a34c3123..25b64f0105cb9 100644 --- a/compiler/rustc_mir_build/src/builder/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/builder/expr/stmt.rs @@ -92,6 +92,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Break { label, value } => { this.break_scope(block, value, BreakableTarget::Break(label), source_info) } + ExprKind::ConstContinue { label, value } => this.break_scope( + block, + Some(value), + BreakableTarget::ConstContinue(label), + source_info, + ), ExprKind::Return { value } => { this.break_scope(block, value, BreakableTarget::Return, source_info) } diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index e42336a1dbbcc..f5381d0ccc13c 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -83,6 +83,7 @@ that contains only loops and breakable blocks. It tracks where a `break`, use std::mem; +use rustc_ast::LitKind; use rustc_data_structures::fx::FxHashMap; use rustc_hir::HirId; use rustc_index::{IndexSlice, IndexVec}; @@ -104,6 +105,8 @@ pub(crate) struct Scopes<'tcx> { /// The current set of breakable scopes. See module comment for more details. breakable_scopes: Vec>, + const_continuable_scopes: Vec>, + /// The scope of the innermost if-then currently being lowered. if_then_scope: Option, @@ -173,6 +176,19 @@ struct BreakableScope<'tcx> { continue_drops: Option, } +#[derive(Debug)] +struct ConstContinuableScope<'tcx> { + /// The if-then scope or arm scope + region_scope: region::Scope, + /// The destination of the loop/block expression itself (i.e., where to put + /// the result of a `break` or `return` expression) + state_place: Place<'tcx>, + + match_arms: SwitchTargets, + /// Drops that happen on the `return` path and would have happened on the `break` path. + break_drops: DropTree, +} + #[derive(Debug)] struct IfThenScope { /// The if-then scope or arm scope @@ -186,6 +202,7 @@ struct IfThenScope { pub(crate) enum BreakableTarget { Continue(region::Scope), Break(region::Scope), + ConstContinue(region::Scope), Return, } @@ -459,6 +476,7 @@ impl<'tcx> Scopes<'tcx> { Self { scopes: Vec::new(), breakable_scopes: Vec::new(), + const_continuable_scopes: Vec::new(), if_then_scope: None, unwind_drops: DropTree::new(), coroutine_drops: DropTree::new(), @@ -550,6 +568,39 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } + /// Start a const-continuable scope, which tracks where `#[const_continue] break` should + /// branch to. + pub(crate) fn in_const_continuable_scope( + &mut self, + match_arms: SwitchTargets, + state_place: Place<'tcx>, + span: Span, + f: F, + ) -> BlockAnd<()> + where + F: FnOnce(&mut Builder<'a, 'tcx>), + { + let region_scope = self.scopes.topmost(); + let scope = ConstContinuableScope { + region_scope, + state_place, + break_drops: DropTree::new(), + match_arms, + }; + self.scopes.const_continuable_scopes.push(scope); + f(self); + let breakable_scope = self.scopes.const_continuable_scopes.pop().unwrap(); + assert!(breakable_scope.region_scope == region_scope); + + let break_block = + self.build_exit_tree(breakable_scope.break_drops, region_scope, span, None); + + match break_block { + Some(block) => block, + None => self.cfg.start_new_block().unit(), + } + } + /// Start an if-then scope which tracks drop for `if` expressions and `if` /// guards. /// @@ -679,6 +730,157 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let break_index = get_scope_index(scope); (break_index, None) } + BreakableTarget::ConstContinue(scope) => { + assert!(value.is_some()); + let break_index = self + .scopes + .const_continuable_scopes + .iter() + .rposition(|const_continuable_scope| { + const_continuable_scope.region_scope == scope + }) + .unwrap_or_else(|| { + span_bug!(span, "no enclosing const-continuable scope found") + }); + + let rustc_middle::thir::ExprKind::Scope { value, .. } = + self.thir[value.unwrap()].kind + else { + panic!(); + }; + + let scope = &self.scopes.const_continuable_scopes[break_index]; + + let state_ty = self.local_decls[scope.state_place.as_local().unwrap()].ty; + let discriminant_ty = match state_ty { + ty if ty.is_enum() => ty.discriminant_ty(self.tcx), + ty if ty.is_integral() => ty, + _ => todo!(), + }; + + let rvalue = match state_ty { + ty if ty.is_enum() => Rvalue::Discriminant(scope.state_place), + ty if ty.is_integral() => Rvalue::Use(Operand::Copy(scope.state_place)), + _ => todo!(), + }; + + let real_target = match &self.thir[value].kind { + rustc_middle::thir::ExprKind::Adt(value_adt) => scope + .match_arms + .target_for_value(u128::from(value_adt.variant_index.as_u32())), + rustc_middle::thir::ExprKind::Literal { lit, neg } => match lit.node { + LitKind::Int(n, _) => { + let n = if *neg { + (n.get() as i128).overflowing_neg().0 as u128 + } else { + n.get() + }; + let result = state_ty.primitive_size(self.tcx).truncate(n); + scope.match_arms.target_for_value(result) + } + _ => todo!(), + }, + other => todo!("{other:?}"), + }; + + self.block_context.push(BlockFrame::SubExpr); + let state_place = scope.state_place; + block = self.expr_into_dest(state_place, block, value).into_block(); + self.block_context.pop(); + + let discr = self.temp(discriminant_ty, source_info.span); + let scope_index = self.scopes.scope_index( + self.scopes.const_continuable_scopes[break_index].region_scope, + span, + ); + let scope = &mut self.scopes.const_continuable_scopes[break_index]; + self.cfg.push_assign(block, source_info, discr, rvalue); + let drop_and_continue_block = self.cfg.start_new_block(); + let imaginary_target = self.cfg.start_new_block(); + self.cfg.terminate( + block, + source_info, + TerminatorKind::FalseEdge { + real_target: drop_and_continue_block, + imaginary_target, + }, + ); + + let drops = &mut scope.break_drops; + + let drop_idx = self.scopes.scopes[scope_index + 1..] + .iter() + .flat_map(|scope| &scope.drops) + .fold(ROOT_NODE, |drop_idx, &drop| drops.add_drop(drop, drop_idx)); + + drops.add_entry_point(imaginary_target, drop_idx); + + self.cfg.terminate(imaginary_target, source_info, TerminatorKind::UnwindResume); + + // FIXME add to drop tree for loop_head + + let region_scope = scope.region_scope; + let scope_index = self.scopes.scope_index(region_scope, span); + let mut drops = DropTree::new(); + + let drop_idx = self.scopes.scopes[scope_index + 1..] + .iter() + .flat_map(|scope| &scope.drops) + .fold(ROOT_NODE, |drop_idx, &drop| drops.add_drop(drop, drop_idx)); + + drops.add_entry_point(drop_and_continue_block, drop_idx); + + // `build_drop_trees` doesn't have access to our source_info, so we + // create a dummy terminator now. `TerminatorKind::UnwindResume` is used + // because MIR type checking will panic if it hasn't been overwritten. + // (See `::link_entry_point`.) + self.cfg.terminate( + drop_and_continue_block, + source_info, + TerminatorKind::UnwindResume, + ); + + { + let this = &mut *self; + let blocks = drops.build_mir::(&mut this.cfg, Some(real_target)); + //let is_coroutine = this.coroutine.is_some(); + + /*// Link the exit drop tree to unwind drop tree. + if drops.drops.iter().any(|drop_node| drop_node.data.kind == DropKind::Value) { + let unwind_target = this.diverge_cleanup_target(region_scope, span); + let mut unwind_indices = IndexVec::from_elem_n(unwind_target, 1); + for (drop_idx, drop_node) in drops.drops.iter_enumerated().skip(1) { + match drop_node.data.kind { + DropKind::Storage | DropKind::ForLint => { + if is_coroutine { + let unwind_drop = this.scopes.unwind_drops.add_drop( + drop_node.data, + unwind_indices[drop_node.next], + ); + unwind_indices.push(unwind_drop); + } else { + unwind_indices.push(unwind_indices[drop_node.next]); + } + } + DropKind::Value => { + let unwind_drop = this + .scopes + .unwind_drops + .add_drop(drop_node.data, unwind_indices[drop_node.next]); + this.scopes.unwind_drops.add_entry_point( + blocks[drop_idx].unwrap(), + unwind_indices[drop_node.next], + ); + unwind_indices.push(unwind_drop); + } + } + } + }*/ + blocks[ROOT_NODE].map(BasicBlock::unit) + }; + + return self.cfg.start_new_block().unit(); + } }; match (destination, value) { diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 2a9bfb25b8421..7a7ae11ba1b11 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -457,10 +457,12 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { | ExprKind::Break { .. } | ExprKind::Closure { .. } | ExprKind::Continue { .. } + | ExprKind::ConstContinue { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } | ExprKind::Yield { .. } | ExprKind::Loop { .. } + | ExprKind::LoopMatch { .. } | ExprKind::Let { .. } | ExprKind::Match { .. } | ExprKind::Box { .. } diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 0e16f871b16f9..c213b2f990a40 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -1161,3 +1161,48 @@ impl Subdiagnostic for Rust2024IncompatiblePatSugg { } } } + +#[derive(Diagnostic)] +#[diag(mir_build_loop_match_invalid_update)] +pub(crate) struct LoopMatchInvalidUpdate { + #[primary_span] + pub lhs: Span, + #[label] + pub scrutinee: Span, +} + +#[derive(Diagnostic)] +#[diag(mir_build_loop_match_invalid_match)] +#[note] +pub(crate) struct LoopMatchInvalidMatch { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(mir_build_loop_match_bad_statements)] +pub(crate) struct LoopMatchBadStatements { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(mir_build_loop_match_bad_rhs)] +pub(crate) struct LoopMatchBadRhs { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(mir_build_loop_match_missing_assignment)] +pub(crate) struct LoopMatchMissingAssignment { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(mir_build_const_continue_missing_value)] +pub(crate) struct ConstContinueMissingValue { + #[primary_span] + pub span: Span, +} diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b8af77245f25d..c344d12d51c42 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -21,6 +21,7 @@ use rustc_middle::{bug, span_bug}; use rustc_span::{Span, sym}; use tracing::{debug, info, instrument, trace}; +use crate::errors::*; use crate::thir::cx::ThirBuildCx; impl<'tcx> ThirBuildCx<'tcx> { @@ -793,16 +794,43 @@ impl<'tcx> ThirBuildCx<'tcx> { } hir::ExprKind::Ret(v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) }, hir::ExprKind::Become(call) => ExprKind::Become { value: self.mirror_expr(call) }, - hir::ExprKind::Break(dest, ref value) => match dest.target_id { - Ok(target_id) => ExprKind::Break { - label: region::Scope { - local_id: target_id.local_id, - data: region::ScopeData::Node, - }, - value: value.map(|value| self.mirror_expr(value)), - }, - Err(err) => bug!("invalid loop id for break: {}", err), - }, + hir::ExprKind::Break(dest, ref value) => { + let is_const_continue = self + .tcx + .hir_attrs(expr.hir_id) + .iter() + .any(|attr| attr.has_name(sym::const_continue)); + if is_const_continue { + match dest.target_id { + Ok(target_id) => { + let Some(value) = value else { + let span = expr.span; + self.tcx.dcx().emit_fatal(ConstContinueMissingValue { span }) + }; + + ExprKind::ConstContinue { + label: region::Scope { + local_id: target_id.local_id, + data: region::ScopeData::Node, + }, + value: self.mirror_expr(value), + } + } + Err(err) => bug!("invalid loop id for break: {}", err), + } + } else { + match dest.target_id { + Ok(target_id) => ExprKind::Break { + label: region::Scope { + local_id: target_id.local_id, + data: region::ScopeData::Node, + }, + value: value.map(|value| self.mirror_expr(value)), + }, + Err(err) => bug!("invalid loop id for break: {}", err), + } + } + } hir::ExprKind::Continue(dest) => match dest.target_id { Ok(loop_id) => ExprKind::Continue { label: region::Scope { @@ -837,18 +865,94 @@ impl<'tcx> ThirBuildCx<'tcx> { match_source, }, hir::ExprKind::Loop(body, ..) => { - let block_ty = self.typeck_results.node_type(body.hir_id); - let (temp_lifetime, backwards_incompatible) = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, body.hir_id.local_id); - let block = self.mirror_block(body); - let body = self.thir.exprs.push(Expr { - ty: block_ty, - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, - span: self.thir[block].span, - kind: ExprKind::Block { block }, - }); - ExprKind::Loop { body } + let is_loop_match = self + .tcx + .hir_attrs(expr.hir_id) + .iter() + .any(|attr| attr.has_name(sym::loop_match)); + if is_loop_match { + let dcx = self.tcx.dcx(); + + // accept either `state = expr` or `state = expr;` + let loop_body_expr = match body.stmts { + [] => match body.expr { + Some(expr) => expr, + None => dcx.emit_fatal(LoopMatchMissingAssignment { span: body.span }), + }, + [single] if body.expr.is_none() => match single.kind { + hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => expr, + _ => dcx.emit_fatal(LoopMatchMissingAssignment { span: body.span }), + }, + [first @ last] | [first, .., last] => dcx + .emit_fatal(LoopMatchBadStatements { span: first.span.to(last.span) }), + }; + + let hir::ExprKind::Assign(state, rhs_expr, _) = loop_body_expr.kind else { + dcx.emit_fatal(LoopMatchMissingAssignment { span: loop_body_expr.span }) + }; + + let hir::ExprKind::Block(block_body, _) = rhs_expr.kind else { + dcx.emit_fatal(LoopMatchBadRhs { span: rhs_expr.span }) + }; + + if let Some(first) = block_body.stmts.first() { + let span = first.span.to(block_body.stmts.last().unwrap().span); + dcx.emit_fatal(LoopMatchBadStatements { span }) + } + + let Some(block_body_expr) = block_body.expr else { + dcx.emit_fatal(LoopMatchBadRhs { span: block_body.span }) + }; + + let hir::ExprKind::Match(scrutinee, arms, _match_source) = block_body_expr.kind + else { + dcx.emit_fatal(LoopMatchBadRhs { span: block_body_expr.span }) + }; + + fn local(expr: &rustc_hir::Expr<'_>) -> Option { + if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = expr.kind { + if let Res::Local(hir_id) = path.res { + return Some(hir_id); + } + } + + None + } + + let Some(scrutinee_hir_id) = local(scrutinee) else { + dcx.emit_fatal(LoopMatchInvalidMatch { span: scrutinee.span }) + }; + + if local(state) != Some(scrutinee_hir_id) { + dcx.emit_fatal(LoopMatchInvalidUpdate { + scrutinee: scrutinee.span, + lhs: state.span, + }) + } + + ExprKind::LoopMatch { + state: self.mirror_expr(state), + region_scope: region::Scope { + local_id: block_body.hir_id.local_id, + data: region::ScopeData::Node, + }, + + arms: arms.iter().map(|a| self.convert_arm(a)).collect(), + } + } else { + let block_ty = self.typeck_results.node_type(body.hir_id); + let (temp_lifetime, backwards_incompatible) = self + .rvalue_scopes + .temporary_scope(self.region_scope_tree, body.hir_id.local_id); + let block = self.mirror_block(body); + let body = self.thir.exprs.push(Expr { + ty: block_ty, + temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + span: self.thir[block].span, + kind: ExprKind::Block { block }, + }); + ExprKind::Loop { body } + } } hir::ExprKind::Field(source, ..) => ExprKind::Field { lhs: self.mirror_expr(source), diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index ea8c7303c0afa..c9ab1ffeef456 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -331,7 +331,11 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { | WrapUnsafeBinder { source } => self.is_known_valid_scrutinee(&self.thir()[*source]), // These diverge. - Become { .. } | Break { .. } | Continue { .. } | Return { .. } => true, + Become { .. } + | Break { .. } + | Continue { .. } + | ConstContinue { .. } + | Return { .. } => true, // These are statements that evaluate to `()`. Assign { .. } | AssignOp { .. } | InlineAsm { .. } | Let { .. } => true, @@ -353,6 +357,7 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { | Literal { .. } | LogicalOp { .. } | Loop { .. } + | LoopMatch { .. } | Match { .. } | NamedConst { .. } | NonHirLiteral { .. } diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index 16cef0ec3acbc..0c522e3acee1e 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -319,6 +319,19 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_expr(*body, depth_lvl + 2); print_indented!(self, ")", depth_lvl); } + LoopMatch { state, region_scope, arms } => { + 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, "arms: [", depth_lvl + 1); + for arm_id in arms.iter() { + self.print_arm(*arm_id, depth_lvl + 2); + } + print_indented!(self, "]", depth_lvl + 1); + print_indented!(self, "}", depth_lvl); + } Let { expr, pat } => { print_indented!(self, "Let {", depth_lvl); print_indented!(self, "expr:", depth_lvl + 1); @@ -416,6 +429,13 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { print_indented!(self, format!("label: {:?}", label), depth_lvl + 1); print_indented!(self, "}", depth_lvl); } + ConstContinue { label, value } => { + print_indented!(self, "ConstContinue (", depth_lvl); + print_indented!(self, format!("label: {:?}", label), depth_lvl + 1); + print_indented!(self, "value:", depth_lvl + 1); + self.print_expr(*value, depth_lvl + 2); + print_indented!(self, ")", depth_lvl); + } Return { value } => { print_indented!(self, "Return {", depth_lvl); print_indented!(self, "value:", depth_lvl + 1); diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index bea86801ed753..23783c8eecfdd 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -99,6 +99,11 @@ passes_collapse_debuginfo = passes_confusables = attribute should be applied to an inherent method .label = not an inherent method +passes_const_continue_attr = + `#[const_continue]` should be applied to a break expression + .label = not a break expression + + passes_const_stable_not_stable = attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]` .label = attribute specified here @@ -462,6 +467,10 @@ passes_linkage = attribute should be applied to a function or static .label = not a function definition or static +passes_loop_match_attr = + `#[loop_match]` should be applied to a loop + .label = not a loop + passes_macro_export = `#[macro_export]` only has an effect on macro definitions diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 669349f3380aa..06e1bcced3f95 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -263,6 +263,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } [sym::linkage, ..] => self.check_linkage(attr, span, target), [sym::rustc_pub_transparent, ..] => self.check_rustc_pub_transparent(attr.span(), span, attrs), + [sym::loop_match, ..] => self.check_loop_match(hir_id, attr.span(), target), + [sym::const_continue, ..] => self.check_const_continue(hir_id, attr.span(), target), [ // ok sym::allow @@ -2643,6 +2645,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } } + + fn check_loop_match(&self, hir_id: HirId, attr_span: Span, target: Target) { + let node_span = self.tcx.hir().span(hir_id); + + if !matches!(target, Target::Expression) { + self.dcx().emit_err(errors::LoopMatchAttr { attr_span, node_span }); + return; + } + + if !matches!(self.tcx.hir_expect_expr(hir_id).kind, hir::ExprKind::Loop(..)) { + self.dcx().emit_err(errors::LoopMatchAttr { attr_span, node_span }); + }; + } + + fn check_const_continue(&self, hir_id: HirId, attr_span: Span, target: Target) { + let node_span = self.tcx.hir().span(hir_id); + + if !matches!(target, Target::Expression) { + self.dcx().emit_err(errors::ConstContinueAttr { attr_span, node_span }); + return; + } + + if !matches!(self.tcx.hir_expect_expr(hir_id).kind, hir::ExprKind::Break(..)) { + self.dcx().emit_err(errors::ConstContinueAttr { attr_span, node_span }); + }; + } } impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 4e3e0324205a4..3455818ff61ee 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -32,6 +32,24 @@ pub(crate) struct AutoDiffAttr { pub attr_span: Span, } +#[derive(Diagnostic)] +#[diag(passes_loop_match_attr)] +pub(crate) struct LoopMatchAttr { + #[primary_span] + pub attr_span: Span, + #[label] + pub node_span: Span, +} + +#[derive(Diagnostic)] +#[diag(passes_const_continue_attr)] +pub(crate) struct ConstContinueAttr { + #[primary_span] + pub attr_span: Span, + #[label] + pub node_span: Span, +} + #[derive(LintDiagnostic)] #[diag(passes_outer_crate_level_attr)] pub(crate) struct OuterCrateLevelAttr; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index bc853fe9079bc..e6336650bccf6 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -680,6 +680,7 @@ symbols! { const_closures, const_compare_raw_pointers, const_constructor, + const_continue, const_deallocate, const_destruct, const_eval_limit, @@ -1268,6 +1269,7 @@ symbols! { logf64, loongarch_target_feature, loop_break_value, + loop_match, lt, m68k_target_feature, macro_at_most_once_rep, diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs index b275cd382ab83..60f8bd9d83ad3 100644 --- a/compiler/rustc_ty_utils/src/consts.rs +++ b/compiler/rustc_ty_utils/src/consts.rs @@ -226,7 +226,11 @@ fn recurse_build<'tcx>( ExprKind::Yield { .. } => { error(GenericConstantTooComplexSub::YieldNotSupported(node.span))? } - ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Loop { .. } => { + ExprKind::Continue { .. } + | ExprKind::ConstContinue { .. } + | ExprKind::Break { .. } + | ExprKind::Loop { .. } + | ExprKind::LoopMatch { .. } => { error(GenericConstantTooComplexSub::LoopNotSupported(node.span))? } ExprKind::Box { .. } => error(GenericConstantTooComplexSub::BoxNotSupported(node.span))?, @@ -329,6 +333,7 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> { | thir::ExprKind::NeverToAny { .. } | thir::ExprKind::PointerCoercion { .. } | thir::ExprKind::Loop { .. } + | thir::ExprKind::LoopMatch { .. } | thir::ExprKind::Let { .. } | thir::ExprKind::Match { .. } | thir::ExprKind::Block { .. } @@ -342,6 +347,7 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> { | thir::ExprKind::RawBorrow { .. } | thir::ExprKind::Break { .. } | thir::ExprKind::Continue { .. } + | thir::ExprKind::ConstContinue { .. } | thir::ExprKind::Return { .. } | thir::ExprKind::Become { .. } | thir::ExprKind::Array { .. } diff --git a/tests/ui/feature-gates/feature-gate-loop-match.rs b/tests/ui/feature-gates/feature-gate-loop-match.rs new file mode 100644 index 0000000000000..e03af6d75d613 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-loop-match.rs @@ -0,0 +1,27 @@ +enum State { + A, + B, + C, +} + +fn main() { + let mut state = State::A; + #[loop_match] //~ ERROR the `#[loop_match]` attribute is an experimental feature + 'a: loop { + state = 'blk: { + match state { + State::A => { + #[const_continue] + //~^ ERROR the `#[const_continue]` attribute is an experimental feature + break 'blk State::B; + } + State::B => { + #[const_continue] + //~^ ERROR the `#[const_continue]` attribute is an experimental feature + break 'blk State::C; + } + State::C => break 'a, + } + }; + } +} diff --git a/tests/ui/feature-gates/feature-gate-loop-match.stderr b/tests/ui/feature-gates/feature-gate-loop-match.stderr new file mode 100644 index 0000000000000..e05b97518d5e9 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-loop-match.stderr @@ -0,0 +1,33 @@ +error[E0658]: the `#[loop_match]` attribute is an experimental feature + --> $DIR/feature-gate-loop-match.rs:9:5 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ + | + = note: see issue #138777 for more information + = help: add `#![feature(loop_match)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: the `#[const_continue]` attribute is an experimental feature + --> $DIR/feature-gate-loop-match.rs:14:21 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #138777 for more information + = help: add `#![feature(loop_match)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: the `#[const_continue]` attribute is an experimental feature + --> $DIR/feature-gate-loop-match.rs:19:21 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #138777 for more information + = help: add `#![feature(loop_match)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/loop-match/break-to-block.rs b/tests/ui/loop-match/break-to-block.rs new file mode 100644 index 0000000000000..0765315936911 --- /dev/null +++ b/tests/ui/loop-match/break-to-block.rs @@ -0,0 +1,20 @@ +//@ run-pass + +#![feature(loop_match)] + +fn main() { + assert_eq!(helper(), 1); +} + +fn helper() -> u8 { + let mut state = 0u8; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + 0 => break 'blk 1, + _ => break 'a state, + } + } + } +} diff --git a/tests/ui/loop-match/drop-in-match-arm.rs b/tests/ui/loop-match/drop-in-match-arm.rs new file mode 100644 index 0000000000000..1507f1bb6d90e --- /dev/null +++ b/tests/ui/loop-match/drop-in-match-arm.rs @@ -0,0 +1,32 @@ +//@ run-pass + +#![feature(loop_match)] + +// test that drops work in match arms (match arms need their own scope) + +fn main() { + assert_eq!(helper(), 1); +} + +struct X; + +impl Drop for X { + fn drop(&mut self) {} +} + +#[no_mangle] +#[inline(never)] +fn helper() -> i32 { + let mut state = 0; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + 0 => match X { + _ => break 'blk 1, + }, + _ => break 'a state, + } + }; + } +} diff --git a/tests/ui/loop-match/integer-patterns.rs b/tests/ui/loop-match/integer-patterns.rs new file mode 100644 index 0000000000000..3a5b9523ad020 --- /dev/null +++ b/tests/ui/loop-match/integer-patterns.rs @@ -0,0 +1,30 @@ +//@ run-pass + +#![feature(loop_match)] + +fn main() { + let mut state = 0; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + -1 => { + if true { + #[const_continue] + break 'blk 2; + } else { + // No drops allowed at this point + #[const_continue] + break 'blk 0; + } + } + 0 => { + #[const_continue] + break 'blk -1; + } + 2 => break 'a, + _ => break 'a, + } + } + } +} diff --git a/tests/ui/loop-match/invalid-attribute.rs b/tests/ui/loop-match/invalid-attribute.rs new file mode 100644 index 0000000000000..dbcddfdd34bd2 --- /dev/null +++ b/tests/ui/loop-match/invalid-attribute.rs @@ -0,0 +1,42 @@ +// Checks that #[loop_match] and #[const_continue] attributes can be +// placed on expressions only. +// +#![feature(loop_match)] +#![loop_match] //~ ERROR should be applied to a loop +#![const_continue] //~ ERROR should be applied to a break expression + +extern "C" { + #[loop_match] //~ ERROR should be applied to a loop + #[const_continue] //~ ERROR should be applied to a break expression + fn f(); +} + +#[loop_match] //~ ERROR should be applied to a loop +#[const_continue] //~ ERROR should be applied to a break expression +#[repr(C)] +struct S { + a: u32, + b: u32, +} + +trait Invoke { + #[loop_match] //~ ERROR should be applied to a loop + #[const_continue] //~ ERROR should be applied to a break expression + extern "C" fn invoke(&self); +} + +#[loop_match] //~ ERROR should be applied to a loop +#[const_continue] //~ ERROR should be applied to a break expression +extern "C" fn ok() {} + +fn main() { + #[loop_match] //~ ERROR should be applied to a loop + #[const_continue] //~ ERROR should be applied to a break expression + || {}; + + { + #[loop_match] //~ ERROR should be applied to a loop + #[const_continue] //~ ERROR should be applied to a break expression + 5 + }; +} diff --git a/tests/ui/loop-match/invalid-attribute.stderr b/tests/ui/loop-match/invalid-attribute.stderr new file mode 100644 index 0000000000000..cb8826a117243 --- /dev/null +++ b/tests/ui/loop-match/invalid-attribute.stderr @@ -0,0 +1,129 @@ +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:14:1 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ +... +LL | struct S { + | -------- not a loop + +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:15:1 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | #[repr(C)] +LL | struct S { + | -------- not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:28:1 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ +LL | #[const_continue] +LL | extern "C" fn ok() {} + | ------------------ not a loop + +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:29:1 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | extern "C" fn ok() {} + | ------------------ not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:33:5 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ +LL | #[const_continue] +LL | || {}; + | -- not a loop + +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:34:5 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | || {}; + | -- not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:38:9 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ +LL | #[const_continue] +LL | 5 + | - not a loop + +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:39:9 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | 5 + | - not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:23:5 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ +LL | #[const_continue] +LL | extern "C" fn invoke(&self); + | ---------------------------- not a loop + +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:24:5 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | extern "C" fn invoke(&self); + | ---------------------------- not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:9:5 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ +LL | #[const_continue] +LL | fn f(); + | ------- not a loop + +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:10:5 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | fn f(); + | ------- not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:5:1 + | +LL | / #![feature(loop_match)] +LL | | #![loop_match] + | | ^^^^^^^^^^^^^^ +LL | | #![const_continue] +... | +LL | | }; +LL | | } + | |_- not a loop + +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:6:1 + | +LL | / #![feature(loop_match)] +LL | | #![loop_match] +LL | | #![const_continue] + | | ^^^^^^^^^^^^^^^^^^ +... | +LL | | }; +LL | | } + | |_- not a break expression + +error: aborting due to 14 previous errors + diff --git a/tests/ui/loop-match/invalid.rs b/tests/ui/loop-match/invalid.rs new file mode 100644 index 0000000000000..fb8862030382d --- /dev/null +++ b/tests/ui/loop-match/invalid.rs @@ -0,0 +1,142 @@ +#![feature(loop_match)] +#![crate_type = "lib"] + +enum State { + A, + B, + C, +} + +fn invalid_update() { + let mut fake = State::A; + let state = State::A; + #[loop_match] + loop { + fake = 'blk: { + //~^ ERROR: invalid update of the `loop_match` state + match state { + _ => State::B, + } + } + } +} + +fn invalid_scrutinee() { + let state = State::A; + #[loop_match] + loop { + state = 'blk: { + match State::A { + //~^ ERROR: invalid match on `loop_match` state + _ => State::B, + } + } + } +} + +fn bad_statements_1() { + let state = State::A; + #[loop_match] + loop { + 1; + //~^ ERROR: statements are not allowed in this position within a `loop_match` + state = 'blk: { + match State::A { + _ => State::B, + } + } + } +} + +fn bad_statements_2() { + let state = State::A; + #[loop_match] + loop { + state = 'blk: { + 1; + //~^ ERROR: statements are not allowed in this position within a `loop_match` + match State::A { + _ => State::B, + } + } + } +} + +fn bad_rhs_1() { + let state = State::A; + #[loop_match] + loop { + state = State::B + //~^ ERROR: this expression must be a single `match` wrapped in a labelled block + } +} + +fn bad_rhs_2() { + let state = State::A; + #[loop_match] + loop { + state = 'blk: { + State::B + //~^ ERROR: this expression must be a single `match` wrapped in a labelled block + } + } +} + +fn bad_rhs_3() { + let state = (); + #[loop_match] + loop { + state = 'blk: { + //~^ ERROR: this expression must be a single `match` wrapped in a labelled block + } + } +} + +fn missing_assignment() { + let state = State::A; + #[loop_match] + loop { + () //~ ERROR: expected a single assignment expression + } +} + +fn empty_loop_body() { + let state = State::A; + #[loop_match] + loop { + //~^ ERROR: expected a single assignment expression + } +} + +fn break_without_value() { + let state = State::A; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + State::A => { + #[const_continue] + break 'blk; + //~^ ERROR: mismatched types + } + _ => break 'a, + } + } + } +} + +fn break_without_value_unit() { + let state = (); + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + () => { + #[const_continue] + break 'blk; + //~^ ERROR: a `const_continue` must break to a label with a value + } + } + } + } +} diff --git a/tests/ui/loop-match/invalid.stderr b/tests/ui/loop-match/invalid.stderr new file mode 100644 index 0000000000000..70b23ff2b81d5 --- /dev/null +++ b/tests/ui/loop-match/invalid.stderr @@ -0,0 +1,85 @@ +error[E0308]: mismatched types + --> $DIR/invalid.rs:119:21 + | +LL | break 'blk; + | ^^^^^^^^^^ expected `State`, found `()` + | +help: give the `break` a value of the expected type + | +LL | break 'blk /* value */; + | +++++++++++ + +error: invalid update of the `loop_match` state + --> $DIR/invalid.rs:15:9 + | +LL | fake = 'blk: { + | ^^^^ +LL | +LL | match state { + | ----- the assignment must update this variable + +error: invalid match on `loop_match` state + --> $DIR/invalid.rs:29:19 + | +LL | match State::A { + | ^^^^^^^^ + | + = note: only matches on local variables are valid + +error: statements are not allowed in this position within a `loop_match` + --> $DIR/invalid.rs:41:9 + | +LL | 1; + | ^^ + +error: statements are not allowed in this position within a `loop_match` + --> $DIR/invalid.rs:56:13 + | +LL | 1; + | ^^ + +error: this expression must be a single `match` wrapped in a labelled block + --> $DIR/invalid.rs:69:17 + | +LL | state = State::B + | ^^^^^^^^ + +error: this expression must be a single `match` wrapped in a labelled block + --> $DIR/invalid.rs:79:13 + | +LL | State::B + | ^^^^^^^^ + +error: this expression must be a single `match` wrapped in a labelled block + --> $DIR/invalid.rs:89:17 + | +LL | state = 'blk: { + | _________________^ +LL | | +LL | | } + | |_________^ + +error: expected a single assignment expression + --> $DIR/invalid.rs:99:9 + | +LL | () + | ^^ + +error: expected a single assignment expression + --> $DIR/invalid.rs:106:10 + | +LL | loop { + | __________^ +LL | | +LL | | } + | |_____^ + +error: a `const_continue` must break to a label with a value + --> $DIR/invalid.rs:136:21 + | +LL | break 'blk; + | ^^^^^^^^^^ + +error: aborting due to 11 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/loop-match/loop-match.rs b/tests/ui/loop-match/loop-match.rs new file mode 100644 index 0000000000000..93ef3e4882368 --- /dev/null +++ b/tests/ui/loop-match/loop-match.rs @@ -0,0 +1,35 @@ +//@ run-pass + +#![feature(loop_match)] + +enum State { + A, + B, + C, +} + +fn main() { + let mut state = State::A; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + State::A => { + #[const_continue] + break 'blk State::B; + } + State::B => { + let _a = 0; + if true { + #[const_continue] + break 'blk State::C; + } else { + #[const_continue] + break 'blk State::A; + } + } + State::C => break 'a, + } + }; + } +} diff --git a/tests/ui/loop-match/nested.rs b/tests/ui/loop-match/nested.rs new file mode 100644 index 0000000000000..02fd69928f4fd --- /dev/null +++ b/tests/ui/loop-match/nested.rs @@ -0,0 +1,78 @@ +//@ run-pass + +#![feature(loop_match)] + +enum State1 { + A, + B, + C, +} + +enum State2 { + X, + Y, + Z, +} + +fn main() { + assert_eq!(run(), concat!("ab", "xyz", "xyz", "c")) +} + +fn run() -> String { + let mut accum = String::new(); + + let mut state1 = State1::A; + let mut state2 = State2::X; + + let mut first = true; + + #[loop_match] + 'a: loop { + state1 = 'blk1: { + match state1 { + State1::A => { + accum.push('a'); + #[const_continue] + break 'blk1 State1::B; + } + State1::B => { + accum.push('b'); + #[loop_match] + loop { + state2 = 'blk2: { + match state2 { + State2::X => { + accum.push('x'); + #[const_continue] + break 'blk2 State2::Y; + } + State2::Y => { + accum.push('y'); + #[const_continue] + break 'blk2 State2::Z; + } + State2::Z => { + accum.push('z'); + if first { + first = false; + #[const_continue] + break 'blk2 State2::X; + } else { + #[const_continue] + break 'blk1 State1::C; + } + } + } + } + } + } + State1::C => { + accum.push('c'); + break 'a; + } + } + } + } + + accum +} diff --git a/tests/ui/loop-match/or-patterns.rs b/tests/ui/loop-match/or-patterns.rs new file mode 100644 index 0000000000000..bdfb4ddf86a71 --- /dev/null +++ b/tests/ui/loop-match/or-patterns.rs @@ -0,0 +1,41 @@ +//@ run-pass + +#![feature(loop_match)] + +enum State { + A, + B, + C, + D, +} + +fn main() { + let mut state = State::A; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + State::A => { + if true { + #[const_continue] + break 'blk State::B; + } else { + #[const_continue] + break 'blk State::D; + } + } + State::B | State::D => { + if true { + #[const_continue] + break 'blk State::C; + } else { + // No drops allowed at this point + #[const_continue] + break 'blk State::A; + } + } + State::C => break 'a, + } + } + } +} From 3c7722d0347b73a04c956eeeb81251127140dad8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 22 Mar 2025 12:05:12 +0100 Subject: [PATCH 02/35] Apply suggestions from code review Co-authored-by: Travis Cross --- compiler/rustc_feature/src/unstable.rs | 3 ++- compiler/rustc_mir_build/messages.ftl | 3 ++- compiler/rustc_mir_build/src/builder/expr/into.rs | 2 +- compiler/rustc_passes/messages.ftl | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 79cf05fe16882..bebc7a680d12a 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -545,7 +545,8 @@ declare_features! ( /// Allows using `#[link(kind = "link-arg", name = "...")]` /// to pass custom arguments to the linker. (unstable, link_arg_attribute, "1.76.0", Some(99427)), - (unstable, loop_match, "CURRENT_RUSTC_VERSION", Some(138777)), + /// Allows fused `loop`/`match` for direct intraprocedural jumps. + (incomplete, loop_match, "CURRENT_RUSTC_VERSION", Some(138777)), /// Give access to additional metadata about declarative macro meta-variables. (unstable, macro_metavar_expr, "1.61.0", Some(83527)), /// Provides a way to concatenate identifiers using metavariable expressions. diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index a0b6bd1781272..6b10c7df71c5b 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -215,7 +215,7 @@ mir_build_literal_in_range_out_of_bounds = .label = this value does not fit into the type `{$ty}` whose range is `{$min}..={$max}` mir_build_loop_match_bad_rhs = - this expression must be a single `match` wrapped in a labelled block + this expression must be a single `match` wrapped in a labeled block mir_build_loop_match_bad_statements = statements are not allowed in this position within a `loop_match` @@ -223,6 +223,7 @@ mir_build_loop_match_bad_statements = mir_build_loop_match_invalid_match = invalid match on `loop_match` state .note = only matches on local variables are valid + mir_build_loop_match_invalid_update = invalid update of the `loop_match` state .label = the assignment must update this variable diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 9090e994d0201..2ee891054045c 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -248,7 +248,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { None }) } - ExprKind::LoopMatch { state, region_scope, ref arms, .. } => { + ExprKind::LoopMatch { state, region_scope, ref arms } => { // FIXME add diagram let dropless_arena = rustc_arena::DroplessArena::default(); diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 23783c8eecfdd..8e40040fdeac5 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -103,7 +103,6 @@ passes_const_continue_attr = `#[const_continue]` should be applied to a break expression .label = not a break expression - passes_const_stable_not_stable = attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]` .label = attribute specified here From eb4ed171e559b4959dda92cf25c7539cb6e42bf8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 22 Mar 2025 13:07:25 +0100 Subject: [PATCH 03/35] add comments to tests --- tests/ui/loop-match/break-to-block.rs | 3 ++ tests/ui/loop-match/drop-in-match-arm.rs | 6 ++-- tests/ui/loop-match/integer-patterns.rs | 5 ++- tests/ui/loop-match/invalid-attribute.rs | 1 + tests/ui/loop-match/invalid-attribute.stderr | 34 +++++++++++--------- tests/ui/loop-match/invalid.rs | 24 +++++++------- tests/ui/loop-match/invalid.stderr | 28 ++++++++-------- tests/ui/loop-match/loop-match.rs | 9 ++++++ tests/ui/loop-match/nested.rs | 4 +++ tests/ui/loop-match/or-patterns.rs | 25 ++++++++++---- 10 files changed, 89 insertions(+), 50 deletions(-) diff --git a/tests/ui/loop-match/break-to-block.rs b/tests/ui/loop-match/break-to-block.rs index 0765315936911..667f65f45354f 100644 --- a/tests/ui/loop-match/break-to-block.rs +++ b/tests/ui/loop-match/break-to-block.rs @@ -1,5 +1,8 @@ +// Test that a break without #[const_continue] still works as expected. + //@ run-pass +#![allow(incomplete_features)] #![feature(loop_match)] fn main() { diff --git a/tests/ui/loop-match/drop-in-match-arm.rs b/tests/ui/loop-match/drop-in-match-arm.rs index 1507f1bb6d90e..6e6c86c9a4728 100644 --- a/tests/ui/loop-match/drop-in-match-arm.rs +++ b/tests/ui/loop-match/drop-in-match-arm.rs @@ -1,9 +1,11 @@ +// Test that dropping values works in match arms, which is nontrivial +// because each match arm needs its own scope. + //@ run-pass +#![allow(incomplete_features)] #![feature(loop_match)] -// test that drops work in match arms (match arms need their own scope) - fn main() { assert_eq!(helper(), 1); } diff --git a/tests/ui/loop-match/integer-patterns.rs b/tests/ui/loop-match/integer-patterns.rs index 3a5b9523ad020..0dc68baa4595b 100644 --- a/tests/ui/loop-match/integer-patterns.rs +++ b/tests/ui/loop-match/integer-patterns.rs @@ -1,5 +1,8 @@ +// Test that signed and unsigned integer patterns work with #[loop_match]. + //@ run-pass +#![allow(incomplete_features)] #![feature(loop_match)] fn main() { @@ -13,7 +16,6 @@ fn main() { #[const_continue] break 'blk 2; } else { - // No drops allowed at this point #[const_continue] break 'blk 0; } @@ -27,4 +29,5 @@ fn main() { } } } + assert_eq!(state, 2); } diff --git a/tests/ui/loop-match/invalid-attribute.rs b/tests/ui/loop-match/invalid-attribute.rs index dbcddfdd34bd2..972b93c8dc926 100644 --- a/tests/ui/loop-match/invalid-attribute.rs +++ b/tests/ui/loop-match/invalid-attribute.rs @@ -1,6 +1,7 @@ // Checks that #[loop_match] and #[const_continue] attributes can be // placed on expressions only. // +#![allow(incomplete_features)] #![feature(loop_match)] #![loop_match] //~ ERROR should be applied to a loop #![const_continue] //~ ERROR should be applied to a break expression diff --git a/tests/ui/loop-match/invalid-attribute.stderr b/tests/ui/loop-match/invalid-attribute.stderr index cb8826a117243..75d0695b05e63 100644 --- a/tests/ui/loop-match/invalid-attribute.stderr +++ b/tests/ui/loop-match/invalid-attribute.stderr @@ -1,5 +1,5 @@ error: `#[loop_match]` should be applied to a loop - --> $DIR/invalid-attribute.rs:14:1 + --> $DIR/invalid-attribute.rs:15:1 | LL | #[loop_match] | ^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | struct S { | -------- not a loop error: `#[const_continue]` should be applied to a break expression - --> $DIR/invalid-attribute.rs:15:1 + --> $DIR/invalid-attribute.rs:16:1 | LL | #[const_continue] | ^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | struct S { | -------- not a break expression error: `#[loop_match]` should be applied to a loop - --> $DIR/invalid-attribute.rs:28:1 + --> $DIR/invalid-attribute.rs:29:1 | LL | #[loop_match] | ^^^^^^^^^^^^^ @@ -26,7 +26,7 @@ LL | extern "C" fn ok() {} | ------------------ not a loop error: `#[const_continue]` should be applied to a break expression - --> $DIR/invalid-attribute.rs:29:1 + --> $DIR/invalid-attribute.rs:30:1 | LL | #[const_continue] | ^^^^^^^^^^^^^^^^^ @@ -34,7 +34,7 @@ LL | extern "C" fn ok() {} | ------------------ not a break expression error: `#[loop_match]` should be applied to a loop - --> $DIR/invalid-attribute.rs:33:5 + --> $DIR/invalid-attribute.rs:34:5 | LL | #[loop_match] | ^^^^^^^^^^^^^ @@ -43,7 +43,7 @@ LL | || {}; | -- not a loop error: `#[const_continue]` should be applied to a break expression - --> $DIR/invalid-attribute.rs:34:5 + --> $DIR/invalid-attribute.rs:35:5 | LL | #[const_continue] | ^^^^^^^^^^^^^^^^^ @@ -51,7 +51,7 @@ LL | || {}; | -- not a break expression error: `#[loop_match]` should be applied to a loop - --> $DIR/invalid-attribute.rs:38:9 + --> $DIR/invalid-attribute.rs:39:9 | LL | #[loop_match] | ^^^^^^^^^^^^^ @@ -60,7 +60,7 @@ LL | 5 | - not a loop error: `#[const_continue]` should be applied to a break expression - --> $DIR/invalid-attribute.rs:39:9 + --> $DIR/invalid-attribute.rs:40:9 | LL | #[const_continue] | ^^^^^^^^^^^^^^^^^ @@ -68,7 +68,7 @@ LL | 5 | - not a break expression error: `#[loop_match]` should be applied to a loop - --> $DIR/invalid-attribute.rs:23:5 + --> $DIR/invalid-attribute.rs:24:5 | LL | #[loop_match] | ^^^^^^^^^^^^^ @@ -77,7 +77,7 @@ LL | extern "C" fn invoke(&self); | ---------------------------- not a loop error: `#[const_continue]` should be applied to a break expression - --> $DIR/invalid-attribute.rs:24:5 + --> $DIR/invalid-attribute.rs:25:5 | LL | #[const_continue] | ^^^^^^^^^^^^^^^^^ @@ -85,7 +85,7 @@ LL | extern "C" fn invoke(&self); | ---------------------------- not a break expression error: `#[loop_match]` should be applied to a loop - --> $DIR/invalid-attribute.rs:9:5 + --> $DIR/invalid-attribute.rs:10:5 | LL | #[loop_match] | ^^^^^^^^^^^^^ @@ -94,7 +94,7 @@ LL | fn f(); | ------- not a loop error: `#[const_continue]` should be applied to a break expression - --> $DIR/invalid-attribute.rs:10:5 + --> $DIR/invalid-attribute.rs:11:5 | LL | #[const_continue] | ^^^^^^^^^^^^^^^^^ @@ -102,9 +102,10 @@ LL | fn f(); | ------- not a break expression error: `#[loop_match]` should be applied to a loop - --> $DIR/invalid-attribute.rs:5:1 + --> $DIR/invalid-attribute.rs:6:1 | -LL | / #![feature(loop_match)] +LL | / #![allow(incomplete_features)] +LL | | #![feature(loop_match)] LL | | #![loop_match] | | ^^^^^^^^^^^^^^ LL | | #![const_continue] @@ -114,9 +115,10 @@ LL | | } | |_- not a loop error: `#[const_continue]` should be applied to a break expression - --> $DIR/invalid-attribute.rs:6:1 + --> $DIR/invalid-attribute.rs:7:1 | -LL | / #![feature(loop_match)] +LL | / #![allow(incomplete_features)] +LL | | #![feature(loop_match)] LL | | #![loop_match] LL | | #![const_continue] | | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/loop-match/invalid.rs b/tests/ui/loop-match/invalid.rs index fb8862030382d..7dd271742320a 100644 --- a/tests/ui/loop-match/invalid.rs +++ b/tests/ui/loop-match/invalid.rs @@ -1,3 +1,5 @@ +// Test that a good error is emitted when #[loop_match] is applied to syntax it does not support. +#![allow(incomplete_features)] #![feature(loop_match)] #![crate_type = "lib"] @@ -13,7 +15,7 @@ fn invalid_update() { #[loop_match] loop { fake = 'blk: { - //~^ ERROR: invalid update of the `loop_match` state + //~^ ERROR invalid update of the `loop_match` state match state { _ => State::B, } @@ -27,7 +29,7 @@ fn invalid_scrutinee() { loop { state = 'blk: { match State::A { - //~^ ERROR: invalid match on `loop_match` state + //~^ ERROR invalid match on `loop_match` state _ => State::B, } } @@ -39,7 +41,7 @@ fn bad_statements_1() { #[loop_match] loop { 1; - //~^ ERROR: statements are not allowed in this position within a `loop_match` + //~^ ERROR statements are not allowed in this position within a `loop_match` state = 'blk: { match State::A { _ => State::B, @@ -54,7 +56,7 @@ fn bad_statements_2() { loop { state = 'blk: { 1; - //~^ ERROR: statements are not allowed in this position within a `loop_match` + //~^ ERROR statements are not allowed in this position within a `loop_match` match State::A { _ => State::B, } @@ -67,7 +69,7 @@ fn bad_rhs_1() { #[loop_match] loop { state = State::B - //~^ ERROR: this expression must be a single `match` wrapped in a labelled block + //~^ ERROR this expression must be a single `match` wrapped in a labeled block } } @@ -77,7 +79,7 @@ fn bad_rhs_2() { loop { state = 'blk: { State::B - //~^ ERROR: this expression must be a single `match` wrapped in a labelled block + //~^ ERROR this expression must be a single `match` wrapped in a labeled block } } } @@ -87,7 +89,7 @@ fn bad_rhs_3() { #[loop_match] loop { state = 'blk: { - //~^ ERROR: this expression must be a single `match` wrapped in a labelled block + //~^ ERROR this expression must be a single `match` wrapped in a labeled block } } } @@ -96,7 +98,7 @@ fn missing_assignment() { let state = State::A; #[loop_match] loop { - () //~ ERROR: expected a single assignment expression + () //~ ERROR expected a single assignment expression } } @@ -104,7 +106,7 @@ fn empty_loop_body() { let state = State::A; #[loop_match] loop { - //~^ ERROR: expected a single assignment expression + //~^ ERROR expected a single assignment expression } } @@ -117,7 +119,7 @@ fn break_without_value() { State::A => { #[const_continue] break 'blk; - //~^ ERROR: mismatched types + //~^ ERROR mismatched types } _ => break 'a, } @@ -134,7 +136,7 @@ fn break_without_value_unit() { () => { #[const_continue] break 'blk; - //~^ ERROR: a `const_continue` must break to a label with a value + //~^ ERROR a `const_continue` must break to a label with a value } } } diff --git a/tests/ui/loop-match/invalid.stderr b/tests/ui/loop-match/invalid.stderr index 70b23ff2b81d5..36fea1d7fba3a 100644 --- a/tests/ui/loop-match/invalid.stderr +++ b/tests/ui/loop-match/invalid.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/invalid.rs:119:21 + --> $DIR/invalid.rs:121:21 | LL | break 'blk; | ^^^^^^^^^^ expected `State`, found `()` @@ -10,7 +10,7 @@ LL | break 'blk /* value */; | +++++++++++ error: invalid update of the `loop_match` state - --> $DIR/invalid.rs:15:9 + --> $DIR/invalid.rs:17:9 | LL | fake = 'blk: { | ^^^^ @@ -19,7 +19,7 @@ LL | match state { | ----- the assignment must update this variable error: invalid match on `loop_match` state - --> $DIR/invalid.rs:29:19 + --> $DIR/invalid.rs:31:19 | LL | match State::A { | ^^^^^^^^ @@ -27,31 +27,31 @@ LL | match State::A { = note: only matches on local variables are valid error: statements are not allowed in this position within a `loop_match` - --> $DIR/invalid.rs:41:9 + --> $DIR/invalid.rs:43:9 | LL | 1; | ^^ error: statements are not allowed in this position within a `loop_match` - --> $DIR/invalid.rs:56:13 + --> $DIR/invalid.rs:58:13 | LL | 1; | ^^ -error: this expression must be a single `match` wrapped in a labelled block - --> $DIR/invalid.rs:69:17 +error: this expression must be a single `match` wrapped in a labeled block + --> $DIR/invalid.rs:71:17 | LL | state = State::B | ^^^^^^^^ -error: this expression must be a single `match` wrapped in a labelled block - --> $DIR/invalid.rs:79:13 +error: this expression must be a single `match` wrapped in a labeled block + --> $DIR/invalid.rs:81:13 | LL | State::B | ^^^^^^^^ -error: this expression must be a single `match` wrapped in a labelled block - --> $DIR/invalid.rs:89:17 +error: this expression must be a single `match` wrapped in a labeled block + --> $DIR/invalid.rs:91:17 | LL | state = 'blk: { | _________________^ @@ -60,13 +60,13 @@ LL | | } | |_________^ error: expected a single assignment expression - --> $DIR/invalid.rs:99:9 + --> $DIR/invalid.rs:101:9 | LL | () | ^^ error: expected a single assignment expression - --> $DIR/invalid.rs:106:10 + --> $DIR/invalid.rs:108:10 | LL | loop { | __________^ @@ -75,7 +75,7 @@ LL | | } | |_____^ error: a `const_continue` must break to a label with a value - --> $DIR/invalid.rs:136:21 + --> $DIR/invalid.rs:138:21 | LL | break 'blk; | ^^^^^^^^^^ diff --git a/tests/ui/loop-match/loop-match.rs b/tests/ui/loop-match/loop-match.rs index 93ef3e4882368..d83ea963d781a 100644 --- a/tests/ui/loop-match/loop-match.rs +++ b/tests/ui/loop-match/loop-match.rs @@ -1,5 +1,8 @@ +// Test a basic correct example of #[loop_match] with #[const_continue]. + //@ run-pass +#![allow(incomplete_features)] #![feature(loop_match)] enum State { @@ -19,7 +22,11 @@ fn main() { break 'blk State::B; } State::B => { + // without special logic, the compiler believes this is a re-assignment to + // an immutable variable because of the `loop`. So, this tests that local + // variables work. let _a = 0; + if true { #[const_continue] break 'blk State::C; @@ -32,4 +39,6 @@ fn main() { } }; } + + assert!(matches!(state, State::C)) } diff --git a/tests/ui/loop-match/nested.rs b/tests/ui/loop-match/nested.rs index 02fd69928f4fd..610c39be665bb 100644 --- a/tests/ui/loop-match/nested.rs +++ b/tests/ui/loop-match/nested.rs @@ -1,5 +1,9 @@ +// Test that a nested #[loop_match] works as expected, and e.g. a #[const_continue] of the inner +// #[loop_match] does not interact with the outer #[loop_match]. + //@ run-pass +#![allow(incomplete_features)] #![feature(loop_match)] enum State1 { diff --git a/tests/ui/loop-match/or-patterns.rs b/tests/ui/loop-match/or-patterns.rs index bdfb4ddf86a71..d01a16bb72650 100644 --- a/tests/ui/loop-match/or-patterns.rs +++ b/tests/ui/loop-match/or-patterns.rs @@ -1,7 +1,11 @@ +// Test that #[loop_match] supportes or patterns. + //@ run-pass +#![allow(incomplete_features)] #![feature(loop_match)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum State { A, B, @@ -10,13 +14,16 @@ enum State { } fn main() { + let mut states = vec![]; + let mut first = true; let mut state = State::A; #[loop_match] 'a: loop { state = 'blk: { match state { State::A => { - if true { + states.push(state); + if first { #[const_continue] break 'blk State::B; } else { @@ -25,17 +32,23 @@ fn main() { } } State::B | State::D => { - if true { + states.push(state); + if first { + first = false; #[const_continue] - break 'blk State::C; + break 'blk State::A; } else { - // No drops allowed at this point #[const_continue] - break 'blk State::A; + break 'blk State::C; } } - State::C => break 'a, + State::C => { + states.push(state); + break 'a; + } } } } + + assert_eq!(states, [State::A, State::B, State::A, State::D, State::C]); } From f777efaf6be93eaef698f9de81929a8dc74cf461 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 22 Mar 2025 13:12:20 +0100 Subject: [PATCH 04/35] remove some comments that are now inaccurate --- compiler/rustc_mir_build/src/builder/expr/into.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 2ee891054045c..f6648b2c93ecf 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -278,9 +278,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Start the loop. this.cfg.goto(block, source_info, loop_block); - // FIXME do we need the breakable scope? this.in_breakable_scope(Some(loop_block), destination, expr_span, |this| { - // conduct the test, if necessary let mut body_block = this.cfg.start_new_block(); this.cfg.terminate( loop_block, From 1b938884c9c09784e1f91123198e193ee6b5504f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 21 Mar 2025 15:46:37 +0100 Subject: [PATCH 05/35] static pattern matching when lowering `#[const_continue]` --- .../rustc_mir_build/src/builder/expr/into.rs | 226 +++--------------- .../src/builder/matches/mod.rs | 93 ++++++- compiler/rustc_mir_build/src/builder/scope.rs | 82 ++++--- .../rustc_pattern_analysis/src/constructor.rs | 3 +- tests/ui/loop-match/integer-patterns.rs | 13 +- 5 files changed, 182 insertions(+), 235 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index f6648b2c93ecf..ce0630fc50c41 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -1,6 +1,5 @@ //! See docs in build/expr/mod.rs -use rustc_abi::VariantIdx; use rustc_ast::{AsmMacro, InlineAsmOptions}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -9,17 +8,14 @@ use rustc_hir::lang_items::LangItem; use rustc_middle::mir::*; use rustc_middle::span_bug; use rustc_middle::thir::*; -use rustc_middle::ty::util::Discr; use rustc_middle::ty::{CanonicalUserTypeAnnotation, Ty}; -use rustc_pattern_analysis::constructor::Constructor; -use rustc_pattern_analysis::rustc::{DeconstructedPat, RustcPatCtxt}; use rustc_span::DUMMY_SP; use rustc_span::source_map::Spanned; use rustc_trait_selection::infer::InferCtxtExt; use tracing::{debug, instrument}; use crate::builder::expr::category::{Category, RvalueFunc}; -use crate::builder::matches::DeclareLetBindings; +use crate::builder::matches::{DeclareLetBindings, HasMatchGuard}; use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder, NeedsTemporary}; impl<'a, 'tcx> Builder<'a, 'tcx> { @@ -251,28 +247,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::LoopMatch { state, region_scope, ref arms } => { // FIXME add diagram - let dropless_arena = rustc_arena::DroplessArena::default(); - let typeck_results = this.tcx.typeck(this.def_id); - - // the PatCtxt is normally used in pattern exhaustiveness checking, but reused here - // because it performs normalization and const evaluation. - let cx = RustcPatCtxt { - tcx: this.tcx, - typeck_results, - module: this.tcx.parent_module(this.hir_id).to_def_id(), - // FIXME(#132279): We're in a body, should handle opaques. - typing_env: rustc_middle::ty::TypingEnv::non_body_analysis( - this.tcx, - this.def_id, - ), - dropless_arena: &dropless_arena, - match_lint_level: this.hir_id, - whole_match_span: Some(rustc_span::Span::default()), - scrut_span: rustc_span::Span::default(), - refutable: true, - known_valid_scrutinee: true, - }; - let loop_block = this.cfg.start_new_block(); // Start the loop. @@ -290,131 +264,52 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); this.diverge_from(loop_block); - let state_place = unpack!(body_block = this.as_place(body_block, state)); - let state_ty = this.thir.exprs[state].ty; - - // the type of the value that is switched on by the `SwitchInt` - let discr_ty = match state_ty { - ty if ty.is_enum() => ty.discriminant_ty(this.tcx), - ty if ty.is_integral() => ty, - other => todo!("{other:?}"), - }; - - let rvalue = match state_ty { - ty if ty.is_enum() => Rvalue::Discriminant(state_place), - ty if ty.is_integral() => Rvalue::Use(Operand::Copy(state_place)), - _ => todo!(), - }; + let scrutinee_place_builder = + unpack!(body_block = this.as_place_builder(body_block, state)); + let scrutinee_span = this.thir.exprs[state].span; + let match_start_span = scrutinee_span; // span.shrink_to_lo().to(scrutinee_span); FIXME + let patterns = arms + .iter() + .map(|&arm_id| { + // FIXME nice error for guards (which are not allowed) + let arm = &this.thir[arm_id]; + assert!(arm.guard.is_none()); + (&*arm.pattern, HasMatchGuard::No) + }) + .collect(); + + let built_tree = this.lower_match_tree( + body_block, + scrutinee_span, + &scrutinee_place_builder, + match_start_span, + patterns, + false, + ); - // block and arm of the wildcard pattern (if any) - let mut otherwise = None; + let state_place = scrutinee_place_builder.to_place(this); unpack!( body_block = this.in_scope( (region_scope, source_info), LintLevel::Inherited, move |this| { - let mut arm_blocks = Vec::with_capacity(arms.len()); - for &arm in arms { - let pat = &this.thir[arm].pattern; - - this.loop_match_patterns( - arm, - &cx.lower_pat(pat), - None, - &mut arm_blocks, - &mut otherwise, - ); - } - - // handle patterns like `None | None` or two different arms that - // have the same pattern. - // - // NOTE: why this works is a bit subtle: we always want to pick the - // first arm for a pattern, and because this is a stable sort that - // works out. - arm_blocks.sort_by_key(|(_, discr, _, _)| discr.val); - arm_blocks.dedup_by_key(|(_, discr, _, _)| discr.val); - - // if we're matching on an enum, the discriminant order in the `SwitchInt` - // targets should match the order yielded by `AdtDef::discriminants`. - if state_ty.is_enum() { - arm_blocks.sort_by_key(|(variant_idx, ..)| *variant_idx); - } - - let targets = SwitchTargets::new( - arm_blocks - .iter() - .map(|&(_, discr, block, _arm)| (discr.val, block)), - if let Some((block, _)) = otherwise { - block - } else { - let unreachable_block = this.cfg.start_new_block(); - this.cfg.terminate( - unreachable_block, - source_info, - TerminatorKind::Unreachable, - ); - unreachable_block - }, - ); - this.in_breakable_scope(None, state_place, expr_span, |this| { Some(this.in_const_continuable_scope( - targets.clone(), + arms.clone(), + built_tree.clone(), state_place, expr_span, |this| { - let discr = this.temp(discr_ty, source_info.span); - this.cfg.push_assign( - body_block, - source_info, - discr, - rvalue, - ); - let discr = Operand::Copy(discr); - this.cfg.terminate( - body_block, - source_info, - TerminatorKind::SwitchInt { discr, targets }, - ); - - let it = arm_blocks - .into_iter() - .map(|(_, _, block, arm)| (block, arm)) - .chain(otherwise); - - for (mut block, arm_id) in it { - if this.cfg.block_data(block).terminator.is_some() { - continue; // this can occur with or-patterns - } - - let arm = &this.thir[arm_id]; - let arm_source_info = this.source_info(arm.span); - let arm_scope = (arm.scope, arm_source_info); - - let empty_place = this.get_unit_temp(); - unpack!( - block = { - this.in_scope( - arm_scope, - arm.lint_level, - |this| { - this.expr_into_dest( - empty_place, - block, - arm.body, - ) - }, - ) - } - ); - this.cfg.terminate( - block, - source_info, - TerminatorKind::Unreachable, - ); - } + this.lower_match_arms( + destination, + scrutinee_place_builder, + scrutinee_span, + arms, + built_tree, + // FIXME this should be the span of just the match + this.source_info(expr_span), + ) }, )) }) @@ -893,55 +788,4 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { _ => false, } } - - fn loop_match_patterns( - &mut self, - arm_id: ArmId, - pat: &DeconstructedPat<'_, 'tcx>, - current_block: Option, - result: &mut Vec<(VariantIdx, Discr<'tcx>, BasicBlock, ArmId)>, - otherwise: &mut Option<(BasicBlock, ArmId)>, - ) { - match pat.ctor() { - Constructor::Variant(variant_index) => { - let PatKind::Variant { adt_def, .. } = pat.data().kind else { unreachable!() }; - - let discr = adt_def.discriminant_for_variant(self.tcx, *variant_index); - - let block = current_block.unwrap_or_else(|| self.cfg.start_new_block()); - result.push((*variant_index, discr, block, arm_id)); - } - Constructor::IntRange(int_range) => { - assert!(int_range.is_singleton()); - - let bits = pat.ty().primitive_size(self.tcx).bits(); - - let value = if pat.ty().is_signed() { - int_range.lo.as_finite_int(bits).unwrap() - } else { - int_range.lo.as_finite_uint().unwrap() - }; - - let discr = Discr { val: value, ty: **pat.ty() }; - - let block = current_block.unwrap_or_else(|| self.cfg.start_new_block()); - result.push((VariantIdx::ZERO, discr, block, arm_id)); - } - Constructor::Wildcard => { - // the first wildcard wins - if otherwise.is_none() { - let block = current_block.unwrap_or_else(|| self.cfg.start_new_block()); - *otherwise = Some((block, arm_id)) - } - } - Constructor::Or => { - let block = current_block.unwrap_or_else(|| self.cfg.start_new_block()); - - for indexed in pat.iter_fields() { - self.loop_match_patterns(arm_id, &indexed.pat, Some(block), result, otherwise); - } - } - other => todo!("{:?}", other), - } - } } diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 3acf2a6a2a61a..5524da80a4892 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -11,6 +11,7 @@ use std::mem; use std::sync::Arc; use rustc_abi::VariantIdx; +use rustc_ast::LitKind; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::{BindingMode, ByRef, LetStmt, LocalSource, Node}; @@ -19,6 +20,7 @@ use rustc_middle::middle::region; use rustc_middle::mir::{self, *}; use rustc_middle::thir::{self, *}; use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty}; +use rustc_pattern_analysis::rustc::{DeconstructedPat, RustcPatCtxt}; use rustc_span::{BytePos, Pos, Span, Symbol, sym}; use tracing::{debug, instrument}; @@ -426,7 +428,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// (by [Builder::lower_match_tree]). /// /// `outer_source_info` is the SourceInfo for the whole match. - fn lower_match_arms( + pub(crate) fn lower_match_arms( &mut self, destination: Place<'tcx>, scrutinee_place_builder: PlaceBuilder<'tcx>, @@ -1394,7 +1396,7 @@ pub(crate) struct ArmHasGuard(pub(crate) bool); /// A sub-branch in the output of match lowering. Match lowering has generated MIR code that will /// branch to `success_block` when the matched value matches the corresponding pattern. If there is /// a guard, its failure must continue to `otherwise_block`, which will resume testing patterns. -#[derive(Debug)] +#[derive(Debug, Clone)] struct MatchTreeSubBranch<'tcx> { span: Span, /// The block that is branched to if the corresponding subpattern matches. @@ -1410,7 +1412,7 @@ struct MatchTreeSubBranch<'tcx> { } /// A branch in the output of match lowering. -#[derive(Debug)] +#[derive(Debug, Clone)] struct MatchTreeBranch<'tcx> { sub_branches: Vec>, } @@ -1429,8 +1431,8 @@ struct MatchTreeBranch<'tcx> { /// Here the first arm gives the first `MatchTreeBranch`, which has two sub-branches, one for each /// alternative of the or-pattern. They are kept separate because each needs to bind `x` to a /// different place. -#[derive(Debug)] -struct BuiltMatchTree<'tcx> { +#[derive(Debug, Clone)] +pub(crate) struct BuiltMatchTree<'tcx> { branches: Vec>, otherwise_block: BasicBlock, /// If any of the branches had a guard, we collect here the places and locals to fakely borrow @@ -1488,7 +1490,7 @@ impl<'tcx> MatchTreeBranch<'tcx> { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum HasMatchGuard { +pub(crate) enum HasMatchGuard { Yes, No, } @@ -1503,7 +1505,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// `refutable` indicates whether the candidate list is refutable (for `if let` and `let else`) /// or not (for `let` and `match`). In the refutable case we return the block to which we branch /// on failure. - fn lower_match_tree( + pub(crate) fn lower_match_tree( &mut self, block: BasicBlock, scrutinee_span: Span, @@ -2863,4 +2865,81 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { true } + + /// Attempt to statically pick the BasicBlock that a value would resolve to at runtime. + pub(crate) fn static_pattern_match( + &self, + cx: &RustcPatCtxt<'_, 'tcx>, + value: ExprId, + arms: &[ArmId], + built_match_tree: &BuiltMatchTree<'tcx>, + ) -> Option { + let it = arms.iter().zip(built_match_tree.branches.iter()); + for (&arm_id, branch) in it { + let pat = cx.lower_pat(&*self.thir.arms[arm_id].pattern); + + if let rustc_pattern_analysis::rustc::Constructor::Or = pat.ctor() { + for pat in pat.iter_fields() { + // when the bindings are the same, the sub_branch is only stored once, + // so we must repeat it manually. + let sub_branch = branch + .sub_branches + .get(pat.idx) + .or_else(|| branch.sub_branches.last()) + .unwrap(); + + match self.static_pattern_match_help(value, &pat.pat) { + true => return Some(sub_branch.success_block), + false => continue, + } + } + } else if self.static_pattern_match_help(value, &pat) { + return Some(branch.sub_branches[0].success_block); + } + } + + None + } + + fn static_pattern_match_help(&self, value: ExprId, pat: &DeconstructedPat<'_, 'tcx>) -> bool { + use rustc_middle::thir::ExprKind; + use rustc_pattern_analysis::constructor::{IntRange, MaybeInfiniteInt}; + use rustc_pattern_analysis::rustc::Constructor; + + match pat.ctor() { + Constructor::Variant(variant_index) => match &self.thir[value].kind { + ExprKind::Adt(value_adt) => { + return *variant_index == value_adt.variant_index; + } + other => todo!("{other:?}"), + }, + Constructor::IntRange(int_range) => match &self.thir[value].kind { + ExprKind::Literal { lit, neg } => match &lit.node { + LitKind::Int(n, _) => { + let n = if pat.ty().is_signed() { + let bits = pat.ty().primitive_size(self.tcx).bits(); + MaybeInfiniteInt::new_finite_int( + if *neg { + (n.get() as i128).overflowing_neg().0 as u128 + & ((1u128 << bits) - 1) + } else { + n.get() + }, + bits, + ) + } else { + MaybeInfiniteInt::new_finite_uint(n.get()) + }; + + return IntRange::from_singleton(n).is_subrange(int_range); + } + + other => todo!("{other:?}"), + }, + other => todo!("{other:?}"), + }, + Constructor::Wildcard => return true, + _ => false, + } + } } diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index f5381d0ccc13c..8cd4d34160501 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -83,19 +83,20 @@ that contains only loops and breakable blocks. It tracks where a `break`, use std::mem; -use rustc_ast::LitKind; use rustc_data_structures::fx::FxHashMap; use rustc_hir::HirId; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::middle::region; use rustc_middle::mir::*; -use rustc_middle::thir::{ExprId, LintLevel}; +use rustc_middle::thir::{ArmId, ExprId, LintLevel}; use rustc_middle::{bug, span_bug}; +use rustc_pattern_analysis::rustc::RustcPatCtxt; use rustc_session::lint::Level; use rustc_span::source_map::Spanned; use rustc_span::{DUMMY_SP, Span}; use tracing::{debug, instrument}; +use super::matches::BuiltMatchTree; use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder, CFG}; #[derive(Debug)] @@ -184,7 +185,9 @@ struct ConstContinuableScope<'tcx> { /// the result of a `break` or `return` expression) state_place: Place<'tcx>, - match_arms: SwitchTargets, + arms: Box<[ArmId]>, + built_match_tree: BuiltMatchTree<'tcx>, + /// Drops that happen on the `return` path and would have happened on the `break` path. break_drops: DropTree, } @@ -572,32 +575,48 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// branch to. pub(crate) fn in_const_continuable_scope( &mut self, - match_arms: SwitchTargets, + arms: Box<[ArmId]>, + built_match_tree: BuiltMatchTree<'tcx>, state_place: Place<'tcx>, span: Span, f: F, ) -> BlockAnd<()> where - F: FnOnce(&mut Builder<'a, 'tcx>), + F: FnOnce(&mut Builder<'a, 'tcx>) -> BlockAnd<()>, { let region_scope = self.scopes.topmost(); let scope = ConstContinuableScope { region_scope, state_place, break_drops: DropTree::new(), - match_arms, + arms, + built_match_tree, }; self.scopes.const_continuable_scopes.push(scope); - f(self); + let normal_exit_block = f(self); let breakable_scope = self.scopes.const_continuable_scopes.pop().unwrap(); assert!(breakable_scope.region_scope == region_scope); let break_block = self.build_exit_tree(breakable_scope.break_drops, region_scope, span, None); - match break_block { - Some(block) => block, - None => self.cfg.start_new_block().unit(), + match (normal_exit_block, break_block) { + (block, None) => block, + (normal_block, Some(exit_block)) => { + let target = self.cfg.start_new_block(); + let source_info = self.source_info(span); + self.cfg.terminate( + normal_block.into_block(), + source_info, + TerminatorKind::Goto { target }, + ); + self.cfg.terminate( + exit_block.into_block(), + source_info, + TerminatorKind::Goto { target }, + ); + target.unit() + } } } @@ -764,23 +783,32 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { _ => todo!(), }; - let real_target = match &self.thir[value].kind { - rustc_middle::thir::ExprKind::Adt(value_adt) => scope - .match_arms - .target_for_value(u128::from(value_adt.variant_index.as_u32())), - rustc_middle::thir::ExprKind::Literal { lit, neg } => match lit.node { - LitKind::Int(n, _) => { - let n = if *neg { - (n.get() as i128).overflowing_neg().0 as u128 - } else { - n.get() - }; - let result = state_ty.primitive_size(self.tcx).truncate(n); - scope.match_arms.target_for_value(result) - } - _ => todo!(), - }, - other => todo!("{other:?}"), + // the PatCtxt is normally used in pattern exhaustiveness checking, but reused here + // because it performs normalization and const evaluation. + let dropless_arena = rustc_arena::DroplessArena::default(); + let typeck_results = self.tcx.typeck(self.def_id); + let cx = RustcPatCtxt { + tcx: self.tcx, + typeck_results, + module: self.tcx.parent_module(self.hir_id).to_def_id(), + // FIXME(#132279): We're in a body, should handle opaques. + typing_env: rustc_middle::ty::TypingEnv::non_body_analysis( + self.tcx, + self.def_id, + ), + dropless_arena: &dropless_arena, + match_lint_level: self.hir_id, + whole_match_span: Some(rustc_span::Span::default()), + scrut_span: rustc_span::Span::default(), + refutable: true, + known_valid_scrutinee: true, + }; + + let Some(real_target) = + self.static_pattern_match(&cx, value, &*scope.arms, &scope.built_match_tree) + else { + // self.tcx.dcx().emit_fatal() + todo!() }; self.block_context.push(BlockFrame::SubExpr); diff --git a/compiler/rustc_pattern_analysis/src/constructor.rs b/compiler/rustc_pattern_analysis/src/constructor.rs index 4ce868f014f42..7e113631ba7f7 100644 --- a/compiler/rustc_pattern_analysis/src/constructor.rs +++ b/compiler/rustc_pattern_analysis/src/constructor.rs @@ -314,7 +314,8 @@ impl IntRange { IntRange { lo, hi } } - fn is_subrange(&self, other: &Self) -> bool { + #[inline] + pub fn is_subrange(&self, other: &Self) -> bool { other.lo <= self.lo && self.hi <= other.hi } diff --git a/tests/ui/loop-match/integer-patterns.rs b/tests/ui/loop-match/integer-patterns.rs index 0dc68baa4595b..eabe07c9a88cf 100644 --- a/tests/ui/loop-match/integer-patterns.rs +++ b/tests/ui/loop-match/integer-patterns.rs @@ -6,26 +6,21 @@ #![feature(loop_match)] fn main() { - let mut state = 0; + let mut state = 0i32; #[loop_match] 'a: loop { state = 'blk: { match state { -1 => { - if true { - #[const_continue] - break 'blk 2; - } else { - #[const_continue] - break 'blk 0; - } + #[const_continue] + break 'blk 2; } 0 => { #[const_continue] break 'blk -1; } 2 => break 'a, - _ => break 'a, + _ => unreachable!("weird value {:?}", state), } } } From 5e73b2d3f92698d5752db991dcd0bc86d0ee42ac Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 24 Mar 2025 11:53:49 +0100 Subject: [PATCH 06/35] clarify an unreachable branch --- compiler/rustc_mir_build/src/builder/scope.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index 8cd4d34160501..e1aa91655f6a6 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -750,7 +750,16 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { (break_index, None) } BreakableTarget::ConstContinue(scope) => { - assert!(value.is_some()); + let Some(value) = value else { + span_bug!(span, "#[const_continue] must break with a value") + }; + + // A break can only break out of a scope, so the value should be a scope + let rustc_middle::thir::ExprKind::Scope { value, .. } = self.thir[value].kind + else { + span_bug!(span, "break value must be a scope") + }; + let break_index = self .scopes .const_continuable_scopes @@ -762,12 +771,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { span_bug!(span, "no enclosing const-continuable scope found") }); - let rustc_middle::thir::ExprKind::Scope { value, .. } = - self.thir[value.unwrap()].kind - else { - panic!(); - }; - let scope = &self.scopes.const_continuable_scopes[break_index]; let state_ty = self.local_decls[scope.state_place.as_local().unwrap()].ty; From b8c57527416405fc05cd5a057003930356c1d7a2 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 24 Mar 2025 16:12:56 +0100 Subject: [PATCH 07/35] add error for unknown jump target --- compiler/rustc_mir_build/messages.ftl | 5 ++++- compiler/rustc_mir_build/src/builder/scope.rs | 4 ++-- compiler/rustc_mir_build/src/errors.rs | 8 ++++++++ tests/ui/loop-match/invalid.rs | 2 +- tests/ui/loop-match/invalid.stderr | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index 6b10c7df71c5b..decc770ab6661 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -84,7 +84,10 @@ mir_build_call_to_unsafe_fn_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = mir_build_confused = missing patterns are not covered because `{$variable}` is interpreted as a constant pattern, not a new variable -mir_build_const_continue_missing_value = a `const_continue` must break to a label with a value +mir_build_const_continue_missing_value = a `#[const_continue]` must break to a label with a value + +mir_build_const_continue_unknown_jump_target = the target of this `#[const_continue]` is not statically known + .note = this value must be an integer or enum literal mir_build_const_defined_here = constant defined here diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index e1aa91655f6a6..5df5398054ea2 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -98,6 +98,7 @@ use tracing::{debug, instrument}; use super::matches::BuiltMatchTree; use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder, CFG}; +use crate::errors::ConstContinueUnknownJumpTarget; #[derive(Debug)] pub(crate) struct Scopes<'tcx> { @@ -810,8 +811,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let Some(real_target) = self.static_pattern_match(&cx, value, &*scope.arms, &scope.built_match_tree) else { - // self.tcx.dcx().emit_fatal() - todo!() + self.tcx.dcx().emit_fatal(ConstContinueUnknownJumpTarget { span }) }; self.block_context.push(BlockFrame::SubExpr); diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index c213b2f990a40..376b5c2e047f7 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -1206,3 +1206,11 @@ pub(crate) struct ConstContinueMissingValue { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag(mir_build_const_continue_unknown_jump_target)] +#[note] +pub(crate) struct ConstContinueUnknownJumpTarget { + #[primary_span] + pub span: Span, +} diff --git a/tests/ui/loop-match/invalid.rs b/tests/ui/loop-match/invalid.rs index 7dd271742320a..5b80aa2d6162f 100644 --- a/tests/ui/loop-match/invalid.rs +++ b/tests/ui/loop-match/invalid.rs @@ -136,7 +136,7 @@ fn break_without_value_unit() { () => { #[const_continue] break 'blk; - //~^ ERROR a `const_continue` must break to a label with a value + //~^ ERROR a `#[const_continue]` must break to a label with a value } } } diff --git a/tests/ui/loop-match/invalid.stderr b/tests/ui/loop-match/invalid.stderr index 36fea1d7fba3a..4b3e509eb49b2 100644 --- a/tests/ui/loop-match/invalid.stderr +++ b/tests/ui/loop-match/invalid.stderr @@ -74,7 +74,7 @@ LL | | LL | | } | |_____^ -error: a `const_continue` must break to a label with a value +error: a `#[const_continue]` must break to a label with a value --> $DIR/invalid.rs:138:21 | LL | break 'blk; From 044acdf582ae04d7f977651da464b18433db0973 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 24 Mar 2025 16:37:03 +0100 Subject: [PATCH 08/35] emit an error when a match arm has a guard --- compiler/rustc_mir_build/messages.ftl | 5 ++++- .../rustc_mir_build/src/builder/expr/into.rs | 22 +++++++++++-------- compiler/rustc_mir_build/src/errors.rs | 7 ++++++ tests/ui/loop-match/invalid.rs | 22 +++++++++++++++++-- tests/ui/loop-match/invalid.stderr | 12 +++++++--- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index decc770ab6661..47d0e5bab1a9a 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -217,11 +217,14 @@ mir_build_literal_in_range_out_of_bounds = literal out of range for `{$ty}` .label = this value does not fit into the type `{$ty}` whose range is `{$min}..={$max}` +mir_build_loop_match_arm_with_guard = + match arms that are part of a `#[loop_match]` cannot have guards + mir_build_loop_match_bad_rhs = this expression must be a single `match` wrapped in a labeled block mir_build_loop_match_bad_statements = - statements are not allowed in this position within a `loop_match` + statements are not allowed in this position within a `#[loop_match]` mir_build_loop_match_invalid_match = invalid match on `loop_match` state diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index ce0630fc50c41..eb46d8fc06a28 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -17,6 +17,7 @@ use tracing::{debug, instrument}; use crate::builder::expr::category::{Category, RvalueFunc}; use crate::builder::matches::{DeclareLetBindings, HasMatchGuard}; use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder, NeedsTemporary}; +use crate::errors::LoopMatchArmWithGuard; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Compile `expr`, storing the result into `destination`, which @@ -268,15 +269,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { unpack!(body_block = this.as_place_builder(body_block, state)); let scrutinee_span = this.thir.exprs[state].span; let match_start_span = scrutinee_span; // span.shrink_to_lo().to(scrutinee_span); FIXME - let patterns = arms - .iter() - .map(|&arm_id| { - // FIXME nice error for guards (which are not allowed) - let arm = &this.thir[arm_id]; - assert!(arm.guard.is_none()); - (&*arm.pattern, HasMatchGuard::No) - }) - .collect(); + + let mut patterns = Vec::with_capacity(arms.len()); + for &arm_id in arms.iter() { + let arm = &this.thir[arm_id]; + + if let Some(guard) = arm.guard { + let span = this.thir.exprs[guard].span; + this.tcx.dcx().emit_fatal(LoopMatchArmWithGuard { span }) + } + + patterns.push((&*arm.pattern, HasMatchGuard::No)); + } let built_tree = this.lower_match_tree( body_block, diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 376b5c2e047f7..a1a752ce69d60 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -1200,6 +1200,13 @@ pub(crate) struct LoopMatchMissingAssignment { pub span: Span, } +#[derive(Diagnostic)] +#[diag(mir_build_loop_match_arm_with_guard)] +pub(crate) struct LoopMatchArmWithGuard { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(mir_build_const_continue_missing_value)] pub(crate) struct ConstContinueMissingValue { diff --git a/tests/ui/loop-match/invalid.rs b/tests/ui/loop-match/invalid.rs index 5b80aa2d6162f..55cd0f282cb3b 100644 --- a/tests/ui/loop-match/invalid.rs +++ b/tests/ui/loop-match/invalid.rs @@ -41,7 +41,7 @@ fn bad_statements_1() { #[loop_match] loop { 1; - //~^ ERROR statements are not allowed in this position within a `loop_match` + //~^ ERROR statements are not allowed in this position within a `#[loop_match]` state = 'blk: { match State::A { _ => State::B, @@ -56,7 +56,7 @@ fn bad_statements_2() { loop { state = 'blk: { 1; - //~^ ERROR statements are not allowed in this position within a `loop_match` + //~^ ERROR statements are not allowed in this position within a `#[loop_match]` match State::A { _ => State::B, } @@ -142,3 +142,21 @@ fn break_without_value_unit() { } } } + +fn arm_has_guard(cond: bool) { + let state = State::A; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + State::A => { + #[const_continue] + break 'blk State::B; + } + State::B if cond => break 'a, + //~^ ERROR match arms that are part of a `#[loop_match]` cannot have guards + _ => break 'a, + } + } + } +} diff --git a/tests/ui/loop-match/invalid.stderr b/tests/ui/loop-match/invalid.stderr index 4b3e509eb49b2..06fd2f7755123 100644 --- a/tests/ui/loop-match/invalid.stderr +++ b/tests/ui/loop-match/invalid.stderr @@ -26,13 +26,13 @@ LL | match State::A { | = note: only matches on local variables are valid -error: statements are not allowed in this position within a `loop_match` +error: statements are not allowed in this position within a `#[loop_match]` --> $DIR/invalid.rs:43:9 | LL | 1; | ^^ -error: statements are not allowed in this position within a `loop_match` +error: statements are not allowed in this position within a `#[loop_match]` --> $DIR/invalid.rs:58:13 | LL | 1; @@ -80,6 +80,12 @@ error: a `#[const_continue]` must break to a label with a value LL | break 'blk; | ^^^^^^^^^^ -error: aborting due to 11 previous errors +error: match arms that are part of a `#[loop_match]` cannot have guards + --> $DIR/invalid.rs:156:29 + | +LL | State::B if cond => break 'a, + | ^^^^ + +error: aborting due to 12 previous errors For more information about this error, try `rustc --explain E0308`. From fc982a2e4d7742323b3d23669a9c7be3792e4b63 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 24 Mar 2025 16:45:56 +0100 Subject: [PATCH 09/35] store the span of the match expression, and use it for diagnostics --- compiler/rustc_middle/src/thir.rs | 3 ++- compiler/rustc_mir_build/src/builder/expr/into.rs | 7 +++---- compiler/rustc_mir_build/src/thir/cx/expr.rs | 1 + compiler/rustc_mir_build/src/thir/print.rs | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index be156e70c0af7..891b4c7cefef4 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -377,10 +377,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 state: ExprId, - region_scope: region::Scope, arms: Box<[ArmId]>, + match_span: Span, }, /// 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 `&&`). diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index eb46d8fc06a28..7a516fc77750f 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -245,7 +245,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { None }) } - ExprKind::LoopMatch { state, region_scope, ref arms } => { + ExprKind::LoopMatch { state, region_scope, match_span, ref arms } => { // FIXME add diagram let loop_block = this.cfg.start_new_block(); @@ -268,7 +268,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let scrutinee_place_builder = unpack!(body_block = this.as_place_builder(body_block, state)); let scrutinee_span = this.thir.exprs[state].span; - let match_start_span = scrutinee_span; // span.shrink_to_lo().to(scrutinee_span); FIXME + let match_start_span = match_span.shrink_to_lo().to(scrutinee_span); let mut patterns = Vec::with_capacity(arms.len()); for &arm_id in arms.iter() { @@ -311,8 +311,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { scrutinee_span, arms, built_tree, - // FIXME this should be the span of just the match - this.source_info(expr_span), + this.source_info(match_span), ) }, )) diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index c344d12d51c42..3d61f47203b69 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -938,6 +938,7 @@ impl<'tcx> ThirBuildCx<'tcx> { }, arms: arms.iter().map(|a| self.convert_arm(a)).collect(), + match_span: block_body_expr.span, } } else { let block_ty = self.typeck_results.node_type(body.hir_id); diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index 0c522e3acee1e..4a93371534b9f 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -319,11 +319,12 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_expr(*body, depth_lvl + 2); print_indented!(self, ")", depth_lvl); } - LoopMatch { state, region_scope, arms } => { + LoopMatch { state, region_scope, match_span, arms } => { 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() { From 5eb72b79bfd6473cae22eb58a82cbba25e1aea5f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 24 Mar 2025 17:01:17 +0100 Subject: [PATCH 10/35] add comments --- .../rustc_mir_build/src/builder/expr/into.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 7a516fc77750f..20b67fcb03401 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -246,7 +246,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { }) } ExprKind::LoopMatch { state, region_scope, match_span, ref arms } => { - // FIXME add diagram + // Intuitively, this is a combination of a loop containing a labeled block + // containing a match. + // + // The only new bit here is that the lowering of the match is wrapped in a + // `in_const_continuable_scope`, which makes the match arms and their target basic + // block available to the lowering of `#[const_continue]`. let loop_block = this.cfg.start_new_block(); @@ -254,6 +259,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { this.cfg.goto(block, source_info, loop_block); this.in_breakable_scope(Some(loop_block), destination, expr_span, |this| { + // logic for `loop` let mut body_block = this.cfg.start_new_block(); this.cfg.terminate( loop_block, @@ -265,6 +271,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); this.diverge_from(loop_block); + // 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; @@ -282,6 +289,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { patterns.push((&*arm.pattern, HasMatchGuard::No)); } + // The `built_tree` maps match arms to their basic block (where control flow + // jumps to when a value matches the arm). This structure is stored so that a + // #[const_continue] can figure out what basic block to jump to. let built_tree = this.lower_match_tree( body_block, scrutinee_span, @@ -293,6 +303,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let state_place = scrutinee_place_builder.to_place(this); + // this is logic for the labeled block: a block is a drop scope, hence + // `in_scope`, and a labeled block can be broken out of with a `break 'label`, + // hence the `in_breakable_scope`. + // + // Inside of that information for #[const_continue] is stored, and the match is + // lowered in the standard way. unpack!( body_block = this.in_scope( (region_scope, source_info), From 3716bed9f426251c89b07aca6ec1d70385870038 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 28 Mar 2025 14:56:06 +0100 Subject: [PATCH 11/35] use `Size::truncate` --- compiler/rustc_mir_build/src/builder/matches/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 5524da80a4892..4fbba98879364 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -2917,15 +2917,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Literal { lit, neg } => match &lit.node { LitKind::Int(n, _) => { let n = if pat.ty().is_signed() { - let bits = pat.ty().primitive_size(self.tcx).bits(); + let size = pat.ty().primitive_size(self.tcx); MaybeInfiniteInt::new_finite_int( if *neg { - (n.get() as i128).overflowing_neg().0 as u128 - & ((1u128 << bits) - 1) + size.truncate((n.get() as i128).overflowing_neg().0 as u128) } else { n.get() }, - bits, + size.bits(), ) } else { MaybeInfiniteInt::new_finite_uint(n.get()) From 74e482e493ae7f3cb9840c1ea6c889bf039e63d1 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 28 Mar 2025 14:20:18 +0100 Subject: [PATCH 12/35] refactor `break_scope` --- .../rustc_mir_build/src/builder/expr/stmt.rs | 9 +- compiler/rustc_mir_build/src/builder/scope.rs | 315 +++++++++--------- 2 files changed, 155 insertions(+), 169 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/expr/stmt.rs b/compiler/rustc_mir_build/src/builder/expr/stmt.rs index 25b64f0105cb9..3209c8da3a4ff 100644 --- a/compiler/rustc_mir_build/src/builder/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/builder/expr/stmt.rs @@ -92,12 +92,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Break { label, value } => { this.break_scope(block, value, BreakableTarget::Break(label), source_info) } - ExprKind::ConstContinue { label, value } => this.break_scope( - block, - Some(value), - BreakableTarget::ConstContinue(label), - source_info, - ), + ExprKind::ConstContinue { label, value } => { + this.break_const_continuable_scope(block, value, label, source_info) + } ExprKind::Return { value } => { this.break_scope(block, value, BreakableTarget::Return, source_info) } diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index 5df5398054ea2..5e112b6969a3d 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -206,7 +206,6 @@ struct IfThenScope { pub(crate) enum BreakableTarget { Continue(region::Scope), Break(region::Scope), - ConstContinue(region::Scope), Return, } @@ -750,168 +749,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let break_index = get_scope_index(scope); (break_index, None) } - BreakableTarget::ConstContinue(scope) => { - let Some(value) = value else { - span_bug!(span, "#[const_continue] must break with a value") - }; - - // A break can only break out of a scope, so the value should be a scope - let rustc_middle::thir::ExprKind::Scope { value, .. } = self.thir[value].kind - else { - span_bug!(span, "break value must be a scope") - }; - - let break_index = self - .scopes - .const_continuable_scopes - .iter() - .rposition(|const_continuable_scope| { - const_continuable_scope.region_scope == scope - }) - .unwrap_or_else(|| { - span_bug!(span, "no enclosing const-continuable scope found") - }); - - let scope = &self.scopes.const_continuable_scopes[break_index]; - - let state_ty = self.local_decls[scope.state_place.as_local().unwrap()].ty; - let discriminant_ty = match state_ty { - ty if ty.is_enum() => ty.discriminant_ty(self.tcx), - ty if ty.is_integral() => ty, - _ => todo!(), - }; - - let rvalue = match state_ty { - ty if ty.is_enum() => Rvalue::Discriminant(scope.state_place), - ty if ty.is_integral() => Rvalue::Use(Operand::Copy(scope.state_place)), - _ => todo!(), - }; - - // the PatCtxt is normally used in pattern exhaustiveness checking, but reused here - // because it performs normalization and const evaluation. - let dropless_arena = rustc_arena::DroplessArena::default(); - let typeck_results = self.tcx.typeck(self.def_id); - let cx = RustcPatCtxt { - tcx: self.tcx, - typeck_results, - module: self.tcx.parent_module(self.hir_id).to_def_id(), - // FIXME(#132279): We're in a body, should handle opaques. - typing_env: rustc_middle::ty::TypingEnv::non_body_analysis( - self.tcx, - self.def_id, - ), - dropless_arena: &dropless_arena, - match_lint_level: self.hir_id, - whole_match_span: Some(rustc_span::Span::default()), - scrut_span: rustc_span::Span::default(), - refutable: true, - known_valid_scrutinee: true, - }; - - let Some(real_target) = - self.static_pattern_match(&cx, value, &*scope.arms, &scope.built_match_tree) - else { - self.tcx.dcx().emit_fatal(ConstContinueUnknownJumpTarget { span }) - }; - - self.block_context.push(BlockFrame::SubExpr); - let state_place = scope.state_place; - block = self.expr_into_dest(state_place, block, value).into_block(); - self.block_context.pop(); - - let discr = self.temp(discriminant_ty, source_info.span); - let scope_index = self.scopes.scope_index( - self.scopes.const_continuable_scopes[break_index].region_scope, - span, - ); - let scope = &mut self.scopes.const_continuable_scopes[break_index]; - self.cfg.push_assign(block, source_info, discr, rvalue); - let drop_and_continue_block = self.cfg.start_new_block(); - let imaginary_target = self.cfg.start_new_block(); - self.cfg.terminate( - block, - source_info, - TerminatorKind::FalseEdge { - real_target: drop_and_continue_block, - imaginary_target, - }, - ); - - let drops = &mut scope.break_drops; - - let drop_idx = self.scopes.scopes[scope_index + 1..] - .iter() - .flat_map(|scope| &scope.drops) - .fold(ROOT_NODE, |drop_idx, &drop| drops.add_drop(drop, drop_idx)); - - drops.add_entry_point(imaginary_target, drop_idx); - - self.cfg.terminate(imaginary_target, source_info, TerminatorKind::UnwindResume); - - // FIXME add to drop tree for loop_head - - let region_scope = scope.region_scope; - let scope_index = self.scopes.scope_index(region_scope, span); - let mut drops = DropTree::new(); - - let drop_idx = self.scopes.scopes[scope_index + 1..] - .iter() - .flat_map(|scope| &scope.drops) - .fold(ROOT_NODE, |drop_idx, &drop| drops.add_drop(drop, drop_idx)); - - drops.add_entry_point(drop_and_continue_block, drop_idx); - - // `build_drop_trees` doesn't have access to our source_info, so we - // create a dummy terminator now. `TerminatorKind::UnwindResume` is used - // because MIR type checking will panic if it hasn't been overwritten. - // (See `::link_entry_point`.) - self.cfg.terminate( - drop_and_continue_block, - source_info, - TerminatorKind::UnwindResume, - ); - - { - let this = &mut *self; - let blocks = drops.build_mir::(&mut this.cfg, Some(real_target)); - //let is_coroutine = this.coroutine.is_some(); - - /*// Link the exit drop tree to unwind drop tree. - if drops.drops.iter().any(|drop_node| drop_node.data.kind == DropKind::Value) { - let unwind_target = this.diverge_cleanup_target(region_scope, span); - let mut unwind_indices = IndexVec::from_elem_n(unwind_target, 1); - for (drop_idx, drop_node) in drops.drops.iter_enumerated().skip(1) { - match drop_node.data.kind { - DropKind::Storage | DropKind::ForLint => { - if is_coroutine { - let unwind_drop = this.scopes.unwind_drops.add_drop( - drop_node.data, - unwind_indices[drop_node.next], - ); - unwind_indices.push(unwind_drop); - } else { - unwind_indices.push(unwind_indices[drop_node.next]); - } - } - DropKind::Value => { - let unwind_drop = this - .scopes - .unwind_drops - .add_drop(drop_node.data, unwind_indices[drop_node.next]); - this.scopes.unwind_drops.add_entry_point( - blocks[drop_idx].unwrap(), - unwind_indices[drop_node.next], - ); - unwind_indices.push(unwind_drop); - } - } - } - }*/ - blocks[ROOT_NODE].map(BasicBlock::unit) - }; - - return self.cfg.start_new_block().unit(); - } }; match (destination, value) { @@ -972,6 +809,158 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.start_new_block().unit() } + /// Sets up the drops for jumping from `block` to `scope`. + pub(crate) fn break_const_continuable_scope( + &mut self, + mut block: BasicBlock, + value: ExprId, + scope: region::Scope, + source_info: SourceInfo, + ) -> BlockAnd<()> { + let span = source_info.span; + + // A break can only break out of a scope, so the value should be a scope. + let rustc_middle::thir::ExprKind::Scope { value, .. } = self.thir[value].kind else { + span_bug!(span, "break value must be a scope") + }; + + let break_index = self + .scopes + .const_continuable_scopes + .iter() + .rposition(|const_continuable_scope| const_continuable_scope.region_scope == scope) + .unwrap_or_else(|| span_bug!(span, "no enclosing const-continuable scope found")); + + let scope = &self.scopes.const_continuable_scopes[break_index]; + + let state_ty = self.local_decls[scope.state_place.as_local().unwrap()].ty; + let discriminant_ty = match state_ty { + ty if ty.is_enum() => ty.discriminant_ty(self.tcx), + ty if ty.is_integral() => ty, + _ => todo!(), + }; + + let rvalue = match state_ty { + ty if ty.is_enum() => Rvalue::Discriminant(scope.state_place), + ty if ty.is_integral() => Rvalue::Use(Operand::Copy(scope.state_place)), + _ => todo!(), + }; + + // the PatCtxt is normally used in pattern exhaustiveness checking, but reused here + // because it performs normalization and const evaluation. + let dropless_arena = rustc_arena::DroplessArena::default(); + let typeck_results = self.tcx.typeck(self.def_id); + let cx = RustcPatCtxt { + tcx: self.tcx, + typeck_results, + module: self.tcx.parent_module(self.hir_id).to_def_id(), + // FIXME(#132279): We're in a body, should handle opaques. + typing_env: rustc_middle::ty::TypingEnv::non_body_analysis(self.tcx, self.def_id), + dropless_arena: &dropless_arena, + match_lint_level: self.hir_id, + whole_match_span: Some(rustc_span::Span::default()), + scrut_span: rustc_span::Span::default(), + refutable: true, + known_valid_scrutinee: true, + }; + + let Some(real_target) = + self.static_pattern_match(&cx, value, &*scope.arms, &scope.built_match_tree) + else { + self.tcx.dcx().emit_fatal(ConstContinueUnknownJumpTarget { span }) + }; + + self.block_context.push(BlockFrame::SubExpr); + let state_place = scope.state_place; + block = self.expr_into_dest(state_place, block, value).into_block(); + self.block_context.pop(); + + let discr = self.temp(discriminant_ty, source_info.span); + let scope_index = self + .scopes + .scope_index(self.scopes.const_continuable_scopes[break_index].region_scope, span); + let scope = &mut self.scopes.const_continuable_scopes[break_index]; + self.cfg.push_assign(block, source_info, discr, rvalue); + let drop_and_continue_block = self.cfg.start_new_block(); + let imaginary_target = self.cfg.start_new_block(); + self.cfg.terminate( + block, + source_info, + TerminatorKind::FalseEdge { real_target: drop_and_continue_block, imaginary_target }, + ); + + let drops = &mut scope.break_drops; + + let drop_idx = self.scopes.scopes[scope_index + 1..] + .iter() + .flat_map(|scope| &scope.drops) + .fold(ROOT_NODE, |drop_idx, &drop| drops.add_drop(drop, drop_idx)); + + drops.add_entry_point(imaginary_target, drop_idx); + + self.cfg.terminate(imaginary_target, source_info, TerminatorKind::UnwindResume); + + // FIXME add to drop tree for loop_head + + let region_scope = scope.region_scope; + let scope_index = self.scopes.scope_index(region_scope, span); + let mut drops = DropTree::new(); + + let drop_idx = self.scopes.scopes[scope_index + 1..] + .iter() + .flat_map(|scope| &scope.drops) + .fold(ROOT_NODE, |drop_idx, &drop| drops.add_drop(drop, drop_idx)); + + drops.add_entry_point(drop_and_continue_block, drop_idx); + + // `build_drop_trees` doesn't have access to our source_info, so we + // create a dummy terminator now. `TerminatorKind::UnwindResume` is used + // because MIR type checking will panic if it hasn't been overwritten. + // (See `::link_entry_point`.) + self.cfg.terminate(drop_and_continue_block, source_info, TerminatorKind::UnwindResume); + + { + let this = &mut *self; + let blocks = drops.build_mir::(&mut this.cfg, Some(real_target)); + //let is_coroutine = this.coroutine.is_some(); + + /*// Link the exit drop tree to unwind drop tree. + if drops.drops.iter().any(|drop_node| drop_node.data.kind == DropKind::Value) { + let unwind_target = this.diverge_cleanup_target(region_scope, span); + let mut unwind_indices = IndexVec::from_elem_n(unwind_target, 1); + for (drop_idx, drop_node) in drops.drops.iter_enumerated().skip(1) { + match drop_node.data.kind { + DropKind::Storage | DropKind::ForLint => { + if is_coroutine { + let unwind_drop = this.scopes.unwind_drops.add_drop( + drop_node.data, + unwind_indices[drop_node.next], + ); + unwind_indices.push(unwind_drop); + } else { + unwind_indices.push(unwind_indices[drop_node.next]); + } + } + DropKind::Value => { + let unwind_drop = this + .scopes + .unwind_drops + .add_drop(drop_node.data, unwind_indices[drop_node.next]); + this.scopes.unwind_drops.add_entry_point( + blocks[drop_idx].unwrap(), + unwind_indices[drop_node.next], + ); + unwind_indices.push(unwind_drop); + } + } + } + }*/ + blocks[ROOT_NODE].map(BasicBlock::unit) + }; + + return self.cfg.start_new_block().unit(); + } + /// Sets up the drops for breaking from `block` due to an `if` condition /// that turned out to be false. /// From 5e59ca168548db3d4c64aa299393749c2d64e86a Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:16:05 +0200 Subject: [PATCH 13/35] Handle drop in unwind paths for #[const_continue] --- compiler/rustc_mir_build/src/builder/scope.rs | 39 +-------------- tests/ui/loop-match/unwind.rs | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 38 deletions(-) create mode 100644 tests/ui/loop-match/unwind.rs diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index 5e112b6969a3d..506e4b986524f 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -919,44 +919,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // (See `::link_entry_point`.) self.cfg.terminate(drop_and_continue_block, source_info, TerminatorKind::UnwindResume); - { - let this = &mut *self; - let blocks = drops.build_mir::(&mut this.cfg, Some(real_target)); - //let is_coroutine = this.coroutine.is_some(); - - /*// Link the exit drop tree to unwind drop tree. - if drops.drops.iter().any(|drop_node| drop_node.data.kind == DropKind::Value) { - let unwind_target = this.diverge_cleanup_target(region_scope, span); - let mut unwind_indices = IndexVec::from_elem_n(unwind_target, 1); - for (drop_idx, drop_node) in drops.drops.iter_enumerated().skip(1) { - match drop_node.data.kind { - DropKind::Storage | DropKind::ForLint => { - if is_coroutine { - let unwind_drop = this.scopes.unwind_drops.add_drop( - drop_node.data, - unwind_indices[drop_node.next], - ); - unwind_indices.push(unwind_drop); - } else { - unwind_indices.push(unwind_indices[drop_node.next]); - } - } - DropKind::Value => { - let unwind_drop = this - .scopes - .unwind_drops - .add_drop(drop_node.data, unwind_indices[drop_node.next]); - this.scopes.unwind_drops.add_entry_point( - blocks[drop_idx].unwrap(), - unwind_indices[drop_node.next], - ); - unwind_indices.push(unwind_drop); - } - } - } - }*/ - blocks[ROOT_NODE].map(BasicBlock::unit) - }; + self.build_exit_tree(drops, region_scope, span, Some(real_target)); return self.cfg.start_new_block().unit(); } diff --git a/tests/ui/loop-match/unwind.rs b/tests/ui/loop-match/unwind.rs new file mode 100644 index 0000000000000..f37a933805cc0 --- /dev/null +++ b/tests/ui/loop-match/unwind.rs @@ -0,0 +1,49 @@ +// Test that #[const_continue] correctly emits cleanup paths for drops. + +//@ run-pass +//@ needs-unwind + +#![allow(incomplete_features)] +#![feature(loop_match)] + +enum State { + A, + B, +} + +struct ExitOnDrop; + +impl Drop for ExitOnDrop { + fn drop(&mut self) { + std::process::exit(0); + } +} + +struct DropBomb; + +impl Drop for DropBomb { + fn drop(&mut self) { + panic!("this must unwind"); + } +} + +fn main() { + let mut state = State::A; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + State::A => { + let _exit = ExitOnDrop; + let _bomb = DropBomb; + + #[const_continue] + break 'blk State::B; + } + State::B => break 'a, + } + }; + } + + unreachable!(); +} From e19b5f1ee7dcf3729969bb9fe64f817dfe9ee1d1 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 28 Mar 2025 15:32:38 +0100 Subject: [PATCH 14/35] fix comment --- compiler/rustc_mir_build/src/builder/expr/into.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 20b67fcb03401..31a1ec27abfe1 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -271,7 +271,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); this.diverge_from(loop_block); - // logic for `match` + // 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; From 3833aed8c0cc6fa9519dc8369d1d61ae5de20075 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 28 Mar 2025 16:59:59 +0100 Subject: [PATCH 15/35] emit error when #[const_continue] jumps to an invalid label --- compiler/rustc_passes/messages.ftl | 12 +++ compiler/rustc_passes/src/errors.rs | 28 ++++++ compiler/rustc_passes/src/loops.rs | 89 +++++++++++++++++-- .../ui/loop-match/const-continue-to-block.rs | 23 +++++ .../loop-match/const-continue-to-block.stderr | 8 ++ tests/ui/loop-match/const-continue-to-loop.rs | 23 +++++ .../loop-match/const-continue-to-loop.stderr | 8 ++ 7 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 tests/ui/loop-match/const-continue-to-block.rs create mode 100644 tests/ui/loop-match/const-continue-to-block.stderr create mode 100644 tests/ui/loop-match/const-continue-to-loop.rs create mode 100644 tests/ui/loop-match/const-continue-to-loop.stderr diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 8e40040fdeac5..ed008d21e9a8e 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -103,6 +103,9 @@ passes_const_continue_attr = `#[const_continue]` should be applied to a break expression .label = not a break expression +passes_const_continue_bad_label = + `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]` + passes_const_stable_not_stable = attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]` .label = attribute specified here @@ -470,6 +473,15 @@ passes_loop_match_attr = `#[loop_match]` should be applied to a loop .label = not a loop +passes_loop_match_bad_rhs = + this expression must be a single `match` wrapped in a labeled block + +passes_loop_match_bad_statements = + statements are not allowed in this position within a `#[loop_match]` + +passes_loop_match_missing_assignment = + expected a single assignment expression + passes_macro_export = `#[macro_export]` only has an effect on macro definitions diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 3455818ff61ee..6a90adb71cfb0 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -50,6 +50,13 @@ pub(crate) struct ConstContinueAttr { pub node_span: Span, } +#[derive(Diagnostic)] +#[diag(passes_const_continue_bad_label)] +pub(crate) struct ConstContinueBadLabel { + #[primary_span] + pub span: Span, +} + #[derive(LintDiagnostic)] #[diag(passes_outer_crate_level_attr)] pub(crate) struct OuterCrateLevelAttr; @@ -1215,6 +1222,27 @@ pub(crate) struct UnlabeledCfInWhileCondition<'a> { pub cf_type: &'a str, } +#[derive(Diagnostic)] +#[diag(passes_loop_match_bad_statements)] +pub(crate) struct LoopMatchBadStatements { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(passes_loop_match_bad_rhs)] +pub(crate) struct LoopMatchBadRhs { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(passes_loop_match_missing_assignment)] +pub(crate) struct LoopMatchMissingAssignment { + #[primary_span] + pub span: Span, +} + #[derive(LintDiagnostic)] #[diag(passes_undefined_naked_function_abi)] pub(crate) struct UndefinedNakedFunctionAbi; diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs index b06f16cc7bd2f..7e749aba429a4 100644 --- a/compiler/rustc_passes/src/loops.rs +++ b/compiler/rustc_passes/src/loops.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::fmt; use Context::*; +use rustc_ast::Label; use rustc_hir as hir; use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::intravisit::{self, Visitor}; @@ -11,11 +12,12 @@ use rustc_middle::query::Providers; use rustc_middle::span_bug; use rustc_middle::ty::TyCtxt; use rustc_span::hygiene::DesugaringKind; -use rustc_span::{BytePos, Span}; +use rustc_span::{BytePos, Span, sym}; use crate::errors::{ - BreakInsideClosure, BreakInsideCoroutine, BreakNonLoop, ContinueLabeledBlock, OutsideLoop, - OutsideLoopSuggestion, UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock, + BreakInsideClosure, BreakInsideCoroutine, BreakNonLoop, ConstContinueBadLabel, + ContinueLabeledBlock, LoopMatchBadRhs, LoopMatchBadStatements, LoopMatchMissingAssignment, + OutsideLoop, OutsideLoopSuggestion, UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock, }; /// The context in which a block is encountered. @@ -37,6 +39,11 @@ enum Context { AnonConst, /// E.g. `const { ... }`. ConstBlock, + /// #[loop_match] loop { state = 'label: { /* ... */ } } + LoopMatch { + /// The label of the labeled block (so not the loop!) + labeled_block: Label, + }, } #[derive(Clone)] @@ -160,7 +167,12 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> { } } hir::ExprKind::Loop(ref b, _, source, _) => { - self.with_context(Loop(source), |v| v.visit_block(b)); + let cx = match self.is_loop_match(e, b) { + Some(labeled_block) => LoopMatch { labeled_block }, + None => Loop(source), + }; + + self.with_context(cx, |v| v.visit_block(b)); } hir::ExprKind::Closure(&hir::Closure { ref fn_decl, body, fn_decl_span, kind, .. @@ -216,6 +228,22 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> { Err(hir::LoopIdError::UnresolvedLabel) => None, }; + // a #[const_continue] must be to a block that participates in #[loop_match] + let attrs = self.tcx.hir_attrs(e.hir_id); + if attrs.iter().any(|attr| attr.has_name(sym::const_continue)) { + if let Some(break_label) = break_label.label { + let is_target_label = |cx: &Context| match cx { + Context::LoopMatch { labeled_block } => break_label == *labeled_block, + _ => false, + }; + + if !self.cx_stack.iter().rev().any(is_target_label) { + let span = break_label.ident.span; + self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span }); + } + } + } + if let Some(Node::Block(_)) = loop_id.map(|id| self.tcx.hir_node(id)) { return; } @@ -318,7 +346,7 @@ impl<'hir> CheckLoopVisitor<'hir> { cx_pos: usize, ) { match self.cx_stack[cx_pos] { - LabeledBlock | Loop(_) => {} + LabeledBlock | Loop(_) | LoopMatch { .. } => {} Closure(closure_span) => { self.tcx.dcx().emit_err(BreakInsideClosure { span, @@ -399,4 +427,55 @@ impl<'hir> CheckLoopVisitor<'hir> { }); } } + + /// Is this a loop annotated with #[loop_match] that looks syntactically sound? + fn is_loop_match( + &self, + e: &'hir hir::Expr<'hir>, + body: &'hir hir::Block<'hir>, + ) -> Option