blob: 6001b1205871afe404bcb9f109e9bf55e53953dc [file] [log] [blame]
// 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Verifies that the types and values of const and static items
// are safe. The rules enforced by this module are:
//
// - For each *mutable* static item, it checks that its **type**:
// - doesn't have a destructor
// - doesn't own a box
//
// - For each *immutable* static item, it checks that its **value**:
// - doesn't own a box
// - doesn't contain a struct literal or a call to an enum variant / struct constructor where
// - the type of the struct/enum has a dtor
//
// Rules Enforced Elsewhere:
// - It's not possible to take the address of a static item with unsafe interior. This is enforced
// by borrowck::gather_loans
use dep_graph::DepNode;
use middle::ty::cast::{CastKind};
use middle::const_eval::{self, ConstEvalErr};
use middle::const_eval::ErrKind::IndexOpFeatureGated;
use middle::const_eval::EvalHint::ExprTypeChecked;
use middle::def;
use middle::def_id::DefId;
use middle::expr_use_visitor as euv;
use middle::infer;
use middle::mem_categorization as mc;
use middle::mem_categorization::Categorization;
use middle::traits;
use middle::ty::{self, Ty};
use util::nodemap::NodeMap;
use rustc_front::hir;
use syntax::ast;
use syntax::codemap::Span;
use syntax::feature_gate::UnstableFeatures;
use rustc_front::intravisit::{self, FnKind, Visitor};
use std::collections::hash_map::Entry;
use std::cmp::Ordering;
// Const qualification, from partial to completely promotable.
bitflags! {
#[derive(RustcEncodable, RustcDecodable)]
flags ConstQualif: u8 {
// 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 = 1 << 0,
// Constant value with a type that implements Drop. Can be copied
// from static memory, similar to MUTABLE_MEM.
const NEEDS_DROP = 1 << 1,
// 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 = 1 << 2,
// 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 = 1 << 3,
// 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 = 1 << 4,
// Invalid const for miscellaneous reasons (e.g. not implemented).
const NOT_CONST = 1 << 5,
// 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 = ConstQualif::MUTABLE_MEM.bits |
ConstQualif::NEEDS_DROP.bits |
ConstQualif::NOT_CONST.bits
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Mode {
Const,
ConstFn,
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<hir::Mutability>
}
impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> {
fn with_mode<F, R>(&mut self, mode: Mode, f: F) -> R where
F: FnOnce(&mut CheckCrateVisitor<'a, 'tcx>) -> R,
{
let (old_mode, old_qualif) = (self.mode, self.qualif);
self.mode = mode;
self.qualif = ConstQualif::empty();
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<ast::NodeId>, f: F) -> R where
F: for<'t> FnOnce(&mut euv::ExprUseVisitor<'b, 't, 'b, 'tcx>) -> R,
{
let param_env = match item_id {
Some(item_id) => ty::ParameterEnvironment::for_item(self.tcx, item_id),
None => self.tcx.empty_parameter_environment()
};
let infcx = infer::new_infer_ctxt(self.tcx, &self.tcx.tables, Some(param_env), false);
f(&mut euv::ExprUseVisitor::new(self, &infcx))
}
fn global_expr(&mut self, mode: Mode, expr: &hir::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(ConstQualif::empty());
}
}
self.with_mode(mode, |this| {
this.with_euv(None, |euv| euv.consume_expr(expr));
this.visit_expr(expr);
this.qualif
})
}
fn fn_like(&mut self,
fk: FnKind,
fd: &hir::FnDecl,
b: &hir::Block,
s: Span,
fn_id: ast::NodeId)
-> ConstQualif {
match self.tcx.const_qualif_map.borrow_mut().entry(fn_id) {
Entry::Occupied(entry) => return *entry.get(),
Entry::Vacant(entry) => {
// Prevent infinite recursion on re-entry.
entry.insert(ConstQualif::empty());
}
}
let mode = match fk {
FnKind::ItemFn(_, _, _, hir::Constness::Const, _, _) => {
Mode::ConstFn
}
FnKind::Method(_, m, _) => {
if m.constness == hir::Constness::Const {
Mode::ConstFn
} else {
Mode::Var
}
}
_ => Mode::Var
};
let qualif = self.with_mode(mode, |this| {
this.with_euv(Some(fn_id), |euv| euv.walk_fn(fd, b));
intravisit::walk_fn(this, fk, fd, b, s);
this.qualif
});
// Keep only bits that aren't affected by function body (NON_ZERO_SIZED),
// and bits that don't change semantics, just optimizations (PREFER_IN_PLACE).
let qualif = qualif & (ConstQualif::NON_ZERO_SIZED | ConstQualif::PREFER_IN_PLACE);
self.tcx.const_qualif_map.borrow_mut().insert(fn_id, qualif);
qualif
}
fn add_qualif(&mut self, qualif: ConstQualif) {
self.qualif = self.qualif | qualif;
}
/// Returns true if the call is to a const fn or method.
fn handle_const_fn_call(&mut self,
expr: &hir::Expr,
def_id: DefId,
ret_ty: Ty<'tcx>)
-> bool {
if let Some(fn_like) = const_eval::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");
fileline_help!(
&mut err,
expr.span,
"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(),
fn_like.span(),
fn_like.id());
self.add_qualif(qualif);
if ret_ty.type_contents(self.tcx).interior_unsafe() {
self.add_qualif(ConstQualif::MUTABLE_MEM);
}
true
} else {
false
}
}
fn record_borrow(&mut self, id: ast::NodeId, mutbl: hir::Mutability) {
match self.rvalue_borrows.entry(id) {
Entry::Occupied(mut entry) => {
// Merge the two borrows, taking the most demanding
// one, mutability-wise.
if mutbl == hir::MutMutable {
entry.insert(mutbl);
}
}
Entry::Vacant(entry) => {
entry.insert(mutbl);
}
}
}
fn msg(&self) -> &'static str {
match self.mode {
Mode::Const => "constant",
Mode::ConstFn => "constant function",
Mode::StaticMut | Mode::Static => "static",
Mode::Var => unreachable!(),
}
}
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, false);
let cause = traits::ObligationCause::new(e.span, e.id, traits::SharedStatic);
let mut fulfill_cx = infcx.fulfillment_cx.borrow_mut();
fulfill_cx.register_builtin_bound(&infcx, ty, ty::BoundSync, cause);
match fulfill_cx.select_all_or_error(&infcx) {
Ok(()) => { },
Err(ref errors) => {
traits::report_fulfillment_errors(&infcx, errors);
}
}
}
}
impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> {
fn visit_item(&mut self, i: &hir::Item) {
debug!("visit_item(item={})", self.tcx.map.node_to_string(i.id));
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) => {
self.global_expr(Mode::Const, &**expr);
}
hir::ItemEnum(ref enum_definition, _) => {
for var in &enum_definition.variants {
if let Some(ref ex) = var.node.disr_expr {
self.global_expr(Mode::Const, &**ex);
}
}
}
_ => {
intravisit::walk_item(self, i);
}
}
}
fn visit_trait_item(&mut self, t: &'v hir::TraitItem) {
match t.node {
hir::ConstTraitItem(_, ref default) => {
if let Some(ref expr) = *default {
self.global_expr(Mode::Const, &*expr);
} else {
intravisit::walk_trait_item(self, t);
}
}
_ => self.with_mode(Mode::Var, |v| intravisit::walk_trait_item(v, t)),
}
}
fn visit_impl_item(&mut self, i: &'v hir::ImplItem) {
match i.node {
hir::ImplItemKind::Const(_, ref expr) => {
self.global_expr(Mode::Const, &*expr);
}
_ => self.with_mode(Mode::Var, |v| intravisit::walk_impl_item(v, i)),
}
}
fn visit_fn(&mut self,
fk: FnKind<'v>,
fd: &'v hir::FnDecl,
b: &'v hir::Block,
s: Span,
fn_id: ast::NodeId) {
self.fn_like(fk, fd, b, s, fn_id);
}
fn visit_pat(&mut self, p: &hir::Pat) {
match p.node {
hir::PatLit(ref lit) => {
self.global_expr(Mode::Const, &**lit);
}
hir::PatRange(ref start, ref end) => {
self.global_expr(Mode::Const, &**start);
self.global_expr(Mode::Const, &**end);
match const_eval::compare_lit_exprs(self.tcx, start, end) {
Some(Ordering::Less) |
Some(Ordering::Equal) => {}
Some(Ordering::Greater) => {
span_err!(self.tcx.sess, start.span, E0030,
"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");
}
}
}
_ => intravisit::walk_pat(self, p)
}
}
fn visit_block(&mut self, block: &hir::Block) {
// Check all statements in the block
for stmt in &block.stmts {
match stmt.node {
hir::StmtDecl(ref decl, _) => {
match decl.node {
hir::DeclLocal(_) => {},
// Item statements are allowed
hir::DeclItem(_) => continue
}
}
hir::StmtExpr(_, _) => {},
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);
}
fn visit_expr(&mut self, ex: &hir::Expr) {
let mut outer = self.qualif;
self.qualif = ConstQualif::empty();
let node_ty = self.tcx.node_id_to_type(ex.id);
check_expr(self, ex, node_ty);
check_adjustments(self, ex);
// Special-case some expressions to avoid certain flags bubbling up.
match ex.node {
hir::ExprCall(ref callee, ref args) => {
for arg in args {
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 - ConstQualif::NON_ZERO_SIZED);
}
hir::ExprRepeat(ref element, _) => {
self.visit_expr(&**element);
// The count is checked elsewhere (typeck).
let count = match node_ty.sty {
ty::TyArray(_, n) => n,
_ => unreachable!()
};
// [element; 0] is always zero-sized.
if count == 0 {
self.qualif.remove(ConstQualif::NON_ZERO_SIZED | ConstQualif::PREFER_IN_PLACE);
}
}
hir::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) {
let pat_borrow = self.rvalue_borrows.remove(&pat.id);
match (borrow, pat_borrow) {
(None, _) | (_, Some(hir::MutMutable)) => {
borrow = pat_borrow;
}
_ => {}
}
}
if let Some(mutbl) = borrow {
self.record_borrow(discr.id, mutbl);
}
intravisit::walk_expr(self, ex);
}
// Division by zero and overflow checking.
hir::ExprBinary(op, _, _) => {
intravisit::walk_expr(self, ex);
let div_or_rem = op.node == hir::BiDiv || op.node == hir::BiRem;
match node_ty.sty {
ty::TyUint(_) | ty::TyInt(_) if div_or_rem => {
if !self.qualif.intersects(ConstQualif::NOT_CONST) {
match const_eval::eval_const_expr_partial(
self.tcx, ex, ExprTypeChecked, None) {
Ok(_) => {}
Err(ConstEvalErr { kind: IndexOpFeatureGated, ..}) => {},
Err(msg) => {
self.tcx.sess.add_lint(::lint::builtin::CONST_ERR, ex.id,
msg.span,
msg.description().into_owned())
}
}
}
}
_ => {}
}
}
_ => intravisit::walk_expr(self, ex)
}
// Handle borrows on (or inside the autorefs of) this expression.
match self.rvalue_borrows.remove(&ex.id) {
Some(hir::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 `ConstQualif::MUTABLE_MEM` has
// propagated from another error, so erroring again would be just noise.
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.
if !self.qualif.intersects(ConstQualif::NON_STATIC_BORROWS) {
self.qualif = self.qualif - ConstQualif::PREFER_IN_PLACE;
self.add_qualif(ConstQualif::HAS_STATIC_BORROWS);
}
}
Some(hir::MutMutable) => {
// `&mut expr` means expr could be mutated, unless it's zero-sized.
if self.qualif.intersects(ConstQualif::NON_ZERO_SIZED) {
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) {
self.add_qualif(ConstQualif::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 - ConstQualif::HAS_STATIC_BORROWS);
}
}
/// This function is used to enforce the constraints on
/// 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, it is qualified for promotion
/// instead of producing errors.
fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
e: &hir::Expr, node_ty: Ty<'tcx>) {
match node_ty.sty {
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());
}
}
_ => {}
}
let method_call = ty::MethodCall::expr(e.id);
match e.node {
hir::ExprUnary(..) |
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 {
ty::TyRawPtr(_) => {
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());
}
}
_ => {}
}
}
hir::ExprBinary(op, ref lhs, _) => {
match v.tcx.node_id_to_type(lhs.id).sty {
ty::TyRawPtr(_) => {
assert!(op.node == hir::BiEq || op.node == hir::BiNe ||
op.node == hir::BiLe || op.node == hir::BiLt ||
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());
}
}
_ => {}
}
}
hir::ExprCast(ref from, _) => {
debug!("Checking const cast(id={})", from.id);
match v.tcx.cast_kinds.borrow().get(&from.id) {
None => v.tcx.sess.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());
}
}
_ => {}
}
}
hir::ExprPath(..) => {
let def = v.tcx.def_map.borrow().get(&e.id).map(|d| d.full_def());
match def {
Some(def::DefVariant(_, _, _)) => {
// Count the discriminator or function pointer.
v.add_qualif(ConstQualif::NON_ZERO_SIZED);
}
Some(def::DefStruct(_)) => {
if let ty::TyBareFn(..) = node_ty.sty {
// Count the function pointer.
v.add_qualif(ConstQualif::NON_ZERO_SIZED);
}
}
Some(def::DefFn(..)) | Some(def::DefMethod(..)) => {
// Count the function pointer.
v.add_qualif(ConstQualif::NON_ZERO_SIZED);
}
Some(def::DefStatic(..)) => {
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::Var => v.add_qualif(ConstQualif::NOT_CONST)
}
}
Some(def::DefConst(did)) |
Some(def::DefAssociatedConst(did)) => {
if let Some(expr) = const_eval::lookup_const_by_id(v.tcx, did,
Some(e.id),
None) {
let inner = v.global_expr(Mode::Const, expr);
v.add_qualif(inner);
}
}
Some(def::DefLocal(..)) if v.mode == Mode::ConstFn => {
// 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());
}
}
}
}
hir::ExprCall(ref callee, _) => {
let mut callee = &**callee;
loop {
callee = match callee.node {
hir::ExprBlock(ref block) => match block.expr {
Some(ref tail) => &**tail,
None => break
},
_ => break
};
}
let def = v.tcx.def_map.borrow().get(&callee.id).map(|d| d.full_def());
let is_const = match def {
Some(def::DefStruct(..)) => true,
Some(def::DefVariant(..)) => {
// Count the discriminator.
v.add_qualif(ConstQualif::NON_ZERO_SIZED);
true
}
Some(def::DefFn(did, _)) => {
v.handle_const_fn_call(e, did, node_ty)
}
Some(def::DefMethod(did)) => {
match v.tcx.impl_or_trait_item(did).container() {
ty::ImplContainer(_) => {
v.handle_const_fn_call(e, did, node_ty)
}
ty::TraitContainer(_) => false
}
}
_ => false
};
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(..) => {
let method = v.tcx.tables.borrow().method_map[&method_call];
let is_const = match v.tcx.impl_or_trait_item(method.def_id).container() {
ty::ImplContainer(_) => v.handle_const_fn_call(e, method.def_id, node_ty),
ty::TraitContainer(_) => false
};
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(..) => {
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(ConstQualif::MUTABLE_MEM);
}
}
hir::ExprLit(_) |
hir::ExprAddrOf(..) => {
v.add_qualif(ConstQualif::NON_ZERO_SIZED);
}
hir::ExprRepeat(..) => {
v.add_qualif(ConstQualif::PREFER_IN_PLACE);
}
hir::ExprClosure(..) => {
// Paths in constant contexts cannot refer to local variables,
// as there are none, and thus closures can't have upvars there.
if v.tcx.with_freevars(e.id, |fv| !fv.is_empty()) {
assert!(v.mode == Mode::Var,
"global closures can't capture anything");
v.add_qualif(ConstQualif::NOT_CONST);
}
}
hir::ExprBlock(_) |
hir::ExprIndex(..) |
hir::ExprField(..) |
hir::ExprTupField(..) |
hir::ExprVec(_) |
hir::ExprType(..) |
hir::ExprTup(..) => {}
// Conditional control flow (possible to implement).
hir::ExprMatch(..) |
hir::ExprIf(..) |
// Loops (not very meaningful in constants).
hir::ExprWhile(..) |
hir::ExprLoop(..) |
// More control flow (also not very meaningful).
hir::ExprBreak(_) |
hir::ExprAgain(_) |
hir::ExprRet(_) |
// Miscellaneous expressions that could be implemented.
hir::ExprRange(..) |
// Expressions with side-effects.
hir::ExprAssign(..) |
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());
}
}
}
}
/// Check the adjustments of an expression
fn check_adjustments<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Expr) {
match v.tcx.tables.borrow().adjustments.get(&e.id) {
None |
Some(&ty::adjustment::AdjustReifyFnPointer) |
Some(&ty::adjustment::AdjustUnsafeFnPointer) => {}
Some(&ty::adjustment::AdjustDerefRef(
ty::adjustment::AutoDerefRef { autoderefs, .. }
)) => {
if (0..autoderefs as u32).any(|autoderef| {
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());
}
}
}
}
}
pub fn check_crate(tcx: &ty::ctxt) {
tcx.visit_all_items_in_krate(DepNode::CheckConst, &mut CheckCrateVisitor {
tcx: tcx,
mode: Mode::Var,
qualif: ConstQualif::NOT_CONST,
rvalue_borrows: NodeMap()
});
tcx.sess.abort_if_errors();
}
impl<'a, 'tcx> euv::Delegate<'tcx> for CheckCrateVisitor<'a, 'tcx> {
fn consume(&mut self,
_consume_id: ast::NodeId,
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, _, _) |
Categorization::Downcast(ref cmt, _) |
Categorization::Interior(ref cmt, _) => cur = cmt,
Categorization::Rvalue(..) |
Categorization::Upvar(..) |
Categorization::Local(..) => break
}
}
}
fn borrow(&mut self,
borrow_id: ast::NodeId,
borrow_span: Span,
cmt: mc::cmt<'tcx>,
_loan_region: ty::Region,
bk: ty::BorrowKind,
loan_cause: euv::LoanCause)
{
// Kind of hacky, but we allow Unsafe coercions in constants.
// These occur when we convert a &T or *T to a *U, as well as
// when making a thin pointer (e.g., `*T`) into a fat pointer
// (e.g., `*Trait`).
match loan_cause {
euv::LoanCause::AutoUnsafe => {
return;
}
_ => { }
}
let mut cur = &cmt;
let mut is_interior = false;
loop {
match cur.cat {
Categorization::Rvalue(..) => {
if loan_cause == euv::MatchDiscriminant {
// Ignore the dummy immutable borrow created by EUV.
break;
}
let mutbl = bk.to_mutbl_lossy();
if mutbl == hir::MutMutable && self.mode == Mode::StaticMut {
// Mutable slices are the only `&mut` allowed in
// globals, but only in `static mut`, nowhere else.
// FIXME: This exception is really weird... there isn't
// any fundamental reason to restrict this based on
// type of the expression. `&mut [1]` has exactly the
// same representation as &mut 1.
match cmt.ty.sty {
ty::TyArray(_, _) | ty::TySlice(_) => break,
_ => {}
}
}
self.record_borrow(borrow_id, mutbl);
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;
}
Categorization::Upvar(..) |
Categorization::Local(..) => break
}
}
}
fn decl_without_init(&mut self,
_id: ast::NodeId,
_span: Span) {}
fn mutate(&mut self,
_assignment_id: ast::NodeId,
_assignment_span: Span,
_assignee_cmt: mc::cmt,
_mode: euv::MutateMode) {}
fn matched_pat(&mut self,
_: &hir::Pat,
_: mc::cmt,
_: euv::MatchMode) {}
fn consume_pat(&mut self,
_consume_pat: &hir::Pat,
_cmt: mc::cmt,
_mode: euv::ConsumeMode) {}
}