diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 1e33e2e9393f7..1308b08bcfcff 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -583,6 +583,19 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ EncodeCrossCrate::Yes, min_generic_const_args, experimental!(type_const), ), + // The `#[loop_match]` and `#[const_continue]` attributes are part of the + // lang experiment for RFC 3720 tracked in: + // + // - https://github.com/rust-lang/rust/issues/132306 + 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..2562dd588b906 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -545,6 +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)), + /// Allows fused `loop`/`match` for direct intraprocedural jumps. + (incomplete, loop_match, "CURRENT_RUSTC_VERSION", Some(132306)), /// 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..3a16084b16096 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -375,6 +375,14 @@ pub enum ExprKind<'tcx> { Loop { body: ExprId, }, + /// 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 `&&`). /// @@ -451,6 +459,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<ExprId>, diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index 7d62ab7970d01..e477a69386bdb 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -79,7 +79,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( visitor.visit_pat(pat); } Loop { body } => visitor.visit_expr(&visitor.thir()[body]), - Match { scrutinee, ref arms, .. } => { + LoopMatch { state: scrutinee, ref arms, .. } | Match { scrutinee, ref arms, .. } => { visitor.visit_expr(&visitor.thir()[scrutinee]); for &arm in &**arms { visitor.visit_arm(&visitor.thir()[arm]); @@ -104,6 +104,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..e339520cd86b8 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -84,6 +84,15 @@ 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_bad_const = could not determine the target branch for this `#[const_continue]` + .label = this value is too generic + .note = the value must be a literal or a monomorphic const + +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 + .label = this value must be a literal or a monomorphic const + mir_build_const_defined_here = constant defined here mir_build_const_param_in_pattern = constant parameters cannot be referenced in patterns @@ -212,6 +221,30 @@ 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]` + +mir_build_loop_match_invalid_match = + invalid match on `#[loop_match]` state + .note = a local variable must be the scrutinee within a `#[loop_match]` + +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_loop_match_unsupported_type = + this `#[loop_match]` state value has type `{$ty}`, which is not supported + .note = only integers, floats, bool, char, and enums without fields are supported + 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..52886994b75a6 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -8,15 +8,16 @@ use rustc_hir::lang_items::LangItem; use rustc_middle::mir::*; use rustc_middle::span_bug; use rustc_middle::thir::*; -use rustc_middle::ty::{CanonicalUserTypeAnnotation, Ty}; +use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty}; 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}; +use crate::errors::{LoopMatchArmWithGuard, LoopMatchUnsupportedType}; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Compile `expr`, storing the result into `destination`, which @@ -244,6 +245,122 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { None }) } + ExprKind::LoopMatch { state, region_scope, match_span, ref arms } => { + // 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]`. + + fn is_supported_loop_match_type(ty: Ty<'_>) -> bool { + match ty.kind() { + ty::Uint(_) | ty::Int(_) | ty::Float(_) | ty::Bool | ty::Char => true, + ty::Adt(adt_def, _) => match adt_def.adt_kind() { + ty::AdtKind::Struct | ty::AdtKind::Union => false, + ty::AdtKind::Enum => { + adt_def.variants().iter().all(|v| v.fields.is_empty()) + } + }, + _ => false, + } + } + + let state_ty = this.thir.exprs[state].ty; + if !is_supported_loop_match_type(state_ty) { + let span = this.thir.exprs[state].span; + this.tcx.dcx().emit_fatal(LoopMatchUnsupportedType { span, ty: state_ty }) + } + + let loop_block = this.cfg.start_new_block(); + + // Start the loop. + 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, + source_info, + TerminatorKind::FalseUnwind { + real_target: body_block, + unwind: UnwindAction::Continue, + }, + ); + 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; + 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() { + 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)); + } + + // 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, + &scrutinee_place_builder, + match_start_span, + patterns, + false, + ); + + 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`. + // + // Then `in_const_continuable_scope` stores information for the lowering of + // `#[const_continue]`, and finally the match is lowered in the standard way. + unpack!( + body_block = this.in_scope( + (region_scope, source_info), + LintLevel::Inherited, + move |this| { + this.in_breakable_scope(None, state_place, expr_span, |this| { + Some(this.in_const_continuable_scope( + arms.clone(), + built_tree.clone(), + state_place, + expr_span, + |this| { + this.lower_match_arms( + destination, + scrutinee_place_builder, + scrutinee_span, + arms, + built_tree, + this.source_info(match_span), + ) + }, + )) + }) + } + ) + ); + + 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 +718,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } ExprKind::Continue { .. } + | ExprKind::ConstContinue { .. } | ExprKind::Break { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } => { diff --git a/compiler/rustc_mir_build/src/builder/expr/stmt.rs b/compiler/rustc_mir_build/src/builder/expr/stmt.rs index 7f8a0a34c3123..3209c8da3a4ff 100644 --- a/compiler/rustc_mir_build/src/builder/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/builder/expr/stmt.rs @@ -92,6 +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_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/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 3acf2a6a2a61a..42c376ead6e7a 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -18,7 +18,9 @@ use rustc_middle::bug; use rustc_middle::middle::region; use rustc_middle::mir::{self, *}; use rustc_middle::thir::{self, *}; -use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty}; +use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty, ValTree, ValTreeKind}; +use rustc_pattern_analysis::constructor::RangeEnd; +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<MatchTreeSubBranch<'tcx>>, } @@ -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<MatchTreeBranch<'tcx>>, 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, @@ -1889,7 +1891,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { debug!("expanding or-pattern: candidate={:#?}\npats={:#?}", candidate, pats); candidate.or_span = Some(match_pair.pattern_span); candidate.subcandidates = pats - .into_vec() .into_iter() .map(|flat_pat| Candidate::from_flat_pat(flat_pat, candidate.has_guard)) .collect(); @@ -2863,4 +2864,128 @@ 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>, + valtree: ValTree<'tcx>, + arms: &[ArmId], + built_match_tree: &BuiltMatchTree<'tcx>, + ) -> Option<BasicBlock> { + 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); + + // Peel off or-patterns if they exist. + if let rustc_pattern_analysis::rustc::Constructor::Or = pat.ctor() { + for pat in pat.iter_fields() { + // For top-level or-patterns (the only ones we accept right now), when the + // bindings are the same (e.g. there are none), the sub_branch is stored just + // once. + let sub_branch = branch + .sub_branches + .get(pat.idx) + .or_else(|| branch.sub_branches.last()) + .unwrap(); + + match self.static_pattern_match_inner(valtree, &pat.pat) { + true => return Some(sub_branch.success_block), + false => continue, + } + } + } else if self.static_pattern_match_inner(valtree, &pat) { + return Some(branch.sub_branches[0].success_block); + } + } + + None + } + + /// Helper for [`Self::static_pattern_match`], checking whether the value represented by the + /// `ValTree` matches the given pattern. This function does not recurse, meaning that it does + /// not handle or-patterns, or patterns for types with fields. + fn static_pattern_match_inner( + &self, + valtree: ty::ValTree<'tcx>, + pat: &DeconstructedPat<'_, 'tcx>, + ) -> bool { + use rustc_pattern_analysis::constructor::{IntRange, MaybeInfiniteInt}; + use rustc_pattern_analysis::rustc::Constructor; + + match pat.ctor() { + Constructor::Variant(variant_index) => { + let ValTreeKind::Branch(box [actual_variant_idx]) = *valtree else { + bug!("malformed valtree for an enum") + }; + + let ValTreeKind::Leaf(actual_variant_idx) = ***actual_variant_idx else { + bug!("malformed valtree for an enum") + }; + + *variant_index == VariantIdx::from_u32(actual_variant_idx.to_u32()) + } + Constructor::IntRange(int_range) => { + let size = pat.ty().primitive_size(self.tcx); + let actual_int = valtree.unwrap_leaf().to_bits(size); + let actual_int = if pat.ty().is_signed() { + MaybeInfiniteInt::new_finite_int(actual_int, size.bits()) + } else { + MaybeInfiniteInt::new_finite_uint(actual_int) + }; + IntRange::from_singleton(actual_int).is_subrange(int_range) + } + Constructor::Bool(pattern_value) => match valtree.unwrap_leaf().try_to_bool() { + Ok(actual_value) => *pattern_value == actual_value, + Err(()) => bug!("bool value with invalid bits"), + }, + Constructor::F16Range(l, h, end) => { + let actual = valtree.unwrap_leaf().to_f16(); + match end { + RangeEnd::Included => (*l..=*h).contains(&actual), + RangeEnd::Excluded => (*l..*h).contains(&actual), + } + } + Constructor::F32Range(l, h, end) => { + let actual = valtree.unwrap_leaf().to_f32(); + match end { + RangeEnd::Included => (*l..=*h).contains(&actual), + RangeEnd::Excluded => (*l..*h).contains(&actual), + } + } + Constructor::F64Range(l, h, end) => { + let actual = valtree.unwrap_leaf().to_f64(); + match end { + RangeEnd::Included => (*l..=*h).contains(&actual), + RangeEnd::Excluded => (*l..*h).contains(&actual), + } + } + Constructor::F128Range(l, h, end) => { + let actual = valtree.unwrap_leaf().to_f128(); + match end { + RangeEnd::Included => (*l..=*h).contains(&actual), + RangeEnd::Excluded => (*l..*h).contains(&actual), + } + } + Constructor::Wildcard => true, + + // These we may eventually support: + Constructor::Struct + | Constructor::Ref + | Constructor::Slice(_) + | Constructor::UnionField + | Constructor::Or + | Constructor::Str(_) => bug!("unsupported pattern constructor {:?}", pat.ctor()), + + // These should never occur here: + Constructor::Opaque(_) + | Constructor::Never + | Constructor::NonExhaustive + | Constructor::Hidden + | Constructor::Missing + | Constructor::PrivateUninhabited => { + bug!("unsupported pattern constructor {:?}", pat.ctor()) + } + } + } } diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index e42336a1dbbcc..2a2585b342c63 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -83,19 +83,24 @@ that contains only loops and breakable blocks. It tracks where a `break`, use std::mem; +use interpret::ErrorHandled; 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::{bug, span_bug}; +use rustc_middle::mir::{self, *}; +use rustc_middle::thir::{AdtExpr, AdtExprBase, ArmId, ExprId, ExprKind, LintLevel}; +use rustc_middle::ty::{Ty, TypeVisitableExt, ValTree}; +use rustc_middle::{bug, span_bug, ty}; +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}; +use crate::errors::{ConstContinueBadConst, ConstContinueUnknownJumpTarget}; #[derive(Debug)] pub(crate) struct Scopes<'tcx> { @@ -104,6 +109,8 @@ pub(crate) struct Scopes<'tcx> { /// The current set of breakable scopes. See module comment for more details. breakable_scopes: Vec<BreakableScope<'tcx>>, + const_continuable_scopes: Vec<ConstContinuableScope<'tcx>>, + /// The scope of the innermost if-then currently being lowered. if_then_scope: Option<IfThenScope>, @@ -173,6 +180,20 @@ struct BreakableScope<'tcx> { continue_drops: Option<DropTree>, } +#[derive(Debug)] +struct ConstContinuableScope<'tcx> { + /// The scope for the `#[loop_match]` which its `#[const_continue]`s will jump to. + region_scope: region::Scope, + /// The place of the state of a `#[loop_match]`, which a `#[const_continue]` must update. + state_place: Place<'tcx>, + + arms: Box<[ArmId]>, + built_match_tree: BuiltMatchTree<'tcx>, + + /// Drops that happen on a `#[const_continue]` + const_continue_drops: DropTree, +} + #[derive(Debug)] struct IfThenScope { /// The if-then scope or arm scope @@ -459,6 +480,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 +572,59 @@ 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<F>( + &mut self, + arms: Box<[ArmId]>, + built_match_tree: BuiltMatchTree<'tcx>, + state_place: Place<'tcx>, + span: Span, + f: F, + ) -> BlockAnd<()> + where + F: FnOnce(&mut Builder<'a, 'tcx>) -> BlockAnd<()>, + { + let region_scope = self.scopes.topmost(); + let scope = ConstContinuableScope { + region_scope, + state_place, + const_continue_drops: DropTree::new(), + arms, + built_match_tree, + }; + self.scopes.const_continuable_scopes.push(scope); + let normal_exit_block = f(self); + let const_continue_scope = self.scopes.const_continuable_scopes.pop().unwrap(); + assert!(const_continue_scope.region_scope == region_scope); + + let break_block = self.build_exit_tree( + const_continue_scope.const_continue_drops, + region_scope, + span, + None, + ); + + 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() + } + } + } + /// Start an if-then scope which tracks drop for `if` expressions and `if` /// guards. /// @@ -739,6 +814,190 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.start_new_block().unit() } + /// Based on `FunctionCx::eval_unevaluated_mir_constant_to_valtree`. + fn eval_unevaluated_mir_constant_to_valtree( + &self, + constant: ConstOperand<'tcx>, + ) -> Result<(ty::ValTree<'tcx>, Ty<'tcx>), interpret::ErrorHandled> { + assert!(!constant.const_.ty().has_param()); + let (uv, ty) = match constant.const_ { + mir::Const::Unevaluated(uv, ty) => (uv.shrink(), ty), + mir::Const::Ty(_, c) => match c.kind() { + // A constant that came from a const generic but was then used as an argument to + // old-style simd_shuffle (passing as argument instead of as a generic param). + ty::ConstKind::Value(cv) => return Ok((cv.valtree, cv.ty)), + other => span_bug!(constant.span, "{other:#?}"), + }, + mir::Const::Val(mir::ConstValue::Scalar(mir::interpret::Scalar::Int(val)), ty) => { + return Ok((ValTree::from_scalar_int(self.tcx, val), ty)); + } + // We should never encounter `Const::Val` unless MIR opts (like const prop) evaluate + // a constant and write that value back into `Operand`s. This could happen, but is + // unlikely. Also: all users of `simd_shuffle` are on unstable and already need to take + // a lot of care around intrinsics. For an issue to happen here, it would require a + // macro expanding to a `simd_shuffle` call without wrapping the constant argument in a + // `const {}` block, but the user pass through arbitrary expressions. + + // FIXME(oli-obk): Replace the magic const generic argument of `simd_shuffle` with a + // real const generic, and get rid of this entire function. + other => span_bug!(constant.span, "{other:#?}"), + }; + + match self.tcx.const_eval_resolve_for_typeck(self.typing_env(), uv, constant.span) { + Ok(Ok(valtree)) => Ok((valtree, ty)), + Ok(Err(ty)) => span_bug!(constant.span, "could not convert {ty:?} to a valtree"), + Err(e) => Err(e), + } + } + + /// 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 constant = match &self.thir[value].kind { + ExprKind::Adt(box AdtExpr { variant_index, fields, base, .. }) => { + assert!(matches!(base, AdtExprBase::None)); + assert!(fields.is_empty()); + ConstOperand { + span: self.thir[value].span, + user_ty: None, + const_: Const::Ty( + self.thir[value].ty, + ty::Const::new_value( + self.tcx, + ValTree::from_branches( + self.tcx, + [ValTree::from_scalar_int(self.tcx, variant_index.as_u32().into())], + ), + self.thir[value].ty, + ), + ), + } + } + _ => self.as_constant(&self.thir[value]), + }; + + 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_decl = &self.local_decls[scope.state_place.as_local().unwrap()]; + let state_ty = state_decl.ty; + let (discriminant_ty, rvalue) = match state_ty.kind() { + ty::Adt(adt_def, _) if adt_def.is_enum() => { + (state_ty.discriminant_ty(self.tcx), Rvalue::Discriminant(scope.state_place)) + } + ty::Uint(_) | ty::Int(_) | ty::Float(_) | ty::Bool | ty::Char => { + (state_ty, Rvalue::Use(Operand::Copy(scope.state_place))) + } + _ => span_bug!(state_decl.source_info.span, "unsupported #[loop_match] state"), + }; + + // 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 valtree = match self.eval_unevaluated_mir_constant_to_valtree(constant) { + Ok((valtree, ty)) => { + // Defensively check that the type is monomorphic. + assert!(!ty.has_param()); + + valtree + } + Err(ErrorHandled::Reported(..)) => return self.cfg.start_new_block().unit(), + Err(ErrorHandled::TooGeneric(_)) => { + self.tcx.dcx().emit_fatal(ConstContinueBadConst { span: constant.span }); + } + }; + + let Some(real_target) = + self.static_pattern_match(&cx, valtree, &*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.const_continue_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); + + 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 `<ExitScopes as DropTreeBuilder>::link_entry_point`.) + self.cfg.terminate(drop_and_continue_block, source_info, TerminatorKind::UnwindResume); + + self.build_exit_tree(drops, region_scope, span, Some(real_target)); + + 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. /// 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..b24f183f38a4f 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -1161,3 +1161,80 @@ 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_unsupported_type)] +#[note] +pub(crate) struct LoopMatchUnsupportedType<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, +} + +#[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_loop_match_arm_with_guard)] +pub(crate) struct LoopMatchArmWithGuard { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(mir_build_const_continue_bad_const)] +pub(crate) struct ConstContinueBadConst { + #[primary_span] + #[label] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(mir_build_const_continue_missing_value)] +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/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b8af77245f25d..b50997f9781c9 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,95 @@ 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<hir::HirId> { + 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(), + match_span: block_body_expr.span, + } + } 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..e25014b6c9636 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -319,6 +319,20 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_expr(*body, depth_lvl + 2); print_indented!(self, ")", depth_lvl); } + LoopMatch { state, region_scope, match_span, arms } => { + print_indented!(self, "LoopMatch {", depth_lvl); + print_indented!(self, "state:", depth_lvl + 1); + self.print_expr(*state, depth_lvl + 2); + print_indented!(self, format!("region_scope: {:?}", region_scope), depth_lvl + 1); + print_indented!(self, format!("match_span: {:?}", match_span), depth_lvl + 1); + + print_indented!(self, "arms: [", depth_lvl + 1); + for arm_id in arms.iter() { + self.print_arm(*arm_id, depth_lvl + 2); + } + print_indented!(self, "]", 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 +430,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..8af4aa28354a9 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -99,6 +99,13 @@ 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_continue_bad_label = + `#[const_continue]` must break to a labeled block 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 @@ -462,6 +469,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..aebd05154316c 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..b9781c7a700fe 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -32,6 +32,31 @@ 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(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; diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs index b06f16cc7bd2f..6a2f6c1218cd2 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, OutsideLoop, OutsideLoopSuggestion, UnlabeledCfInWhileCondition, + UnlabeledInLabeledBlock, }; /// The context in which a block is encountered. @@ -37,6 +39,11 @@ enum Context { AnonConst, /// E.g. `const { ... }`. ConstBlock, + /// E.g. `#[loop_match] loop { state = 'label: { /* ... */ } }`. + LoopMatch { + /// The label of the labeled block (not of the loop itself). + 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 break to a block in a `#[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,36 @@ 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<Label> { + if !self.tcx.hir_attrs(e.hir_id).iter().any(|attr| attr.has_name(sym::loop_match)) { + return None; + } + + // NOTE: Diagnostics are emitted during MIR construction. + + // Accept either `state = expr` or `state = expr;`. + let loop_body_expr = match body.stmts { + [] => match body.expr { + Some(expr) => expr, + None => return None, + }, + [single] if body.expr.is_none() => match single.kind { + hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => expr, + _ => return None, + }, + [..] => return None, + }; + + let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else { return None }; + + let hir::ExprKind::Block(_, label) = rhs_expr.kind else { return None }; + + label + } } 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/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..399b20234f32e --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-loop-match.rs @@ -0,0 +1,30 @@ +// Test that `#[loop_match]` and `#[const_continue]` cannot be used without +// `#![feature(loop_match)]`. + +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..9b12047cf4dde --- /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:12:5 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ + | + = note: see issue #132306 <https://github.com/rust-lang/rust/issues/132306> 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:17:21 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #132306 <https://github.com/rust-lang/rust/issues/132306> 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:22:21 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #132306 <https://github.com/rust-lang/rust/issues/132306> 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..e7451a944c391 --- /dev/null +++ b/tests/ui/loop-match/break-to-block.rs @@ -0,0 +1,23 @@ +// Test that a `break` without `#[const_continue]` still works as expected. + +//@ run-pass + +#![allow(incomplete_features)] +#![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/const-continue-to-block.rs b/tests/ui/loop-match/const-continue-to-block.rs new file mode 100644 index 0000000000000..9273292c81ee6 --- /dev/null +++ b/tests/ui/loop-match/const-continue-to-block.rs @@ -0,0 +1,26 @@ +// Test that a `#[const_continue]` that breaks to a normal labeled block (that +// is not part of a `#[loop_match]`) produces an error. + +#![allow(incomplete_features)] +#![feature(loop_match)] +#![crate_type = "lib"] + +fn const_continue_to_block() -> u8 { + let state = 0; + #[loop_match] + loop { + state = 'blk: { + match state { + 0 => { + #[const_continue] + break 'blk 1; + } + _ => 'b: { + #[const_continue] + break 'b 2; + //~^ ERROR `#[const_continue]` must break to a labeled block in a `#[loop_match]` + } + } + } + } +} diff --git a/tests/ui/loop-match/const-continue-to-block.stderr b/tests/ui/loop-match/const-continue-to-block.stderr new file mode 100644 index 0000000000000..10b101107570c --- /dev/null +++ b/tests/ui/loop-match/const-continue-to-block.stderr @@ -0,0 +1,8 @@ +error: `#[const_continue]` must break to a labeled block in a `#[loop_match]` + --> $DIR/const-continue-to-block.rs:20:27 + | +LL | break 'b 2; + | ^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/loop-match/const-continue-to-loop.rs b/tests/ui/loop-match/const-continue-to-loop.rs new file mode 100644 index 0000000000000..80d483d95fbbc --- /dev/null +++ b/tests/ui/loop-match/const-continue-to-loop.rs @@ -0,0 +1,27 @@ +// Test that a `#[const_continue]` that breaks to the label of the loop itself +// rather than to the label of the block within the `#[loop_match]` produces an +// error. + +#![allow(incomplete_features)] +#![feature(loop_match)] +#![crate_type = "lib"] + +fn const_continue_to_loop() -> u8 { + let state = 0; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + 0 => { + #[const_continue] + break 'blk 1; + } + _ => { + #[const_continue] + break 'a 2; + //~^ ERROR `#[const_continue]` must break to a labeled block in a `#[loop_match]` + } + } + } + } +} diff --git a/tests/ui/loop-match/const-continue-to-loop.stderr b/tests/ui/loop-match/const-continue-to-loop.stderr new file mode 100644 index 0000000000000..ecd5d001e62c3 --- /dev/null +++ b/tests/ui/loop-match/const-continue-to-loop.stderr @@ -0,0 +1,8 @@ +error: `#[const_continue]` must break to a labeled block in a `#[loop_match]` + --> $DIR/const-continue-to-loop.rs:21:27 + | +LL | break 'a 2; + | ^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/loop-match/const-continue-to-polymorphic-const.rs b/tests/ui/loop-match/const-continue-to-polymorphic-const.rs new file mode 100644 index 0000000000000..ee0d87517c843 --- /dev/null +++ b/tests/ui/loop-match/const-continue-to-polymorphic-const.rs @@ -0,0 +1,29 @@ +// Test that a `#[const_continue]` that breaks on a polymorphic constant produces an error. +// A polymorphic constant does not have a concrete value at MIR building time, and therefore the +// `#[loop_match]~ desugaring can't handle such values. +#![allow(incomplete_features)] +#![feature(loop_match)] +#![crate_type = "lib"] + +trait Foo { + const Target: u8; + + fn test_u8(mut state: u8) -> &'static str { + #[loop_match] + loop { + state = 'blk: { + match state { + 0 => { + #[const_continue] + break 'blk Self::Target; + //~^ ERROR could not determine the target branch for this `#[const_continue]` + } + + 1 => return "bar", + 2 => return "baz", + _ => unreachable!(), + } + } + } + } +} diff --git a/tests/ui/loop-match/const-continue-to-polymorphic-const.stderr b/tests/ui/loop-match/const-continue-to-polymorphic-const.stderr new file mode 100644 index 0000000000000..a2bac7acafd30 --- /dev/null +++ b/tests/ui/loop-match/const-continue-to-polymorphic-const.stderr @@ -0,0 +1,8 @@ +error: could not determine the target branch for this `#[const_continue]` + --> $DIR/const-continue-to-polymorphic-const.rs:18:36 + | +LL | break 'blk Self::Target; + | ^^^^^^^^^^^^ this value is too generic + +error: aborting due to 1 previous error + 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..731af6590129a --- /dev/null +++ b/tests/ui/loop-match/drop-in-match-arm.rs @@ -0,0 +1,47 @@ +// 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)] + +use std::sync::atomic::{AtomicBool, Ordering}; + +fn main() { + assert_eq!(helper(), 1); + assert!(DROPPED.load(Ordering::Relaxed)); +} + +static DROPPED: AtomicBool = AtomicBool::new(false); + +struct X; + +impl Drop for X { + fn drop(&mut self) { + DROPPED.store(true, Ordering::Relaxed); + } +} + +#[no_mangle] +#[inline(never)] +fn helper() -> i32 { + let mut state = 0; + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + 0 => match X { + _ => { + assert!(!DROPPED.load(Ordering::Relaxed)); + break 'blk 1; + } + }, + _ => { + assert!(DROPPED.load(Ordering::Relaxed)); + break 'a state; + } + } + }; + } +} diff --git a/tests/ui/loop-match/invalid-attribute.rs b/tests/ui/loop-match/invalid-attribute.rs new file mode 100644 index 0000000000000..d8d2f605eb496 --- /dev/null +++ b/tests/ui/loop-match/invalid-attribute.rs @@ -0,0 +1,43 @@ +// Test that the `#[loop_match]` and `#[const_continue]` attributes can only be +// placed on expressions. + +#![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 + +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..75d0695b05e63 --- /dev/null +++ b/tests/ui/loop-match/invalid-attribute.stderr @@ -0,0 +1,131 @@ +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:15:1 + | +LL | #[loop_match] + | ^^^^^^^^^^^^^ +... +LL | struct S { + | -------- not a loop + +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:16: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:29: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:30: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:34: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:35:5 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | || {}; + | -- not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:39: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:40:9 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | 5 + | - not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:24: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:25: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:10: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:11:5 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | fn f(); + | ------- not a break expression + +error: `#[loop_match]` should be applied to a loop + --> $DIR/invalid-attribute.rs:6:1 + | +LL | / #![allow(incomplete_features)] +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:7:1 + | +LL | / #![allow(incomplete_features)] +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..5a9c3a71c8086 --- /dev/null +++ b/tests/ui/loop-match/invalid.rs @@ -0,0 +1,163 @@ +// Test that the correct error is emitted when `#[loop_match]` is applied to +// syntax it does not support. +#![allow(incomplete_features)] +#![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 labeled 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 labeled block + } + } +} + +fn bad_rhs_3() { + let state = (); + #[loop_match] + loop { + state = 'blk: { + //~^ ERROR this expression must be a single `match` wrapped in a labeled 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 + } + } + } + } +} + +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 new file mode 100644 index 0000000000000..c32e5c1b034bb --- /dev/null +++ b/tests/ui/loop-match/invalid.stderr @@ -0,0 +1,91 @@ +error[E0308]: mismatched types + --> $DIR/invalid.rs:122: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:18:9 + | +LL | fake = 'blk: { + | ^^^^ +LL | +LL | match state { + | ----- the assignment must update this variable + +error: invalid match on `#[loop_match]` state + --> $DIR/invalid.rs:32:19 + | +LL | match State::A { + | ^^^^^^^^ + | + = note: a local variable must be the scrutinee within a `#[loop_match]` + +error: statements are not allowed in this position within a `#[loop_match]` + --> $DIR/invalid.rs:44:9 + | +LL | 1; + | ^^ + +error: statements are not allowed in this position within a `#[loop_match]` + --> $DIR/invalid.rs:59:13 + | +LL | 1; + | ^^ + +error: this expression must be a single `match` wrapped in a labeled block + --> $DIR/invalid.rs:72:17 + | +LL | state = State::B + | ^^^^^^^^ + +error: this expression must be a single `match` wrapped in a labeled block + --> $DIR/invalid.rs:82:13 + | +LL | State::B + | ^^^^^^^^ + +error: this expression must be a single `match` wrapped in a labeled block + --> $DIR/invalid.rs:92:17 + | +LL | state = 'blk: { + | _________________^ +LL | | +LL | | } + | |_________^ + +error: expected a single assignment expression + --> $DIR/invalid.rs:102:9 + | +LL | () + | ^^ + +error: expected a single assignment expression + --> $DIR/invalid.rs:109:10 + | +LL | loop { + | __________^ +LL | | +LL | | } + | |_____^ + +error: a `#[const_continue]` must break to a label with a value + --> $DIR/invalid.rs:139:21 + | +LL | break 'blk; + | ^^^^^^^^^^ + +error: match arms that are part of a `#[loop_match]` cannot have guards + --> $DIR/invalid.rs:157: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`. diff --git a/tests/ui/loop-match/loop-match.rs b/tests/ui/loop-match/loop-match.rs new file mode 100644 index 0000000000000..f38bc01f3338d --- /dev/null +++ b/tests/ui/loop-match/loop-match.rs @@ -0,0 +1,45 @@ +// Test that a basic correct example of `#[loop_match]` with `#[const_continue]` +// works correctly. + +//@ run-pass + +#![allow(incomplete_features)] +#![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 => { + // Without special logic, the compiler believes this is a + // reassignment 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; + } else { + #[const_continue] + break 'blk State::A; + } + } + State::C => break 'a, + } + }; + } + + assert!(matches!(state, State::C)) +} diff --git a/tests/ui/loop-match/nested.rs b/tests/ui/loop-match/nested.rs new file mode 100644 index 0000000000000..aaddfae11defa --- /dev/null +++ b/tests/ui/loop-match/nested.rs @@ -0,0 +1,83 @@ +// Test that a nested `#[loop_match]` works as expected, and that 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 { + 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..775243b9c620b --- /dev/null +++ b/tests/ui/loop-match/or-patterns.rs @@ -0,0 +1,54 @@ +// Test that `#[loop_match]` supports or-patterns. + +//@ run-pass + +#![allow(incomplete_features)] +#![feature(loop_match)] + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum State { + A, + B, + C, + D, +} + +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 => { + states.push(state); + if first { + #[const_continue] + break 'blk State::B; + } else { + #[const_continue] + break 'blk State::D; + } + } + State::B | State::D => { + states.push(state); + if first { + first = false; + #[const_continue] + break 'blk State::A; + } else { + #[const_continue] + break 'blk State::C; + } + } + State::C => { + states.push(state); + break 'a; + } + } + } + } + + assert_eq!(states, [State::A, State::B, State::A, State::D, State::C]); +} diff --git a/tests/ui/loop-match/unsupported-type.rs b/tests/ui/loop-match/unsupported-type.rs new file mode 100644 index 0000000000000..9100a1103ab75 --- /dev/null +++ b/tests/ui/loop-match/unsupported-type.rs @@ -0,0 +1,27 @@ +// Test that the right error is emitted when the `#[loop_match]` state is an +// unsupported type. + +#![allow(incomplete_features)] +#![feature(loop_match)] +#![crate_type = "lib"] + +fn unsupported_type() { + let mut state = Some(false); + #[loop_match] + 'a: loop { + state = 'blk: { + //~^ ERROR this `#[loop_match]` state value has type `Option<bool>`, which is not supported + match state { + Some(false) => { + #[const_continue] + break 'blk Some(true); + } + Some(true) => { + #[const_continue] + break 'blk None; + } + None => break 'a, + } + } + } +} diff --git a/tests/ui/loop-match/unsupported-type.stderr b/tests/ui/loop-match/unsupported-type.stderr new file mode 100644 index 0000000000000..ede3d86796fd0 --- /dev/null +++ b/tests/ui/loop-match/unsupported-type.stderr @@ -0,0 +1,10 @@ +error: this `#[loop_match]` state value has type `Option<bool>`, which is not supported + --> $DIR/unsupported-type.rs:12:9 + | +LL | state = 'blk: { + | ^^^^^ + | + = note: only integers, floats, bool, char, and enums without fields are supported + +error: aborting due to 1 previous error + diff --git a/tests/ui/loop-match/unwind.rs b/tests/ui/loop-match/unwind.rs new file mode 100644 index 0000000000000..39e2e4537b17f --- /dev/null +++ b/tests/ui/loop-match/unwind.rs @@ -0,0 +1,53 @@ +// Test that `#[const_continue]` correctly emits cleanup paths for drops. +// +// Here, we first drop `DropBomb`, causing an unwind. Then `ExitOnDrop` should +// be dropped, causing us to exit with `0` rather than with some non-zero value +// due to the panic, which is what causes the test to pass. + +//@ 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!(); +} diff --git a/tests/ui/loop-match/valid-patterns.rs b/tests/ui/loop-match/valid-patterns.rs new file mode 100644 index 0000000000000..4e0e4798a0bde --- /dev/null +++ b/tests/ui/loop-match/valid-patterns.rs @@ -0,0 +1,117 @@ +// Test that signed and unsigned integer patterns work with `#[loop_match]`. + +//@ run-pass + +#![allow(incomplete_features)] +#![feature(loop_match)] + +fn main() { + assert_eq!(integer(0), 2); + assert_eq!(integer(-1), 2); + assert_eq!(integer(2), 2); + + assert_eq!(boolean(true), false); + assert_eq!(boolean(false), false); + + assert_eq!(character('a'), 'b'); + assert_eq!(character('b'), 'b'); + assert_eq!(character('c'), 'd'); + assert_eq!(character('d'), 'd'); + + assert_eq!(test_f32(1.0), core::f32::consts::PI); + assert_eq!(test_f32(2.5), core::f32::consts::PI); + assert_eq!(test_f32(4.0), 4.0); + + assert_eq!(test_f64(1.0), core::f64::consts::PI); + assert_eq!(test_f64(2.5), core::f64::consts::PI); + assert_eq!(test_f64(4.0), 4.0); +} + +fn integer(mut state: i32) -> i32 { + #[loop_match] + 'a: loop { + state = 'blk: { + match state { + -1 => { + #[const_continue] + break 'blk 2; + } + 0 => { + #[const_continue] + break 'blk -1; + } + 2 => break 'a, + _ => unreachable!("weird value {:?}", state), + } + } + } + + state +} + +fn boolean(mut state: bool) -> bool { + #[loop_match] + loop { + state = 'blk: { + match state { + true => { + #[const_continue] + break 'blk false; + } + false => return state, + } + } + } +} + +fn character(mut state: char) -> char { + #[loop_match] + loop { + state = 'blk: { + match state { + 'a' => { + #[const_continue] + break 'blk 'b'; + } + 'b' => return state, + 'c' => { + #[const_continue] + break 'blk 'd'; + } + _ => return state, + } + } + } +} + +fn test_f32(mut state: f32) -> f32 { + #[loop_match] + loop { + state = 'blk: { + match state { + 1.0 => { + #[const_continue] + break 'blk 2.5; + } + 2.0..3.0 => return core::f32::consts::PI, + _ => return state, + } + } + } +} + +fn test_f64(mut state: f64) -> f64 { + #[loop_match] + loop { + state = 'blk: { + match state { + 1.0 => { + #[const_continue] + break 'blk 2.5; + } + 2.0..3.0 => return core::f64::consts::PI, + _ => return state, + } + } + } +} diff --git a/tests/ui/thir-print/thir-tree-loop-match.rs b/tests/ui/thir-print/thir-tree-loop-match.rs new file mode 100644 index 0000000000000..8c5f2244d5465 --- /dev/null +++ b/tests/ui/thir-print/thir-tree-loop-match.rs @@ -0,0 +1,22 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=thir-tree + +#![allow(incomplete_features)] +#![feature(loop_match)] + +fn boolean(mut state: bool) -> bool { + #[loop_match] + loop { + state = 'blk: { + match state { + true => { + #[const_continue] + break 'blk false; + } + false => return state, + } + } + } +} + +fn main() {} diff --git a/tests/ui/thir-print/thir-tree-loop-match.stdout b/tests/ui/thir-print/thir-tree-loop-match.stdout new file mode 100644 index 0000000000000..828b93da6beb5 --- /dev/null +++ b/tests/ui/thir-print/thir-tree-loop-match.stdout @@ -0,0 +1,301 @@ +DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean): +params: [ + Param { + ty: bool + ty_span: Some($DIR/thir-tree-loop-match.rs:7:23: 7:27 (#0)) + self_kind: None + hir_id: Some(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).1)) + param: Some( + Pat: { + ty: bool + span: $DIR/thir-tree-loop-match.rs:7:12: 7:21 (#0) + kind: PatKind { + Binding { + name: "state" + mode: BindingMode(No, Mut) + var: LocalVarId(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).2)) + ty: bool + is_primary: true + subpattern: None + } + } + } + ) + } +] +body: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(28)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:7:37: 20:2 (#0) + kind: + Scope { + region_scope: Node(28) + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).28)) + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(28)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:7:37: 20:2 (#0) + kind: + Block { + targeted_by_break: false + span: $DIR/thir-tree-loop-match.rs:7:37: 20:2 (#0) + region_scope: Node(3) + safety_mode: Safe + stmts: [] + expr: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(28)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:9:5: 19:6 (#0) + kind: + Scope { + region_scope: Node(4) + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).4)) + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(28)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:9:5: 19:6 (#0) + kind: + NeverToAny { + source: + Expr { + ty: ! + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(28)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:9:5: 19:6 (#0) + kind: + LoopMatch { + state: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(5)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:10:9: 10:14 (#0) + kind: + Scope { + region_scope: Node(7) + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).7)) + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(5)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:10:9: 10:14 (#0) + kind: + VarRef { + id: LocalVarId(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).2)) + } + } + } + } + region_scope: Node(10) + match_span: $DIR/thir-tree-loop-match.rs:11:13: 17:14 (#0) + arms: [ + Arm { + pattern: + Pat: { + ty: bool + span: $DIR/thir-tree-loop-match.rs:12:17: 12:21 (#0) + kind: PatKind { + Constant { + value: Ty(bool, true) + } + } + } + guard: None + body: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0) + kind: + Scope { + region_scope: Node(17) + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).17)) + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0) + kind: + NeverToAny { + source: + Expr { + ty: ! + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(16)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0) + kind: + Block { + targeted_by_break: false + span: $DIR/thir-tree-loop-match.rs:12:25: 15:18 (#0) + region_scope: Node(18) + safety_mode: Safe + stmts: [ + Stmt { + kind: Expr { + scope: Node(21) + expr: + Expr { + ty: ! + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:14:21: 14:37 (#0) + kind: + Scope { + region_scope: Node(19) + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).19)) + value: + Expr { + ty: ! + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:14:21: 14:37 (#0) + kind: + ConstContinue ( + label: Node(10) + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0) + kind: + Scope { + region_scope: Node(20) + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).20)) + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(21)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0) + kind: + Literal( lit: Spanned { node: Bool(false), span: $DIR/thir-tree-loop-match.rs:14:32: 14:37 (#0) }, neg: false) + + } + } + } + ) + } + } + } + } + } + ] + expr: [] + } + } + } + } + } + } + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).16)) + scope: Node(16) + span: $DIR/thir-tree-loop-match.rs:12:17: 15:18 (#0) + } + Arm { + pattern: + Pat: { + ty: bool + span: $DIR/thir-tree-loop-match.rs:16:17: 16:22 (#0) + kind: PatKind { + Constant { + value: Ty(bool, false) + } + } + } + guard: None + body: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0) + kind: + Scope { + region_scope: Node(25) + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).25)) + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0) + kind: + NeverToAny { + source: + Expr { + ty: ! + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:16:26: 16:38 (#0) + kind: + Return { + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:16:33: 16:38 (#0) + kind: + Scope { + region_scope: Node(26) + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).26)) + value: + Expr { + ty: bool + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:16:33: 16:38 (#0) + kind: + VarRef { + id: LocalVarId(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).2)) + } + } + } + } + } + } + } + } + } + } + lint_level: Explicit(HirId(DefId(0:3 ~ thir_tree_loop_match[3c53]::boolean).24)) + scope: Node(24) + span: $DIR/thir-tree-loop-match.rs:16:17: 16:38 (#0) + } + ] + } + } + } + } + } + } + } + } + } + } + + +DefId(0:4 ~ thir_tree_loop_match[3c53]::main): +params: [ +] +body: + Expr { + ty: () + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:22:11: 22:13 (#0) + kind: + Scope { + region_scope: Node(2) + lint_level: Explicit(HirId(DefId(0:4 ~ thir_tree_loop_match[3c53]::main).2)) + value: + Expr { + ty: () + temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None } + span: $DIR/thir-tree-loop-match.rs:22:11: 22:13 (#0) + kind: + Block { + targeted_by_break: false + span: $DIR/thir-tree-loop-match.rs:22:11: 22:13 (#0) + region_scope: Node(1) + safety_mode: Safe + stmts: [] + expr: [] + } + } + } + } + +