Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_mir_build/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ mir_build_loop_match_invalid_update =
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 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
Expand Down
23 changes: 21 additions & 2 deletions compiler/rustc_mir_build/src/builder/expr/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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;
Expand All @@ -17,7 +17,7 @@ use tracing::{debug, instrument};
use crate::builder::expr::category::{Category, RvalueFunc};
use crate::builder::matches::{DeclareLetBindings, HasMatchGuard};
use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder, NeedsTemporary};
use crate::errors::LoopMatchArmWithGuard;
use crate::errors::{LoopMatchArmWithGuard, LoopMatchUnsupportedType};

impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Compile `expr`, storing the result into `destination`, which
Expand Down Expand Up @@ -253,6 +253,25 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// `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(_) => 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.
Expand Down
133 changes: 94 additions & 39 deletions compiler/rustc_mir_build/src/builder/matches/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ use std::mem;
use std::sync::Arc;

use rustc_abi::VariantIdx;
use rustc_ast::LitKind;
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir::{BindingMode, ByRef, LetStmt, LocalSource, Node};
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, TypeVisitableExt, ValTree, ValTreeKind,
};
use rustc_middle::{bug, span_bug};
use rustc_pattern_analysis::rustc::{DeconstructedPat, RustcPatCtxt};
use rustc_span::{BytePos, Pos, Span, Symbol, sym};
use tracing::{debug, instrument};
Expand Down Expand Up @@ -2870,7 +2871,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn static_pattern_match(
&self,
cx: &RustcPatCtxt<'_, 'tcx>,
value: ExprId,
constant: ConstOperand<'tcx>,
arms: &[ArmId],
built_match_tree: &BuiltMatchTree<'tcx>,
) -> Option<BasicBlock> {
Expand All @@ -2889,57 +2890,111 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
.or_else(|| branch.sub_branches.last())
.unwrap();

match self.static_pattern_match_help(value, &pat.pat) {
match self.static_pattern_match_help(constant, &pat.pat) {
true => return Some(sub_branch.success_block),
false => continue,
}
}
} else if self.static_pattern_match_help(value, &pat) {
} else if self.static_pattern_match_help(constant, &pat) {
return Some(branch.sub_branches[0].success_block);
}
}

None
}

fn static_pattern_match_help(&self, value: ExprId, pat: &DeconstructedPat<'_, 'tcx>) -> bool {
use rustc_middle::thir::ExprKind;
/// Based on `FunctionCx::eval_unevaluated_mir_constant_to_valtree`.
fn eval_unevaluated_mir_constant_to_valtree(
&self,
constant: ConstOperand<'tcx>,
) -> (ty::ValTree<'tcx>, Ty<'tcx>) {
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 (cv.valtree, cv.ty),
other => span_bug!(constant.span, "{other:#?}"),
},
mir::Const::Val(mir::ConstValue::Scalar(mir::interpret::Scalar::Int(val)), ty) => {
return (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)) => (valtree, ty),
Ok(Err(ty)) => span_bug!(constant.span, "could not convert {ty:?} to a valtree"),
Err(_) => span_bug!(constant.span, "unable to evaluate this constant"),
}
}

fn static_pattern_match_help(
&self,
constant: ConstOperand<'tcx>,
pat: &DeconstructedPat<'_, 'tcx>,
) -> bool {
use rustc_pattern_analysis::constructor::{IntRange, MaybeInfiniteInt};
use rustc_pattern_analysis::rustc::Constructor;

let (valtree, ty) = self.eval_unevaluated_mir_constant_to_valtree(constant);
assert!(!ty.has_param());

match pat.ctor() {
Constructor::Variant(variant_index) => match &self.thir[value].kind {
ExprKind::Adt(value_adt) => {
return *variant_index == value_adt.variant_index;
}
other => todo!("{other:?}"),
},
Constructor::IntRange(int_range) => match &self.thir[value].kind {
ExprKind::Literal { lit, neg } => match &lit.node {
LitKind::Int(n, _) => {
let n = if pat.ty().is_signed() {
let size = pat.ty().primitive_size(self.tcx);
MaybeInfiniteInt::new_finite_int(
if *neg {
size.truncate((n.get() as i128).overflowing_neg().0 as u128)
} else {
n.get()
},
size.bits(),
)
} else {
MaybeInfiniteInt::new_finite_uint(n.get())
};

return IntRange::from_singleton(n).is_subrange(int_range);
}
Constructor::Variant(variant_index) => {
let ValTreeKind::Branch(box [actual_variant_idx]) = *valtree else {
bug!("malformed valtree for an enum")
};

other => todo!("{other:?}"),
},
other => todo!("{other:?}"),
},
Constructor::Wildcard => return true,
_ => false,
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::Wildcard => true,

// these we may eventually support
Constructor::Struct
| Constructor::Ref
| Constructor::Slice(_)
| Constructor::UnionField
| Constructor::Or
| Constructor::Bool(_)
| Constructor::F16Range(..)
| Constructor::F32Range(..)
| Constructor::F64Range(..)
| Constructor::F128Range(..)
| 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())
}
}
}
}
37 changes: 31 additions & 6 deletions compiler/rustc_mir_build/src/builder/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ use rustc_hir::HirId;
use rustc_index::{IndexSlice, IndexVec};
use rustc_middle::middle::region;
use rustc_middle::mir::*;
use rustc_middle::thir::{ArmId, ExprId, LintLevel};
use rustc_middle::{bug, span_bug};
use rustc_middle::thir::{AdtExpr, AdtExprBase, ArmId, ExprId, ExprKind, LintLevel};
use rustc_middle::ty::ValTree;
use rustc_middle::{bug, span_bug, ty};
use rustc_pattern_analysis::rustc::RustcPatCtxt;
use rustc_session::lint::Level;
use rustc_span::source_map::Spanned;
Expand Down Expand Up @@ -827,6 +828,29 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
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
Expand All @@ -836,17 +860,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

let scope = &self.scopes.const_continuable_scopes[break_index];

let state_ty = self.local_decls[scope.state_place.as_local().unwrap()].ty;
let state_decl = &self.local_decls[scope.state_place.as_local().unwrap()];
let state_ty = state_decl.ty;
let discriminant_ty = match state_ty {
ty if ty.is_enum() => ty.discriminant_ty(self.tcx),
ty if ty.is_integral() => ty,
_ => todo!(),
_ => span_bug!(state_decl.source_info.span, "unsupported #[loop_match] state"),
};

let rvalue = match state_ty {
ty if ty.is_enum() => Rvalue::Discriminant(scope.state_place),
ty if ty.is_integral() => Rvalue::Use(Operand::Copy(scope.state_place)),
_ => todo!(),
_ => span_bug!(state_decl.source_info.span, "unsupported #[loop_match] state"),
};

// The PatCtxt is normally used in pattern exhaustiveness checking, but reused here
Expand All @@ -868,7 +893,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
};

let Some(real_target) =
self.static_pattern_match(&cx, value, &*scope.arms, &scope.built_match_tree)
self.static_pattern_match(&cx, constant, &*scope.arms, &scope.built_match_tree)
else {
self.tcx.dcx().emit_fatal(ConstContinueUnknownJumpTarget { span })
};
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_mir_build/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,15 @@ pub(crate) struct LoopMatchInvalidMatch {
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 {
Expand Down
2 changes: 2 additions & 0 deletions tests/ui/feature-gates/feature-gate-loop-match.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// A #[const_continue] to a labeled loop should error.

enum State {
A,
B,
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/feature-gates/feature-gate-loop-match.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0658]: the `#[loop_match]` attribute is an experimental feature
--> $DIR/feature-gate-loop-match.rs:9:5
--> $DIR/feature-gate-loop-match.rs:11:5
|
LL | #[loop_match]
| ^^^^^^^^^^^^^
Expand All @@ -9,7 +9,7 @@ LL | #[loop_match]
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error[E0658]: the `#[const_continue]` attribute is an experimental feature
--> $DIR/feature-gate-loop-match.rs:14:21
--> $DIR/feature-gate-loop-match.rs:16:21
|
LL | #[const_continue]
| ^^^^^^^^^^^^^^^^^
Expand All @@ -19,7 +19,7 @@ LL | #[const_continue]
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error[E0658]: the `#[const_continue]` attribute is an experimental feature
--> $DIR/feature-gate-loop-match.rs:19:21
--> $DIR/feature-gate-loop-match.rs:21:21
|
LL | #[const_continue]
| ^^^^^^^^^^^^^^^^^
Expand Down
3 changes: 3 additions & 0 deletions tests/ui/loop-match/const-continue-to-block.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// A #[const_continue] to a normal labeled block (that does is not part of a #[loop_match]) should
// error.

#![allow(incomplete_features)]
#![feature(loop_match)]
#![crate_type = "lib"]
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/loop-match/const-continue-to-block.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
--> $DIR/const-continue-to-block.rs:17:27
--> $DIR/const-continue-to-block.rs:20:27
|
LL | break 'b 2;
| ^^
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/loop-match/invalid-attribute.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Checks that #[loop_match] and #[const_continue] attributes can be
// placed on expressions only.
//

#![allow(incomplete_features)]
#![feature(loop_match)]
#![loop_match] //~ ERROR should be applied to a loop
Expand Down
25 changes: 25 additions & 0 deletions tests/ui/loop-match/unsupported-type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Test that a good 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,
}
}
}
}
Loading
Loading