Skip to content

Commit

Permalink
Auto merge of #49172 - oli-obk:const_let, r=eddyb
Browse files Browse the repository at this point in the history
Allow let bindings and destructuring in constants and const fn

r? @eddyb

cc #48821
  • Loading branch information
bors committed May 22, 2018
2 parents ff8fa5c + 2483c81 commit 9f80ea3
Show file tree
Hide file tree
Showing 22 changed files with 354 additions and 113 deletions.
15 changes: 0 additions & 15 deletions src/librustc_mir/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,21 +597,6 @@ See [RFC 911] for more details on the design of `const fn`s.
[RFC 911]: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md
"##,

E0016: r##"
Blocks in constants may only contain items (such as constant, function
definition, etc...) and a tail expression. Erroneous code example:
```compile_fail,E0016
const FOO: i32 = { let x = 0; x }; // 'x' isn't an item!
```
To avoid it, you have to replace the non-item object:
```
const FOO: i32 = { const X : i32 = 0; X };
```
"##,

E0017: r##"
References in statics and constants may only refer to immutable values.
Erroneous code example:
Expand Down
134 changes: 73 additions & 61 deletions src/librustc_mir/transform/qualify_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use rustc::middle::lang_items;
use rustc_target::spec::abi::Abi;
use syntax::attr;
use syntax::ast::LitKind;
use syntax::feature_gate::UnstableFeatures;
use syntax::feature_gate::{UnstableFeatures, feature_err, emit_feature_err, GateIssue};
use syntax_pos::{Span, DUMMY_SP};

use std::fmt;
Expand Down Expand Up @@ -120,8 +120,7 @@ struct Qualifier<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
rpo: ReversePostorder<'a, 'tcx>,
tcx: TyCtxt<'a, 'gcx, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
temp_qualif: IndexVec<Local, Option<Qualif>>,
return_qualif: Option<Qualif>,
local_qualif: IndexVec<Local, Option<Qualif>>,
qualif: Qualif,
const_fn_arg_vars: BitVector,
temp_promotion_state: IndexVec<Local, TempState>,
Expand All @@ -140,11 +139,11 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {

let param_env = tcx.param_env(def_id);

let mut temp_qualif = IndexVec::from_elem(None, &mir.local_decls);
let mut local_qualif = IndexVec::from_elem(None, &mir.local_decls);
for arg in mir.args_iter() {
let mut qualif = Qualif::NEEDS_DROP;
qualif.restrict(mir.local_decls[arg].ty, tcx, param_env);
temp_qualif[arg] = Some(qualif);
local_qualif[arg] = Some(qualif);
}

Qualifier {
Expand All @@ -155,8 +154,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
rpo,
tcx,
param_env,
temp_qualif,
return_qualif: None,
local_qualif,
qualif: Qualif::empty(),
const_fn_arg_vars: BitVector::new(mir.local_decls.len()),
temp_promotion_state: temps,
Expand Down Expand Up @@ -191,12 +189,12 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
fn statement_like(&mut self) {
self.add(Qualif::NOT_CONST);
if self.mode != Mode::Fn {
let mut err = struct_span_err!(
self.tcx.sess,
let mut err = feature_err(
&self.tcx.sess.parse_sess,
"const_let",
self.span,
E0016,
"blocks in {}s are limited to items and tail expressions",
self.mode
GateIssue::Language,
&format!("statements in {}s are unstable", self.mode),
);
if self.tcx.sess.teach(&err.get_code().unwrap()) {
err.note("Blocks in constants may only contain items (such as constant, function \
Expand Down Expand Up @@ -266,6 +264,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {

/// Assign the current qualification to the given destination.
fn assign(&mut self, dest: &Place<'tcx>, location: Location) {
trace!("assign: {:?}", dest);
let qualif = self.qualif;
let span = self.span;
let store = |slot: &mut Option<Qualif>| {
Expand All @@ -281,28 +280,31 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
if self.mir.local_kind(index) == LocalKind::Temp
&& self.temp_promotion_state[index].is_promotable() {
debug!("store to promotable temp {:?}", index);
store(&mut self.temp_qualif[index]);
store(&mut self.local_qualif[index]);
}
}
return;
}

match *dest {
Place::Local(index) if self.mir.local_kind(index) == LocalKind::Temp => {
debug!("store to temp {:?}", index);
store(&mut self.temp_qualif[index])
Place::Local(index) if (self.mir.local_kind(index) == LocalKind::Var ||
self.mir.local_kind(index) == LocalKind::Arg) &&
self.tcx.sess.features_untracked().const_let => {
debug!("store to var {:?}", index);
self.local_qualif[index] = Some(self.qualif);
}
Place::Local(index) if self.mir.local_kind(index) == LocalKind::ReturnPointer => {
debug!("store to return place {:?}", index);
store(&mut self.return_qualif)
Place::Local(index) if self.mir.local_kind(index) == LocalKind::Temp ||
self.mir.local_kind(index) == LocalKind::ReturnPointer => {
debug!("store to {:?} (temp or return pointer)", index);
store(&mut self.local_qualif[index])
}

Place::Projection(box Projection {
base: Place::Local(index),
elem: ProjectionElem::Deref
}) if self.mir.local_kind(index) == LocalKind::Temp
&& self.mir.local_decls[index].ty.is_box()
&& self.temp_qualif[index].map_or(false, |qualif| {
&& self.local_qualif[index].map_or(false, |qualif| {
qualif.intersects(Qualif::NOT_CONST)
}) => {
// Part of `box expr`, we should've errored
Expand Down Expand Up @@ -355,40 +357,42 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
TerminatorKind::FalseUnwind { .. } => None,

TerminatorKind::Return => {
// Check for unused values. This usually means
// there are extra statements in the AST.
for temp in mir.temps_iter() {
if self.temp_qualif[temp].is_none() {
continue;
}

let state = self.temp_promotion_state[temp];
if let TempState::Defined { location, uses: 0 } = state {
let data = &mir[location.block];
let stmt_idx = location.statement_index;

// Get the span for the initialization.
let source_info = if stmt_idx < data.statements.len() {
data.statements[stmt_idx].source_info
} else {
data.terminator().source_info
};
self.span = source_info.span;
if !self.tcx.sess.features_untracked().const_let {
// Check for unused values. This usually means
// there are extra statements in the AST.
for temp in mir.temps_iter() {
if self.local_qualif[temp].is_none() {
continue;
}

// Treat this as a statement in the AST.
self.statement_like();
let state = self.temp_promotion_state[temp];
if let TempState::Defined { location, uses: 0 } = state {
let data = &mir[location.block];
let stmt_idx = location.statement_index;

// Get the span for the initialization.
let source_info = if stmt_idx < data.statements.len() {
data.statements[stmt_idx].source_info
} else {
data.terminator().source_info
};
self.span = source_info.span;

// Treat this as a statement in the AST.
self.statement_like();
}
}
}

// Make sure there are no extra unassigned variables.
self.qualif = Qualif::NOT_CONST;
for index in mir.vars_iter() {
if !self.const_fn_arg_vars.contains(index.index()) {
debug!("unassigned variable {:?}", index);
self.assign(&Place::Local(index), Location {
block: bb,
statement_index: usize::MAX,
});
// Make sure there are no extra unassigned variables.
self.qualif = Qualif::NOT_CONST;
for index in mir.vars_iter() {
if !self.const_fn_arg_vars.contains(index.index()) {
debug!("unassigned variable {:?}", index);
self.assign(&Place::Local(index), Location {
block: bb,
statement_index: usize::MAX,
});
}
}
}

Expand All @@ -408,7 +412,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
}
}

self.qualif = self.return_qualif.unwrap_or(Qualif::NOT_CONST);
self.qualif = self.local_qualif[RETURN_PLACE].unwrap_or(Qualif::NOT_CONST);

// Account for errors in consts by using the
// conservative type qualification instead.
Expand Down Expand Up @@ -453,9 +457,15 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
LocalKind::ReturnPointer => {
self.not_const();
}
LocalKind::Var => {
LocalKind::Var if !self.tcx.sess.features_untracked().const_let => {
if self.mode != Mode::Fn {
emit_feature_err(&self.tcx.sess.parse_sess, "const_let",
self.span, GateIssue::Language,
&format!("let bindings in {}s are unstable",self.mode));
}
self.add(Qualif::NOT_CONST);
}
LocalKind::Var |
LocalKind::Arg |
LocalKind::Temp => {
if let LocalKind::Arg = kind {
Expand All @@ -466,7 +476,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
self.add(Qualif::NOT_PROMOTABLE);
}

if let Some(qualif) = self.temp_qualif[local] {
if let Some(qualif) = self.local_qualif[local] {
self.add(qualif);
} else {
self.not_const();
Expand Down Expand Up @@ -588,7 +598,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {

// Mark the consumed locals to indicate later drops are noops.
if let Operand::Move(Place::Local(local)) = *operand {
self.temp_qualif[local] = self.temp_qualif[local].map(|q|
self.local_qualif[local] = self.local_qualif[local].map(|q|
q - Qualif::NEEDS_DROP
);
}
Expand Down Expand Up @@ -759,7 +769,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
}
if let Place::Local(local) = *place {
if self.mir.local_kind(local) == LocalKind::Temp {
if let Some(qualif) = self.temp_qualif[local] {
if let Some(qualif) = self.local_qualif[local] {
// `forbidden_mut` is false, so we can safely ignore
// `MUTABLE_INTERIOR` from the local's qualifications.
// This allows borrowing fields which don't have
Expand Down Expand Up @@ -1033,7 +1043,7 @@ This does not pose a problem by itself because they can't be accessed directly."
// HACK(eddyb) Emulate a bit of dataflow analysis,
// conservatively, that drop elaboration will do.
let needs_drop = if let Place::Local(local) = *place {
if self.temp_qualif[local].map_or(true, |q| q.intersects(Qualif::NEEDS_DROP)) {
if self.local_qualif[local].map_or(true, |q| q.intersects(Qualif::NEEDS_DROP)) {
Some(self.mir.local_decls[local].source_info.span)
} else {
None
Expand Down Expand Up @@ -1070,7 +1080,8 @@ This does not pose a problem by itself because they can't be accessed directly."
// Check the allowed const fn argument forms.
if let (Mode::ConstFn, &Place::Local(index)) = (self.mode, dest) {
if self.mir.local_kind(index) == LocalKind::Var &&
self.const_fn_arg_vars.insert(index.index()) {
self.const_fn_arg_vars.insert(index.index()) &&
!self.tcx.sess.features_untracked().const_let {

// Direct use of an argument is permitted.
match *rvalue {
Expand All @@ -1086,10 +1097,11 @@ This does not pose a problem by itself because they can't be accessed directly."
// Avoid a generic error for other uses of arguments.
if self.qualif.intersects(Qualif::FN_ARGUMENT) {
let decl = &self.mir.local_decls[index];
let mut err = struct_span_err!(
self.tcx.sess,
let mut err = feature_err(
&self.tcx.sess.parse_sess,
"const_let",
decl.source_info.span,
E0022,
GateIssue::Language,
"arguments of constant functions can only be immutable by-value bindings"
);
if self.tcx.sess.teach(&err.get_code().unwrap()) {
Expand Down
3 changes: 3 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ declare_features! (
// Allows the definition of `const fn` functions.
(active, const_fn, "1.2.0", Some(24111), None),

// Allows let bindings and destructuring in `const fn` functions and constants.
(active, const_let, "1.22.1", Some(48821), None),

// Allows using #[prelude_import] on glob `use` items.
//
// rustc internal
Expand Down
12 changes: 7 additions & 5 deletions src/test/compile-fail/const-block-non-item-statement-2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@
// except according to those terms.

const A: usize = { 1; 2 };
//~^ ERROR: blocks in constants are limited to items and tail expressions
//~^ ERROR statements in constants are unstable

const B: usize = { { } 2 };
//~^ ERROR: blocks in constants are limited to items and tail expressions
//~^ ERROR statements in constants are unstable

macro_rules! foo {
() => (()) //~ ERROR: blocks in constants are limited to items and tail expressions
() => (()) //~ ERROR statements in constants are unstable
}
const C: usize = { foo!(); 2 };

const D: usize = { let x = 4; 2 };
//~^ ERROR: blocks in constants are limited to items and tail expressions
//~^^ ERROR: blocks in constants are limited to items and tail expressions
//~^ ERROR let bindings in constants are unstable
//~| ERROR statements in constants are unstable
//~| ERROR let bindings in constants are unstable
//~| ERROR statements in constants are unstable

pub fn main() {}
6 changes: 4 additions & 2 deletions src/test/compile-fail/const-block-non-item-statement-3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
// except according to those terms.

type Array = [u32; { let x = 2; 5 }];
//~^ ERROR: blocks in constants are limited to items and tail expressions
//~^^ ERROR: blocks in constants are limited to items and tail expressions
//~^ ERROR let bindings in constants are unstable
//~| ERROR statements in constants are unstable
//~| ERROR let bindings in constants are unstable
//~| ERROR statements in constants are unstable

pub fn main() {}
6 changes: 4 additions & 2 deletions src/test/compile-fail/const-block-non-item-statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

enum Foo {
Bar = { let x = 1; 3 }
//~^ ERROR: blocks in constants are limited to items and tail expressions
//~^^ ERROR: blocks in constants are limited to items and tail expressions
//~^ ERROR let bindings in constants are unstable
//~| ERROR statements in constants are unstable
//~| ERROR let bindings in constants are unstable
//~| ERROR statements in constants are unstable
}

pub fn main() {}
10 changes: 7 additions & 3 deletions src/test/compile-fail/const-fn-destructuring-arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// test that certain things are disallowed in const fn signatures
// test that certain things are disallowed in constant functions

#![feature(const_fn)]

// no destructuring
const fn i((
a, //~ ERROR: E0022
b //~ ERROR: E0022
a,
//~^ ERROR arguments of constant functions can only be immutable by-value bindings
b
//~^ ERROR arguments of constant functions can only be immutable by-value bindings
): (u32, u32)) -> u32 {
a + b
//~^ ERROR let bindings in constant functions are unstable
//~| ERROR let bindings in constant functions are unstable
}

fn main() {}
10 changes: 8 additions & 2 deletions src/test/compile-fail/const-fn-not-safe-for-const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@ const fn get_Y_addr() -> &'static u32 {
}

const fn get() -> u32 {
let x = 22; //~ ERROR E0016
let y = 44; //~ ERROR E0016
let x = 22;
//~^ ERROR let bindings in constant functions are unstable
//~| ERROR statements in constant functions are unstable
let y = 44;
//~^ ERROR let bindings in constant functions are unstable
//~| ERROR statements in constant functions are unstable
x + y
//~^ ERROR let bindings in constant functions are unstable
//~| ERROR let bindings in constant functions are unstable
}

fn main() {
Expand Down
Loading

0 comments on commit 9f80ea3

Please sign in to comment.