Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare ConstVal for constant propagators and reduce eval_const_expr in MIR #33274

Closed
wants to merge 9 commits into from
47 changes: 33 additions & 14 deletions src/librustc/middle/const_val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@ use std::mem::transmute;
use rustc_const_math::*;
use self::ConstVal::*;

use std::collections::BTreeMap;

#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
pub enum ConstVal {
Float(f64),
Integral(ConstInt),
Str(InternedString),
ByteStr(Rc<Vec<u8>>),
Bool(bool),
Struct(ast::NodeId),
Tuple(ast::NodeId),
Struct(DefId, BTreeMap<ast::Name, ConstVal>),
/// Tuple or Tuple structs
Tuple(Option<DefId>, Vec<ConstVal>),
/// A function pointer
Function(DefId),
Array(ast::NodeId, u64),
Repeat(ast::NodeId, u64),
Array(Vec<ConstVal>),
Repeat(Box<ConstVal>, u64),
Char(char),
/// A value that only occurs in case `eval_const_expr` reported an error. You should never
/// handle this case. Its sole purpose is to allow more errors to be reported instead of
Expand All @@ -44,11 +48,26 @@ impl hash::Hash for ConstVal {
Str(ref a) => a.hash(state),
ByteStr(ref a) => a.hash(state),
Bool(a) => a.hash(state),
Struct(a) => a.hash(state),
Tuple(a) => a.hash(state),
Struct(did, ref tree) => {
did.hash(state);
for (name, val) in tree {
name.hash(state);
val.hash(state);
}
},
Tuple(did, ref v) => {
did.hash(state);
for elem in v {
elem.hash(state);
}
},
Function(a) => a.hash(state),
Array(a, n) => { a.hash(state); n.hash(state) },
Repeat(a, n) => { a.hash(state); n.hash(state) },
Array(ref v) => {
for elem in v {
elem.hash(state);
}
}
Repeat(ref a, n) => { a.hash(state); n.hash(state) },
Char(c) => c.hash(state),
Dummy => ().hash(state),
}
Expand All @@ -67,11 +86,11 @@ impl PartialEq for ConstVal {
(&Str(ref a), &Str(ref b)) => a == b,
(&ByteStr(ref a), &ByteStr(ref b)) => a == b,
(&Bool(a), &Bool(b)) => a == b,
(&Struct(a), &Struct(b)) => a == b,
(&Tuple(a), &Tuple(b)) => a == b,
(&Struct(a_did, ref a), &Struct(b_did, ref b)) => (a == b) && (a_did == b_did),
(&Tuple(ref a_did, ref a), &Tuple(ref b_did, ref b)) => (a == b) && (a_did == b_did),
(&Function(a), &Function(b)) => a == b,
(&Array(a, an), &Array(b, bn)) => (a == b) && (an == bn),
(&Repeat(a, an), &Repeat(b, bn)) => (a == b) && (an == bn),
(&Array(ref a), &Array(ref b)) => a == b,
(&Repeat(ref a, an), &Repeat(ref b, bn)) => (a == b) && (an == bn),
(&Char(a), &Char(b)) => a == b,
(&Dummy, &Dummy) => true, // FIXME: should this be false?
_ => false,
Expand All @@ -89,8 +108,8 @@ impl ConstVal {
Str(_) => "string literal",
ByteStr(_) => "byte string literal",
Bool(_) => "boolean",
Struct(_) => "struct",
Tuple(_) => "tuple",
Struct(..) => "struct",
Tuple(..) => "tuple",
Function(_) => "function definition",
Array(..) => "array",
Repeat(..) => "repeat",
Expand Down
45 changes: 38 additions & 7 deletions src/librustc/mir/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::borrow::{Cow};
use std::fmt::{self, Debug, Formatter, Write};
use std::{iter, u32};
use std::ops::{Index, IndexMut};
use syntax::ast::{self, Name};
use syntax::ast::Name;
use syntax::codemap::Span;

/// Lowered representation of a single function.
Expand Down Expand Up @@ -1039,17 +1039,48 @@ fn fmt_const_val<W: Write>(fmt: &mut W, const_val: &ConstVal) -> fmt::Result {
}
Bool(b) => write!(fmt, "{:?}", b),
Function(def_id) => write!(fmt, "{}", item_path_str(def_id)),
Struct(node_id) | Tuple(node_id) | Array(node_id, _) | Repeat(node_id, _) =>
write!(fmt, "{}", node_to_string(node_id)),
Struct(def_id, ref tree) => {
write!(fmt, "{}", item_path_str(def_id))?;
if !tree.is_empty() {
write!(fmt, "{{")?;
for (name, val) in tree {
write!(fmt, "{}:", name)?;
fmt_const_val(fmt, val)?;
write!(fmt, ",")?;
}
write!(fmt, "}}")?;
}
Ok(())
},
Tuple(def_id, ref v) => {
if let Some(def_id) = def_id {
write!(fmt, "{}", item_path_str(def_id))?;
}
write!(fmt, "(")?;
for val in v {
fmt_const_val(fmt, val)?;
write!(fmt, ",")?;
}
write!(fmt, ")")
},
Array(ref v) => {
write!(fmt, "[")?;
for val in v {
fmt_const_val(fmt, val)?;
write!(fmt, ",")?;
}
write!(fmt, "]")
},
Repeat(ref v, n) => {
write!(fmt, "[")?;
fmt_const_val(fmt, v)?;
write!(fmt, ";{}]", n)
},
Char(c) => write!(fmt, "{:?}", c),
Dummy => bug!(),
}
}

fn node_to_string(node_id: ast::NodeId) -> String {
ty::tls::with(|tcx| tcx.map.node_to_user_string(node_id))
}

fn item_path_str(def_id: DefId) -> String {
ty::tls::with(|tcx| tcx.item_path_str(def_id))
}
141 changes: 87 additions & 54 deletions src/librustc_const_eval/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use syntax::attr::IntType;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::hash_map::Entry::Vacant;
use std::collections::BTreeMap;

use rustc_const_math::*;

Expand Down Expand Up @@ -410,9 +411,11 @@ pub enum ErrKind {
IntermediateUnsignedNegative,
/// Expected, Got
TypeMismatch(String, ConstInt),
BadType(ConstVal),
/// target type, got value
BadType(String, ConstVal),
ErroneousReferencedConstant(Box<ConstEvalErr>),
CharCast(ConstInt),
Aggregate(Vec<ConstEvalErr>),
}

impl From<ConstMathErr> for ErrKind {
Expand Down Expand Up @@ -471,11 +474,12 @@ impl ConstEvalErr {
format!("mismatched types: expected `{}`, found `{}`",
expected, got.description()).into_cow()
},
BadType(ref i) => format!("value of wrong type: {:?}", i).into_cow(),
BadType(ref ty, ref i) => format!("expected `{}`, found `{:?}`", ty, i).into_cow(),
ErroneousReferencedConstant(_) => "could not evaluate referenced constant".into_cow(),
CharCast(ref got) => {
format!("only `u8` can be cast as `char`, not `{}`", got.description()).into_cow()
},
Aggregate(ref v) => format!("evaluation of {} fields failed", v.len()).into_cow(),
}
}
}
Expand Down Expand Up @@ -771,9 +775,7 @@ pub fn eval_const_expr_partial<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
signal!(e, UnimplementedConstVal("enum variants"));
}
}
Def::Struct(..) => {
ConstVal::Struct(e.id)
}
Def::Struct(did) => Struct(did, BTreeMap::new()),
Def::Local(_, id) => {
debug!("Def::Local({:?}): {:?}", id, fn_args);
if let Some(val) = fn_args.and_then(|args| args.get(&id)) {
Expand All @@ -791,15 +793,14 @@ pub fn eval_const_expr_partial<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
let callee_val = eval_const_expr_partial(tcx, callee, sub_ty_hint, fn_args)?;
let did = match callee_val {
Function(did) => did,
Struct(_) => signal!(e, UnimplementedConstVal("tuple struct constructors")),
Struct(..) => signal!(e, UnimplementedConstVal("tuple struct constructors")),
callee => signal!(e, CallOn(callee)),
};
let (decl, result) = if let Some(fn_like) = lookup_const_fn_by_id(tcx, did) {
(fn_like.decl(), &fn_like.body().expr)
} else {
signal!(e, NonConstPath)
};
let result = result.as_ref().expect("const fn has no result expression");
assert_eq!(decl.inputs.len(), args.len());

let mut call_args = NodeMap();
Expand All @@ -816,7 +817,11 @@ pub fn eval_const_expr_partial<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
assert!(old.is_none());
}
debug!("const call({:?})", call_args);
eval_const_expr_partial(tcx, &result, ty_hint, Some(&call_args))?
if let &Some(ref result) = result {
eval_const_expr_partial(tcx, &result, ty_hint, Some(&call_args))?
} else {
Tuple(None, Vec::new())
}
},
hir::ExprLit(ref lit) => match lit_to_const(&lit.node, tcx, ety, lit.span) {
Ok(val) => val,
Expand All @@ -825,12 +830,46 @@ pub fn eval_const_expr_partial<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
hir::ExprBlock(ref block) => {
match block.expr {
Some(ref expr) => eval_const_expr_partial(tcx, &expr, ty_hint, fn_args)?,
None => signal!(e, UnimplementedConstVal("empty block")),
None => Tuple(None, Vec::new()), // unit value
}
}
hir::ExprType(ref e, _) => eval_const_expr_partial(tcx, &e, ty_hint, fn_args)?,
hir::ExprTup(_) => Tuple(e.id),
hir::ExprStruct(..) => Struct(e.id),
hir::ExprTup(ref v) => {
let mut fields = Vec::with_capacity(v.len());
let mut errors = Vec::new();
for field in v {
match eval_const_expr_partial(tcx, field, ty_hint.erase_hint(), fn_args) {
Ok(v) => fields.push(v),
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
signal!(e, Aggregate(errors));
}
assert_eq!(fields.len(), v.len());
Tuple(None, fields)
},
hir::ExprStruct(_, _, Some(_)) => signal!(e, UnimplementedConstVal("struct base")),
hir::ExprStruct(_, ref fields, None) => {
let def_id = match tcx.def_map.borrow().get(&e.id).map(|def| def.full_def()) {
Some(Def::Struct(def_id)) => def_id,
Some(Def::Variant(..)) => signal!(e, UnimplementedConstVal("enums")),
_ => signal!(e, NonConstPath),
};
let mut new_fields = BTreeMap::new();
let mut errors = Vec::new();
for field in fields {
match eval_const_expr_partial(tcx, &field.expr, ty_hint.erase_hint(), fn_args) {
Ok(f_val) => assert!(new_fields.insert(field.name.node, f_val).is_none()),
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
signal!(e, Aggregate(errors));
}
assert_eq!(new_fields.len(), fields.len());
Struct(def_id, new_fields)
},
hir::ExprIndex(ref arr, ref idx) => {
if !tcx.sess.features.borrow().const_indexing {
signal!(e, IndexOpFeatureGated);
Expand All @@ -845,21 +884,11 @@ pub fn eval_const_expr_partial<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
};
assert_eq!(idx as usize as u64, idx);
match arr {
Array(_, n) if idx >= n => signal!(e, IndexOutOfBounds),
Array(v, n) => if let hir::ExprVec(ref v) = tcx.map.expect_expr(v).node {
assert_eq!(n as usize as u64, n);
eval_const_expr_partial(tcx, &v[idx as usize], ty_hint, fn_args)?
} else {
bug!()
},
Array(ref v) if idx as usize >= v.len() => signal!(e, IndexOutOfBounds),
Array(ref v) => v[idx as usize].clone(),

Repeat(_, n) if idx >= n => signal!(e, IndexOutOfBounds),
Repeat(elem, _) => eval_const_expr_partial(
tcx,
&tcx.map.expect_expr(elem),
ty_hint,
fn_args,
)?,
Repeat(elem, _) => *elem,

ByteStr(ref data) if idx >= data.len() as u64 => signal!(e, IndexOutOfBounds),
ByteStr(data) => {
Expand All @@ -872,11 +901,30 @@ pub fn eval_const_expr_partial<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
_ => signal!(e, IndexedNonVec),
}
}
hir::ExprVec(ref v) => Array(e.id, v.len() as u64),
hir::ExprRepeat(_, ref n) => {
hir::ExprVec(ref v) => {
let mut elems = Vec::with_capacity(v.len());
let mut errors = Vec::new();
for elem in v {
match eval_const_expr_partial(tcx, elem, ty_hint.erase_hint(), fn_args) {
Ok(elem) => elems.push(elem),
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
signal!(e, Aggregate(errors));
}
assert_eq!(elems.len(), v.len());
Array(elems)
},
hir::ExprRepeat(ref elem, ref n) => {
let len_hint = ty_hint.checked_or(tcx.types.usize);
let val_hint = match ty_hint {
ExprTypeChecked => ExprTypeChecked,
UncheckedExprNoHint => UncheckedExprNoHint,
UncheckedExprHint(ty) => UncheckedExprHint(ty.sequence_element_type(tcx)),
};
Repeat(
e.id,
box eval_const_expr_partial(tcx, elem, val_hint, fn_args)?,
match eval_const_expr_partial(tcx, &n, len_hint, fn_args)? {
Integral(Usize(i)) => i.as_u64(tcx.sess.target.uint_type),
Integral(_) => signal!(e, RepeatCountNotNatural),
Expand All @@ -886,40 +934,24 @@ pub fn eval_const_expr_partial<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
},
hir::ExprTupField(ref base, index) => {
let base_hint = ty_hint.erase_hint();
let c = eval_const_expr_partial(tcx, base, base_hint, fn_args)?;
if let Tuple(tup_id) = c {
if let hir::ExprTup(ref fields) = tcx.map.expect_expr(tup_id).node {
if index.node < fields.len() {
eval_const_expr_partial(tcx, &fields[index.node], ty_hint, fn_args)?
} else {
signal!(e, TupleIndexOutOfBounds);
}
} else {
bug!()
}
} else {
signal!(base, ExpectedConstTuple);
match eval_const_expr_partial(tcx, base, base_hint, fn_args)? {
Tuple(_, ref v) if index.node >= v.len() => signal!(e, TupleIndexOutOfBounds),
Tuple(_, v) => v[index.node as usize].clone(),
_ => signal!(base, ExpectedConstTuple),
}
}
hir::ExprField(ref base, field_name) => {
let base_hint = ty_hint.erase_hint();
// Get the base expression if it is a struct and it is constant
let c = eval_const_expr_partial(tcx, base, base_hint, fn_args)?;
if let Struct(struct_id) = c {
if let hir::ExprStruct(_, ref fields, _) = tcx.map.expect_expr(struct_id).node {
// Check that the given field exists and evaluate it
// if the idents are compared run-pass/issue-19244 fails
if let Some(f) = fields.iter().find(|f| f.name.node
== field_name.node) {
eval_const_expr_partial(tcx, &f.expr, ty_hint, fn_args)?
match eval_const_expr_partial(tcx, base, base_hint, fn_args)? {
Struct(_, fields) => {
if let Some(f) = fields.get(&field_name.node) {
f.clone()
} else {
signal!(e, MissingStructField);
}
} else {
bug!()
}
} else {
signal!(base, ExpectedConstStruct);
},
_ => signal!(base, ExpectedConstStruct),
}
}
hir::ExprAddrOf(..) => signal!(e, UnimplementedConstVal("address operator")),
Expand Down Expand Up @@ -996,7 +1028,8 @@ fn infer<'a, 'tcx>(i: ConstInt,
let int_ty = tcx.enum_repr_type(hints.iter().next());
infer(i, tcx, &int_ty.to_ty(tcx).sty)
},
(_, i) => Err(BadType(ConstVal::Integral(i))),
(&ty::TyParam(_), i) => Ok(i),
(ty, i) => Err(BadType(ty.to_string(), ConstVal::Integral(i))),
}
}

Expand Down
Loading