Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable if and match in constants behind a feature flag #66507

Merged
merged 14 commits into from
Nov 23, 2019
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
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()))
}

oli-obk marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -264,9 +266,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 @@ -324,10 +327,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 => {
oli-obk marked this conversation as resolved.
Show resolved Hide resolved
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 {
ecstatic-morse marked this conversation as resolved.
Show resolved Hide resolved
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.
ecstatic-morse marked this conversation as resolved.
Show resolved Hide resolved
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),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw: a useful trick for the future, if you want to avoid rebasing due to this, is to move this before never_type_fallback.


// -------------------------------------------------------------------------
// 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 @@ -202,6 +202,7 @@ symbols! {
const_fn,
const_fn_union,
const_generics,
const_if_match,
const_indexing,
const_in_array_repeat_expressions,
const_let,
Expand Down
3 changes: 1 addition & 2 deletions src/test/incremental/warnings-reemitted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
// compile-flags: -Coverflow-checks=on
// build-pass (FIXME(62277): could be check-pass?)

#![allow(warnings)]
#![warn(const_err)]

fn main() {
255u8 + 1; //~ WARNING this expression will panic at run-time
let _ = 255u8 + 1; //~ WARNING attempt to add with overflow
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,4 @@ fn transmute<'a,'b>(x: &'a u32, y: &'b u32) -> (&'a u32, &'b u32) {
}

#[rustc_error]
fn main() { }
//[ok]~^ ERROR fatal error triggered by #[rustc_error]
//[oneuse]~^^ ERROR fatal error triggered by #[rustc_error]
fn main() { } //[ok,oneuse]~ ERROR fatal error triggered by #[rustc_error]
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