Skip to content

Commit

Permalink
Auto merge of #66507 - ecstatic-morse:const-if-match, r=oli-obk
Browse files Browse the repository at this point in the history
Enable `if` and `match` in constants behind a feature flag

This PR is an initial implementation of #49146. It introduces a `const_if_match` feature flag and does the following if it is enabled:
- Allows `Downcast` projections, `SwitchInt` terminators and `FakeRead`s for matched places through the MIR const-checker.
- Allows `if` and `match` expressions through the HIR const-checker.
- Stops converting `&&` to `&` and `||` to `|` in `const` and `static` items.

As a result, the following operations are now allowed in a const context behind the feature flag:
- `if` and `match`
- short circuiting logic operators (`&&` and `||`)
- the `assert` and `debug_assert` macros (if the `const_panic` feature flag is also enabled)

However, the following operations remain forbidden:
- `while`, `loop` and `for` (see #52000)
- the `?` operator (calls `From::from` on its error variant)
- the `assert_eq` and `assert_ne` macros, along with their `debug` variants (calls `fmt::Debug`)

This PR is possible now that we use dataflow for const qualification (see #64470 and #66385).

r? @oli-obk
cc @rust-lang/wg-const-eval @eddyb
  • Loading branch information
bors committed Nov 23, 2019
2 parents a449535 + b09bb15 commit 6d523ee
Show file tree
Hide file tree
Showing 57 changed files with 1,321 additions and 347 deletions.
14 changes: 14 additions & 0 deletions src/doc/unstable-book/src/language-features/const-if-match.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# `const_if_match`

The tracking issue for this feature is: [#49146]

[#49146]: https://github.com/rust-lang/rust/issues/49146

------------------------

Allows for the use of conditionals (`if` and `match`) in a const context.
Const contexts include `static`, `static mut`, `const`, `const fn`, const
generics, and array initializers. Enabling this feature flag will also make
`&&` and `||` function normally in a const-context by removing the hack that
replaces them with their non-short-circuiting equivalents, `&` and `|`, in a
`const` or `static`.
17 changes: 15 additions & 2 deletions src/librustc/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1749,6 +1749,20 @@ pub enum MatchSource {
AwaitDesugar,
}

impl MatchSource {
pub fn name(self) -> &'static str {
use MatchSource::*;
match self {
Normal => "match",
IfDesugar { .. } | IfLetDesugar { .. } => "if",
WhileDesugar | WhileLetDesugar => "while",
ForLoopDesugar => "for",
TryDesugar => "?",
AwaitDesugar => ".await",
}
}
}

/// The loop type that yielded an `ExprKind::Loop`.
#[derive(Copy, Clone, PartialEq, RustcEncodable, RustcDecodable, Debug, HashStable)]
pub enum LoopSource {
Expand All @@ -1766,8 +1780,7 @@ impl LoopSource {
pub fn name(self) -> &'static str {
match self {
LoopSource::Loop => "loop",
LoopSource::While => "while",
LoopSource::WhileLet => "while let",
LoopSource::While | LoopSource::WhileLet => "while",
LoopSource::ForLoop => "for",
}
}
Expand Down
15 changes: 9 additions & 6 deletions src/librustc_mir/hair/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,10 @@ fn make_mirror_unadjusted<'a, 'tcx>(
} else {
// FIXME overflow
match (op.node, cx.constness) {
// FIXME(eddyb) use logical ops in constants when
// they can handle that kind of control-flow.
(hir::BinOpKind::And, hir::Constness::Const) => {
// Destroy control flow if `#![feature(const_if_match)]` is not enabled.
(hir::BinOpKind::And, hir::Constness::Const)
if !cx.tcx.features().const_if_match =>
{
cx.control_flow_destroyed.push((
op.span,
"`&&` operator".into(),
Expand All @@ -354,7 +355,9 @@ fn make_mirror_unadjusted<'a, 'tcx>(
rhs: rhs.to_ref(),
}
}
(hir::BinOpKind::Or, hir::Constness::Const) => {
(hir::BinOpKind::Or, hir::Constness::Const)
if !cx.tcx.features().const_if_match =>
{
cx.control_flow_destroyed.push((
op.span,
"`||` operator".into(),
Expand All @@ -366,14 +369,14 @@ fn make_mirror_unadjusted<'a, 'tcx>(
}
}

(hir::BinOpKind::And, hir::Constness::NotConst) => {
(hir::BinOpKind::And, _) => {
ExprKind::LogicalOp {
op: LogicalOp::And,
lhs: lhs.to_ref(),
rhs: rhs.to_ref(),
}
}
(hir::BinOpKind::Or, hir::Constness::NotConst) => {
(hir::BinOpKind::Or, _) => {
ExprKind::LogicalOp {
op: LogicalOp::Or,
lhs: lhs.to_ref(),
Expand Down
10 changes: 9 additions & 1 deletion src/librustc_mir/transform/check_consts/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ pub trait NonConstOp: std::fmt::Debug {
/// A `Downcast` projection.
#[derive(Debug)]
pub struct Downcast;
impl NonConstOp for Downcast {}
impl NonConstOp for Downcast {
fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
Some(tcx.features().const_if_match)
}
}

/// A function call where the callee is a pointer.
#[derive(Debug)]
Expand Down Expand Up @@ -139,6 +143,10 @@ impl NonConstOp for HeapAllocation {
#[derive(Debug)]
pub struct IfOrMatch;
impl NonConstOp for IfOrMatch {
fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
Some(tcx.features().const_if_match)
}

fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
// This should be caught by the HIR const-checker.
item.tcx.sess.delay_span_bug(
Expand Down
22 changes: 17 additions & 5 deletions src/librustc_mir/transform/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ fn check_statement(
check_rvalue(tcx, body, def_id, rval, span)
}

StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _) => {
| StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _)
if !tcx.features().const_if_match
=> {
Err((span, "loops and conditional expressions are not stable in const fn".into()))
}

Expand Down Expand Up @@ -267,9 +269,10 @@ fn check_place(
while let &[ref proj_base @ .., elem] = cursor {
cursor = proj_base;
match elem {
ProjectionElem::Downcast(..) => {
return Err((span, "`match` or `if let` in `const fn` is unstable".into()));
}
ProjectionElem::Downcast(..) if !tcx.features().const_if_match
=> return Err((span, "`match` or `if let` in `const fn` is unstable".into())),
ProjectionElem::Downcast(_symbol, _variant_index) => {}

ProjectionElem::Field(..) => {
let base_ty = Place::ty_from(&place.base, &proj_base, body, tcx).ty;
if let Some(def) = base_ty.ty_adt_def() {
Expand Down Expand Up @@ -321,10 +324,19 @@ fn check_terminator(
check_operand(tcx, value, span, def_id, body)
},

TerminatorKind::FalseEdges { .. } | TerminatorKind::SwitchInt { .. } => Err((
| TerminatorKind::FalseEdges { .. }
| TerminatorKind::SwitchInt { .. }
if !tcx.features().const_if_match
=> Err((
span,
"loops and conditional expressions are not stable in const fn".into(),
)),

TerminatorKind::FalseEdges { .. } => Ok(()),
TerminatorKind::SwitchInt { discr, switch_ty: _, values: _, targets: _ } => {
check_operand(tcx, discr, span, def_id, body)
}

| TerminatorKind::Abort | TerminatorKind::Unreachable => {
Err((span, "const fn with unreachable code is not stable".into()))
}
Expand Down
98 changes: 73 additions & 25 deletions src/librustc_passes/check_const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,46 @@ use rustc::hir::def_id::DefId;
use rustc::hir::intravisit::{Visitor, NestedVisitorMap};
use rustc::hir::map::Map;
use rustc::hir;
use rustc::session::Session;
use rustc::ty::TyCtxt;
use rustc::ty::query::Providers;
use syntax::ast::Mutability;
use syntax::feature_gate::{emit_feature_err, Features, GateIssue};
use syntax::span_err;
use syntax_pos::Span;
use syntax_pos::{sym, Span};
use rustc_error_codes::*;

use std::fmt;

/// An expression that is not *always* legal in a const context.
#[derive(Clone, Copy)]
enum NonConstExpr {
Loop(hir::LoopSource),
Match(hir::MatchSource),
}

impl NonConstExpr {
fn name(self) -> &'static str {
match self {
Self::Loop(src) => src.name(),
Self::Match(src) => src.name(),
}
}

/// Returns `true` if all feature gates required to enable this expression are turned on, or
/// `None` if there is no feature gate corresponding to this expression.
fn is_feature_gate_enabled(self, features: &Features) -> Option<bool> {
use hir::MatchSource::*;
match self {
| Self::Match(Normal)
| Self::Match(IfDesugar { .. })
| Self::Match(IfLetDesugar { .. })
=> Some(features.const_if_match),

_ => None,
}
}
}

#[derive(Copy, Clone)]
enum ConstKind {
Static,
Expand Down Expand Up @@ -75,31 +105,51 @@ pub(crate) fn provide(providers: &mut Providers<'_>) {

#[derive(Copy, Clone)]
struct CheckConstVisitor<'tcx> {
sess: &'tcx Session,
hir_map: &'tcx Map<'tcx>,
tcx: TyCtxt<'tcx>,
const_kind: Option<ConstKind>,
}

impl<'tcx> CheckConstVisitor<'tcx> {
fn new(tcx: TyCtxt<'tcx>) -> Self {
CheckConstVisitor {
sess: &tcx.sess,
hir_map: tcx.hir(),
tcx,
const_kind: None,
}
}

/// Emits an error when an unsupported expression is found in a const context.
fn const_check_violated(&self, bad_op: &str, span: Span) {
if self.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
self.sess.span_warn(span, "skipping const checks");
return;
fn const_check_violated(&self, expr: NonConstExpr, span: Span) {
match expr.is_feature_gate_enabled(self.tcx.features()) {
// Don't emit an error if the user has enabled the requisite feature gates.
Some(true) => return,

// Users of `-Zunleash-the-miri-inside-of-you` must use feature gates when possible.
None if self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you => {
self.tcx.sess.span_warn(span, "skipping const checks");
return;
}

_ => {}
}

let const_kind = self.const_kind
.expect("`const_check_violated` may only be called inside a const context");

span_err!(self.sess, span, E0744, "`{}` is not allowed in a `{}`", bad_op, const_kind);
let msg = format!("`{}` is not allowed in a `{}`", expr.name(), const_kind);
match expr {
| NonConstExpr::Match(hir::MatchSource::Normal)
| NonConstExpr::Match(hir::MatchSource::IfDesugar { .. })
| NonConstExpr::Match(hir::MatchSource::IfLetDesugar { .. })
=> emit_feature_err(
&self.tcx.sess.parse_sess,
sym::const_if_match,
span,
GateIssue::Language,
&msg
),

_ => span_err!(self.tcx.sess, span, E0744, "{}", msg),
}
}

/// Saves the parent `const_kind` before calling `f` and restores it afterwards.
Expand All @@ -113,7 +163,7 @@ impl<'tcx> CheckConstVisitor<'tcx> {

impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
NestedVisitorMap::OnlyBodies(&self.hir_map)
NestedVisitorMap::OnlyBodies(&self.tcx.hir())
}

fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) {
Expand All @@ -122,7 +172,7 @@ impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
}

fn visit_body(&mut self, body: &'tcx hir::Body) {
let kind = ConstKind::for_body(body, self.hir_map);
let kind = ConstKind::for_body(body, self.tcx.hir());
self.recurse_into(kind, |this| hir::intravisit::walk_body(this, body));
}

Expand All @@ -132,24 +182,22 @@ impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
_ if self.const_kind.is_none() => {}

hir::ExprKind::Loop(_, _, source) => {
self.const_check_violated(source.name(), e.span);
self.const_check_violated(NonConstExpr::Loop(*source), e.span);
}

hir::ExprKind::Match(_, _, source) => {
use hir::MatchSource::*;

let op = match source {
Normal => Some("match"),
IfDesugar { .. } | IfLetDesugar { .. } => Some("if"),
TryDesugar => Some("?"),
AwaitDesugar => Some(".await"),

let non_const_expr = match source {
// These are handled by `ExprKind::Loop` above.
WhileDesugar | WhileLetDesugar | ForLoopDesugar => None,
| hir::MatchSource::WhileDesugar
| hir::MatchSource::WhileLetDesugar
| hir::MatchSource::ForLoopDesugar
=> None,

_ => Some(NonConstExpr::Match(*source)),
};

if let Some(op) = op {
self.const_check_violated(op, e.span);
if let Some(expr) = non_const_expr {
self.const_check_violated(expr, e.span);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/libsyntax/feature_gate/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ declare_features! (
/// Allows using the `#[register_attr]` attribute.
(active, register_tool, "1.41.0", Some(66079), None),

/// Allows the use of `if` and `match` in constants.
(active, const_if_match, "1.41.0", Some(49146), None),

// -------------------------------------------------------------------------
// feature-group-end: actual feature gates
// -------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax_pos/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ symbols! {
const_fn,
const_fn_union,
const_generics,
const_if_match,
const_indexing,
const_in_array_repeat_expressions,
const_let,
Expand Down
6 changes: 4 additions & 2 deletions src/test/ui/borrowck/issue-64453.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
error[E0744]: `match` is not allowed in a `static`
error[E0658]: `match` is not allowed in a `static`
--> $DIR/issue-64453.rs:4:31
|
LL | static settings_dir: String = format!("");
| ^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/49146
= help: add `#![feature(const_if_match)]` to the crate attributes to enable
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0744`.
For more information about this error, try `rustc --explain E0658`.
7 changes: 5 additions & 2 deletions src/test/ui/consts/const-eval/infinite_loop.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ LL | |
LL | | }
| |_________^

error[E0744]: `if` is not allowed in a `const`
error[E0658]: `if` is not allowed in a `const`
--> $DIR/infinite_loop.rs:9:17
|
LL | n = if n % 2 == 0 { n/2 } else { 3*n + 1 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/49146
= help: add `#![feature(const_if_match)]` to the crate attributes to enable

warning: Constant evaluating a complex constant, this might take some time
--> $DIR/infinite_loop.rs:4:18
Expand All @@ -36,5 +39,5 @@ LL | n = if n % 2 == 0 { n/2 } else { 3*n + 1 };

error: aborting due to 3 previous errors

Some errors have detailed explanations: E0080, E0744.
Some errors have detailed explanations: E0080, E0658, E0744.
For more information about an error, try `rustc --explain E0080`.
Loading

0 comments on commit 6d523ee

Please sign in to comment.