From 03295a715fe1231b04e389c5ee24180705e768b0 Mon Sep 17 00:00:00 2001 From: Eduard Burtescu Date: Thu, 29 Jan 2015 13:57:06 +0200 Subject: [PATCH] rustc: qualify expressions in check_const for potential promotion. --- src/librustc/diagnostics.rs | 1 + src/librustc/metadata/common.rs | 1 + src/librustc/middle/astencode.rs | 14 + src/librustc/middle/check_const.rs | 573 ++++++++++++++++++++++------- src/librustc/middle/ty.rs | 5 + 5 files changed, 454 insertions(+), 140 deletions(-) diff --git a/src/librustc/diagnostics.rs b/src/librustc/diagnostics.rs index efcc3715d3e..133bef30e40 100644 --- a/src/librustc/diagnostics.rs +++ b/src/librustc/diagnostics.rs @@ -59,6 +59,7 @@ register_diagnostics! { E0010, E0011, E0012, + E0013, E0014, E0015, E0016, diff --git a/src/librustc/metadata/common.rs b/src/librustc/metadata/common.rs index 1de0c018d42..4930eddb35a 100644 --- a/src/librustc/metadata/common.rs +++ b/src/librustc/metadata/common.rs @@ -143,6 +143,7 @@ pub enum astencode_tag { // Reserves 0x40 -- 0x5f tag_table_upvar_capture_map = 0x56, tag_table_capture_modes = 0x57, tag_table_object_cast_map = 0x58, + tag_table_const_qualif = 0x59, } pub const tag_item_trait_item_sort: uint = 0x60; diff --git a/src/librustc/middle/astencode.rs b/src/librustc/middle/astencode.rs index edb14f7ed4c..c3302debdfa 100644 --- a/src/librustc/middle/astencode.rs +++ b/src/librustc/middle/astencode.rs @@ -23,6 +23,7 @@ use metadata::tydecode; use metadata::tydecode::{DefIdSource, NominalType, TypeWithId, TypeParameter}; use metadata::tydecode::{RegionParameter, ClosureSource}; use metadata::tyencode; +use middle::check_const::ConstQualif; use middle::mem_categorization::Typer; use middle::subst; use middle::subst::VecPerParamSpace; @@ -1306,6 +1307,15 @@ fn encode_side_tables_for_id(ecx: &e::EncodeContext, }) }) } + + for &qualif in tcx.const_qualif_map.borrow().get(&id).iter() { + rbml_w.tag(c::tag_table_const_qualif, |rbml_w| { + rbml_w.id(id); + rbml_w.tag(c::tag_table_val, |rbml_w| { + qualif.encode(rbml_w).unwrap() + }) + }) + } } trait doc_decoder_helpers { @@ -1920,6 +1930,10 @@ fn decode_side_tables(dcx: &DecodeContext, dcx.tcx.closure_kinds.borrow_mut().insert(ast_util::local_def(id), closure_kind); } + c::tag_table_const_qualif => { + let qualif: ConstQualif = Decodable::decode(val_dsr).unwrap(); + dcx.tcx.const_qualif_map.borrow_mut().insert(id, qualif); + } _ => { dcx.tcx.sess.bug( &format!("unknown tag found in side tables: {:x}", diff --git a/src/librustc/middle/check_const.rs b/src/librustc/middle/check_const.rs index 34d1654ec4b..41d425cd2f6 100644 --- a/src/librustc/middle/check_const.rs +++ b/src/librustc/middle/check_const.rs @@ -24,14 +24,14 @@ // - It's not possible to take the address of a static item with unsafe interior. This is enforced // by borrowck::gather_loans -use self::Mode::*; - +use middle::const_eval; use middle::def; use middle::expr_use_visitor as euv; use middle::infer; use middle::mem_categorization as mc; use middle::traits; -use middle::ty; +use middle::ty::{self, Ty}; +use util::nodemap::NodeMap; use util::ppaux; use syntax::ast; @@ -39,34 +39,127 @@ use syntax::codemap::Span; use syntax::print::pprust; use syntax::visit::{self, Visitor}; +use std::collections::hash_map::Entry; + +// Const qualification, from partial to completely promotable. +bitflags! { + #[derive(RustcEncodable, RustcDecodable)] + flags ConstQualif: u8 { + // Const rvalue which can be placed behind a reference. + const PURE_CONST = 0b000000, + // Inner mutability (can not be placed behind a reference) or behind + // &mut in a non-global expression. Can be copied from static memory. + const MUTABLE_MEM = 0b000001, + // Constant value with a type that implements Drop. Can be copied + // from static memory, similar to MUTABLE_MEM. + const NEEDS_DROP = 0b000010, + // Even if the value can be placed in static memory, copying it from + // there is more expensive than in-place instantiation, and/or it may + // be too large. This applies to [T; N] and everything containing it. + // N.B.: references need to clear this flag to not end up on the stack. + const PREFER_IN_PLACE = 0b000100, + // May use more than 0 bytes of memory, doesn't impact the constness + // directly, but is not allowed to be borrowed mutably in a constant. + const NON_ZERO_SIZED = 0b001000, + // Actually borrowed, has to always be in static memory. Does not + // propagate, and requires the expression to behave like a 'static + // lvalue. The set of expressions with this flag is the minimum + // that have to be promoted. + const HAS_STATIC_BORROWS = 0b010000, + // Invalid const for miscellaneous reasons (e.g. not implemented). + const NOT_CONST = 0b100000, + + // Borrowing the expression won't produce &'static T if any of these + // bits are set, though the value could be copied from static memory + // if `NOT_CONST` isn't set. + const NON_STATIC_BORROWS = MUTABLE_MEM.bits | NEEDS_DROP.bits | NOT_CONST.bits + } +} + #[derive(Copy, Eq, PartialEq)] enum Mode { - InConstant, - InStatic, - InStaticMut, - InNothing, + Const, + Static, + StaticMut, + + // An expression that occurs outside of any constant context + // (i.e. `const`, `static`, array lengths, etc.). The value + // can be variable at runtime, but will be promotable to + // static memory if we can prove it is actually constant. + Var, } struct CheckCrateVisitor<'a, 'tcx: 'a> { tcx: &'a ty::ctxt<'tcx>, mode: Mode, + qualif: ConstQualif, + rvalue_borrows: NodeMap } impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> { - fn with_mode(&mut self, mode: Mode, f: F) where - F: FnOnce(&mut CheckCrateVisitor<'a, 'tcx>), + fn with_mode(&mut self, mode: Mode, f: F) -> R where + F: FnOnce(&mut CheckCrateVisitor<'a, 'tcx>) -> R, { - let old = self.mode; + let (old_mode, old_qualif) = (self.mode, self.qualif); self.mode = mode; - f(self); - self.mode = old; + self.qualif = PURE_CONST; + let r = f(self); + self.mode = old_mode; + self.qualif = old_qualif; + r + } + + fn with_euv<'b, F, R>(&'b mut self, item_id: Option, f: F) -> R where + F: for<'t> FnOnce(&mut euv::ExprUseVisitor<'b, 't, 'tcx, + ty::ParameterEnvironment<'a, 'tcx>>) -> R, + { + let param_env = match item_id { + Some(item_id) => ty::ParameterEnvironment::for_item(self.tcx, item_id), + None => ty::empty_parameter_environment(self.tcx) + }; + f(&mut euv::ExprUseVisitor::new(self, ¶m_env)) + } + + fn global_expr(&mut self, mode: Mode, expr: &ast::Expr) -> ConstQualif { + assert!(mode != Mode::Var); + match self.tcx.const_qualif_map.borrow_mut().entry(expr.id) { + Entry::Occupied(entry) => return *entry.get(), + Entry::Vacant(entry) => { + // Prevent infinite recursion on re-entry. + entry.insert(PURE_CONST); + } + } + self.with_mode(mode, |this| { + this.with_euv(None, |euv| euv.consume_expr(expr)); + this.visit_expr(expr); + this.qualif + }) + } + + fn add_qualif(&mut self, qualif: ConstQualif) { + self.qualif = self.qualif | qualif; + } + + fn record_borrow(&mut self, id: ast::NodeId, mutbl: ast::Mutability) { + match self.rvalue_borrows.entry(id) { + Entry::Occupied(mut entry) => { + // Merge the two borrows, taking the most demanding + // one, mutability-wise. + if mutbl == ast::MutMutable { + entry.insert(mutbl); + } + } + Entry::Vacant(entry) => { + entry.insert(mutbl); + } + } } fn msg(&self) -> &'static str { match self.mode { - InConstant => "constant", - InStaticMut | InStatic => "static", - InNothing => unreachable!(), + Mode::Const => "constant", + Mode::StaticMut | Mode::Static => "static", + Mode::Var => unreachable!(), } } @@ -108,43 +201,169 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { match i.node { ast::ItemStatic(_, ast::MutImmutable, ref expr) => { self.check_static_type(&**expr); - self.with_mode(InStatic, |v| v.visit_expr(&**expr)); + self.global_expr(Mode::Static, &**expr); } ast::ItemStatic(_, ast::MutMutable, ref expr) => { self.check_static_mut_type(&**expr); - self.with_mode(InStaticMut, |v| v.visit_expr(&**expr)); + self.global_expr(Mode::StaticMut, &**expr); } ast::ItemConst(_, ref expr) => { - self.with_mode(InConstant, |v| v.visit_expr(&**expr)); + self.global_expr(Mode::Const, &**expr); } ast::ItemEnum(ref enum_definition, _) => { - self.with_mode(InConstant, |v| { - for var in &enum_definition.variants { - if let Some(ref ex) = var.node.disr_expr { - v.visit_expr(&**ex); - } + for var in &enum_definition.variants { + if let Some(ref ex) = var.node.disr_expr { + self.global_expr(Mode::Const, &**ex); } - }); + } } _ => { - self.with_mode(InNothing, |v| visit::walk_item(v, i)); + self.with_mode(Mode::Var, |v| visit::walk_item(v, i)); } } } + fn visit_fn(&mut self, + fk: visit::FnKind<'v>, + fd: &'v ast::FnDecl, + b: &'v ast::Block, + s: Span, + fn_id: ast::NodeId) { + assert!(self.mode == Mode::Var); + self.with_euv(Some(fn_id), |euv| euv.walk_fn(fd, b)); + visit::walk_fn(self, fk, fd, b, s); + } + fn visit_pat(&mut self, p: &ast::Pat) { - let mode = match p.node { - ast::PatLit(_) | ast::PatRange(..) => InConstant, - _ => InNothing - }; - self.with_mode(mode, |v| visit::walk_pat(v, p)) + match p.node { + ast::PatLit(ref lit) => { + self.global_expr(Mode::Const, &**lit); + } + ast::PatRange(ref start, ref end) => { + self.global_expr(Mode::Const, &**start); + self.global_expr(Mode::Const, &**end); + } + _ => visit::walk_pat(self, p) + } } fn visit_expr(&mut self, ex: &ast::Expr) { - if self.mode != InNothing { - check_expr(self, ex); + let mut outer = self.qualif; + self.qualif = PURE_CONST; + + let node_ty = ty::node_id_to_type(self.tcx, ex.id); + check_expr(self, ex, node_ty); + + // Special-case some expressions to avoid certain flags bubbling up. + match ex.node { + ast::ExprCall(ref callee, ref args) => { + for arg in args.iter() { + self.visit_expr(&**arg) + } + + let inner = self.qualif; + self.visit_expr(&**callee); + // The callee's size doesn't count in the call. + let added = self.qualif - inner; + self.qualif = inner | (added - NON_ZERO_SIZED); + } + ast::ExprRepeat(ref element, _) => { + self.visit_expr(&**element); + // The count is checked elsewhere (typeck). + let count = match node_ty.sty { + ty::ty_vec(_, Some(n)) => n, + _ => unreachable!() + }; + // [element; 0] is always zero-sized. + if count == 0 { + self.qualif = self.qualif - (NON_ZERO_SIZED | PREFER_IN_PLACE); + } + } + ast::ExprMatch(ref discr, ref arms, _) => { + // Compute the most demanding borrow from all the arms' + // patterns and set that on the discriminator. + let mut borrow = None; + for pat in arms.iter().flat_map(|arm| arm.pats.iter()) { + let pat_borrow = self.rvalue_borrows.remove(&pat.id); + match (borrow, pat_borrow) { + (None, _) | (_, Some(ast::MutMutable)) => { + borrow = pat_borrow; + } + _ => {} + } + } + if let Some(mutbl) = borrow { + self.record_borrow(discr.id, mutbl); + } + visit::walk_expr(self, ex); + } + // Division by zero and overflow checking. + ast::ExprBinary(op, _, _) => { + visit::walk_expr(self, ex); + let div_or_rem = op.node == ast::BiDiv || op.node == ast::BiRem; + match node_ty.sty { + ty::ty_uint(_) | ty::ty_int(_) if div_or_rem => { + if !self.qualif.intersects(NOT_CONST) { + match const_eval::eval_const_expr_partial(self.tcx, ex, None) { + Ok(_) => {} + Err(msg) => { + span_err!(self.tcx.sess, ex.span, E0020, + "{} in a constant expression", msg) + } + } + } + } + _ => {} + } + } + _ => visit::walk_expr(self, ex) } - visit::walk_expr(self, ex); + + // Handle borrows on (or inside the autorefs of) this expression. + match self.rvalue_borrows.remove(&ex.id) { + Some(ast::MutImmutable) => { + // 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 the type doesn't have interior mutability, then `MUTABLE_MEM` has + // propagated from another error, so erroring again would be just noise. + let tc = ty::type_contents(self.tcx, node_ty); + if self.qualif.intersects(MUTABLE_MEM) && tc.interior_unsafe() { + outer = outer | NOT_CONST; + if self.mode != Mode::Var { + self.tcx.sess.span_err(ex.span, + "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. + if !self.qualif.intersects(NON_STATIC_BORROWS) { + self.qualif = self.qualif - PREFER_IN_PLACE; + self.add_qualif(HAS_STATIC_BORROWS); + } + } + Some(ast::MutMutable) => { + // `&mut expr` means expr could be mutated, unless it's zero-sized. + if self.qualif.intersects(NON_ZERO_SIZED) { + if self.mode == Mode::Var { + outer = outer | NOT_CONST; + self.add_qualif(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(NON_STATIC_BORROWS) { + self.add_qualif(HAS_STATIC_BORROWS); + } + } + None => {} + } + self.tcx.const_qualif_map.borrow_mut().insert(ex.id, self.qualif); + // Don't propagate certain flags. + self.qualif = outer | (self.qualif - HAS_STATIC_BORROWS); } } @@ -152,32 +371,49 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { /// const/static items. It walks through the *value* /// of the item walking down the expression and evaluating /// every nested expression. If the expression is not part -/// of a const/static item, this function does nothing but -/// walking down through it. -fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) { - let node_ty = ty::node_id_to_type(v.tcx, e.id); - +/// of a const/static item, it is qualified for promotion +/// instead of producing errors. +fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, + e: &ast::Expr, node_ty: Ty<'tcx>) { match node_ty.sty { ty::ty_struct(did, _) | ty::ty_enum(did, _) if ty::has_dtor(v.tcx, did) => { - v.tcx.sess.span_err(e.span, - &format!("{}s are not allowed to have destructors", - v.msg())[]) + v.add_qualif(NEEDS_DROP); + if v.mode != Mode::Var { + v.tcx.sess.span_err(e.span, + &format!("{}s are not allowed to have destructors", + v.msg())[]); + } } _ => {} } + let method_call = ty::MethodCall::expr(e.id); match e.node { + ast::ExprUnary(..) | + ast::ExprBinary(..) | + ast::ExprIndex(..) if v.tcx.method_map.borrow().contains_key(&method_call) => { + v.add_qualif(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()); + } + } ast::ExprBox(..) | ast::ExprUnary(ast::UnUniq, _) => { - span_err!(v.tcx.sess, e.span, E0010, - "allocations are not allowed in {}s", v.msg()); + v.add_qualif(NOT_CONST); + if v.mode != Mode::Var { + span_err!(v.tcx.sess, e.span, E0010, + "allocations are not allowed in {}s", v.msg()); + } } - ast::ExprBinary(..) | ast::ExprUnary(..) => { - let method_call = ty::MethodCall::expr(e.id); - if v.tcx.method_map.borrow().contains_key(&method_call) { - span_err!(v.tcx.sess, e.span, E0011, - "user-defined operators are not allowed in {}s", v.msg()); + ast::ExprUnary(ast::UnDeref, ref ptr) => { + match ty::node_id_to_type(v.tcx, ptr.id).sty { + ty::ty_ptr(_) => { + // This shouldn't be allowed in constants at all. + v.add_qualif(NOT_CONST); + } + _ => {} } } ast::ExprCast(ref from, _) => { @@ -188,51 +424,110 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) { ty::type_is_unsafe_ptr(toty) || (ty::type_is_bare_fn(toty) && ty::type_is_bare_fn_item(fromty)); if !is_legal_cast { - span_err!(v.tcx.sess, e.span, E0012, - "can not cast to `{}` in {}s", - ppaux::ty_to_string(v.tcx, toty), v.msg()); + v.add_qualif(NOT_CONST); + if v.mode != Mode::Var { + span_err!(v.tcx.sess, e.span, E0012, + "can not cast to `{}` in {}s", + ppaux::ty_to_string(v.tcx, toty), v.msg()); + } } if ty::type_is_unsafe_ptr(fromty) && ty::type_is_numeric(toty) { - span_err!(v.tcx.sess, e.span, E0018, - "can not cast a pointer to an integer in {}s", v.msg()); + v.add_qualif(NOT_CONST); + if v.mode != Mode::Var { + span_err!(v.tcx.sess, e.span, E0018, + "can not cast a pointer to an integer in {}s", v.msg()); + } } } ast::ExprPath(_) | ast::ExprQPath(_) => { - match v.tcx.def_map.borrow()[e.id] { - def::DefStatic(..) if v.mode == InConstant => { - span_err!(v.tcx.sess, e.span, E0013, - "constants cannot refer to other statics, \ - insert an intermediate constant instead"); + let def = v.tcx.def_map.borrow().get(&e.id).cloned(); + match def { + Some(def::DefVariant(_, _, _)) => { + // Count the discriminator or function pointer. + v.add_qualif(NON_ZERO_SIZED); + } + Some(def::DefStruct(_)) => { + if let ty::ty_bare_fn(..) = node_ty.sty { + // Count the function pointer. + v.add_qualif(NON_ZERO_SIZED); + } + } + Some(def::DefFn(..)) | + Some(def::DefStaticMethod(..)) | Some(def::DefMethod(..)) => { + // Count the function pointer. + v.add_qualif(NON_ZERO_SIZED); + } + Some(def::DefStatic(..)) => { + match v.mode { + Mode::Static | Mode::StaticMut => {} + Mode::Const => { + span_err!(v.tcx.sess, e.span, E0013, + "constants cannot refer to other statics, \ + insert an intermediate constant instead"); + } + Mode::Var => v.add_qualif(NOT_CONST) + } + } + Some(def::DefConst(did)) => { + if let Some(expr) = const_eval::lookup_const_by_id(v.tcx, did) { + let inner = v.global_expr(Mode::Const, expr); + v.add_qualif(inner); + } else { + v.tcx.sess.span_bug(e.span, "DefConst doesn't point \ + to an ItemConst"); + } } - def::DefStatic(..) | def::DefConst(..) | - def::DefFn(..) | def::DefStaticMethod(..) | def::DefMethod(..) | - def::DefStruct(_) | def::DefVariant(_, _, _) => {} - def => { - debug!("(checking const) found bad def: {:?}", def); - span_err!(v.tcx.sess, e.span, E0014, - "paths in constants may only refer to constants \ - or functions"); + v.add_qualif(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()); + } } } } ast::ExprCall(ref callee, _) => { - match v.tcx.def_map.borrow()[callee.id] { - def::DefStruct(..) | def::DefVariant(..) => {} // OK. + let mut callee = &**callee; + loop { + callee = match callee.node { + ast::ExprParen(ref inner) => &**inner, + ast::ExprBlock(ref block) => match block.expr { + Some(ref tail) => &**tail, + None => break + }, + _ => break + }; + } + let def = v.tcx.def_map.borrow().get(&callee.id).cloned(); + match def { + Some(def::DefStruct(..)) => {} + Some(def::DefVariant(..)) => { + // Count the discriminator. + v.add_qualif(NON_ZERO_SIZED); + } _ => { - span_err!(v.tcx.sess, e.span, E0015, - "function calls in constants are limited to \ - struct and enum constructors"); + v.add_qualif(NOT_CONST); + if v.mode != Mode::Var { + span_err!(v.tcx.sess, e.span, E0015, + "function calls in {}s are limited to \ + struct and enum constructors", v.msg()); + } } } } ast::ExprBlock(ref block) => { // Check all statements in the block - for stmt in &block.stmts { - let block_span_err = |span| + let mut block_span_err = |span| { + v.add_qualif(NOT_CONST); + if v.mode != Mode::Var { span_err!(v.tcx.sess, span, E0016, - "blocks in constants are limited to items and \ - tail expressions"); + "blocks in {}s are limited to items and \ + tail expressions", v.msg()); + } + }; + for stmt in &block.stmts { match stmt.node { ast::StmtDecl(ref decl, _) => { match decl.node { @@ -251,26 +546,40 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) { } } } - ast::ExprAddrOf(ast::MutMutable, ref inner) => { - match inner.node { - // Mutable slices are allowed. Only in `static mut`. - ast::ExprVec(_) if v.mode == InStaticMut => {} - _ => span_err!(v.tcx.sess, e.span, E0017, - "references in {}s may only refer \ - to immutable values", v.msg()) + ast::ExprStruct(..) => { + let did = v.tcx.def_map.borrow().get(&e.id).map(|def| def.def_id()); + if did == v.tcx.lang_items.unsafe_cell_type() { + v.add_qualif(MUTABLE_MEM); } } ast::ExprLit(_) | - ast::ExprVec(_) | - ast::ExprAddrOf(ast::MutImmutable, _) | - ast::ExprParen(..) | + ast::ExprAddrOf(..) => { + v.add_qualif(NON_ZERO_SIZED); + } + + ast::ExprRepeat(..) => { + v.add_qualif(PREFER_IN_PLACE); + } + + ast::ExprClosure(..) => { + // Paths in constant constexts cannot refer to local variables, + // as there are none, and thus closures can't have upvars there. + if ty::with_freevars(v.tcx, e.id, |fv| !fv.is_empty()) { + assert!(v.mode == Mode::Var, + "global closures can't capture anything"); + v.add_qualif(NOT_CONST); + } + } + + ast::ExprUnary(..) | + ast::ExprBinary(..) | + ast::ExprIndex(..) | ast::ExprField(..) | ast::ExprTupField(..) | - ast::ExprIndex(..) | - ast::ExprTup(..) | - ast::ExprRepeat(..) | - ast::ExprStruct(..) => {} + ast::ExprVec(_) | + ast::ExprParen(..) | + ast::ExprTup(..) => {} // Conditional control flow (possible to implement). ast::ExprMatch(..) | @@ -289,7 +598,6 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) { ast::ExprRet(_) | // Miscellaneous expressions that could be implemented. - ast::ExprClosure(..) | ast::ExprRange(..) | // Various other expressions. @@ -298,50 +606,27 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) { ast::ExprAssignOp(..) | ast::ExprInlineAsm(_) | ast::ExprMac(_) => { - span_err!(v.tcx.sess, e.span, E0019, - "{} contains unimplemented expression type", v.msg()); + v.add_qualif(NOT_CONST); + if v.mode != Mode::Var { + span_err!(v.tcx.sess, e.span, E0019, + "{} contains unimplemented expression type", v.msg()); + } } } } -struct GlobalVisitor<'a,'b,'tcx:'a+'b>( - euv::ExprUseVisitor<'a,'b,'tcx,ty::ParameterEnvironment<'b,'tcx>>); - -struct GlobalChecker<'a,'tcx:'a> { - tcx: &'a ty::ctxt<'tcx> -} - pub fn check_crate(tcx: &ty::ctxt) { - let param_env = ty::empty_parameter_environment(tcx); - let mut checker = GlobalChecker { - tcx: tcx - }; - let visitor = euv::ExprUseVisitor::new(&mut checker, ¶m_env); - visit::walk_crate(&mut GlobalVisitor(visitor), tcx.map.krate()); - visit::walk_crate(&mut CheckCrateVisitor { tcx: tcx, - mode: InNothing, + mode: Mode::Var, + qualif: NOT_CONST, + rvalue_borrows: NodeMap() }, tcx.map.krate()); tcx.sess.abort_if_errors(); } -impl<'a,'b,'t,'v> Visitor<'v> for GlobalVisitor<'a,'b,'t> { - fn visit_item(&mut self, item: &ast::Item) { - match item.node { - ast::ItemConst(_, ref e) | - ast::ItemStatic(_, _, ref e) => { - let GlobalVisitor(ref mut v) = *self; - v.consume_expr(&**e); - } - _ => {} - } - visit::walk_item(self, item); - } -} - -impl<'a, 'tcx> euv::Delegate<'tcx> for GlobalChecker<'a, 'tcx> { +impl<'a, 'tcx> euv::Delegate<'tcx> for CheckCrateVisitor<'a, 'tcx> { fn consume(&mut self, _consume_id: ast::NodeId, consume_span: Span, @@ -351,12 +636,14 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for GlobalChecker<'a, 'tcx> { loop { match cur.cat { mc::cat_static_item => { - // 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. - self.tcx.sess.span_err(consume_span, - "cannot refer to other statics by value, use the \ - address-of operator or a constant instead"); + 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. + self.tcx.sess.span_err(consume_span, + "cannot refer to other statics by value, use the \ + address-of operator or a constant instead"); + } break; } mc::cat_deref(ref cmt, _, _) | @@ -370,29 +657,35 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for GlobalChecker<'a, 'tcx> { } } fn borrow(&mut self, - _borrow_id: ast::NodeId, + borrow_id: ast::NodeId, borrow_span: Span, cmt: mc::cmt<'tcx>, _loan_region: ty::Region, - _bk: ty::BorrowKind, - _loan_cause: euv::LoanCause) { + bk: ty::BorrowKind, + loan_cause: euv::LoanCause) { let mut cur = &cmt; let mut is_interior = false; loop { match cur.cat { mc::cat_rvalue(..) => { - // 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 ty::type_contents(self.tcx, cur.ty).interior_unsafe() { - self.tcx.sess.span_err(borrow_span, - "cannot borrow a constant which contains \ - interior mutability, create a static instead"); + if loan_cause == euv::MatchDiscriminant { + // Ignore the dummy immutable borrow created by EUV. + break; } + let mutbl = bk.to_mutbl_lossy(); + if mutbl == ast::MutMutable && self.mode == Mode::StaticMut { + // Mutable slices are the only `&mut` allowed in globals, + // but only in `static mut`, nowhere else. + match cmt.ty.sty { + ty::ty_vec(_, _) => break, + _ => {} + } + } + self.record_borrow(borrow_id, mutbl); break; } mc::cat_static_item => { - if is_interior { + 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. @@ -433,4 +726,4 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for GlobalChecker<'a, 'tcx> { _consume_pat: &ast::Pat, _cmt: mc::cmt, _mode: euv::ConsumeMode) {} -} \ No newline at end of file +} diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 6e14f7b09f8..fe5c81bf2c0 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -44,6 +44,7 @@ use session::Session; use lint; use metadata::csearch; use middle; +use middle::check_const; use middle::const_eval; use middle::def::{self, DefMap, ExportMap}; use middle::dependency_format; @@ -838,6 +839,9 @@ pub struct ctxt<'tcx> { /// Caches whether traits are object safe pub object_safety_cache: RefCell>, + + /// Maps Expr NodeId's to their constant qualification. + pub const_qualif_map: RefCell>, } // Flags that we track on types. These flags are propagated upwards @@ -2472,6 +2476,7 @@ pub fn mk_ctxt<'tcx>(s: Session, type_impls_copy_cache: RefCell::new(HashMap::new()), type_impls_sized_cache: RefCell::new(HashMap::new()), object_safety_cache: RefCell::new(DefIdMap()), + const_qualif_map: RefCell::new(NodeMap()), } }