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

Allow let bindings and destructuring in constants and const fn #49172

Merged
merged 6 commits into from
May 22, 2018
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
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