From 78884b765915c551e5f3fe71b185d91ec4c186de Mon Sep 17 00:00:00 2001 From: Eduard Burtescu Date: Sat, 7 May 2016 19:14:28 +0300 Subject: [PATCH] mir: qualify and promote constants. --- mk/crates.mk | 2 +- src/librustc_driver/driver.rs | 7 +- src/librustc_mir/Cargo.toml | 1 + src/librustc_mir/build/mod.rs | 2 +- src/librustc_mir/diagnostics.rs | 387 +++++++ src/librustc_mir/hair/cx/expr.rs | 24 +- src/librustc_mir/hair/cx/mod.rs | 6 +- src/librustc_mir/lib.rs | 8 + src/librustc_mir/mir_map.rs | 23 +- src/librustc_mir/transform/mod.rs | 2 + src/librustc_mir/transform/promote_consts.rs | 412 +++++++ src/librustc_mir/transform/qualify_consts.rs | 1010 +++++++++++++++++ src/librustc_passes/const_fn.rs | 118 -- src/librustc_passes/consts.rs | 177 +-- src/librustc_passes/diagnostics.rs | 482 -------- src/librustc_passes/lib.rs | 1 - src/rustc/Cargo.lock | 3 + .../const-block-non-item-statement.rs | 14 + 18 files changed, 1895 insertions(+), 784 deletions(-) create mode 100644 src/librustc_mir/diagnostics.rs create mode 100644 src/librustc_mir/transform/promote_consts.rs create mode 100644 src/librustc_mir/transform/qualify_consts.rs delete mode 100644 src/librustc_passes/const_fn.rs diff --git a/mk/crates.mk b/mk/crates.mk index fec9e985e04..48c1e4ad59d 100644 --- a/mk/crates.mk +++ b/mk/crates.mk @@ -111,7 +111,7 @@ DEPS_rustc_lint := rustc log syntax rustc_const_eval DEPS_rustc_llvm := native:rustllvm libc std rustc_bitflags DEPS_rustc_metadata := rustc syntax rbml rustc_const_math DEPS_rustc_passes := syntax rustc core rustc_const_eval -DEPS_rustc_mir := rustc syntax rustc_const_math rustc_const_eval +DEPS_rustc_mir := rustc syntax rustc_const_math rustc_const_eval rustc_bitflags DEPS_rustc_resolve := arena rustc log syntax DEPS_rustc_platform_intrinsics := std DEPS_rustc_plugin := rustc rustc_metadata syntax rustc_mir diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index 2b56366f1c6..1b570933bee 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -37,7 +37,7 @@ use rustc_privacy; use rustc_plugin::registry::Registry; use rustc_plugin as plugin; use rustc::hir::lowering::{lower_crate, LoweringContext}; -use rustc_passes::{no_asm, loops, consts, const_fn, rvalues, static_recursion}; +use rustc_passes::{no_asm, loops, consts, rvalues, static_recursion}; use rustc_const_eval::check_match; use super::Compilation; @@ -726,10 +726,6 @@ pub fn phase_2_configure_and_expand(sess: &Session, }) })?; - time(time_passes, - "const fn bodies and arguments", - || const_fn::check_crate(sess, &krate))?; - if sess.opts.debugging_opts.input_stats { println!("Post-expansion node count: {}", count_nodes(&krate)); } @@ -903,6 +899,7 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session, let mut passes = sess.mir_passes.borrow_mut(); // Push all the built-in passes. passes.push_pass(box mir::transform::remove_dead_blocks::RemoveDeadBlocks); + passes.push_pass(box mir::transform::qualify_consts::QualifyAndPromoteConstants); passes.push_pass(box mir::transform::type_check::TypeckMir); passes.push_pass(box mir::transform::simplify_cfg::SimplifyCfg); passes.push_pass(box mir::transform::remove_dead_blocks::RemoveDeadBlocks); diff --git a/src/librustc_mir/Cargo.toml b/src/librustc_mir/Cargo.toml index 1c41ca6f416..77dccb7e0d4 100644 --- a/src/librustc_mir/Cargo.toml +++ b/src/librustc_mir/Cargo.toml @@ -16,4 +16,5 @@ rustc_back = { path = "../librustc_back" } rustc_const_eval = { path = "../librustc_const_eval" } rustc_const_math = { path = "../librustc_const_math" } rustc_data_structures = { path = "../librustc_data_structures" } +rustc_bitflags = { path = "../librustc_bitflags" } syntax = { path = "../libsyntax" } diff --git a/src/librustc_mir/build/mod.rs b/src/librustc_mir/build/mod.rs index 4ce77d162e0..c6765f95d33 100644 --- a/src/librustc_mir/build/mod.rs +++ b/src/librustc_mir/build/mod.rs @@ -84,7 +84,7 @@ pub struct ScopeAuxiliary { pub postdoms: Vec, } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Location { /// the location is within this block pub block: BasicBlock, diff --git a/src/librustc_mir/diagnostics.rs b/src/librustc_mir/diagnostics.rs new file mode 100644 index 00000000000..30d3a39e473 --- /dev/null +++ b/src/librustc_mir/diagnostics.rs @@ -0,0 +1,387 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(non_snake_case)] + +register_long_diagnostics! { + +E0010: r##" +The value of statics and constants must be known at compile time, and they live +for the entire lifetime of a program. Creating a boxed value allocates memory on +the heap at runtime, and therefore cannot be done at compile time. Erroneous +code example: + +```compile_fail +#![feature(box_syntax)] + +const CON : Box = box 0; +``` +"##, + +E0013: r##" +Static and const variables can refer to other const variables. But a const +variable cannot refer to a static variable. For example, `Y` cannot refer to +`X` here: + +```compile_fail +static X: i32 = 42; +const Y: i32 = X; +``` + +To fix this, the value can be extracted as a const and then used: + +``` +const A: i32 = 42; +static X: i32 = A; +const Y: i32 = A; +``` +"##, + +// FIXME(#24111) Change the language here when const fn stabilizes +E0015: r##" +The only functions that can be called in static or constant expressions are +`const` functions, and struct/enum constructors. `const` functions are only +available on a nightly compiler. Rust currently does not support more general +compile-time function execution. + +``` +const FOO: Option = Some(1); // enum constructor +struct Bar {x: u8} +const BAR: Bar = Bar {x: 1}; // struct constructor +``` + +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 +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: + +```compile_fail +static X: i32 = 1; +const C: i32 = 2; + +// these three are not allowed: +const CR: &'static mut i32 = &mut C; +static STATIC_REF: &'static mut i32 = &mut X; +static CONST_REF: &'static mut i32 = &mut C; +``` + +Statics are shared everywhere, and if they refer to mutable data one might +violate memory safety since holding multiple mutable references to shared data +is not allowed. + +If you really want global mutable state, try using `static mut` or a global +`UnsafeCell`. +"##, + +E0018: r##" + +The value of static and constant integers must be known at compile time. You +can't cast a pointer to an integer because the address of a pointer can +vary. + +For example, if you write: + +```compile_fail +static MY_STATIC: u32 = 42; +static MY_STATIC_ADDR: usize = &MY_STATIC as *const _ as usize; +static WHAT: usize = (MY_STATIC_ADDR^17) + MY_STATIC_ADDR; +``` + +Then `MY_STATIC_ADDR` would contain the address of `MY_STATIC`. However, +the address can change when the program is linked, as well as change +between different executions due to ASLR, and many linkers would +not be able to calculate the value of `WHAT`. + +On the other hand, static and constant pointers can point either to +a known numeric address or to the address of a symbol. + +``` +static MY_STATIC_ADDR: &'static u32 = &MY_STATIC; +// ... and also +static MY_STATIC_ADDR2: *const u32 = &MY_STATIC; + +const CONST_ADDR: *const u8 = 0x5f3759df as *const u8; +``` + +This does not pose a problem by itself because they can't be +accessed directly. +"##, + +E0019: r##" +A function call isn't allowed in the const's initialization expression +because the expression's value must be known at compile-time. Erroneous code +example: + +```compile_fail +enum Test { + V1 +} + +impl Test { + fn test(&self) -> i32 { + 12 + } +} + +fn main() { + const FOO: Test = Test::V1; + + const A: i32 = FOO.test(); // You can't call Test::func() here ! +} +``` + +Remember: you can't use a function call inside a const's initialization +expression! However, you can totally use it anywhere else: + +``` +fn main() { + const FOO: Test = Test::V1; + + FOO.func(); // here is good + let x = FOO.func(); // or even here! +} +``` +"##, + +E0022: r##" +Constant functions are not allowed to mutate anything. Thus, binding to an +argument with a mutable pattern is not allowed. For example, + +```compile_fail +const fn foo(mut x: u8) { + // do stuff +} +``` + +Is incorrect because the function body may not mutate `x`. + +Remove any mutable bindings from the argument list to fix this error. In case +you need to mutate the argument, try lazily initializing a global variable +instead of using a `const fn`, or refactoring the code to a functional style to +avoid mutation if possible. +"##, + +E0394: r##" +From [RFC 246]: + + > It is invalid for a static to reference another static by value. It is + > required that all references be borrowed. + +[RFC 246]: https://github.com/rust-lang/rfcs/pull/246 +"##, + + +E0395: r##" +The value assigned to a constant scalar must be known at compile time, +which is not the case when comparing raw pointers. + +Erroneous code example: + +```compile_fail +static FOO: i32 = 42; +static BAR: i32 = 42; + +static BAZ: bool = { (&FOO as *const i32) == (&BAR as *const i32) }; +// error: raw pointers cannot be compared in statics! +``` + +The address assigned by the linker to `FOO` and `BAR` may or may not +be identical, so the value of `BAZ` can't be determined. + +If you want to do the comparison, please do it at run-time. + +For example: + +``` +static FOO: i32 = 42; +static BAR: i32 = 42; + +let baz: bool = { (&FOO as *const i32) == (&BAR as *const i32) }; +// baz isn't a constant expression so it's ok +``` +"##, + +E0396: r##" +The value behind a raw pointer can't be determined at compile-time +(or even link-time), which means it can't be used in a constant +expression. Erroneous code example: + +```compile_fail +const REG_ADDR: *const u8 = 0x5f3759df as *const u8; + +const VALUE: u8 = unsafe { *REG_ADDR }; +// error: raw pointers cannot be dereferenced in constants +``` + +A possible fix is to dereference your pointer at some point in run-time. + +For example: + +``` +const REG_ADDR: *const u8 = 0x5f3759df as *const u8; + +let reg_value = unsafe { *REG_ADDR }; +``` +"##, + +E0492: r##" +A borrow of a constant containing interior mutability was attempted. Erroneous +code example: + +```compile_fail +use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; + +const A: AtomicUsize = ATOMIC_USIZE_INIT; +static B: &'static AtomicUsize = &A; +// error: cannot borrow a constant which contains interior mutability, create a +// static instead +``` + +A `const` represents a constant value that should never change. If one takes +a `&` reference to the constant, then one is taking a pointer to some memory +location containing the value. Normally this is perfectly fine: most values +can't be changed via a shared `&` pointer, but interior mutability would allow +it. That is, a constant value could be mutated. On the other hand, a `static` is +explicitly a single memory location, which can be mutated at will. + +So, in order to solve this error, either use statics which are `Sync`: + +``` +use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; + +static A: AtomicUsize = ATOMIC_USIZE_INIT; +static B: &'static AtomicUsize = &A; // ok! +``` + +You can also have this error while using a cell type: + +```compile_fail +#![feature(const_fn)] + +use std::cell::Cell; + +const A: Cell = Cell::new(1); +const B: &'static Cell = &A; +// error: cannot borrow a constant which contains interior mutability, create +// a static instead + +// or: +struct C { a: Cell } + +const D: C = C { a: Cell::new(1) }; +const E: &'static Cell = &D.a; // error + +// or: +const F: &'static C = &D; // error +``` + +This is because cell types do operations that are not thread-safe. Due to this, +they don't implement Sync and thus can't be placed in statics. In this +case, `StaticMutex` would work just fine, but it isn't stable yet: +https://doc.rust-lang.org/nightly/std/sync/struct.StaticMutex.html + +However, if you still wish to use these types, you can achieve this by an unsafe +wrapper: + +``` +#![feature(const_fn)] + +use std::cell::Cell; +use std::marker::Sync; + +struct NotThreadSafe { + value: Cell, +} + +unsafe impl Sync for NotThreadSafe {} + +static A: NotThreadSafe = NotThreadSafe { value : Cell::new(1) }; +static B: &'static NotThreadSafe = &A; // ok! +``` + +Remember this solution is unsafe! You will have to ensure that accesses to the +cell are synchronized. +"##, + +E0493: r##" +A type with a destructor was assigned to an invalid type of variable. Erroneous +code example: + +```compile_fail +struct Foo { + a: u32 +} + +impl Drop for Foo { + fn drop(&mut self) {} +} + +const F : Foo = Foo { a : 0 }; +// error: constants are not allowed to have destructors +static S : Foo = Foo { a : 0 }; +// error: statics are not allowed to have destructors +``` + +To solve this issue, please use a type which does allow the usage of type with +destructors. +"##, + +E0494: r##" +A reference of an interior static was assigned to another const/static. +Erroneous code example: + +```compile_fail +struct Foo { + a: u32 +} + +static S : Foo = Foo { a : 0 }; +static A : &'static u32 = &S.a; +// error: cannot refer to the interior of another static, use a +// constant instead +``` + +The "base" variable has to be a const if you want another static/const variable +to refer to one of its fields. Example: + +``` +struct Foo { + a: u32 +} + +const S : Foo = Foo { a : 0 }; +static A : &'static u32 = &S.a; // ok! +``` +"##, + +} + +register_diagnostics! { + E0526, // shuffle indices are not constant +} diff --git a/src/librustc_mir/hair/cx/expr.rs b/src/librustc_mir/hair/cx/expr.rs index 6d527f77800..049426db2f4 100644 --- a/src/librustc_mir/hair/cx/expr.rs +++ b/src/librustc_mir/hair/cx/expr.rs @@ -351,21 +351,39 @@ fn make_mirror_unadjusted<'a, 'tcx>(cx: &mut Cx<'a, 'tcx>, expr: &'tcx hir::Expr pass_args, lhs.to_ref(), vec![rhs]) } else { // FIXME overflow - match op.node { - hir::BinOp_::BiAnd => { + match (op.node, cx.constness) { + // FIXME(eddyb) use logical ops in constants when + // they can handle that kind of control-flow. + (hir::BinOp_::BiAnd, hir::Constness::Const) => { + ExprKind::Binary { + op: BinOp::BitAnd, + lhs: lhs.to_ref(), + rhs: rhs.to_ref(), + } + } + (hir::BinOp_::BiOr, hir::Constness::Const) => { + ExprKind::Binary { + op: BinOp::BitOr, + lhs: lhs.to_ref(), + rhs: rhs.to_ref(), + } + } + + (hir::BinOp_::BiAnd, hir::Constness::NotConst) => { ExprKind::LogicalOp { op: LogicalOp::And, lhs: lhs.to_ref(), rhs: rhs.to_ref(), } } - hir::BinOp_::BiOr => { + (hir::BinOp_::BiOr, hir::Constness::NotConst) => { ExprKind::LogicalOp { op: LogicalOp::Or, lhs: lhs.to_ref(), rhs: rhs.to_ref(), } } + _ => { let op = bin_op(op.node); ExprKind::Binary { diff --git a/src/librustc_mir/hair/cx/mod.rs b/src/librustc_mir/hair/cx/mod.rs index c3a5fbd967c..5274b5e9aba 100644 --- a/src/librustc_mir/hair/cx/mod.rs +++ b/src/librustc_mir/hair/cx/mod.rs @@ -32,13 +32,17 @@ use rustc_const_math::{ConstInt, ConstUsize}; pub struct Cx<'a, 'tcx: 'a> { tcx: &'a TyCtxt<'tcx>, infcx: &'a InferCtxt<'a, 'tcx>, + constness: hir::Constness } impl<'a,'tcx> Cx<'a,'tcx> { - pub fn new(infcx: &'a InferCtxt<'a, 'tcx>) -> Cx<'a, 'tcx> { + pub fn new(infcx: &'a InferCtxt<'a, 'tcx>, + constness: hir::Constness) + -> Cx<'a, 'tcx> { Cx { tcx: infcx.tcx, infcx: infcx, + constness: constness, } } } diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs index ced73f34e0d..79d11e78bde 100644 --- a/src/librustc_mir/lib.rs +++ b/src/librustc_mir/lib.rs @@ -20,7 +20,9 @@ Rust MIR: a lowered representation of Rust. Also: an experiment! #![cfg_attr(not(stage0), deny(warnings))] #![unstable(feature = "rustc_private", issue = "27812")] +#![feature(associated_consts)] #![feature(box_patterns)] +#![feature(rustc_diagnostic_macros)] #![feature(rustc_private)] #![feature(staged_api)] #![feature(question_mark)] @@ -31,10 +33,16 @@ extern crate graphviz as dot; extern crate rustc; extern crate rustc_data_structures; extern crate rustc_back; +#[macro_use] +#[no_link] +extern crate rustc_bitflags; +#[macro_use] extern crate syntax; extern crate rustc_const_math; extern crate rustc_const_eval; +pub mod diagnostics; + pub mod build; pub mod graphviz; mod hair; diff --git a/src/librustc_mir/mir_map.rs b/src/librustc_mir/mir_map.rs index 8f22f491455..d771c804389 100644 --- a/src/librustc_mir/mir_map.rs +++ b/src/librustc_mir/mir_map.rs @@ -29,7 +29,8 @@ use rustc::traits::ProjectionMode; use rustc::ty::{self, Ty, TyCtxt}; use rustc::util::nodemap::NodeMap; use rustc::hir; -use rustc::hir::intravisit::{self, Visitor}; +use rustc::hir::intravisit::{self, FnKind, Visitor}; +use rustc::hir::map::blocks::FnLikeNode; use syntax::ast; use syntax::codemap::Span; @@ -59,13 +60,27 @@ impl<'a, 'tcx> BuildMir<'a, 'tcx> { fn build(&mut self, src: MirSource, f: F) where F: for<'b> FnOnce(Cx<'b, 'tcx>) -> (Mir<'tcx>, build::ScopeAuxiliaryVec) { + let constness = match src { + MirSource::Const(_) | + MirSource::Static(..) => hir::Constness::Const, + MirSource::Fn(id) => { + let fn_like = FnLikeNode::from_node(self.tcx.map.get(id)); + match fn_like.map(|f| f.kind()) { + Some(FnKind::ItemFn(_, _, _, c, _, _, _)) => c, + Some(FnKind::Method(_, m, _, _)) => m.constness, + _ => hir::Constness::NotConst + } + } + MirSource::Promoted(..) => bug!() + }; + let param_env = ty::ParameterEnvironment::for_item(self.tcx, src.item_id()); let infcx = infer::new_infer_ctxt(self.tcx, &self.tcx.tables, Some(param_env), ProjectionMode::AnyFinal); - let (mir, scope_auxiliary) = f(Cx::new(&infcx)); + let (mir, scope_auxiliary) = f(Cx::new(&infcx, constness)); pretty::dump_mir(self.tcx, "mir_map", &0, src, &mir, Some(&scope_auxiliary)); @@ -151,7 +166,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BuildMir<'a, 'tcx> { } fn visit_fn(&mut self, - fk: intravisit::FnKind<'tcx>, + fk: FnKind<'tcx>, decl: &'tcx hir::FnDecl, body: &'tcx hir::Block, span: Span, @@ -165,7 +180,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BuildMir<'a, 'tcx> { } }; - let implicit_argument = if let intravisit::FnKind::Closure(..) = fk { + let implicit_argument = if let FnKind::Closure(..) = fk { Some((closure_self_ty(&self.tcx, id, body.id), None)) } else { None diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index a52a8edc211..51f5c3cd7f5 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -14,3 +14,5 @@ pub mod erase_regions; pub mod no_landing_pads; pub mod type_check; pub mod break_critical_edges; +pub mod promote_consts; +pub mod qualify_consts; diff --git a/src/librustc_mir/transform/promote_consts.rs b/src/librustc_mir/transform/promote_consts.rs new file mode 100644 index 00000000000..c5ebe708eb4 --- /dev/null +++ b/src/librustc_mir/transform/promote_consts.rs @@ -0,0 +1,412 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A pass that promotes borrows of constant rvalues. +//! +//! The rvalues considered constant are trees of temps, +//! each with exactly one initialization, and holding +//! a constant value with no interior mutability. +//! They are placed into a new MIR constant body in +//! `promoted` and the borrow rvalue is replaced with +//! a `Literal::Promoted` using the index into `promoted` +//! of that constant MIR. +//! +//! This pass assumes that every use is dominated by an +//! initialization and can otherwise silence errors, if +//! move analysis runs after promotion on broken MIR. + +use rustc::mir::repr::*; +use rustc::mir::visit::{LvalueContext, MutVisitor, Visitor}; +use rustc::ty::{self, TyCtxt}; +use syntax::codemap::Span; + +use build::Location; +use traversal::ReversePostorder; + +use std::mem; + +/// State of a temporary during collection and promotion. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum TempState { + /// No references to this temp. + Undefined, + /// One direct assignment and any number of direct uses. + /// A borrow of this temp is promotable if the assigned + /// value is qualified as constant. + Defined { + location: Location, + uses: usize + }, + /// Any other combination of assignments/uses. + Unpromotable, + /// This temp was part of an rvalue which got extracted + /// during promotion and needs cleanup. + PromotedOut +} + +impl TempState { + pub fn is_promotable(&self) -> bool { + if let TempState::Defined { uses, .. } = *self { + uses > 0 + } else { + false + } + } +} + +/// A "root candidate" for promotion, which will become the +/// returned value in a promoted MIR, unless it's a subset +/// of a larger candidate. +pub enum Candidate { + /// Borrow of a constant temporary. + Ref(Location), + + /// Array of indices found in the third argument of + /// a call to one of the simd_shuffleN intrinsics. + ShuffleIndices(BasicBlock) +} + +struct TempCollector { + temps: Vec, + location: Location, + span: Span +} + +impl<'tcx> Visitor<'tcx> for TempCollector { + fn visit_lvalue(&mut self, lvalue: &Lvalue<'tcx>, context: LvalueContext) { + self.super_lvalue(lvalue, context); + if let Lvalue::Temp(index) = *lvalue { + // Ignore drops, if the temp gets promoted, + // then it's constant and thus drop is noop. + if let LvalueContext::Drop = context { + return; + } + + let temp = &mut self.temps[index as usize]; + if *temp == TempState::Undefined { + match context { + LvalueContext::Store | + LvalueContext::Call => { + *temp = TempState::Defined { + location: self.location, + uses: 0 + }; + return; + } + _ => { /* mark as unpromotable below */ } + } + } else if let TempState::Defined { ref mut uses, .. } = *temp { + match context { + LvalueContext::Borrow {..} | + LvalueContext::Consume | + LvalueContext::Inspect => { + *uses += 1; + return; + } + _ => { /* mark as unpromotable below */ } + } + } + *temp = TempState::Unpromotable; + } + } + + fn visit_statement(&mut self, bb: BasicBlock, statement: &Statement<'tcx>) { + assert_eq!(self.location.block, bb); + self.span = statement.span; + self.super_statement(bb, statement); + self.location.statement_index += 1; + } + + fn visit_terminator(&mut self, bb: BasicBlock, terminator: &Terminator<'tcx>) { + self.span = terminator.span; + self.super_terminator(bb, terminator); + } + + fn visit_basic_block_data(&mut self, bb: BasicBlock, data: &BasicBlockData<'tcx>) { + self.location.statement_index = 0; + self.location.block = bb; + self.super_basic_block_data(bb, data); + } +} + +pub fn collect_temps(mir: &Mir, rpo: &mut ReversePostorder) -> Vec { + let mut collector = TempCollector { + temps: vec![TempState::Undefined; mir.temp_decls.len()], + location: Location { + block: START_BLOCK, + statement_index: 0 + }, + span: mir.span + }; + for (bb, data) in rpo { + collector.visit_basic_block_data(bb, data); + } + collector.temps +} + +struct Promoter<'a, 'tcx: 'a> { + source: &'a mut Mir<'tcx>, + promoted: Mir<'tcx>, + temps: &'a mut Vec, + + /// If true, all nested temps are also kept in the + /// source MIR, not moved to the promoted MIR. + keep_original: bool +} + +impl<'a, 'tcx> Promoter<'a, 'tcx> { + fn new_block(&mut self) -> BasicBlock { + let index = self.promoted.basic_blocks.len(); + self.promoted.basic_blocks.push(BasicBlockData { + statements: vec![], + terminator: Some(Terminator { + span: self.promoted.span, + scope: ScopeId::new(0), + kind: TerminatorKind::Return + }), + is_cleanup: false + }); + BasicBlock::new(index) + } + + fn assign(&mut self, dest: Lvalue<'tcx>, rvalue: Rvalue<'tcx>, span: Span) { + let data = self.promoted.basic_blocks.last_mut().unwrap(); + data.statements.push(Statement { + span: span, + scope: ScopeId::new(0), + kind: StatementKind::Assign(dest, rvalue) + }); + } + + /// Copy the initialization of this temp to the + /// promoted MIR, recursing through temps. + fn promote_temp(&mut self, index: u32) -> u32 { + let index = index as usize; + let old_keep_original = self.keep_original; + let (bb, stmt_idx) = match self.temps[index] { + TempState::Defined { + location: Location { block, statement_index }, + uses + } if uses > 0 => { + if uses > 1 { + self.keep_original = true; + } + (block, statement_index) + } + temp => { + span_bug!(self.promoted.span, "tmp{} not promotable: {:?}", + index, temp); + } + }; + if !self.keep_original { + self.temps[index] = TempState::PromotedOut; + } + + let no_stmts = self.source[bb].statements.len(); + + // First, take the Rvalue or Call out of the source MIR, + // or duplicate it, depending on keep_original. + let (mut rvalue, mut call) = (None, None); + let span = if stmt_idx < no_stmts { + let statement = &mut self.source[bb].statements[stmt_idx]; + let StatementKind::Assign(_, ref mut rhs) = statement.kind; + if self.keep_original { + rvalue = Some(rhs.clone()); + } else { + let unit = Rvalue::Aggregate(AggregateKind::Tuple, vec![]); + rvalue = Some(mem::replace(rhs, unit)); + } + statement.span + } else if self.keep_original { + let terminator = self.source[bb].terminator().clone(); + call = Some(terminator.kind); + terminator.span + } else { + let terminator = self.source[bb].terminator_mut(); + let target = match terminator.kind { + TerminatorKind::Call { + destination: ref mut dest @ Some(_), + ref mut cleanup, .. + } => { + // No cleanup necessary. + cleanup.take(); + + // We'll put a new destination in later. + dest.take().unwrap().1 + } + ref kind => { + span_bug!(terminator.span, "{:?} not promotable", kind); + } + }; + call = Some(mem::replace(&mut terminator.kind, TerminatorKind::Goto { + target: target + })); + terminator.span + }; + + // Then, recurse for components in the Rvalue or Call. + if stmt_idx < no_stmts { + self.visit_rvalue(rvalue.as_mut().unwrap()); + } else { + self.visit_terminator_kind(bb, call.as_mut().unwrap()); + } + + let new_index = self.promoted.temp_decls.len() as u32; + let new_temp = Lvalue::Temp(new_index); + self.promoted.temp_decls.push(TempDecl { + ty: self.source.temp_decls[index].ty + }); + + // Inject the Rvalue or Call into the promoted MIR. + if stmt_idx < no_stmts { + self.assign(new_temp, rvalue.unwrap(), span); + } else { + let last = self.promoted.basic_blocks.len() - 1; + let new_target = self.new_block(); + let mut call = call.unwrap(); + match call { + TerminatorKind::Call { ref mut destination, ..} => { + *destination = Some((new_temp, new_target)); + } + _ => bug!() + } + let terminator = &mut self.promoted.basic_blocks[last].terminator_mut(); + terminator.span = span; + terminator.kind = call; + } + + // Restore the old duplication state. + self.keep_original = old_keep_original; + + new_index + } + + fn promote_candidate(mut self, candidate: Candidate) { + let span = self.promoted.span; + let new_operand = Operand::Constant(Constant { + span: span, + ty: self.promoted.return_ty.unwrap(), + literal: Literal::Promoted { + index: self.source.promoted.len() + } + }); + let mut rvalue = match candidate { + Candidate::Ref(Location { block: bb, statement_index: stmt_idx }) => { + match self.source[bb].statements[stmt_idx].kind { + StatementKind::Assign(_, ref mut rvalue) => { + mem::replace(rvalue, Rvalue::Use(new_operand)) + } + } + } + Candidate::ShuffleIndices(bb) => { + match self.source[bb].terminator_mut().kind { + TerminatorKind::Call { ref mut args, .. } => { + Rvalue::Use(mem::replace(&mut args[2], new_operand)) + } + _ => bug!() + } + } + }; + self.visit_rvalue(&mut rvalue); + self.assign(Lvalue::ReturnPointer, rvalue, span); + self.source.promoted.push(self.promoted); + } +} + +/// Replaces all temporaries with their promoted counterparts. +impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> { + fn visit_lvalue(&mut self, lvalue: &mut Lvalue<'tcx>, context: LvalueContext) { + if let Lvalue::Temp(ref mut index) = *lvalue { + *index = self.promote_temp(*index); + } + self.super_lvalue(lvalue, context); + } +} + +pub fn promote_candidates<'tcx>(mir: &mut Mir<'tcx>, + tcx: &TyCtxt<'tcx>, + mut temps: Vec, + candidates: Vec) { + // Visit candidates in reverse, in case they're nested. + for candidate in candidates.into_iter().rev() { + let (span, ty) = match candidate { + Candidate::Ref(Location { block: bb, statement_index: stmt_idx }) => { + let statement = &mir[bb].statements[stmt_idx]; + let StatementKind::Assign(ref dest, _) = statement.kind; + if let Lvalue::Temp(index) = *dest { + if temps[index as usize] == TempState::PromotedOut { + // Already promoted. + continue; + } + } + (statement.span, mir.lvalue_ty(tcx, dest).to_ty(tcx)) + } + Candidate::ShuffleIndices(bb) => { + let terminator = mir[bb].terminator(); + let ty = match terminator.kind { + TerminatorKind::Call { ref args, .. } => { + mir.operand_ty(tcx, &args[2]) + } + _ => { + span_bug!(terminator.span, + "expected simd_shuffleN call to promote"); + } + }; + (terminator.span, ty) + } + }; + + let mut promoter = Promoter { + source: mir, + promoted: Mir { + basic_blocks: vec![], + scopes: vec![ScopeData { + span: span, + parent_scope: None + }], + promoted: vec![], + return_ty: ty::FnConverging(ty), + var_decls: vec![], + arg_decls: vec![], + temp_decls: vec![], + upvar_decls: vec![], + span: span + }, + temps: &mut temps, + keep_original: false + }; + assert_eq!(promoter.new_block(), START_BLOCK); + promoter.promote_candidate(candidate); + } + + // Eliminate assignments to, and drops of promoted temps. + let promoted = |index: u32| temps[index as usize] == TempState::PromotedOut; + for block in &mut mir.basic_blocks { + block.statements.retain(|statement| { + match statement.kind { + StatementKind::Assign(Lvalue::Temp(index), _) => { + !promoted(index) + } + _ => true + } + }); + let terminator = block.terminator_mut(); + match terminator.kind { + TerminatorKind::Drop { value: Lvalue::Temp(index), target, .. } => { + if promoted(index) { + terminator.kind = TerminatorKind::Goto { + target: target + }; + } + } + _ => {} + } + } +} diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs new file mode 100644 index 00000000000..7728d43774f --- /dev/null +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -0,0 +1,1010 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A pass that qualifies constness of temporaries in constants, +//! static initializers and functions and also drives promotion. +//! +//! The Qualif flags below can be used to also provide better +//! diagnostics as to why a constant rvalue wasn't promoted. + +use rustc_data_structures::bitvec::BitVector; +use rustc::hir; +use rustc::hir::def_id::DefId; +use rustc::hir::intravisit::FnKind; +use rustc::hir::map::blocks::FnLikeNode; +use rustc::infer; +use rustc::traits::{self, ProjectionMode}; +use rustc::ty::{self, TyCtxt, Ty}; +use rustc::ty::cast::CastTy; +use rustc::mir::repr::*; +use rustc::mir::mir_map::MirMap; +use rustc::mir::transform::{Pass, MirMapPass, MirSource}; +use rustc::mir::visit::{LvalueContext, Visitor}; +use rustc::util::nodemap::DefIdMap; +use syntax::abi::Abi; +use syntax::codemap::Span; +use syntax::feature_gate::UnstableFeatures; + +use std::collections::hash_map::Entry; +use std::fmt; + +use build::Location; +use traversal::{self, ReversePostorder}; + +use super::promote_consts::{self, Candidate, TempState}; + +bitflags! { + flags Qualif: u8 { + // Const item's qualification while recursing. + // Recursive consts are an error. + const RECURSIVE = 1 << 0, + + // Constant containing interior mutability (UnsafeCell). + const MUTABLE_INTERIOR = 1 << 1, + + // Constant containing an ADT that implements Drop. + const NEEDS_DROP = 1 << 2, + + // Function argument. + const FN_ARGUMENT = 1 << 3, + + // Static lvalue or move from a static. + const STATIC = 1 << 4, + + // Reference to a static. + const STATIC_REF = 1 << 5, + + // Not constant at all - non-`const fn` calls, asm!, + // pointer comparisons, ptr-to-int casts, etc. + const NOT_CONST = 1 << 6, + + // Borrows of temporaries can be promoted only + // if they have none of the above qualifications. + const UNPROMOTABLE = !0, + + // Const items can only have MUTABLE_INTERIOR + // without producing an error. + const CONST_ERROR = !Qualif::MUTABLE_INTERIOR.bits + } +} + +impl Qualif { + /// Remove flags which are impossible for the given type. + fn restrict<'a, 'tcx>(&mut self, ty: Ty<'tcx>, + param_env: &ty::ParameterEnvironment<'a, 'tcx>) { + if !ty.type_contents(param_env.tcx).interior_unsafe() { + *self = *self - Qualif::MUTABLE_INTERIOR; + } + if !param_env.tcx.type_needs_drop_given_env(ty, param_env) { + *self = *self - Qualif::NEEDS_DROP; + } + } +} + +/// What kind of item we are in. +#[derive(Copy, Clone, PartialEq, Eq)] +enum Mode { + Const, + Static, + StaticMut, + ConstFn, + Fn +} + +impl fmt::Display for Mode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Mode::Const => write!(f, "constant"), + Mode::Static | Mode::StaticMut => write!(f, "static"), + Mode::ConstFn => write!(f, "constant function"), + Mode::Fn => write!(f, "function") + } + } +} + +fn is_const_fn(tcx: &TyCtxt, def_id: DefId) -> bool { + if let Some(node_id) = tcx.map.as_local_node_id(def_id) { + let fn_like = FnLikeNode::from_node(tcx.map.get(node_id)); + match fn_like.map(|f| f.kind()) { + Some(FnKind::ItemFn(_, _, _, c, _, _, _)) => { + c == hir::Constness::Const + } + Some(FnKind::Method(_, m, _, _)) => { + m.constness == hir::Constness::Const + } + _ => false + } + } else { + tcx.sess.cstore.is_const_fn(def_id) + } +} + +struct Qualifier<'a, 'tcx: 'a> { + mode: Mode, + span: Span, + def_id: DefId, + mir: &'a Mir<'tcx>, + rpo: ReversePostorder<'a, 'tcx>, + tcx: &'a TyCtxt<'tcx>, + param_env: ty::ParameterEnvironment<'a, 'tcx>, + qualif_map: &'a mut DefIdMap, + mir_map: Option<&'a MirMap<'tcx>>, + temp_qualif: Vec>, + return_qualif: Option, + qualif: Qualif, + const_fn_arg_vars: BitVector, + location: Location, + temp_promotion_state: Vec, + promotion_candidates: Vec +} + +impl<'a, 'tcx> Qualifier<'a, 'tcx> { + fn new(param_env: ty::ParameterEnvironment<'a, 'tcx>, + qualif_map: &'a mut DefIdMap, + mir_map: Option<&'a MirMap<'tcx>>, + def_id: DefId, + mir: &'a Mir<'tcx>, + mode: Mode) + -> Qualifier<'a, 'tcx> { + let mut rpo = traversal::reverse_postorder(mir); + let temps = promote_consts::collect_temps(mir, &mut rpo); + rpo.reset(); + Qualifier { + mode: mode, + span: mir.span, + def_id: def_id, + mir: mir, + rpo: rpo, + tcx: param_env.tcx, + param_env: param_env, + qualif_map: qualif_map, + mir_map: mir_map, + temp_qualif: vec![None; mir.temp_decls.len()], + return_qualif: None, + qualif: Qualif::empty(), + const_fn_arg_vars: BitVector::new(mir.var_decls.len()), + location: Location { + block: START_BLOCK, + statement_index: 0 + }, + temp_promotion_state: temps, + promotion_candidates: vec![] + } + } + + // FIXME(eddyb) we could split the errors into meaningful + // categories, but enabling full miri would make that + // slightly pointless (even with feature-gating). + fn not_const(&mut self) { + self.add(Qualif::NOT_CONST); + if self.mode != Mode::Fn { + span_err!(self.tcx.sess, self.span, E0019, + "{} contains unimplemented expression type", self.mode); + } + } + + /// Error about extra statements in a constant. + fn statement_like(&mut self) { + self.add(Qualif::NOT_CONST); + if self.mode != Mode::Fn { + span_err!(self.tcx.sess, self.span, E0016, + "blocks in {}s are limited to items and tail expressions", + self.mode); + } + } + + /// Add the given qualification to self.qualif. + fn add(&mut self, qualif: Qualif) { + self.qualif = self.qualif | qualif; + } + + /// Add the given type's qualification to self.qualif. + fn add_type(&mut self, ty: Ty<'tcx>) { + self.add(Qualif::MUTABLE_INTERIOR | Qualif::NEEDS_DROP); + self.qualif.restrict(ty, &self.param_env); + } + + /// Within the provided closure, self.qualif will start + /// out empty, and its value after the closure returns will + /// be combined with the value before the call to nest. + fn nest(&mut self, f: F) { + let original = self.qualif; + self.qualif = Qualif::empty(); + f(self); + self.add(original); + } + + /// Check for NEEDS_DROP (from an ADT or const fn call) and + /// error, unless we're in a function. + fn deny_drop(&self) { + if self.mode != Mode::Fn && self.qualif.intersects(Qualif::NEEDS_DROP) { + span_err!(self.tcx.sess, self.span, E0493, + "{}s are not allowed to have destructors", + self.mode); + } + } + + /// Check if an Lvalue with the current qualifications could + /// be consumed, by either an operand or a Deref projection. + fn try_consume(&mut self) -> bool { + if self.qualif.intersects(Qualif::STATIC) && self.mode != Mode::Fn { + let msg = if self.mode == Mode::Static || + self.mode == Mode::StaticMut { + "cannot refer to other statics by value, use the \ + address-of operator or a constant instead" + } else { + "cannot refer to statics by value, use a constant instead" + }; + span_err!(self.tcx.sess, self.span, E0394, "{}", msg); + + // Replace STATIC with NOT_CONST to avoid further errors. + self.qualif = self.qualif - Qualif::STATIC; + self.add(Qualif::NOT_CONST); + + false + } else { + true + } + } + + /// Assign the current qualification to the given destination. + fn assign(&mut self, dest: &Lvalue<'tcx>) { + let qualif = self.qualif; + let span = self.span; + let store = |slot: &mut Option| { + if slot.is_some() { + span_bug!(span, "multiple assignments to {:?}", dest); + } + *slot = Some(qualif); + }; + + // Only handle promotable temps in non-const functions. + if self.mode == Mode::Fn { + if let Lvalue::Temp(index) = *dest { + if self.temp_promotion_state[index as usize].is_promotable() { + store(&mut self.temp_qualif[index as usize]); + } + } + return; + } + + match *dest { + Lvalue::Temp(index) => store(&mut self.temp_qualif[index as usize]), + Lvalue::ReturnPointer => store(&mut self.return_qualif), + + Lvalue::Projection(box Projection { + base: Lvalue::Temp(index), + elem: ProjectionElem::Deref + }) if self.mir.temp_decls[index as usize].ty.is_unique() + && self.temp_qualif[index as usize].map_or(false, |qualif| { + qualif.intersects(Qualif::NOT_CONST) + }) => { + // Part of `box expr`, we should've errored + // already for the Box allocation Rvalue. + } + + // This must be an explicit assignment. + _ => { + // Catch more errors in the destination. + self.visit_lvalue(dest, LvalueContext::Store); + self.statement_like(); + } + } + } + + /// Returns true if the block ends in a bounds check branch, i.e.: + /// len = Len(array); + /// cond = Lt(idx, len); + /// if cond { + /// ... + /// } else { + /// loc = (...); + /// loc_ref = &loc; + /// panic_bounds_check(loc_ref, idx, len); + /// } + fn is_bounds_check(&self, bb: BasicBlock, + cond_op: &Operand<'tcx>, + if_else: BasicBlock) -> bool { + use rustc::mir::repr::Lvalue::*; + use rustc::mir::repr::Operand::Consume; + use rustc::mir::repr::Rvalue::*; + use rustc::mir::repr::StatementKind::*; + use rustc::mir::repr::TerminatorKind::*; + + let stmts = &self.mir[bb].statements; + let stmts_panic = &self.mir[if_else].statements; + if stmts.len() < 2 || stmts_panic.len() != 2 { + return false; + } + + let all = (&stmts[stmts.len() - 2].kind, + &stmts[stmts.len() - 1].kind, + cond_op, + &stmts_panic[0].kind, + &stmts_panic[1].kind, + &self.mir[if_else].terminator().kind); + match all { + (&Assign(Temp(len), Len(_)), + &Assign(Temp(cond), BinaryOp(BinOp::Lt, ref idx, Consume(Temp(len2)))), + /* if */ &Consume(Temp(cond2)), /* {...} else */ + &Assign(Temp(loc), Aggregate(..)), + &Assign(Temp(loc_ref), Ref(_, _, Temp(loc2))), + &Call { + func: Operand::Constant(Constant { + literal: Literal::Item { def_id, .. }, .. + }), + ref args, + destination: None, + .. + }) => { + len == len2 && cond == cond2 && loc == loc2 && + args[0] == Consume(Temp(loc_ref)) && + args[1] == *idx && + args[2] == Consume(Temp(len)) && + Some(def_id) == self.tcx.lang_items.panic_bounds_check_fn() + } + _ => false + } + } + + /// Qualify a whole const, static initializer or const fn. + fn qualify_const(&mut self) -> Qualif { + let mir = self.mir; + + let mut seen_blocks = BitVector::new(mir.basic_blocks.len()); + let mut bb = START_BLOCK; + loop { + seen_blocks.insert(bb.index()); + + self.visit_basic_block_data(bb, &mir[bb]); + + let target = match mir[bb].terminator().kind { + TerminatorKind::Goto { target } | + // Drops are considered noops. + TerminatorKind::Drop { target, .. } | + TerminatorKind::Call { destination: Some((_, target)), .. } => { + Some(target) + } + + // Non-terminating calls cannot produce any value. + TerminatorKind::Call { destination: None, .. } => { + return Qualif::empty(); + } + + // Need to allow bounds checking branches. + TerminatorKind::If { ref cond, targets: (if_true, if_else) } => { + if self.is_bounds_check(bb, cond, if_else) { + Some(if_true) + } else { + None + } + } + + TerminatorKind::Switch {..} | + TerminatorKind::SwitchInt {..} | + TerminatorKind::Resume => None, + + TerminatorKind::Return => { + // Check for unused values. This usually means + // there are extra statements in the AST. + for i in 0..mir.temp_decls.len() { + if self.temp_qualif[i].is_none() { + continue; + } + + let state = self.temp_promotion_state[i]; + 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. + if stmt_idx < data.statements.len() { + self.span = data.statements[stmt_idx].span; + } else { + self.span = data.terminator().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 0..mir.var_decls.len() { + if !self.const_fn_arg_vars.contains(index) { + self.assign(&Lvalue::Var(index as u32)); + } + } + + break; + } + }; + + match target { + // No loops allowed. + Some(target) if !seen_blocks.contains(target.index()) => { + bb = target; + } + _ => { + self.not_const(); + break; + } + } + } + + let return_ty = mir.return_ty.unwrap(); + self.qualif = self.return_qualif.unwrap_or(Qualif::NOT_CONST); + + match self.mode { + Mode::StaticMut => { + // Check for destructors in static mut. + self.add_type(return_ty); + self.deny_drop(); + } + _ => { + // Account for errors in consts by using the + // conservative type qualification instead. + if self.qualif.intersects(Qualif::CONST_ERROR) { + self.qualif = Qualif::empty(); + self.add_type(return_ty); + } + } + } + self.qualif + } +} + +/// Accumulates an Rvalue or Call's effects in self.qualif. +/// For functions (constant or not), it also records +/// candidates for promotion in promotion_candidates. +impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx> { + fn visit_lvalue(&mut self, lvalue: &Lvalue<'tcx>, context: LvalueContext) { + match *lvalue { + Lvalue::Arg(_) => { + self.add(Qualif::FN_ARGUMENT); + } + Lvalue::Var(_) => { + self.add(Qualif::NOT_CONST); + } + Lvalue::Temp(index) => { + if let Some(qualif) = self.temp_qualif[index as usize] { + self.add(qualif); + } else { + self.not_const(); + } + } + Lvalue::Static(_) => { + self.add(Qualif::STATIC); + if self.mode == Mode::Const || self.mode == Mode::ConstFn { + span_err!(self.tcx.sess, self.span, E0013, + "{}s cannot refer to statics, use \ + a constant instead", self.mode); + } + } + Lvalue::ReturnPointer => { + self.not_const(); + } + Lvalue::Projection(ref proj) => { + self.nest(|this| { + this.super_lvalue(lvalue, context); + match proj.elem { + ProjectionElem::Deref => { + if !this.try_consume() { + return; + } + + if this.qualif.intersects(Qualif::STATIC_REF) { + this.qualif = this.qualif - Qualif::STATIC_REF; + this.add(Qualif::STATIC); + } + + let base_ty = this.mir.lvalue_ty(this.tcx, &proj.base) + .to_ty(this.tcx); + if let ty::TyRawPtr(_) = base_ty.sty { + this.add(Qualif::NOT_CONST); + if this.mode != Mode::Fn { + span_err!(this.tcx.sess, this.span, E0396, + "raw pointers cannot be dereferenced in {}s", + this.mode); + } + } + } + + ProjectionElem::Field(..) | + ProjectionElem::Index(_) => { + if this.mode != Mode::Fn && + this.qualif.intersects(Qualif::STATIC) { + span_err!(this.tcx.sess, this.span, E0494, + "cannot refer to the interior of another \ + static, use a constant instead"); + } + let ty = this.mir.lvalue_ty(this.tcx, lvalue) + .to_ty(this.tcx); + this.qualif.restrict(ty, &this.param_env); + } + + ProjectionElem::ConstantIndex {..} | + ProjectionElem::Downcast(..) => { + this.not_const() + } + } + }); + } + } + } + + fn visit_operand(&mut self, operand: &Operand<'tcx>) { + match *operand { + Operand::Consume(_) => { + self.nest(|this| { + this.super_operand(operand); + this.try_consume(); + }); + } + Operand::Constant(ref constant) => { + // Only functions and methods can have these types. + if let ty::TyFnDef(..) = constant.ty.sty { + return; + } + + if let Literal::Item { def_id, substs } = constant.literal { + // Don't peek inside generic (associated) constants. + if !substs.types.is_empty() { + self.add_type(constant.ty); + } else { + let qualif = qualify_const_item_cached(self.tcx, + self.qualif_map, + self.mir_map, + def_id); + self.add(qualif); + } + + // FIXME(eddyb) check recursive constants here, + // instead of rustc_passes::static_recursion. + if self.qualif.intersects(Qualif::RECURSIVE) { + span_bug!(constant.span, + "recursive constant wasn't caught earlier"); + } + + // Let `const fn` transitively have destructors, + // but they do get stopped in `const` or `static`. + if self.mode != Mode::ConstFn { + self.deny_drop(); + } + } + } + } + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>) { + // Recurse through operands and lvalues. + self.super_rvalue(rvalue); + + match *rvalue { + Rvalue::Use(_) | + Rvalue::Repeat(..) | + Rvalue::UnaryOp(..) | + Rvalue::Cast(CastKind::ReifyFnPointer, _, _) | + Rvalue::Cast(CastKind::UnsafeFnPointer, _, _) | + Rvalue::Cast(CastKind::Unsize, _, _) => {} + + Rvalue::Len(_) => { + // Static lvalues in consts would have errored already, + // don't treat length checks as reads from statics. + self.qualif = self.qualif - Qualif::STATIC; + } + + Rvalue::Ref(_, kind, ref lvalue) => { + // Static lvalues in consts would have errored already, + // only keep track of references to them here. + if self.qualif.intersects(Qualif::STATIC) { + self.qualif = self.qualif - Qualif::STATIC; + self.add(Qualif::STATIC_REF); + } + + let ty = self.mir.lvalue_ty(self.tcx, lvalue).to_ty(self.tcx); + if kind == BorrowKind::Mut { + // In theory, any zero-sized value could be borrowed + // mutably without consequences. However, only &mut [] + // is allowed right now, and only in functions. + let allow = if let ty::TyArray(_, 0) = ty.sty { + self.mode == Mode::Fn + } else if self.mode == Mode::StaticMut { + // Inside a `static mut`, &mut [...] is also allowed. + match ty.sty { + ty::TyArray(..) | ty::TySlice(_) => { + // Mutating can expose drops, be conservative. + self.add_type(ty); + self.deny_drop(); + true + } + _ => false + } + } else { + false + }; + + if !allow { + self.add(Qualif::NOT_CONST); + if self.mode != Mode::Fn { + span_err!(self.tcx.sess, self.span, E0017, + "references in {}s may only refer \ + to immutable values", self.mode); + } + } + } else { + // Constants cannot be borrowed if they contain interior mutability as + // it means that our "silent insertion of statics" could change + // initializer values (very bad). + if self.qualif.intersects(Qualif::MUTABLE_INTERIOR) { + // Replace MUTABLE_INTERIOR with NOT_CONST to avoid + // duplicate errors (from reborrowing, for example). + self.qualif = self.qualif - Qualif::MUTABLE_INTERIOR; + self.add(Qualif::NOT_CONST); + if self.mode != Mode::Fn { + span_err!(self.tcx.sess, self.span, E0492, + "cannot borrow a constant which contains \ + interior mutability, create a static instead"); + } + } + } + + // We might have a candidate for promotion. + let candidate = Candidate::Ref(self.location); + if self.mode == Mode::Fn || self.mode == Mode::ConstFn { + if !self.qualif.intersects(Qualif::UNPROMOTABLE) { + self.promotion_candidates.push(candidate); + } + } + } + + Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => { + let operand_ty = self.mir.operand_ty(self.tcx, operand); + let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast"); + let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast"); + match (cast_in, cast_out) { + (CastTy::Ptr(_), CastTy::Int(_)) | + (CastTy::FnPtr, CastTy::Int(_)) => { + self.add(Qualif::NOT_CONST); + if self.mode != Mode::Fn { + span_err!(self.tcx.sess, self.span, E0018, + "raw pointers cannot be cast to integers in {}s", + self.mode); + } + } + _ => {} + } + } + + Rvalue::BinaryOp(op, ref lhs, _) => { + if let ty::TyRawPtr(_) = self.mir.operand_ty(self.tcx, lhs).sty { + assert!(op == BinOp::Eq || op == BinOp::Ne || + op == BinOp::Le || op == BinOp::Lt || + op == BinOp::Ge || op == BinOp::Gt); + + self.add(Qualif::NOT_CONST); + if self.mode != Mode::Fn { + span_err!(self.tcx.sess, self.span, E0395, + "raw pointers cannot be compared in {}s", + self.mode); + } + } + } + + Rvalue::Box(_) => { + self.add(Qualif::NOT_CONST); + if self.mode != Mode::Fn { + span_err!(self.tcx.sess, self.span, E0010, + "allocations are not allowed in {}s", self.mode); + } + } + + Rvalue::Aggregate(ref kind, _) => { + if let AggregateKind::Adt(def, _, _) = *kind { + if def.has_dtor() { + self.add(Qualif::NEEDS_DROP); + self.deny_drop(); + } + + if Some(def.did) == self.tcx.lang_items.unsafe_cell_type() { + let ty = self.mir.rvalue_ty(self.tcx, rvalue).unwrap(); + self.add_type(ty); + assert!(self.qualif.intersects(Qualif::MUTABLE_INTERIOR)); + // Even if the value inside may not need dropping, + // mutating it would change that. + if !self.qualif.intersects(Qualif::NOT_CONST) { + self.deny_drop(); + } + } + } + } + + Rvalue::Slice {..} | + Rvalue::InlineAsm {..} => { + self.not_const(); + } + } + } + + fn visit_terminator_kind(&mut self, bb: BasicBlock, kind: &TerminatorKind<'tcx>) { + if let TerminatorKind::Call { ref func, ref args, ref destination, .. } = *kind { + self.visit_operand(func); + + let fn_ty = self.mir.operand_ty(self.tcx, func); + let (is_shuffle, is_const_fn) = match fn_ty.sty { + ty::TyFnDef(def_id, _, f) => { + (f.abi == Abi::PlatformIntrinsic && + self.tcx.item_name(def_id).as_str().starts_with("simd_shuffle"), + is_const_fn(self.tcx, def_id)) + } + _ => (false, false) + }; + + for (i, arg) in args.iter().enumerate() { + self.nest(|this| { + this.visit_operand(arg); + if is_shuffle && i == 2 && this.mode == Mode::Fn { + let candidate = Candidate::ShuffleIndices(bb); + if !this.qualif.intersects(Qualif::UNPROMOTABLE) { + this.promotion_candidates.push(candidate); + } else { + span_err!(this.tcx.sess, this.span, E0526, + "shuffle indices are not constant"); + } + } + }); + } + + // Const fn calls. + if is_const_fn { + // We are in a const or static initializer, + if self.mode != Mode::Fn && + + // feature-gate is not enabled, + !self.tcx.sess.features.borrow().const_fn && + + // this doesn't come from a crate with the feature-gate enabled, + self.def_id.is_local() && + + // this doesn't come from a macro that has #[allow_internal_unstable] + !self.tcx.sess.codemap().span_allows_unstable(self.span) + { + let mut err = self.tcx.sess.struct_span_err(self.span, + "const fns are an unstable feature"); + help!(&mut err, + "in Nightly builds, add `#![feature(const_fn)]` \ + to the crate attributes to enable"); + err.emit(); + } + } else { + self.qualif = Qualif::NOT_CONST; + if self.mode != Mode::Fn { + // FIXME(#24111) Remove this check when const fn stabilizes + let (msg, note) = if let UnstableFeatures::Disallow = + self.tcx.sess.opts.unstable_features { + (format!("calls in {}s are limited to \ + struct and enum constructors", + self.mode), + Some("a limited form of compile-time function \ + evaluation is available on a nightly \ + compiler via `const fn`")) + } else { + (format!("calls in {}s are limited \ + to constant functions, \ + struct and enum constructors", + self.mode), + None) + }; + let mut err = struct_span_err!(self.tcx.sess, self.span, E0015, "{}", msg); + if let Some(note) = note { + err.span_note(self.span, note); + } + err.emit(); + } + } + + if let Some((ref dest, _)) = *destination { + // Avoid propagating irrelevant callee/argument qualifications. + if self.qualif.intersects(Qualif::CONST_ERROR) { + self.qualif = Qualif::NOT_CONST; + } else { + // Be conservative about the returned value of a const fn. + let tcx = self.tcx; + let ty = self.mir.lvalue_ty(tcx, dest).to_ty(tcx); + self.qualif = Qualif::empty(); + self.add_type(ty); + + // Let `const fn` transitively have destructors, + // but they do get stopped in `const` or `static`. + if self.mode != Mode::ConstFn { + self.deny_drop(); + } + } + self.assign(dest); + } + } else { + // Qualify any operands inside other terminators. + self.super_terminator_kind(bb, kind); + } + } + + fn visit_assign(&mut self, _: BasicBlock, dest: &Lvalue<'tcx>, rvalue: &Rvalue<'tcx>) { + self.visit_rvalue(rvalue); + + // Check the allowed const fn argument forms. + if let (Mode::ConstFn, &Lvalue::Var(index)) = (self.mode, dest) { + if self.const_fn_arg_vars.insert(index as usize) { + // Direct use of an argument is permitted. + if let Rvalue::Use(Operand::Consume(Lvalue::Arg(_))) = *rvalue { + return; + } + + // Avoid a generic error for other uses of arguments. + if self.qualif.intersects(Qualif::FN_ARGUMENT) { + let decl = &self.mir.var_decls[index as usize]; + span_err!(self.tcx.sess, decl.span, E0022, + "arguments of constant functions can only \ + be immutable by-value bindings"); + return; + } + } + } + + self.assign(dest); + } + + fn visit_statement(&mut self, bb: BasicBlock, statement: &Statement<'tcx>) { + assert_eq!(self.location.block, bb); + self.span = statement.span; + self.nest(|this| this.super_statement(bb, statement)); + self.location.statement_index += 1; + } + + fn visit_terminator(&mut self, bb: BasicBlock, terminator: &Terminator<'tcx>) { + assert_eq!(self.location.block, bb); + self.span = terminator.span; + self.nest(|this| this.super_terminator(bb, terminator)); + } + + fn visit_basic_block_data(&mut self, bb: BasicBlock, data: &BasicBlockData<'tcx>) { + self.location.statement_index = 0; + self.location.block = bb; + self.super_basic_block_data(bb, data); + } +} + +fn qualify_const_item_cached<'tcx>(tcx: &TyCtxt<'tcx>, + qualif_map: &mut DefIdMap, + mir_map: Option<&MirMap<'tcx>>, + def_id: DefId) + -> Qualif { + match qualif_map.entry(def_id) { + Entry::Occupied(entry) => return *entry.get(), + Entry::Vacant(entry) => { + // Guard against `const` recursion. + entry.insert(Qualif::RECURSIVE); + } + } + + let extern_mir; + let param_env_and_mir = if def_id.is_local() { + let node_id = tcx.map.as_local_node_id(def_id).unwrap(); + mir_map.and_then(|map| map.map.get(&node_id)).map(|mir| { + (ty::ParameterEnvironment::for_item(tcx, node_id), mir) + }) + } else if let Some(mir) = tcx.sess.cstore.maybe_get_item_mir(tcx, def_id) { + // These should only be monomorphic constants. + extern_mir = mir; + Some((tcx.empty_parameter_environment(), &extern_mir)) + } else { + None + }; + + let (param_env, mir) = param_env_and_mir.unwrap_or_else(|| { + bug!("missing constant MIR for {}", tcx.item_path_str(def_id)) + }); + + let mut qualifier = Qualifier::new(param_env, qualif_map, mir_map, + def_id, mir, Mode::Const); + let qualif = qualifier.qualify_const(); + qualifier.qualif_map.insert(def_id, qualif); + qualif +} + +pub struct QualifyAndPromoteConstants; + +impl Pass for QualifyAndPromoteConstants {} + +impl<'tcx> MirMapPass<'tcx> for QualifyAndPromoteConstants { + fn run_pass(&mut self, tcx: &TyCtxt<'tcx>, map: &mut MirMap<'tcx>) { + let mut qualif_map = DefIdMap(); + + // First, visit `const` items, potentially recursing, to get + // accurate MUTABLE_INTERIOR and NEEDS_DROP qualifications. + for &id in map.map.keys() { + let def_id = tcx.map.local_def_id(id); + let _task = tcx.dep_graph.in_task(self.dep_node(def_id)); + let src = MirSource::from_node(tcx, id); + if let MirSource::Const(_) = src { + qualify_const_item_cached(tcx, &mut qualif_map, Some(map), def_id); + } + } + + // Then, handle everything else, without recursing, + // as the MIR map is not shared, since promotion + // in functions (including `const fn`) mutates it. + for (&id, mir) in &mut map.map { + let def_id = tcx.map.local_def_id(id); + let _task = tcx.dep_graph.in_task(self.dep_node(def_id)); + let src = MirSource::from_node(tcx, id); + let mode = match src { + MirSource::Fn(_) => { + if is_const_fn(tcx, def_id) { + Mode::ConstFn + } else { + Mode::Fn + } + } + MirSource::Const(_) => continue, + MirSource::Static(_, hir::MutImmutable) => Mode::Static, + MirSource::Static(_, hir::MutMutable) => Mode::StaticMut, + MirSource::Promoted(..) => bug!() + }; + let param_env = ty::ParameterEnvironment::for_item(tcx, id); + + if mode == Mode::Fn || mode == Mode::ConstFn { + // This is ugly because Qualifier holds onto mir, + // which can't be mutated until its scope ends. + let (temps, candidates) = { + let mut qualifier = Qualifier::new(param_env, &mut qualif_map, + None, def_id, mir, mode); + if mode == Mode::ConstFn { + // Enforce a constant-like CFG for `const fn`. + qualifier.qualify_const(); + } else { + while let Some((bb, data)) = qualifier.rpo.next() { + qualifier.visit_basic_block_data(bb, data); + } + } + + (qualifier.temp_promotion_state, + qualifier.promotion_candidates) + }; + + // Do the actual promotion, now that we know what's viable. + promote_consts::promote_candidates(mir, tcx, temps, candidates); + } else { + let mut qualifier = Qualifier::new(param_env, &mut qualif_map, + None, def_id, mir, mode); + qualifier.qualify_const(); + } + + // Statics must be Sync. + if mode == Mode::Static { + let ty = mir.return_ty.unwrap(); + let infcx = infer::new_infer_ctxt(tcx, + &tcx.tables, + None, + ProjectionMode::AnyFinal); + let cause = traits::ObligationCause::new(mir.span, id, traits::SharedStatic); + let mut fulfillment_cx = traits::FulfillmentContext::new(); + fulfillment_cx.register_builtin_bound(&infcx, ty, ty::BoundSync, cause); + if let Err(err) = fulfillment_cx.select_all_or_error(&infcx) { + traits::report_fulfillment_errors(&infcx, &err); + } + + if let Err(ref errors) = fulfillment_cx.select_rfc1592_obligations(&infcx) { + traits::report_fulfillment_errors_as_warnings(&infcx, errors, id); + } + } + } + } +} diff --git a/src/librustc_passes/const_fn.rs b/src/librustc_passes/const_fn.rs deleted file mode 100644 index 97a4c14863d..00000000000 --- a/src/librustc_passes/const_fn.rs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Verifies that const fn arguments are immutable by value bindings -//! and the const fn body doesn't contain any statements - -use rustc::session::{Session, CompileResult}; - -use syntax::ast::{self, PatKind}; -use syntax::visit::{self, Visitor, FnKind}; -use syntax::codemap::Span; - -pub fn check_crate(sess: &Session, krate: &ast::Crate) -> CompileResult { - sess.track_errors(|| { - visit::walk_crate(&mut CheckConstFn{ sess: sess }, krate); - }) -} - -struct CheckConstFn<'a> { - sess: &'a Session, -} - -struct CheckBlock<'a> { - sess: &'a Session, - kind: &'static str, -} - -impl<'a, 'v> Visitor<'v> for CheckBlock<'a> { - fn visit_block(&mut self, block: &'v ast::Block) { - check_block(&self.sess, block, self.kind); - CheckConstFn{ sess: self.sess}.visit_block(block); - } - fn visit_expr(&mut self, e: &'v ast::Expr) { - if let ast::ExprKind::Closure(..) = e.node { - CheckConstFn{ sess: self.sess}.visit_expr(e); - } else { - visit::walk_expr(self, e); - } - } - fn visit_item(&mut self, _i: &'v ast::Item) { bug!("should be handled in CheckConstFn") } - fn visit_fn(&mut self, - _fk: FnKind<'v>, - _fd: &'v ast::FnDecl, - _b: &'v ast::Block, - _s: Span, - _fn_id: ast::NodeId) { bug!("should be handled in CheckConstFn") } -} - -fn check_block(sess: &Session, b: &ast::Block, kind: &'static str) { - // Check all statements in the block - for stmt in &b.stmts { - let span = match stmt.node { - ast::StmtKind::Decl(ref decl, _) => { - match decl.node { - ast::DeclKind::Local(_) => decl.span, - - // Item statements are allowed - ast::DeclKind::Item(_) => continue, - } - } - ast::StmtKind::Expr(ref expr, _) => expr.span, - ast::StmtKind::Semi(ref semi, _) => semi.span, - ast::StmtKind::Mac(..) => bug!(), - }; - span_err!(sess, span, E0016, - "blocks in {}s are limited to items and tail expressions", kind); - } -} - -impl<'a, 'v> Visitor<'v> for CheckConstFn<'a> { - fn visit_item(&mut self, i: &'v ast::Item) { - visit::walk_item(self, i); - match i.node { - ast::ItemKind::Const(_, ref e) => { - CheckBlock{ sess: self.sess, kind: "constant"}.visit_expr(e) - }, - ast::ItemKind::Static(_, _, ref e) => { - CheckBlock{ sess: self.sess, kind: "static"}.visit_expr(e) - }, - _ => {}, - } - } - - fn visit_fn(&mut self, - fk: FnKind<'v>, - fd: &'v ast::FnDecl, - b: &'v ast::Block, - s: Span, - _fn_id: ast::NodeId) { - visit::walk_fn(self, fk, fd, b, s); - match fk { - FnKind::ItemFn(_, _, _, ast::Constness::Const, _, _) => {}, - FnKind::Method(_, m, _) if m.constness == ast::Constness::Const => {}, - _ => return, - } - - // Ensure the arguments are simple, not mutable/by-ref or patterns. - for arg in &fd.inputs { - match arg.pat.node { - PatKind::Wild => {} - PatKind::Ident(ast::BindingMode::ByValue(ast::Mutability::Immutable), _, None) => {} - _ => { - span_err!(self.sess, arg.pat.span, E0022, - "arguments of constant functions can only \ - be immutable by-value bindings"); - } - } - } - check_block(&self.sess, b, "const function"); - } -} diff --git a/src/librustc_passes/consts.rs b/src/librustc_passes/consts.rs index 6fb9739fca4..c417ec79ff2 100644 --- a/src/librustc_passes/consts.rs +++ b/src/librustc_passes/consts.rs @@ -40,7 +40,7 @@ use rustc::infer; use rustc::middle::mem_categorization as mc; use rustc::middle::mem_categorization::Categorization; use rustc::ty::{self, Ty, TyCtxt}; -use rustc::traits::{self, ProjectionMode}; +use rustc::traits::ProjectionMode; use rustc::util::nodemap::NodeMap; use rustc::middle::const_qualif::ConstQualif; use rustc::lint::builtin::CONST_ERR; @@ -48,7 +48,6 @@ use rustc::lint::builtin::CONST_ERR; use rustc::hir::{self, PatKind}; use syntax::ast; use syntax::codemap::Span; -use syntax::feature_gate::UnstableFeatures; use rustc::hir::intravisit::{self, FnKind, Visitor}; use std::collections::hash_map::Entry; @@ -180,31 +179,11 @@ impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> { /// Returns true if the call is to a const fn or method. fn handle_const_fn_call(&mut self, - expr: &hir::Expr, + _expr: &hir::Expr, def_id: DefId, ret_ty: Ty<'tcx>) -> bool { if let Some(fn_like) = lookup_const_fn_by_id(self.tcx, def_id) { - if - // we are in a static/const initializer - self.mode != Mode::Var && - - // feature-gate is not enabled - !self.tcx.sess.features.borrow().const_fn && - - // this doesn't come from a macro that has #[allow_internal_unstable] - !self.tcx.sess.codemap().span_allows_unstable(expr.span) - { - let mut err = self.tcx.sess.struct_span_err( - expr.span, - "const fns are an unstable feature"); - help!( - &mut err, - "in Nightly builds, add `#![feature(const_fn)]` to the crate \ - attributes to enable"); - err.emit(); - } - let qualif = self.fn_like(fn_like.kind(), fn_like.decl(), fn_like.body(), @@ -245,42 +224,6 @@ impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> { Mode::Var => bug!(), } } - - fn check_static_mut_type(&self, e: &hir::Expr) { - let node_ty = self.tcx.node_id_to_type(e.id); - let tcontents = node_ty.type_contents(self.tcx); - - let suffix = if tcontents.has_dtor() { - "destructors" - } else if tcontents.owns_owned() { - "boxes" - } else { - return - }; - - span_err!(self.tcx.sess, e.span, E0397, - "mutable statics are not allowed to have {}", suffix); - } - - fn check_static_type(&self, e: &hir::Expr) { - let ty = self.tcx.node_id_to_type(e.id); - let infcx = infer::new_infer_ctxt(self.tcx, - &self.tcx.tables, - None, - ProjectionMode::AnyFinal); - let cause = traits::ObligationCause::new(e.span, e.id, traits::SharedStatic); - let mut fulfillment_cx = traits::FulfillmentContext::new(); - fulfillment_cx.register_builtin_bound(&infcx, ty, ty::BoundSync, cause); - match fulfillment_cx.select_all_or_error(&infcx) { - Ok(()) => { }, - Err(ref errors) => { - traits::report_fulfillment_errors(&infcx, errors); - } - } - if let Err(ref errors) = fulfillment_cx.select_rfc1592_obligations(&infcx) { - traits::report_fulfillment_errors_as_warnings(&infcx, errors, e.id); - } - } } impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { @@ -289,11 +232,9 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { assert_eq!(self.mode, Mode::Var); match i.node { hir::ItemStatic(_, hir::MutImmutable, ref expr) => { - self.check_static_type(&expr); self.global_expr(Mode::Static, &expr); } hir::ItemStatic(_, hir::MutMutable, ref expr) => { - self.check_static_mut_type(&expr); self.global_expr(Mode::StaticMut, &expr); } hir::ItemConst(_, ref expr) => { @@ -360,8 +301,9 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { "lower range bound must be less than or equal to upper"); } None => { - self.tcx.sess.delay_span_bug(start.span, - "non-constant path in constant expr"); + span_err!(self.tcx.sess, p.span, E0014, + "paths in {}s may only refer to constants", + self.msg()); } } } @@ -384,8 +326,6 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { hir::StmtSemi(_, _) => {}, } self.add_qualif(ConstQualif::NOT_CONST); - // anything else should have been caught by check_const_fn - assert_eq!(self.mode, Mode::Var); } intravisit::walk_block(self, block); } @@ -455,11 +395,6 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { let tc = node_ty.type_contents(self.tcx); if self.qualif.intersects(ConstQualif::MUTABLE_MEM) && tc.interior_unsafe() { outer = outer | ConstQualif::NOT_CONST; - if self.mode != Mode::Var { - span_err!(self.tcx.sess, ex.span, E0492, - "cannot borrow a constant which contains \ - interior mutability, create a static instead"); - } } // If the reference has to be 'static, avoid in-place initialization // as that will end up pointing to the stack instead. @@ -474,10 +409,6 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { if self.mode == Mode::Var { outer = outer | ConstQualif::NOT_CONST; self.add_qualif(ConstQualif::MUTABLE_MEM); - } else { - span_err!(self.tcx.sess, ex.span, E0017, - "references in {}s may only refer \ - to immutable values", self.msg()) } } if !self.qualif.intersects(ConstQualif::NON_STATIC_BORROWS) { @@ -525,11 +456,6 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, ty::TyStruct(def, _) | ty::TyEnum(def, _) if def.has_dtor() => { v.add_qualif(ConstQualif::NEEDS_DROP); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0493, - "{}s are not allowed to have destructors", - v.msg()); - } } _ => {} } @@ -540,17 +466,9 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, hir::ExprBinary(..) | hir::ExprIndex(..) if v.tcx.tables.borrow().method_map.contains_key(&method_call) => { v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0011, - "user-defined operators are not allowed in {}s", v.msg()); - } } hir::ExprBox(_) => { v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0010, - "allocations are not allowed in {}s", v.msg()); - } } hir::ExprUnary(op, ref inner) => { match v.tcx.node_id_to_type(inner.id).sty { @@ -558,10 +476,6 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, assert!(op == hir::UnDeref); v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0396, - "raw pointers cannot be dereferenced in {}s", v.msg()); - } } _ => {} } @@ -574,10 +488,6 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, op.node == hir::BiGe || op.node == hir::BiGt); v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0395, - "raw pointers cannot be compared in {}s", v.msg()); - } } _ => {} } @@ -588,10 +498,6 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, None => span_bug!(e.span, "no kind for cast"), Some(&CastKind::PtrAddrCast) | Some(&CastKind::FnPtrAddrCast) => { v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0018, - "raw pointers cannot be cast to integers in {}s", v.msg()); - } } _ => {} } @@ -616,11 +522,7 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, Some(Def::Static(..)) => { match v.mode { Mode::Static | Mode::StaticMut => {} - Mode::Const | Mode::ConstFn => { - span_err!(v.tcx.sess, e.span, E0013, - "{}s cannot refer to other statics, insert \ - an intermediate constant instead", v.msg()); - } + Mode::Const | Mode::ConstFn => {} Mode::Var => v.add_qualif(ConstQualif::NOT_CONST) } } @@ -636,14 +538,8 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, // Sadly, we can't determine whether the types are zero-sized. v.add_qualif(ConstQualif::NOT_CONST | ConstQualif::NON_ZERO_SIZED); } - def => { + _ => { v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - debug!("(checking const) found bad def: {:?}", def); - span_err!(v.tcx.sess, e.span, E0014, - "paths in {}s may only refer to constants \ - or functions", v.msg()); - } } } } @@ -681,29 +577,6 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, }; if !is_const { v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - // FIXME(#24111) Remove this check when const fn stabilizes - let (msg, note) = - if let UnstableFeatures::Disallow = v.tcx.sess.opts.unstable_features { - (format!("function calls in {}s are limited to \ - struct and enum constructors", - v.msg()), - Some("a limited form of compile-time function \ - evaluation is available on a nightly \ - compiler via `const fn`")) - } else { - (format!("function calls in {}s are limited \ - to constant functions, \ - struct and enum constructors", - v.msg()), - None) - }; - let mut err = struct_span_err!(v.tcx.sess, e.span, E0015, "{}", msg); - if let Some(note) = note { - err.span_note(e.span, note); - } - err.emit(); - } } } hir::ExprMethodCall(..) => { @@ -714,11 +587,6 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, }; if !is_const { v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0378, - "method calls in {}s are limited to \ - constant inherent methods", v.msg()); - } } } hir::ExprStruct(..) => { @@ -773,10 +641,6 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, hir::ExprAssignOp(..) | hir::ExprInlineAsm(..) => { v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0019, - "{} contains unimplemented expression type", v.msg()); - } } } } @@ -796,11 +660,6 @@ fn check_adjustments<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Exp v.tcx.is_overloaded_autoderef(e.id, autoderef) }) { v.add_qualif(ConstQualif::NOT_CONST); - if v.mode != Mode::Var { - span_err!(v.tcx.sess, e.span, E0400, - "user-defined dereference operators are not allowed in {}s", - v.msg()); - } } } } @@ -819,21 +678,13 @@ pub fn check_crate(tcx: &TyCtxt) { impl<'a, 'tcx> euv::Delegate<'tcx> for CheckCrateVisitor<'a, 'tcx> { fn consume(&mut self, _consume_id: ast::NodeId, - consume_span: Span, + _consume_span: Span, cmt: mc::cmt, _mode: euv::ConsumeMode) { let mut cur = &cmt; loop { match cur.cat { Categorization::StaticItem => { - if self.mode != Mode::Var { - // statics cannot be consumed by value at any time, that would imply - // that they're an initializer (what a const is for) or kept in sync - // over time (not feasible), so deny it outright. - span_err!(self.tcx.sess, consume_span, E0394, - "cannot refer to other statics by value, use the \ - address-of operator or a constant instead"); - } break; } Categorization::Deref(ref cmt, _, _) | @@ -848,7 +699,7 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for CheckCrateVisitor<'a, 'tcx> { } fn borrow(&mut self, borrow_id: ast::NodeId, - borrow_span: Span, + _borrow_span: Span, cmt: mc::cmt<'tcx>, _loan_region: ty::Region, bk: ty::BorrowKind, @@ -866,7 +717,6 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for CheckCrateVisitor<'a, 'tcx> { } let mut cur = &cmt; - let mut is_interior = false; loop { match cur.cat { Categorization::Rvalue(..) => { @@ -891,20 +741,11 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for CheckCrateVisitor<'a, 'tcx> { break; } Categorization::StaticItem => { - if is_interior && self.mode != Mode::Var { - // Borrowed statics can specifically *only* have their address taken, - // not any number of other borrows such as borrowing fields, reading - // elements of an array, etc. - span_err!(self.tcx.sess, borrow_span, E0494, - "cannot refer to the interior of another \ - static, use a constant instead"); - } break; } Categorization::Deref(ref cmt, _, _) | Categorization::Downcast(ref cmt, _) | Categorization::Interior(ref cmt, _) => { - is_interior = true; cur = cmt; } diff --git a/src/librustc_passes/diagnostics.rs b/src/librustc_passes/diagnostics.rs index c89e9bb1959..77f896e011b 100644 --- a/src/librustc_passes/diagnostics.rs +++ b/src/librustc_passes/diagnostics.rs @@ -12,70 +12,6 @@ register_long_diagnostics! { -E0010: r##" -The value of statics and constants must be known at compile time, and they live -for the entire lifetime of a program. Creating a boxed value allocates memory on -the heap at runtime, and therefore cannot be done at compile time. Erroneous -code example: - -```compile_fail -#![feature(box_syntax)] - -const CON : Box = box 0; -``` -"##, - -E0011: r##" -Initializers for constants and statics are evaluated at compile time. -User-defined operators rely on user-defined functions, which cannot be evaluated -at compile time. - -Erroneous code example: - -```compile_fail -use std::ops::Index; - -struct Foo { a: u8 } - -impl Index for Foo { - type Output = u8; - - fn index<'a>(&'a self, idx: u8) -> &'a u8 { &self.a } -} - -const a: Foo = Foo { a: 0u8 }; -const b: u8 = a[0]; // Index trait is defined by the user, bad! -``` - -Only operators on builtin types are allowed. - -Example: - -``` -const a: &'static [i32] = &[1, 2, 3]; -const b: i32 = a[0]; // Ok! -``` -"##, - -E0013: r##" -Static and const variables can refer to other const variables. But a const -variable cannot refer to a static variable. For example, `Y` cannot refer to -`X` here: - -```compile_fail -static X: i32 = 42; -const Y: i32 = X; -``` - -To fix this, the value can be extracted as a const and then used: - -``` -const A: i32 = 42; -static X: i32 = A; -const Y: i32 = A; -``` -"##, - E0014: r##" Constants can only be initialized by a constant value or, in a future version of Rust, a call to a const function. This error indicates the use @@ -95,149 +31,6 @@ const FOO2: i32 = { 0 }; // but brackets are useless here ``` "##, -// FIXME(#24111) Change the language here when const fn stabilizes -E0015: r##" -The only functions that can be called in static or constant expressions are -`const` functions, and struct/enum constructors. `const` functions are only -available on a nightly compiler. Rust currently does not support more general -compile-time function execution. - -``` -const FOO: Option = Some(1); // enum constructor -struct Bar {x: u8} -const BAR: Bar = Bar {x: 1}; // struct constructor -``` - -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 -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: - -```compile_fail -static X: i32 = 1; -const C: i32 = 2; - -// these three are not allowed: -const CR: &'static mut i32 = &mut C; -static STATIC_REF: &'static mut i32 = &mut X; -static CONST_REF: &'static mut i32 = &mut C; -``` - -Statics are shared everywhere, and if they refer to mutable data one might -violate memory safety since holding multiple mutable references to shared data -is not allowed. - -If you really want global mutable state, try using `static mut` or a global -`UnsafeCell`. -"##, - -E0018: r##" - -The value of static and constant integers must be known at compile time. You -can't cast a pointer to an integer because the address of a pointer can -vary. - -For example, if you write: - -```compile_fail -static MY_STATIC: u32 = 42; -static MY_STATIC_ADDR: usize = &MY_STATIC as *const _ as usize; -static WHAT: usize = (MY_STATIC_ADDR^17) + MY_STATIC_ADDR; -``` - -Then `MY_STATIC_ADDR` would contain the address of `MY_STATIC`. However, -the address can change when the program is linked, as well as change -between different executions due to ASLR, and many linkers would -not be able to calculate the value of `WHAT`. - -On the other hand, static and constant pointers can point either to -a known numeric address or to the address of a symbol. - -``` -static MY_STATIC_ADDR: &'static u32 = &MY_STATIC; -// ... and also -static MY_STATIC_ADDR2: *const u32 = &MY_STATIC; - -const CONST_ADDR: *const u8 = 0x5f3759df as *const u8; -``` - -This does not pose a problem by itself because they can't be -accessed directly. -"##, - -E0019: r##" -A function call isn't allowed in the const's initialization expression -because the expression's value must be known at compile-time. Erroneous code -example: - -```compile_fail -enum Test { - V1 -} - -impl Test { - fn test(&self) -> i32 { - 12 - } -} - -fn main() { - const FOO: Test = Test::V1; - - const A: i32 = FOO.test(); // You can't call Test::func() here ! -} -``` - -Remember: you can't use a function call inside a const's initialization -expression! However, you can totally use it anywhere else: - -``` -fn main() { - const FOO: Test = Test::V1; - - FOO.func(); // here is good - let x = FOO.func(); // or even here! -} -``` -"##, - -E0022: r##" -Constant functions are not allowed to mutate anything. Thus, binding to an -argument with a mutable pattern is not allowed. For example, - -```compile_fail -const fn foo(mut x: u8) { - // do stuff -} -``` - -Is incorrect because the function body may not mutate `x`. - -Remove any mutable bindings from the argument list to fix this error. In case -you need to mutate the argument, try lazily initializing a global variable -instead of using a `const fn`, or refactoring the code to a functional style to -avoid mutation if possible. -"##, - E0030: r##" When matching against a range, the compiler verifies that the range is non-empty. Range patterns include both end-points, so this is equivalent to @@ -325,281 +118,6 @@ fn some_func() { ``` "##, -E0378: r##" -Method calls that aren't calls to inherent `const` methods are disallowed -in statics, constants, and constant functions. - -For example: - -```compile_fail -const BAZ: i32 = Foo(25).bar(); // error, `bar` isn't `const` - -struct Foo(i32); - -impl Foo { - const fn foo(&self) -> i32 { - self.bar() // error, `bar` isn't `const` - } - - fn bar(&self) -> i32 { self.0 } -} -``` - -For more information about `const fn`'s, see [RFC 911]. - -[RFC 911]: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md -"##, - -E0394: r##" -From [RFC 246]: - - > It is invalid for a static to reference another static by value. It is - > required that all references be borrowed. - -[RFC 246]: https://github.com/rust-lang/rfcs/pull/246 -"##, - - -E0395: r##" -The value assigned to a constant scalar must be known at compile time, -which is not the case when comparing raw pointers. - -Erroneous code example: - -```compile_fail -static FOO: i32 = 42; -static BAR: i32 = 42; - -static BAZ: bool = { (&FOO as *const i32) == (&BAR as *const i32) }; -// error: raw pointers cannot be compared in statics! -``` - -The address assigned by the linker to `FOO` and `BAR` may or may not -be identical, so the value of `BAZ` can't be determined. - -If you want to do the comparison, please do it at run-time. - -For example: - -``` -static FOO: i32 = 42; -static BAR: i32 = 42; - -let baz: bool = { (&FOO as *const i32) == (&BAR as *const i32) }; -// baz isn't a constant expression so it's ok -``` -"##, - -E0396: r##" -The value behind a raw pointer can't be determined at compile-time -(or even link-time), which means it can't be used in a constant -expression. Erroneous code example: - -```compile_fail -const REG_ADDR: *const u8 = 0x5f3759df as *const u8; - -const VALUE: u8 = unsafe { *REG_ADDR }; -// error: raw pointers cannot be dereferenced in constants -``` - -A possible fix is to dereference your pointer at some point in run-time. - -For example: - -``` -const REG_ADDR: *const u8 = 0x5f3759df as *const u8; - -let reg_value = unsafe { *REG_ADDR }; -``` -"##, - -E0397: r##" -It is not allowed for a mutable static to allocate or have destructors. For -example: - -```compile_fail -// error: mutable statics are not allowed to have boxes -static mut FOO: Option> = None; - -// error: mutable statics are not allowed to have destructors -static mut BAR: Option> = None; -``` -"##, - -E0400: r##" -A user-defined dereference was attempted in an invalid context. Erroneous -code example: - -```compile_fail -use std::ops::Deref; - -struct A; - -impl Deref for A { - type Target = str; - - fn deref(&self)-> &str { "foo" } -} - -const S: &'static str = &A; -// error: user-defined dereference operators are not allowed in constants - -fn main() { - let foo = S; -} -``` - -You cannot directly use a dereference operation whilst initializing a constant -or a static. To fix this error, restructure your code to avoid this dereference, -perhaps moving it inline: - -``` -use std::ops::Deref; - -struct A; - -impl Deref for A { - type Target = str; - - fn deref(&self)-> &str { "foo" } -} - -fn main() { - let foo : &str = &A; -} -``` -"##, - -E0492: r##" -A borrow of a constant containing interior mutability was attempted. Erroneous -code example: - -```compile_fail -use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; - -const A: AtomicUsize = ATOMIC_USIZE_INIT; -static B: &'static AtomicUsize = &A; -// error: cannot borrow a constant which contains interior mutability, create a -// static instead -``` - -A `const` represents a constant value that should never change. If one takes -a `&` reference to the constant, then one is taking a pointer to some memory -location containing the value. Normally this is perfectly fine: most values -can't be changed via a shared `&` pointer, but interior mutability would allow -it. That is, a constant value could be mutated. On the other hand, a `static` is -explicitly a single memory location, which can be mutated at will. - -So, in order to solve this error, either use statics which are `Sync`: - -``` -use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; - -static A: AtomicUsize = ATOMIC_USIZE_INIT; -static B: &'static AtomicUsize = &A; // ok! -``` - -You can also have this error while using a cell type: - -```compile_fail -#![feature(const_fn)] - -use std::cell::Cell; - -const A: Cell = Cell::new(1); -const B: &'static Cell = &A; -// error: cannot borrow a constant which contains interior mutability, create -// a static instead - -// or: -struct C { a: Cell } - -const D: C = C { a: Cell::new(1) }; -const E: &'static Cell = &D.a; // error - -// or: -const F: &'static C = &D; // error -``` - -This is because cell types do operations that are not thread-safe. Due to this, -they don't implement Sync and thus can't be placed in statics. In this -case, `StaticMutex` would work just fine, but it isn't stable yet: -https://doc.rust-lang.org/nightly/std/sync/struct.StaticMutex.html - -However, if you still wish to use these types, you can achieve this by an unsafe -wrapper: - -``` -#![feature(const_fn)] - -use std::cell::Cell; -use std::marker::Sync; - -struct NotThreadSafe { - value: Cell, -} - -unsafe impl Sync for NotThreadSafe {} - -static A: NotThreadSafe = NotThreadSafe { value : Cell::new(1) }; -static B: &'static NotThreadSafe = &A; // ok! -``` - -Remember this solution is unsafe! You will have to ensure that accesses to the -cell are synchronized. -"##, - -E0493: r##" -A type with a destructor was assigned to an invalid type of variable. Erroneous -code example: - -```compile_fail -struct Foo { - a: u32 -} - -impl Drop for Foo { - fn drop(&mut self) {} -} - -const F : Foo = Foo { a : 0 }; -// error: constants are not allowed to have destructors -static S : Foo = Foo { a : 0 }; -// error: statics are not allowed to have destructors -``` - -To solve this issue, please use a type which does allow the usage of type with -destructors. -"##, - -E0494: r##" -A reference of an interior static was assigned to another const/static. -Erroneous code example: - -```compile_fail -struct Foo { - a: u32 -} - -static S : Foo = Foo { a : 0 }; -static A : &'static u32 = &S.a; -// error: cannot refer to the interior of another static, use a -// constant instead -``` - -The "base" variable has to be a const if you want another static/const variable -to refer to one of its fields. Example: - -``` -struct Foo { - a: u32 -} - -const S : Foo = Foo { a : 0 }; -static A : &'static u32 = &S.a; // ok! -``` -"##, - } register_diagnostics! { diff --git a/src/librustc_passes/lib.rs b/src/librustc_passes/lib.rs index b235962eb9a..67a9c2fd17e 100644 --- a/src/librustc_passes/lib.rs +++ b/src/librustc_passes/lib.rs @@ -37,7 +37,6 @@ extern crate rustc_const_math; pub mod diagnostics; -pub mod const_fn; pub mod consts; pub mod loops; pub mod no_asm; diff --git a/src/rustc/Cargo.lock b/src/rustc/Cargo.lock index 1fa4d5398f4..9dc1899e5a0 100644 --- a/src/rustc/Cargo.lock +++ b/src/rustc/Cargo.lock @@ -220,6 +220,7 @@ dependencies = [ "log 0.0.0", "rustc 0.0.0", "rustc_back 0.0.0", + "rustc_bitflags 0.0.0", "rustc_const_eval 0.0.0", "rustc_const_math 0.0.0", "rustc_data_structures 0.0.0", @@ -233,6 +234,7 @@ dependencies = [ "log 0.0.0", "rustc 0.0.0", "rustc_const_eval 0.0.0", + "rustc_const_math 0.0.0", "syntax 0.0.0", ] @@ -278,6 +280,7 @@ version = "0.0.0" dependencies = [ "log 0.0.0", "rustc 0.0.0", + "serialize 0.0.0", "syntax 0.0.0", ] diff --git a/src/test/compile-fail/const-block-non-item-statement.rs b/src/test/compile-fail/const-block-non-item-statement.rs index 5ccfb1ddec7..edb85023c9b 100644 --- a/src/test/compile-fail/const-block-non-item-statement.rs +++ b/src/test/compile-fail/const-block-non-item-statement.rs @@ -21,6 +21,20 @@ 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 + +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 +} + +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 pub fn main() { + let _: Array = [0; { let x = 3; 5 }]; + //~^ ERROR: blocks in constants are limited to items and tail expressions + //~^^ ERROR: blocks in constants are limited to items and tail expressions }