Add #[loop_match] for improved DFA codegen
Co-authored-by: Folkert de Vries <folkert@folkertdev.nl>
This commit is contained in:
@@ -4117,6 +4117,7 @@ dependencies = [
|
||||
"rustc_apfloat",
|
||||
"rustc_arena",
|
||||
"rustc_ast",
|
||||
"rustc_attr_data_structures",
|
||||
"rustc_data_structures",
|
||||
"rustc_errors",
|
||||
"rustc_fluent_macro",
|
||||
|
||||
@@ -212,6 +212,9 @@ pub enum AttributeKind {
|
||||
first_span: Span,
|
||||
},
|
||||
|
||||
/// Represents `#[const_continue]`.
|
||||
ConstContinue(Span),
|
||||
|
||||
/// Represents `#[rustc_const_stable]` and `#[rustc_const_unstable]`.
|
||||
ConstStability {
|
||||
stability: PartialConstStability,
|
||||
@@ -231,6 +234,9 @@ pub enum AttributeKind {
|
||||
/// Represents `#[inline]` and `#[rustc_force_inline]`.
|
||||
Inline(InlineAttr, Span),
|
||||
|
||||
/// Represents `#[loop_match]`.
|
||||
LoopMatch(Span),
|
||||
|
||||
/// Represents `#[rustc_macro_transparency]`.
|
||||
MacroTransparency(Transparency),
|
||||
|
||||
|
||||
31
compiler/rustc_attr_parsing/src/attributes/loop_match.rs
Normal file
31
compiler/rustc_attr_parsing/src/attributes/loop_match.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
use rustc_feature::{AttributeTemplate, template};
|
||||
use rustc_span::{Symbol, sym};
|
||||
|
||||
use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
|
||||
use crate::context::{AcceptContext, Stage};
|
||||
use crate::parser::ArgParser;
|
||||
|
||||
pub(crate) struct LoopMatchParser;
|
||||
impl<S: Stage> SingleAttributeParser<S> for LoopMatchParser {
|
||||
const PATH: &[Symbol] = &[sym::loop_match];
|
||||
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
|
||||
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
|
||||
const TEMPLATE: AttributeTemplate = template!(Word);
|
||||
|
||||
fn convert(cx: &mut AcceptContext<'_, '_, S>, _args: &ArgParser<'_>) -> Option<AttributeKind> {
|
||||
Some(AttributeKind::LoopMatch(cx.attr_span))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ConstContinueParser;
|
||||
impl<S: Stage> SingleAttributeParser<S> for ConstContinueParser {
|
||||
const PATH: &[Symbol] = &[sym::const_continue];
|
||||
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
|
||||
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
|
||||
const TEMPLATE: AttributeTemplate = template!(Word);
|
||||
|
||||
fn convert(cx: &mut AcceptContext<'_, '_, S>, _args: &ArgParser<'_>) -> Option<AttributeKind> {
|
||||
Some(AttributeKind::ConstContinue(cx.attr_span))
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ pub(crate) mod confusables;
|
||||
pub(crate) mod deprecation;
|
||||
pub(crate) mod inline;
|
||||
pub(crate) mod lint_helpers;
|
||||
pub(crate) mod loop_match;
|
||||
pub(crate) mod must_use;
|
||||
pub(crate) mod repr;
|
||||
pub(crate) mod semantics;
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::attributes::confusables::ConfusablesParser;
|
||||
use crate::attributes::deprecation::DeprecationParser;
|
||||
use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
|
||||
use crate::attributes::lint_helpers::{AsPtrParser, PubTransparentParser};
|
||||
use crate::attributes::loop_match::{ConstContinueParser, LoopMatchParser};
|
||||
use crate::attributes::must_use::MustUseParser;
|
||||
use crate::attributes::repr::{AlignParser, ReprParser};
|
||||
use crate::attributes::semantics::MayDangleParser;
|
||||
@@ -110,9 +111,11 @@ attribute_parsers!(
|
||||
// tidy-alphabetical-start
|
||||
Single<AsPtrParser>,
|
||||
Single<ColdParser>,
|
||||
Single<ConstContinueParser>,
|
||||
Single<ConstStabilityIndirectParser>,
|
||||
Single<DeprecationParser>,
|
||||
Single<InlineParser>,
|
||||
Single<LoopMatchParser>,
|
||||
Single<MayDangleParser>,
|
||||
Single<MustUseParser>,
|
||||
Single<NoMangleParser>,
|
||||
|
||||
@@ -657,6 +657,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:
|
||||
// ==========================================================================
|
||||
|
||||
@@ -557,6 +557,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.
|
||||
|
||||
@@ -79,6 +79,9 @@ hir_typeck_cast_unknown_pointer = cannot cast {$to ->
|
||||
.note = the type information given here is insufficient to check whether the pointer cast is valid
|
||||
.label_from = the type information given here is insufficient to check whether the pointer cast is valid
|
||||
|
||||
hir_typeck_const_continue_bad_label =
|
||||
`#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
|
||||
|
||||
hir_typeck_const_select_must_be_const = this argument must be a `const fn`
|
||||
.help = consult the documentation on `const_eval_select` for more information
|
||||
|
||||
|
||||
@@ -1167,3 +1167,10 @@ pub(crate) struct AbiCannotBeCalled {
|
||||
pub span: Span,
|
||||
pub abi: ExternAbi,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(hir_typeck_const_continue_bad_label)]
|
||||
pub(crate) struct ConstContinueBadLabel {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
|
||||
use Context::*;
|
||||
use rustc_ast::Label;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
@@ -14,8 +16,9 @@ use rustc_span::hygiene::DesugaringKind;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
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 +40,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)]
|
||||
@@ -141,7 +149,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, ..
|
||||
@@ -197,6 +210,23 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
|
||||
Err(hir::LoopIdError::UnresolvedLabel) => None,
|
||||
};
|
||||
|
||||
// A `#[const_continue]` must break to a block in a `#[loop_match]`.
|
||||
if find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::ConstContinue(_)) {
|
||||
if let Some(break_label) = break_label.label {
|
||||
let is_target_label = |cx: &Context| match cx {
|
||||
Context::LoopMatch { labeled_block } => {
|
||||
break_label.ident.name == labeled_block.ident.name
|
||||
}
|
||||
_ => 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;
|
||||
}
|
||||
@@ -299,7 +329,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,
|
||||
@@ -380,4 +410,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 !find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::LoopMatch(_)) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +378,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 `&&`).
|
||||
///
|
||||
@@ -454,6 +462,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>,
|
||||
|
||||
@@ -83,7 +83,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]);
|
||||
@@ -108,6 +108,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])
|
||||
|
||||
@@ -11,6 +11,7 @@ rustc_abi = { path = "../rustc_abi" }
|
||||
rustc_apfloat = "0.2.0"
|
||||
rustc_arena = { path = "../rustc_arena" }
|
||||
rustc_ast = { path = "../rustc_ast" }
|
||||
rustc_attr_data_structures = { path = "../rustc_attr_data_structures" }
|
||||
rustc_data_structures = { path = "../rustc_data_structures" }
|
||||
rustc_errors = { path = "../rustc_errors" }
|
||||
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -565,12 +565,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 { .. }
|
||||
|
||||
@@ -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 { .. }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 { .. } => {
|
||||
|
||||
@@ -98,6 +98,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)
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
@@ -1395,7 +1397,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.
|
||||
@@ -1411,7 +1413,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>>,
|
||||
}
|
||||
@@ -1430,8 +1432,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
|
||||
@@ -1489,7 +1491,7 @@ impl<'tcx> MatchTreeBranch<'tcx> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum HasMatchGuard {
|
||||
pub(crate) enum HasMatchGuard {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
@@ -1504,7 +1506,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,
|
||||
@@ -1890,7 +1892,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();
|
||||
@@ -2864,4 +2865,129 @@ 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::DerefPattern(_)
|
||||
| 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,20 +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::ty::{self, TyCtxt};
|
||||
use rustc_middle::mir::{self, *};
|
||||
use rustc_middle::thir::{AdtExpr, AdtExprBase, ArmId, ExprId, ExprKind, LintLevel};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, ValTree};
|
||||
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};
|
||||
use crate::errors::{ConstContinueBadConst, ConstContinueUnknownJumpTarget};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Scopes<'tcx> {
|
||||
@@ -105,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>,
|
||||
|
||||
@@ -174,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
|
||||
@@ -461,6 +481,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(),
|
||||
@@ -552,6 +573,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.
|
||||
///
|
||||
@@ -742,6 +816,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.
|
||||
///
|
||||
|
||||
@@ -465,10 +465,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 { .. }
|
||||
|
||||
@@ -1149,3 +1149,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,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use itertools::Itertools;
|
||||
use rustc_abi::{FIRST_VARIANT, FieldIdx};
|
||||
use rustc_ast::UnsafeBinderCastKind;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_data_structures::stack::ensure_sufficient_stack;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
||||
@@ -21,6 +22,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> {
|
||||
@@ -796,16 +798,38 @@ 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) => {
|
||||
if find_attr!(self.tcx.hir_attrs(expr.hir_id), AttributeKind::ConstContinue(_)) {
|
||||
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 {
|
||||
@@ -840,18 +864,93 @@ 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 }
|
||||
if find_attr!(self.tcx.hir_attrs(expr.hir_id), AttributeKind::LoopMatch(_)) {
|
||||
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 })
|
||||
};
|
||||
|
||||
// The labeled block should contain one match expression, but defining items is
|
||||
// allowed.
|
||||
for stmt in block_body.stmts {
|
||||
if !matches!(stmt.kind, rustc_hir::StmtKind::Item(_)) {
|
||||
dcx.emit_fatal(LoopMatchBadStatements { span: stmt.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),
|
||||
|
||||
@@ -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 { .. }
|
||||
|
||||
@@ -318,6 +318,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);
|
||||
@@ -415,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);
|
||||
|
||||
@@ -82,11 +82,14 @@ 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
|
||||
|
||||
|
||||
passes_coroutine_on_non_closure =
|
||||
attribute should be applied to closures
|
||||
.label = not a closure
|
||||
@@ -446,6 +449,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
|
||||
|
||||
|
||||
@@ -132,6 +132,12 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
||||
Attribute::Parsed(AttributeKind::Optimize(_, attr_span)) => {
|
||||
self.check_optimize(hir_id, *attr_span, span, target)
|
||||
}
|
||||
Attribute::Parsed(AttributeKind::LoopMatch(attr_span)) => {
|
||||
self.check_loop_match(hir_id, *attr_span, target)
|
||||
}
|
||||
Attribute::Parsed(AttributeKind::ConstContinue(attr_span)) => {
|
||||
self.check_const_continue(hir_id, *attr_span, target)
|
||||
}
|
||||
Attribute::Parsed(AttributeKind::AllowInternalUnstable(syms)) => self
|
||||
.check_allow_internal_unstable(
|
||||
hir_id,
|
||||
@@ -2630,6 +2636,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> {
|
||||
|
||||
@@ -31,6 +31,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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -692,6 +692,7 @@ symbols! {
|
||||
const_closures,
|
||||
const_compare_raw_pointers,
|
||||
const_constructor,
|
||||
const_continue,
|
||||
const_deallocate,
|
||||
const_destruct,
|
||||
const_eval_limit,
|
||||
@@ -1304,6 +1305,7 @@ symbols! {
|
||||
logf64,
|
||||
loongarch_target_feature,
|
||||
loop_break_value,
|
||||
loop_match,
|
||||
lt,
|
||||
m68k_target_feature,
|
||||
macro_at_most_once_rep,
|
||||
|
||||
@@ -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 { .. }
|
||||
|
||||
52
src/doc/unstable-book/src/language-features/loop-match.md
Normal file
52
src/doc/unstable-book/src/language-features/loop-match.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# `loop_match`
|
||||
|
||||
The tracking issue for this feature is: [#132306]
|
||||
|
||||
[#132306]: https://github.com/rust-lang/rust/issues/132306
|
||||
|
||||
------
|
||||
|
||||
The `#[loop_match]` and `#[const_continue]` attributes can be used to improve the code
|
||||
generation of logic that fits this shape:
|
||||
|
||||
```ignore (pseudo-rust)
|
||||
loop {
|
||||
state = 'blk: {
|
||||
match state {
|
||||
State::A => {
|
||||
break 'blk State::B
|
||||
}
|
||||
State::B => { /* ... */ }
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here the loop itself can be annotated with `#[loop_match]`, and any `break 'blk` with
|
||||
`#[const_continue]` if the value is know at compile time:
|
||||
|
||||
```ignore (pseudo-rust)
|
||||
#[loop_match]
|
||||
loop {
|
||||
state = 'blk: {
|
||||
match state {
|
||||
State::A => {
|
||||
#[const_continue]
|
||||
break 'blk State::B
|
||||
}
|
||||
State::B => { /* ... */ }
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The observable behavior of this loop is exactly the same as without the extra attributes.
|
||||
The difference is in the generated output: normally, when the state is `A`, control flow
|
||||
moves from the `A` branch, back to the top of the loop, then to the `B` branch. With the
|
||||
attributes, The `A` branch will immediately jump to the `B` branch.
|
||||
|
||||
Removing the indirection can be beneficial for stack usage and branch prediction, and
|
||||
enables other optimizations by clearly splitting out the control flow paths that your
|
||||
program will actually use.
|
||||
30
tests/ui/feature-gates/feature-gate-loop-match.rs
Normal file
30
tests/ui/feature-gates/feature-gate-loop-match.rs
Normal file
@@ -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,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
33
tests/ui/feature-gates/feature-gate-loop-match.stderr
Normal file
33
tests/ui/feature-gates/feature-gate-loop-match.stderr
Normal file
@@ -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`.
|
||||
23
tests/ui/loop-match/break-to-block.rs
Normal file
23
tests/ui/loop-match/break-to-block.rs
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
tests/ui/loop-match/const-continue-to-block.rs
Normal file
26
tests/ui/loop-match/const-continue-to-block.rs
Normal file
@@ -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 that participates in a `#[loop_match]`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
tests/ui/loop-match/const-continue-to-block.stderr
Normal file
8
tests/ui/loop-match/const-continue-to-block.stderr
Normal file
@@ -0,0 +1,8 @@
|
||||
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
|
||||
--> $DIR/const-continue-to-block.rs:20:27
|
||||
|
|
||||
LL | break 'b 2;
|
||||
| ^^
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
27
tests/ui/loop-match/const-continue-to-loop.rs
Normal file
27
tests/ui/loop-match/const-continue-to-loop.rs
Normal file
@@ -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 mut 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 that participates in a `#[loop_match]`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
tests/ui/loop-match/const-continue-to-loop.stderr
Normal file
8
tests/ui/loop-match/const-continue-to-loop.stderr
Normal file
@@ -0,0 +1,8 @@
|
||||
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
|
||||
--> $DIR/const-continue-to-loop.rs:21:27
|
||||
|
|
||||
LL | break 'a 2;
|
||||
| ^^
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
29
tests/ui/loop-match/const-continue-to-polymorphic-const.rs
Normal file
29
tests/ui/loop-match/const-continue-to-polymorphic-const.rs
Normal file
@@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
47
tests/ui/loop-match/drop-in-match-arm.rs
Normal file
47
tests/ui/loop-match/drop-in-match-arm.rs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
43
tests/ui/loop-match/invalid-attribute.rs
Normal file
43
tests/ui/loop-match/invalid-attribute.rs
Normal file
@@ -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
|
||||
};
|
||||
}
|
||||
131
tests/ui/loop-match/invalid-attribute.stderr
Normal file
131
tests/ui/loop-match/invalid-attribute.stderr
Normal file
@@ -0,0 +1,131 @@
|
||||
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: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: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: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:35:5
|
||||
|
|
||||
LL | #[const_continue]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
LL | || {};
|
||||
| -- 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:40:9
|
||||
|
|
||||
LL | #[const_continue]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
LL | 5
|
||||
| - 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: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: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: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: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:7:1
|
||||
|
|
||||
LL | / #![allow(incomplete_features)]
|
||||
LL | | #![feature(loop_match)]
|
||||
LL | | #![loop_match]
|
||||
LL | | #![const_continue]
|
||||
| | ^^^^^^^^^^^^^^^^^^
|
||||
... |
|
||||
LL | | };
|
||||
LL | | }
|
||||
| |_- 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: aborting due to 14 previous errors
|
||||
|
||||
161
tests/ui/loop-match/invalid.rs
Normal file
161
tests/ui/loop-match/invalid.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
// 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 mut 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 mut 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 mut 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 mut 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 mut 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 mut state = ();
|
||||
#[loop_match]
|
||||
loop {
|
||||
state = 'blk: {
|
||||
//~^ ERROR this expression must be a single `match` wrapped in a labeled block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_assignment() {
|
||||
#[loop_match]
|
||||
loop {
|
||||
() //~ ERROR expected a single assignment expression
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_loop_body() {
|
||||
#[loop_match]
|
||||
loop {
|
||||
//~^ ERROR expected a single assignment expression
|
||||
}
|
||||
}
|
||||
|
||||
fn break_without_value() {
|
||||
let mut 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 mut 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 mut 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
tests/ui/loop-match/invalid.stderr
Normal file
91
tests/ui/loop-match/invalid.stderr
Normal file
@@ -0,0 +1,91 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/invalid.rs:120: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:101:9
|
||||
|
|
||||
LL | ()
|
||||
| ^^
|
||||
|
||||
error: expected a single assignment expression
|
||||
--> $DIR/invalid.rs:107:10
|
||||
|
|
||||
LL | loop {
|
||||
| __________^
|
||||
LL | |
|
||||
LL | | }
|
||||
| |_____^
|
||||
|
||||
error: a `#[const_continue]` must break to a label with a value
|
||||
--> $DIR/invalid.rs:137:21
|
||||
|
|
||||
LL | break 'blk;
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: match arms that are part of a `#[loop_match]` cannot have guards
|
||||
--> $DIR/invalid.rs:155: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`.
|
||||
45
tests/ui/loop-match/loop-match.rs
Normal file
45
tests/ui/loop-match/loop-match.rs
Normal file
@@ -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))
|
||||
}
|
||||
48
tests/ui/loop-match/macro.rs
Normal file
48
tests/ui/loop-match/macro.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
// Test that macros can be defined in the labeled block. This should not trigger an error about
|
||||
// statements not being allowed in that position, and should of course work as expected.
|
||||
|
||||
//@ 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: {
|
||||
macro_rules! const_continue {
|
||||
($e:expr) => {
|
||||
#[const_continue]
|
||||
break 'blk $e;
|
||||
};
|
||||
}
|
||||
match state {
|
||||
State::A => {
|
||||
const_continue!(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!(State::C);
|
||||
} else {
|
||||
const_continue!(State::A);
|
||||
}
|
||||
}
|
||||
State::C => break 'a,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
assert!(matches!(state, State::C))
|
||||
}
|
||||
83
tests/ui/loop-match/nested.rs
Normal file
83
tests/ui/loop-match/nested.rs
Normal file
@@ -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
|
||||
}
|
||||
54
tests/ui/loop-match/or-patterns.rs
Normal file
54
tests/ui/loop-match/or-patterns.rs
Normal file
@@ -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]);
|
||||
}
|
||||
27
tests/ui/loop-match/unsupported-type.rs
Normal file
27
tests/ui/loop-match/unsupported-type.rs
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
tests/ui/loop-match/unsupported-type.stderr
Normal file
10
tests/ui/loop-match/unsupported-type.stderr
Normal file
@@ -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
|
||||
|
||||
53
tests/ui/loop-match/unwind.rs
Normal file
53
tests/ui/loop-match/unwind.rs
Normal file
@@ -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!();
|
||||
}
|
||||
117
tests/ui/loop-match/valid-patterns.rs
Normal file
117
tests/ui/loop-match/valid-patterns.rs
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
tests/ui/thir-print/thir-tree-loop-match.rs
Normal file
22
tests/ui/thir-print/thir-tree-loop-match.rs
Normal file
@@ -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() {}
|
||||
301
tests/ui/thir-print/thir-tree-loop-match.stdout
Normal file
301
tests/ui/thir-print/thir-tree-loop-match.stdout
Normal file
@@ -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: []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user