X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2Flibrustc%2Fmiddle%2Fconst_eval.rs;h=ed06ccf1ec649990b8f82f2942eade35761da25f;hb=62682a34a512e840017dd5171b19c58f817a095a;hp=d610b007f3503429a2a8c2cd56a8b3ec6808dd2b;hpb=223e47cc311a837a9ffe67e75bd445e343902380;p=rustc.git diff --git a/src/librustc/middle/const_eval.rs b/src/librustc/middle/const_eval.rs index d610b007f3..ed06ccf1ec 100644 --- a/src/librustc/middle/const_eval.rs +++ b/src/librustc/middle/const_eval.rs @@ -1,4 +1,4 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -8,493 +8,1184 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use core::prelude::*; +#![allow(non_camel_case_types)] +#![allow(unsigned_negation)] -use metadata::csearch; -use middle::astencode; -use middle::ty; -use middle; +use self::ConstVal::*; -use core::float; -use core::vec; -use syntax::{ast, ast_map, ast_util, visit}; -use syntax::ast::*; +use self::ErrKind::*; -use core::hashmap::linear::{LinearMap, LinearSet}; +use ast_map; +use ast_map::blocks::FnLikeNode; +use metadata::csearch; +use middle::{astencode, def, infer, subst, traits}; +use middle::pat_util::def_to_path; +use middle::ty::{self, Ty}; +use middle::astconv_util::ast_ty_to_prim_ty; +use util::num::ToPrimitive; -// -// This pass classifies expressions by their constant-ness. -// -// Constant-ness comes in 3 flavours: -// -// - Integer-constants: can be evaluated by the frontend all the way down -// to their actual value. They are used in a few places (enum -// discriminants, switch arms) and are a subset of -// general-constants. They cover all the integer and integer-ish -// literals (nil, bool, int, uint, char, iNN, uNN) and all integer -// operators and copies applied to them. -// -// - General-constants: can be evaluated by LLVM but not necessarily by -// the frontend; usually due to reliance on target-specific stuff such -// as "where in memory the value goes" or "what floating point mode the -// target uses". This _includes_ integer-constants, plus the following -// constructors: -// -// fixed-size vectors and strings: [] and ""/_ -// vector and string slices: &[] and &"" -// tuples: (,) -// records: {...} -// enums: foo(...) -// floating point literals and operators -// & and * pointers -// copies of general constants -// -// (in theory, probably not at first: if/match on integer-const -// conditions / descriminants) -// -// - Non-constants: everything else. -// +use syntax::ast::{self, Expr}; +use syntax::ast_util; +use syntax::codemap::Span; +use syntax::feature_gate; +use syntax::parse::token::InternedString; +use syntax::ptr::P; +use syntax::{codemap, visit}; + +use std::borrow::{Cow, IntoCow}; +use std::num::wrapping::OverflowingOps; +use std::cmp::Ordering; +use std::collections::hash_map::Entry::Vacant; +use std::{i8, i16, i32, i64, u8, u16, u32, u64}; +use std::rc::Rc; -pub enum constness { - integral_const, - general_const, - non_const +fn lookup_const<'a>(tcx: &'a ty::ctxt, e: &Expr) -> Option<&'a Expr> { + let opt_def = tcx.def_map.borrow().get(&e.id).map(|d| d.full_def()); + match opt_def { + Some(def::DefConst(def_id)) | + Some(def::DefAssociatedConst(def_id, _)) => { + lookup_const_by_id(tcx, def_id, Some(e.id)) + } + Some(def::DefVariant(enum_def, variant_def, _)) => { + lookup_variant_by_id(tcx, enum_def, variant_def) + } + _ => None + } } -pub fn join(a: constness, b: constness) -> constness { - match (a, b) { - (integral_const, integral_const) => integral_const, - (integral_const, general_const) - | (general_const, integral_const) - | (general_const, general_const) => general_const, - _ => non_const +fn lookup_variant_by_id<'a>(tcx: &'a ty::ctxt, + enum_def: ast::DefId, + variant_def: ast::DefId) + -> Option<&'a Expr> { + fn variant_expr<'a>(variants: &'a [P], id: ast::NodeId) + -> Option<&'a Expr> { + for variant in variants { + if variant.node.id == id { + return variant.node.disr_expr.as_ref().map(|e| &**e); + } + } + None + } + + if ast_util::is_local(enum_def) { + match tcx.map.find(enum_def.node) { + None => None, + Some(ast_map::NodeItem(it)) => match it.node { + ast::ItemEnum(ast::EnumDef { ref variants }, _) => { + variant_expr(&variants[..], variant_def.node) + } + _ => None + }, + Some(_) => None + } + } else { + match tcx.extern_const_variants.borrow().get(&variant_def) { + Some(&ast::DUMMY_NODE_ID) => return None, + Some(&expr_id) => { + return Some(tcx.map.expect_expr(expr_id)); + } + None => {} + } + let expr_id = match csearch::maybe_get_item_ast(tcx, enum_def, + Box::new(|a, b, c, d| astencode::decode_inlined_item(a, b, c, d))) { + csearch::FoundAst::Found(&ast::IIItem(ref item)) => match item.node { + ast::ItemEnum(ast::EnumDef { ref variants }, _) => { + // NOTE this doesn't do the right thing, it compares inlined + // NodeId's to the original variant_def's NodeId, but they + // come from different crates, so they will likely never match. + variant_expr(&variants[..], variant_def.node).map(|e| e.id) + } + _ => None + }, + _ => None + }; + tcx.extern_const_variants.borrow_mut().insert(variant_def, + expr_id.unwrap_or(ast::DUMMY_NODE_ID)); + expr_id.map(|id| tcx.map.expect_expr(id)) } } -pub fn join_all(cs: &[constness]) -> constness { - vec::foldl(integral_const, cs, |a, b| join(a, *b)) -} - -pub fn classify(e: @expr, - tcx: ty::ctxt) - -> constness { - let did = ast_util::local_def(e.id); - match tcx.ccache.find(&did) { - Some(&x) => x, - None => { - let cn = - match e.node { - ast::expr_lit(lit) => { - match lit.node { - ast::lit_str(*) | - ast::lit_float(*) => general_const, - _ => integral_const +pub fn lookup_const_by_id<'a, 'tcx: 'a>(tcx: &'a ty::ctxt<'tcx>, + def_id: ast::DefId, + maybe_ref_id: Option) + -> Option<&'tcx Expr> { + if ast_util::is_local(def_id) { + match tcx.map.find(def_id.node) { + None => None, + Some(ast_map::NodeItem(it)) => match it.node { + ast::ItemConst(_, ref const_expr) => { + Some(&*const_expr) } - } + _ => None + }, + Some(ast_map::NodeTraitItem(ti)) => match ti.node { + ast::ConstTraitItem(_, _) => { + match maybe_ref_id { + // If we have a trait item, and we know the expression + // that's the source of the obligation to resolve it, + // `resolve_trait_associated_const` will select an impl + // or the default. + Some(ref_id) => { + let trait_id = ty::trait_of_item(tcx, def_id) + .unwrap(); + let substs = ty::node_id_item_substs(tcx, ref_id) + .substs; + resolve_trait_associated_const(tcx, ti, trait_id, + substs) + } + // Technically, without knowing anything about the + // expression that generates the obligation, we could + // still return the default if there is one. However, + // it's safer to return `None` than to return some value + // that may differ from what you would get from + // correctly selecting an impl. + None => None + } + } + _ => None + }, + Some(ast_map::NodeImplItem(ii)) => match ii.node { + ast::ConstImplItem(_, ref expr) => { + Some(&*expr) + } + _ => None + }, + Some(_) => None + } + } else { + match tcx.extern_const_statics.borrow().get(&def_id) { + Some(&ast::DUMMY_NODE_ID) => return None, + Some(&expr_id) => { + return Some(tcx.map.expect_expr(expr_id)); + } + None => {} + } + let mut used_ref_id = false; + let expr_id = match csearch::maybe_get_item_ast(tcx, def_id, + Box::new(|a, b, c, d| astencode::decode_inlined_item(a, b, c, d))) { + csearch::FoundAst::Found(&ast::IIItem(ref item)) => match item.node { + ast::ItemConst(_, ref const_expr) => Some(const_expr.id), + _ => None + }, + csearch::FoundAst::Found(&ast::IITraitItem(trait_id, ref ti)) => match ti.node { + ast::ConstTraitItem(_, _) => { + used_ref_id = true; + match maybe_ref_id { + // As mentioned in the comments above for in-crate + // constants, we only try to find the expression for + // a trait-associated const if the caller gives us + // the expression that refers to it. + Some(ref_id) => { + let substs = ty::node_id_item_substs(tcx, ref_id) + .substs; + resolve_trait_associated_const(tcx, ti, trait_id, + substs).map(|e| e.id) + } + None => None + } + } + _ => None + }, + csearch::FoundAst::Found(&ast::IIImplItem(_, ref ii)) => match ii.node { + ast::ConstImplItem(_, ref expr) => Some(expr.id), + _ => None + }, + _ => None + }; + // If we used the reference expression, particularly to choose an impl + // of a trait-associated const, don't cache that, because the next + // lookup with the same def_id may yield a different result. + if !used_ref_id { + tcx.extern_const_statics + .borrow_mut().insert(def_id, + expr_id.unwrap_or(ast::DUMMY_NODE_ID)); + } + expr_id.map(|id| tcx.map.expect_expr(id)) + } +} - ast::expr_copy(inner) | - ast::expr_unary(_, inner) | - ast::expr_paren(inner) => { - classify(inner, tcx) - } +fn inline_const_fn_from_external_crate(tcx: &ty::ctxt, def_id: ast::DefId) + -> Option { + match tcx.extern_const_fns.borrow().get(&def_id) { + Some(&ast::DUMMY_NODE_ID) => return None, + Some(&fn_id) => return Some(fn_id), + None => {} + } - ast::expr_binary(_, a, b) => { - join(classify(a, tcx), - classify(b, tcx)) - } + if !csearch::is_const_fn(&tcx.sess.cstore, def_id) { + tcx.extern_const_fns.borrow_mut().insert(def_id, ast::DUMMY_NODE_ID); + return None; + } - ast::expr_tup(ref es) | - ast::expr_vec(ref es, ast::m_imm) => { - join_all(vec::map(*es, |e| classify(*e, tcx))) - } + let fn_id = match csearch::maybe_get_item_ast(tcx, def_id, + box |a, b, c, d| astencode::decode_inlined_item(a, b, c, d)) { + csearch::FoundAst::Found(&ast::IIItem(ref item)) => Some(item.id), + csearch::FoundAst::Found(&ast::IIImplItem(_, ref item)) => Some(item.id), + _ => None + }; + tcx.extern_const_fns.borrow_mut().insert(def_id, + fn_id.unwrap_or(ast::DUMMY_NODE_ID)); + fn_id +} - ast::expr_vstore(e, vstore) => { - match vstore { - ast::expr_vstore_slice => classify(e, tcx), - ast::expr_vstore_uniq | - ast::expr_vstore_box | - ast::expr_vstore_mut_box | - ast::expr_vstore_mut_slice => non_const - } - } +pub fn lookup_const_fn_by_id<'tcx>(tcx: &ty::ctxt<'tcx>, def_id: ast::DefId) + -> Option> +{ + let fn_id = if !ast_util::is_local(def_id) { + if let Some(fn_id) = inline_const_fn_from_external_crate(tcx, def_id) { + fn_id + } else { + return None; + } + } else { + def_id.node + }; - ast::expr_struct(_, ref fs, None) => { - let cs = do vec::map((*fs)) |f| { - if f.node.mutbl == ast::m_imm { - classify(f.node.expr, tcx) - } else { - non_const - } - }; - join_all(cs) - } + let fn_like = match FnLikeNode::from_node(tcx.map.get(fn_id)) { + Some(fn_like) => fn_like, + None => return None + }; - ast::expr_cast(base, _) => { - let ty = ty::expr_ty(tcx, e); - let base = classify(base, tcx); - if ty::type_is_integral(ty) { - join(integral_const, base) - } else if ty::type_is_fp(ty) { - join(general_const, base) - } else { - non_const - } - } + match fn_like.kind() { + visit::FkItemFn(_, _, _, ast::Constness::Const, _, _) => { + Some(fn_like) + } + visit::FkMethod(_, m, _) => { + if m.constness == ast::Constness::Const { + Some(fn_like) + } else { + None + } + } + _ => None + } +} - ast::expr_field(base, _, _) => { - classify(base, tcx) - } +#[derive(Clone, PartialEq)] +pub enum ConstVal { + Float(f64), + Int(i64), + Uint(u64), + Str(InternedString), + Binary(Rc>), + Bool(bool), + Struct(ast::NodeId), + Tuple(ast::NodeId), +} - ast::expr_index(base, idx) => { - join(classify(base, tcx), - classify(idx, tcx)) - } +pub fn const_expr_to_pat(tcx: &ty::ctxt, expr: &Expr, span: Span) -> P { + let pat = match expr.node { + ast::ExprTup(ref exprs) => + ast::PatTup(exprs.iter().map(|expr| const_expr_to_pat(tcx, &**expr, span)).collect()), - ast::expr_addr_of(ast::m_imm, base) => { - classify(base, tcx) - } + ast::ExprCall(ref callee, ref args) => { + let def = *tcx.def_map.borrow().get(&callee.id).unwrap(); + if let Vacant(entry) = tcx.def_map.borrow_mut().entry(expr.id) { + entry.insert(def); + } + let path = match def.full_def() { + def::DefStruct(def_id) => def_to_path(tcx, def_id), + def::DefVariant(_, variant_did, _) => def_to_path(tcx, variant_did), + _ => unreachable!() + }; + let pats = args.iter().map(|expr| const_expr_to_pat(tcx, &**expr, span)).collect(); + ast::PatEnum(path, Some(pats)) + } - // FIXME: (#3728) we can probably do something CCI-ish - // surrounding nonlocal constants. But we don't yet. - ast::expr_path(_) => { - lookup_constness(tcx, e) - } + ast::ExprStruct(ref path, ref fields, None) => { + let field_pats = fields.iter().map(|field| codemap::Spanned { + span: codemap::DUMMY_SP, + node: ast::FieldPat { + ident: field.ident.node, + pat: const_expr_to_pat(tcx, &*field.expr, span), + is_shorthand: false, + }, + }).collect(); + ast::PatStruct(path.clone(), field_pats, false) + } - _ => non_const - }; - tcx.ccache.insert(did, cn); - cn - } + ast::ExprVec(ref exprs) => { + let pats = exprs.iter().map(|expr| const_expr_to_pat(tcx, &**expr, span)).collect(); + ast::PatVec(pats, None, vec![]) + } + + ast::ExprPath(_, ref path) => { + let opt_def = tcx.def_map.borrow().get(&expr.id).map(|d| d.full_def()); + match opt_def { + Some(def::DefStruct(..)) => + ast::PatStruct(path.clone(), vec![], false), + Some(def::DefVariant(..)) => + ast::PatEnum(path.clone(), None), + _ => { + match lookup_const(tcx, expr) { + Some(actual) => return const_expr_to_pat(tcx, actual, span), + _ => unreachable!() + } + } + } + } + + _ => ast::PatLit(P(expr.clone())) + }; + P(ast::Pat { id: expr.id, node: pat, span: span }) +} + +pub fn eval_const_expr(tcx: &ty::ctxt, e: &Expr) -> ConstVal { + match eval_const_expr_partial(tcx, e, None) { + Ok(r) => r, + Err(s) => tcx.sess.span_fatal(s.span, &s.description()) } } -pub fn lookup_const(tcx: ty::ctxt, e: @expr) -> Option<@expr> { - match tcx.def_map.find(&e.id) { - Some(&ast::def_const(def_id)) => lookup_const_by_id(tcx, def_id), - _ => None + +#[derive(Clone)] +pub struct ConstEvalErr { + pub span: Span, + pub kind: ErrKind, +} + +#[derive(Clone)] +pub enum ErrKind { + CannotCast, + CannotCastTo(&'static str), + InvalidOpForBools(ast::BinOp_), + InvalidOpForFloats(ast::BinOp_), + InvalidOpForIntUint(ast::BinOp_), + InvalidOpForUintInt(ast::BinOp_), + NegateOnString, + NegateOnBoolean, + NegateOnBinary, + NegateOnStruct, + NegateOnTuple, + NotOnFloat, + NotOnString, + NotOnBinary, + NotOnStruct, + NotOnTuple, + + NegateWithOverflow(i64), + AddiWithOverflow(i64, i64), + SubiWithOverflow(i64, i64), + MuliWithOverflow(i64, i64), + AdduWithOverflow(u64, u64), + SubuWithOverflow(u64, u64), + MuluWithOverflow(u64, u64), + DivideByZero, + DivideWithOverflow, + ModuloByZero, + ModuloWithOverflow, + ShiftLeftWithOverflow, + ShiftRightWithOverflow, + MissingStructField, + NonConstPath, + ExpectedConstTuple, + ExpectedConstStruct, + TupleIndexOutOfBounds, + + MiscBinaryOp, + MiscCatchAll, +} + +impl ConstEvalErr { + pub fn description(&self) -> Cow { + use self::ErrKind::*; + + match self.kind { + CannotCast => "can't cast this type".into_cow(), + CannotCastTo(s) => format!("can't cast this type to {}", s).into_cow(), + InvalidOpForBools(_) => "can't do this op on bools".into_cow(), + InvalidOpForFloats(_) => "can't do this op on floats".into_cow(), + InvalidOpForIntUint(..) => "can't do this op on an isize and usize".into_cow(), + InvalidOpForUintInt(..) => "can't do this op on a usize and isize".into_cow(), + NegateOnString => "negate on string".into_cow(), + NegateOnBoolean => "negate on boolean".into_cow(), + NegateOnBinary => "negate on binary literal".into_cow(), + NegateOnStruct => "negate on struct".into_cow(), + NegateOnTuple => "negate on tuple".into_cow(), + NotOnFloat => "not on float or string".into_cow(), + NotOnString => "not on float or string".into_cow(), + NotOnBinary => "not on binary literal".into_cow(), + NotOnStruct => "not on struct".into_cow(), + NotOnTuple => "not on tuple".into_cow(), + + NegateWithOverflow(..) => "attempted to negate with overflow".into_cow(), + AddiWithOverflow(..) => "attempted to add with overflow".into_cow(), + SubiWithOverflow(..) => "attempted to sub with overflow".into_cow(), + MuliWithOverflow(..) => "attempted to mul with overflow".into_cow(), + AdduWithOverflow(..) => "attempted to add with overflow".into_cow(), + SubuWithOverflow(..) => "attempted to sub with overflow".into_cow(), + MuluWithOverflow(..) => "attempted to mul with overflow".into_cow(), + DivideByZero => "attempted to divide by zero".into_cow(), + DivideWithOverflow => "attempted to divide with overflow".into_cow(), + ModuloByZero => "attempted remainder with a divisor of zero".into_cow(), + ModuloWithOverflow => "attempted remainder with overflow".into_cow(), + ShiftLeftWithOverflow => "attempted left shift with overflow".into_cow(), + ShiftRightWithOverflow => "attempted right shift with overflow".into_cow(), + MissingStructField => "nonexistent struct field".into_cow(), + NonConstPath => "non-constant path in constant expr".into_cow(), + ExpectedConstTuple => "expected constant tuple".into_cow(), + ExpectedConstStruct => "expected constant struct".into_cow(), + TupleIndexOutOfBounds => "tuple index out of bounds".into_cow(), + + MiscBinaryOp => "bad operands for binary".into_cow(), + MiscCatchAll => "unsupported constant expr".into_cow(), + } } } -pub fn lookup_const_by_id(tcx: ty::ctxt, - def_id: ast::def_id) - -> Option<@expr> { - if ast_util::is_local(def_id) { - match tcx.items.find(&def_id.node) { - None => None, - Some(&ast_map::node_item(it, _)) => match it.node { - item_const(_, const_expr) => Some(const_expr), - _ => None - }, - Some(_) => None +pub type EvalResult = Result; +pub type CastResult = Result; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum IntTy { I8, I16, I32, I64 } +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum UintTy { U8, U16, U32, U64 } + +impl IntTy { + pub fn from(tcx: &ty::ctxt, t: ast::IntTy) -> IntTy { + let t = if let ast::TyIs = t { + tcx.sess.target.int_type + } else { + t + }; + match t { + ast::TyIs => unreachable!(), + ast::TyI8 => IntTy::I8, + ast::TyI16 => IntTy::I16, + ast::TyI32 => IntTy::I32, + ast::TyI64 => IntTy::I64, } - } else { - let maps = astencode::Maps { - mutbl_map: @mut LinearSet::new(), - root_map: @mut LinearMap::new(), - last_use_map: @mut LinearMap::new(), - method_map: @mut LinearMap::new(), - vtable_map: @mut LinearMap::new(), - write_guard_map: @mut LinearSet::new(), - moves_map: @mut LinearSet::new(), - capture_map: @mut LinearMap::new() + } +} + +impl UintTy { + pub fn from(tcx: &ty::ctxt, t: ast::UintTy) -> UintTy { + let t = if let ast::TyUs = t { + tcx.sess.target.uint_type + } else { + t }; - match csearch::maybe_get_item_ast(tcx, def_id, - |a, b, c, d| astencode::decode_inlined_item(a, b, maps, /*bar*/ copy c, d)) { - csearch::found(ast::ii_item(item)) => match item.node { - item_const(_, const_expr) => Some(const_expr), - _ => None - }, - _ => None + match t { + ast::TyUs => unreachable!(), + ast::TyU8 => UintTy::U8, + ast::TyU16 => UintTy::U16, + ast::TyU32 => UintTy::U32, + ast::TyU64 => UintTy::U64, } } } -pub fn lookup_constness(tcx: ty::ctxt, e: @expr) -> constness { - match lookup_const(tcx, e) { - Some(rhs) => { - let ty = ty::expr_ty(tcx, rhs); - if ty::type_is_integral(ty) { - integral_const - } else { - general_const +macro_rules! signal { + ($e:expr, $exn:expr) => { + return Err(ConstEvalErr { span: $e.span, kind: $exn }) + } +} + +// The const_{int,uint}_checked_{neg,add,sub,mul,div,shl,shr} family +// of functions catch and signal overflow errors during constant +// evaluation. +// +// They all take the operator's arguments (`a` and `b` if binary), the +// overall expression (`e`) and, if available, whole expression's +// concrete type (`opt_ety`). +// +// If the whole expression's concrete type is None, then this is a +// constant evaluation happening before type check (e.g. in the check +// to confirm that a pattern range's left-side is not greater than its +// right-side). We do not do arithmetic modulo the type's bitwidth in +// such a case; we just do 64-bit arithmetic and assume that later +// passes will do it again with the type information, and thus do the +// overflow checks then. + +pub fn const_int_checked_neg<'a>( + a: i64, e: &'a Expr, opt_ety: Option) -> EvalResult { + + let (min,max) = match opt_ety { + // (-i8::MIN is itself not an i8, etc, but this is an easy way + // to allow literals to pass the check. Of course that does + // not work for i64::MIN.) + Some(IntTy::I8) => (-(i8::MAX as i64), -(i8::MIN as i64)), + Some(IntTy::I16) => (-(i16::MAX as i64), -(i16::MIN as i64)), + Some(IntTy::I32) => (-(i32::MAX as i64), -(i32::MIN as i64)), + None | Some(IntTy::I64) => (-i64::MAX, -(i64::MIN+1)), + }; + + let oflo = a < min || a > max; + if oflo { + signal!(e, NegateWithOverflow(a)); + } else { + Ok(Int(-a)) + } +} + +pub fn const_uint_checked_neg<'a>( + a: u64, _e: &'a Expr, _opt_ety: Option) -> EvalResult { + // This always succeeds, and by definition, returns `(!a)+1`. + Ok(Uint((!a).wrapping_add(1))) +} + +fn const_uint_not(a: u64, opt_ety: Option) -> ConstVal { + let mask = match opt_ety { + Some(UintTy::U8) => u8::MAX as u64, + Some(UintTy::U16) => u16::MAX as u64, + Some(UintTy::U32) => u32::MAX as u64, + None | Some(UintTy::U64) => u64::MAX, + }; + Uint(!a & mask) +} + +macro_rules! overflow_checking_body { + ($a:ident, $b:ident, $ety:ident, $overflowing_op:ident, + lhs: $to_8_lhs:ident $to_16_lhs:ident $to_32_lhs:ident, + rhs: $to_8_rhs:ident $to_16_rhs:ident $to_32_rhs:ident $to_64_rhs:ident, + $EnumTy:ident $T8: ident $T16: ident $T32: ident $T64: ident, + $result_type: ident) => { { + let (a,b,opt_ety) = ($a,$b,$ety); + match opt_ety { + Some($EnumTy::$T8) => match (a.$to_8_lhs(), b.$to_8_rhs()) { + (Some(a), Some(b)) => { + let (a, oflo) = a.$overflowing_op(b); + (a as $result_type, oflo) + } + (None, _) | (_, None) => (0, true) + }, + Some($EnumTy::$T16) => match (a.$to_16_lhs(), b.$to_16_rhs()) { + (Some(a), Some(b)) => { + let (a, oflo) = a.$overflowing_op(b); + (a as $result_type, oflo) + } + (None, _) | (_, None) => (0, true) + }, + Some($EnumTy::$T32) => match (a.$to_32_lhs(), b.$to_32_rhs()) { + (Some(a), Some(b)) => { + let (a, oflo) = a.$overflowing_op(b); + (a as $result_type, oflo) + } + (None, _) | (_, None) => (0, true) + }, + None | Some($EnumTy::$T64) => match b.$to_64_rhs() { + Some(b) => a.$overflowing_op(b), + None => (0, true), } } - None => non_const + } } +} + +macro_rules! int_arith_body { + ($a:ident, $b:ident, $ety:ident, $overflowing_op:ident) => { + overflow_checking_body!( + $a, $b, $ety, $overflowing_op, + lhs: to_i8 to_i16 to_i32, + rhs: to_i8 to_i16 to_i32 to_i64, IntTy I8 I16 I32 I64, i64) } } -pub fn process_crate(crate: @ast::crate, - tcx: ty::ctxt) { - let v = visit::mk_simple_visitor(@visit::SimpleVisitor { - visit_expr_post: |e| { classify(e, tcx); }, - .. *visit::default_simple_visitor() - }); - visit::visit_crate(*crate, (), v); - tcx.sess.abort_if_errors(); +macro_rules! uint_arith_body { + ($a:ident, $b:ident, $ety:ident, $overflowing_op:ident) => { + overflow_checking_body!( + $a, $b, $ety, $overflowing_op, + lhs: to_u8 to_u16 to_u32, + rhs: to_u8 to_u16 to_u32 to_u64, UintTy U8 U16 U32 U64, u64) + } } +macro_rules! int_shift_body { + ($a:ident, $b:ident, $ety:ident, $overflowing_op:ident) => { + overflow_checking_body!( + $a, $b, $ety, $overflowing_op, + lhs: to_i8 to_i16 to_i32, + rhs: to_u32 to_u32 to_u32 to_u32, IntTy I8 I16 I32 I64, i64) + } +} -// FIXME (#33): this doesn't handle big integer/float literals correctly -// (nor does the rest of our literal handling). -#[deriving(Eq)] -pub enum const_val { - const_float(f64), - const_int(i64), - const_uint(u64), - const_str(~str), - const_bool(bool) +macro_rules! uint_shift_body { + ($a:ident, $b:ident, $ety:ident, $overflowing_op:ident) => { + overflow_checking_body!( + $a, $b, $ety, $overflowing_op, + lhs: to_u8 to_u16 to_u32, + rhs: to_u32 to_u32 to_u32 to_u32, UintTy U8 U16 U32 U64, u64) + } } -pub fn eval_const_expr(tcx: middle::ty::ctxt, e: @expr) -> const_val { - match eval_const_expr_partial(tcx, e) { - Ok(ref r) => (/*bad*/copy *r), - Err(ref s) => fail!(/*bad*/copy *s) +macro_rules! pub_fn_checked_op { + {$fn_name:ident ($a:ident : $a_ty:ty, $b:ident : $b_ty:ty,.. $WhichTy:ident) { + $ret_oflo_body:ident $overflowing_op:ident + $const_ty:ident $signal_exn:expr + }} => { + pub fn $fn_name<'a>($a: $a_ty, + $b: $b_ty, + e: &'a Expr, + opt_ety: Option<$WhichTy>) -> EvalResult { + let (ret, oflo) = $ret_oflo_body!($a, $b, opt_ety, $overflowing_op); + if !oflo { Ok($const_ty(ret)) } else { signal!(e, $signal_exn) } + } } } -pub fn eval_const_expr_partial(tcx: middle::ty::ctxt, e: @expr) - -> Result { - use middle::ty; - fn fromb(b: bool) -> Result { Ok(const_int(b as i64)) } - match e.node { - expr_unary(neg, inner) => { - match eval_const_expr_partial(tcx, inner) { - Ok(const_float(f)) => Ok(const_float(-f)), - Ok(const_int(i)) => Ok(const_int(-i)), - Ok(const_uint(i)) => Ok(const_uint(-i)), - Ok(const_str(_)) => Err(~"Negate on string"), - Ok(const_bool(_)) => Err(~"Negate on boolean"), - ref err => (/*bad*/copy *err) +pub_fn_checked_op!{ const_int_checked_add(a: i64, b: i64,.. IntTy) { + int_arith_body overflowing_add Int AddiWithOverflow(a, b) +}} + +pub_fn_checked_op!{ const_int_checked_sub(a: i64, b: i64,.. IntTy) { + int_arith_body overflowing_sub Int SubiWithOverflow(a, b) +}} + +pub_fn_checked_op!{ const_int_checked_mul(a: i64, b: i64,.. IntTy) { + int_arith_body overflowing_mul Int MuliWithOverflow(a, b) +}} + +pub fn const_int_checked_div<'a>( + a: i64, b: i64, e: &'a Expr, opt_ety: Option) -> EvalResult { + if b == 0 { signal!(e, DivideByZero); } + let (ret, oflo) = int_arith_body!(a, b, opt_ety, overflowing_div); + if !oflo { Ok(Int(ret)) } else { signal!(e, DivideWithOverflow) } +} + +pub fn const_int_checked_rem<'a>( + a: i64, b: i64, e: &'a Expr, opt_ety: Option) -> EvalResult { + if b == 0 { signal!(e, ModuloByZero); } + let (ret, oflo) = int_arith_body!(a, b, opt_ety, overflowing_rem); + if !oflo { Ok(Int(ret)) } else { signal!(e, ModuloWithOverflow) } +} + +pub_fn_checked_op!{ const_int_checked_shl(a: i64, b: i64,.. IntTy) { + int_shift_body overflowing_shl Int ShiftLeftWithOverflow +}} + +pub_fn_checked_op!{ const_int_checked_shl_via_uint(a: i64, b: u64,.. IntTy) { + int_shift_body overflowing_shl Int ShiftLeftWithOverflow +}} + +pub_fn_checked_op!{ const_int_checked_shr(a: i64, b: i64,.. IntTy) { + int_shift_body overflowing_shr Int ShiftRightWithOverflow +}} + +pub_fn_checked_op!{ const_int_checked_shr_via_uint(a: i64, b: u64,.. IntTy) { + int_shift_body overflowing_shr Int ShiftRightWithOverflow +}} + +pub_fn_checked_op!{ const_uint_checked_add(a: u64, b: u64,.. UintTy) { + uint_arith_body overflowing_add Uint AdduWithOverflow(a, b) +}} + +pub_fn_checked_op!{ const_uint_checked_sub(a: u64, b: u64,.. UintTy) { + uint_arith_body overflowing_sub Uint SubuWithOverflow(a, b) +}} + +pub_fn_checked_op!{ const_uint_checked_mul(a: u64, b: u64,.. UintTy) { + uint_arith_body overflowing_mul Uint MuluWithOverflow(a, b) +}} + +pub fn const_uint_checked_div<'a>( + a: u64, b: u64, e: &'a Expr, opt_ety: Option) -> EvalResult { + if b == 0 { signal!(e, DivideByZero); } + let (ret, oflo) = uint_arith_body!(a, b, opt_ety, overflowing_div); + if !oflo { Ok(Uint(ret)) } else { signal!(e, DivideWithOverflow) } +} + +pub fn const_uint_checked_rem<'a>( + a: u64, b: u64, e: &'a Expr, opt_ety: Option) -> EvalResult { + if b == 0 { signal!(e, ModuloByZero); } + let (ret, oflo) = uint_arith_body!(a, b, opt_ety, overflowing_rem); + if !oflo { Ok(Uint(ret)) } else { signal!(e, ModuloWithOverflow) } +} + +pub_fn_checked_op!{ const_uint_checked_shl(a: u64, b: u64,.. UintTy) { + uint_shift_body overflowing_shl Uint ShiftLeftWithOverflow +}} + +pub_fn_checked_op!{ const_uint_checked_shl_via_int(a: u64, b: i64,.. UintTy) { + uint_shift_body overflowing_shl Uint ShiftLeftWithOverflow +}} + +pub_fn_checked_op!{ const_uint_checked_shr(a: u64, b: u64,.. UintTy) { + uint_shift_body overflowing_shr Uint ShiftRightWithOverflow +}} + +pub_fn_checked_op!{ const_uint_checked_shr_via_int(a: u64, b: i64,.. UintTy) { + uint_shift_body overflowing_shr Uint ShiftRightWithOverflow +}} + +// After type checking, `eval_const_expr_partial` should always suffice. The +// reason for providing `eval_const_expr_with_substs` is to allow +// trait-associated consts to be evaluated *during* type checking, when the +// substs for each expression have not been written into `tcx` yet. +pub fn eval_const_expr_partial<'tcx>(tcx: &ty::ctxt<'tcx>, + e: &Expr, + ty_hint: Option>) -> EvalResult { + eval_const_expr_with_substs(tcx, e, ty_hint, |id| { + ty::node_id_item_substs(tcx, id).substs + }) +} + +pub fn eval_const_expr_with_substs<'tcx, S>(tcx: &ty::ctxt<'tcx>, + e: &Expr, + ty_hint: Option>, + get_substs: S) -> EvalResult + where S: Fn(ast::NodeId) -> subst::Substs<'tcx> { + fn fromb(b: bool) -> ConstVal { Int(b as i64) } + + let ety = ty_hint.or_else(|| ty::expr_ty_opt(tcx, e)); + + // If type of expression itself is int or uint, normalize in these + // bindings so that isize/usize is mapped to a type with an + // inherently known bitwidth. + let expr_int_type = ety.and_then(|ty| { + if let ty::TyInt(t) = ty.sty { + Some(IntTy::from(tcx, t)) } else { None } + }); + let expr_uint_type = ety.and_then(|ty| { + if let ty::TyUint(t) = ty.sty { + Some(UintTy::from(tcx, t)) } else { None } + }); + + let result = match e.node { + ast::ExprUnary(ast::UnNeg, ref inner) => { + match try!(eval_const_expr_partial(tcx, &**inner, ety)) { + Float(f) => Float(-f), + Int(n) => try!(const_int_checked_neg(n, e, expr_int_type)), + Uint(i) => { + if !tcx.sess.features.borrow().negate_unsigned { + feature_gate::emit_feature_err( + &tcx.sess.parse_sess.span_diagnostic, + "negate_unsigned", + e.span, + "unary negation of unsigned integers may be removed in the future"); + } + try!(const_uint_checked_neg(i, e, expr_uint_type)) + } + Str(_) => signal!(e, NegateOnString), + Bool(_) => signal!(e, NegateOnBoolean), + Binary(_) => signal!(e, NegateOnBinary), + Tuple(_) => signal!(e, NegateOnTuple), + Struct(..) => signal!(e, NegateOnStruct), } } - expr_unary(not, inner) => { - match eval_const_expr_partial(tcx, inner) { - Ok(const_int(i)) => Ok(const_int(!i)), - Ok(const_uint(i)) => Ok(const_uint(!i)), - Ok(const_bool(b)) => Ok(const_bool(!b)), - _ => Err(~"Not on float or string") + ast::ExprUnary(ast::UnNot, ref inner) => { + match try!(eval_const_expr_partial(tcx, &**inner, ety)) { + Int(i) => Int(!i), + Uint(i) => const_uint_not(i, expr_uint_type), + Bool(b) => Bool(!b), + Str(_) => signal!(e, NotOnString), + Float(_) => signal!(e, NotOnFloat), + Binary(_) => signal!(e, NotOnBinary), + Tuple(_) => signal!(e, NotOnTuple), + Struct(..) => signal!(e, NotOnStruct), } } - expr_binary(op, a, b) => { - match (eval_const_expr_partial(tcx, a), - eval_const_expr_partial(tcx, b)) { - (Ok(const_float(a)), Ok(const_float(b))) => { - match op { - add => Ok(const_float(a + b)), - subtract => Ok(const_float(a - b)), - mul => Ok(const_float(a * b)), - div => Ok(const_float(a / b)), - rem => Ok(const_float(a % b)), - eq => fromb(a == b), - lt => fromb(a < b), - le => fromb(a <= b), - ne => fromb(a != b), - ge => fromb(a >= b), - gt => fromb(a > b), - _ => Err(~"Can't do this op on floats") + ast::ExprBinary(op, ref a, ref b) => { + let b_ty = match op.node { + ast::BiShl | ast::BiShr => Some(tcx.types.usize), + _ => ety + }; + match (try!(eval_const_expr_partial(tcx, &**a, ety)), + try!(eval_const_expr_partial(tcx, &**b, b_ty))) { + (Float(a), Float(b)) => { + match op.node { + ast::BiAdd => Float(a + b), + ast::BiSub => Float(a - b), + ast::BiMul => Float(a * b), + ast::BiDiv => Float(a / b), + ast::BiRem => Float(a % b), + ast::BiEq => fromb(a == b), + ast::BiLt => fromb(a < b), + ast::BiLe => fromb(a <= b), + ast::BiNe => fromb(a != b), + ast::BiGe => fromb(a >= b), + ast::BiGt => fromb(a > b), + _ => signal!(e, InvalidOpForFloats(op.node)) } } - (Ok(const_int(a)), Ok(const_int(b))) => { - match op { - add => Ok(const_int(a + b)), - subtract => Ok(const_int(a - b)), - mul => Ok(const_int(a * b)), - div if b == 0 => Err(~"divide by zero"), - div => Ok(const_int(a / b)), - rem if b == 0 => Err(~"modulo zero"), - rem => Ok(const_int(a % b)), - and | bitand => Ok(const_int(a & b)), - or | bitor => Ok(const_int(a | b)), - bitxor => Ok(const_int(a ^ b)), - shl => Ok(const_int(a << b)), - shr => Ok(const_int(a >> b)), - eq => fromb(a == b), - lt => fromb(a < b), - le => fromb(a <= b), - ne => fromb(a != b), - ge => fromb(a >= b), - gt => fromb(a > b) + (Int(a), Int(b)) => { + match op.node { + ast::BiAdd => try!(const_int_checked_add(a,b,e,expr_int_type)), + ast::BiSub => try!(const_int_checked_sub(a,b,e,expr_int_type)), + ast::BiMul => try!(const_int_checked_mul(a,b,e,expr_int_type)), + ast::BiDiv => try!(const_int_checked_div(a,b,e,expr_int_type)), + ast::BiRem => try!(const_int_checked_rem(a,b,e,expr_int_type)), + ast::BiAnd | ast::BiBitAnd => Int(a & b), + ast::BiOr | ast::BiBitOr => Int(a | b), + ast::BiBitXor => Int(a ^ b), + ast::BiShl => try!(const_int_checked_shl(a,b,e,expr_int_type)), + ast::BiShr => try!(const_int_checked_shr(a,b,e,expr_int_type)), + ast::BiEq => fromb(a == b), + ast::BiLt => fromb(a < b), + ast::BiLe => fromb(a <= b), + ast::BiNe => fromb(a != b), + ast::BiGe => fromb(a >= b), + ast::BiGt => fromb(a > b) } } - (Ok(const_uint(a)), Ok(const_uint(b))) => { - match op { - add => Ok(const_uint(a + b)), - subtract => Ok(const_uint(a - b)), - mul => Ok(const_uint(a * b)), - div if b == 0 => Err(~"divide by zero"), - div => Ok(const_uint(a / b)), - rem if b == 0 => Err(~"modulo zero"), - rem => Ok(const_uint(a % b)), - and | bitand => Ok(const_uint(a & b)), - or | bitor => Ok(const_uint(a | b)), - bitxor => Ok(const_uint(a ^ b)), - shl => Ok(const_uint(a << b)), - shr => Ok(const_uint(a >> b)), - eq => fromb(a == b), - lt => fromb(a < b), - le => fromb(a <= b), - ne => fromb(a != b), - ge => fromb(a >= b), - gt => fromb(a > b), + (Uint(a), Uint(b)) => { + match op.node { + ast::BiAdd => try!(const_uint_checked_add(a,b,e,expr_uint_type)), + ast::BiSub => try!(const_uint_checked_sub(a,b,e,expr_uint_type)), + ast::BiMul => try!(const_uint_checked_mul(a,b,e,expr_uint_type)), + ast::BiDiv => try!(const_uint_checked_div(a,b,e,expr_uint_type)), + ast::BiRem => try!(const_uint_checked_rem(a,b,e,expr_uint_type)), + ast::BiAnd | ast::BiBitAnd => Uint(a & b), + ast::BiOr | ast::BiBitOr => Uint(a | b), + ast::BiBitXor => Uint(a ^ b), + ast::BiShl => try!(const_uint_checked_shl(a,b,e,expr_uint_type)), + ast::BiShr => try!(const_uint_checked_shr(a,b,e,expr_uint_type)), + ast::BiEq => fromb(a == b), + ast::BiLt => fromb(a < b), + ast::BiLe => fromb(a <= b), + ast::BiNe => fromb(a != b), + ast::BiGe => fromb(a >= b), + ast::BiGt => fromb(a > b), } } // shifts can have any integral type as their rhs - (Ok(const_int(a)), Ok(const_uint(b))) => { - match op { - shl => Ok(const_int(a << b)), - shr => Ok(const_int(a >> b)), - _ => Err(~"Can't do this op on an int and uint") + (Int(a), Uint(b)) => { + match op.node { + ast::BiShl => try!(const_int_checked_shl_via_uint(a,b,e,expr_int_type)), + ast::BiShr => try!(const_int_checked_shr_via_uint(a,b,e,expr_int_type)), + _ => signal!(e, InvalidOpForIntUint(op.node)), } } - (Ok(const_uint(a)), Ok(const_int(b))) => { - match op { - shl => Ok(const_uint(a << b)), - shr => Ok(const_uint(a >> b)), - _ => Err(~"Can't do this op on a uint and int") + (Uint(a), Int(b)) => { + match op.node { + ast::BiShl => try!(const_uint_checked_shl_via_int(a,b,e,expr_uint_type)), + ast::BiShr => try!(const_uint_checked_shr_via_int(a,b,e,expr_uint_type)), + _ => signal!(e, InvalidOpForUintInt(op.node)), } } - (Ok(const_bool(a)), Ok(const_bool(b))) => { - Ok(const_bool(match op { - and => a && b, - or => a || b, - bitxor => a ^ b, - bitand => a & b, - bitor => a | b, - eq => a == b, - ne => a != b, - _ => return Err(~"Can't do this op on bools") - })) + (Bool(a), Bool(b)) => { + Bool(match op.node { + ast::BiAnd => a && b, + ast::BiOr => a || b, + ast::BiBitXor => a ^ b, + ast::BiBitAnd => a & b, + ast::BiBitOr => a | b, + ast::BiEq => a == b, + ast::BiNe => a != b, + _ => signal!(e, InvalidOpForBools(op.node)), + }) } - _ => Err(~"Bad operands for binary") + + _ => signal!(e, MiscBinaryOp), } } - expr_cast(base, _) => { - let ety = ty::expr_ty(tcx, e); - let base = eval_const_expr_partial(tcx, base); - match ty::get(ety).sty { - ty::ty_float(_) => { - match base { - Ok(const_uint(u)) => Ok(const_float(u as f64)), - Ok(const_int(i)) => Ok(const_float(i as f64)), - Ok(const_float(_)) => base, - _ => Err(~"Can't cast float to str") - } - } - ty::ty_uint(_) => { - match base { - Ok(const_uint(_)) => base, - Ok(const_int(i)) => Ok(const_uint(i as u64)), - Ok(const_float(f)) => Ok(const_uint(f as u64)), - _ => Err(~"Can't cast str to uint") - } - } - ty::ty_int(_) | ty::ty_bool => { - match base { - Ok(const_uint(u)) => Ok(const_int(u as i64)), - Ok(const_int(_)) => base, - Ok(const_float(f)) => Ok(const_int(f as i64)), - _ => Err(~"Can't cast str to int") + ast::ExprCast(ref base, ref target_ty) => { + // This tends to get called w/o the type actually having been + // populated in the ctxt, which was causing things to blow up + // (#5900). Fall back to doing a limited lookup to get past it. + let ety = ety.or_else(|| ast_ty_to_prim_ty(tcx, &**target_ty)) + .unwrap_or_else(|| { + tcx.sess.span_fatal(target_ty.span, + "target type not found for const cast") + }); + + // Prefer known type to noop, but always have a type hint. + // + // FIXME (#23833): the type-hint can cause problems, + // e.g. `(i8::MAX + 1_i8) as u32` feeds in `u32` as result + // type to the sum, and thus no overflow is signaled. + let base_hint = ty::expr_ty_opt(tcx, &**base).unwrap_or(ety); + let val = try!(eval_const_expr_partial(tcx, &**base, Some(base_hint))); + match cast_const(tcx, val, ety) { + Ok(val) => val, + Err(kind) => return Err(ConstEvalErr { span: e.span, kind: kind }), + } + } + ast::ExprPath(..) => { + let opt_def = tcx.def_map.borrow().get(&e.id).map(|d| d.full_def()); + let (const_expr, const_ty) = match opt_def { + Some(def::DefConst(def_id)) => { + if ast_util::is_local(def_id) { + match tcx.map.find(def_id.node) { + Some(ast_map::NodeItem(it)) => match it.node { + ast::ItemConst(ref ty, ref expr) => { + (Some(&**expr), Some(&**ty)) + } + _ => (None, None) + }, + _ => (None, None) + } + } else { + (lookup_const_by_id(tcx, def_id, Some(e.id)), None) + } + } + Some(def::DefAssociatedConst(def_id, provenance)) => { + if ast_util::is_local(def_id) { + match provenance { + def::FromTrait(trait_id) => match tcx.map.find(def_id.node) { + Some(ast_map::NodeTraitItem(ti)) => match ti.node { + ast::ConstTraitItem(ref ty, _) => { + let substs = get_substs(e.id); + (resolve_trait_associated_const(tcx, + ti, + trait_id, + substs), + Some(&**ty)) + } + _ => (None, None) + }, + _ => (None, None) + }, + def::FromImpl(_) => match tcx.map.find(def_id.node) { + Some(ast_map::NodeImplItem(ii)) => match ii.node { + ast::ConstImplItem(ref ty, ref expr) => { + (Some(&**expr), Some(&**ty)) + } + _ => (None, None) + }, + _ => (None, None) + }, + } + } else { + (lookup_const_by_id(tcx, def_id, Some(e.id)), None) + } + } + Some(def::DefVariant(enum_def, variant_def, _)) => { + (lookup_variant_by_id(tcx, enum_def, variant_def), None) + } + _ => (None, None) + }; + let const_expr = match const_expr { + Some(actual_e) => actual_e, + None => signal!(e, NonConstPath) + }; + let ety = ety.or_else(|| const_ty.and_then(|ty| ast_ty_to_prim_ty(tcx, ty))); + try!(eval_const_expr_partial(tcx, const_expr, ety)) + } + ast::ExprLit(ref lit) => { + lit_to_const(&**lit, ety) + } + ast::ExprParen(ref e) => try!(eval_const_expr_partial(tcx, &**e, ety)), + ast::ExprBlock(ref block) => { + match block.expr { + Some(ref expr) => try!(eval_const_expr_partial(tcx, &**expr, ety)), + None => Int(0) + } + } + ast::ExprTup(_) => Tuple(e.id), + ast::ExprStruct(..) => Struct(e.id), + ast::ExprTupField(ref base, index) => { + if let Ok(c) = eval_const_expr_partial(tcx, base, None) { + if let Tuple(tup_id) = c { + if let ast::ExprTup(ref fields) = tcx.map.expect_expr(tup_id).node { + if index.node < fields.len() { + return eval_const_expr_partial(tcx, &fields[index.node], None) + } else { + signal!(e, TupleIndexOutOfBounds); + } + } else { + unreachable!() + } + } else { + signal!(base, ExpectedConstTuple); } - } - _ => Err(~"Can't cast this type") + } else { + signal!(base, NonConstPath) } } - expr_path(_) => { - match lookup_const(tcx, e) { - Some(actual_e) => eval_const_expr_partial(tcx, actual_e), - None => Err(~"Non-constant path in constant expr") - } + ast::ExprField(ref base, field_name) => { + // Get the base expression if it is a struct and it is constant + if let Ok(c) = eval_const_expr_partial(tcx, base, None) { + if let Struct(struct_id) = c { + if let ast::ExprStruct(_, ref fields, _) = tcx.map.expect_expr(struct_id).node { + // Check that the given field exists and evaluate it + if let Some(f) = fields.iter().find(|f| f.ident.node.as_str() + == field_name.node.as_str()) { + return eval_const_expr_partial(tcx, &*f.expr, None) + } else { + signal!(e, MissingStructField); + } + } else { + unreachable!() + } + } else { + signal!(base, ExpectedConstStruct); + } + } else { + signal!(base, NonConstPath); + } } - expr_lit(lit) => Ok(lit_to_const(lit)), - // If we have a vstore, just keep going; it has to be a string - expr_vstore(e, _) => eval_const_expr_partial(tcx, e), - expr_paren(e) => eval_const_expr_partial(tcx, e), - _ => Err(~"Unsupported constant expr") - } -} + _ => signal!(e, MiscCatchAll) + }; -pub fn lit_to_const(lit: @lit) -> const_val { - match lit.node { - lit_str(s) => const_str(/*bad*/copy *s), - lit_int(n, _) => const_int(n), - lit_uint(n, _) => const_uint(n), - lit_int_unsuffixed(n) => const_int(n), - lit_float(n, _) => const_float(float::from_str(*n).get() as f64), - lit_float_unsuffixed(n) => - const_float(float::from_str(*n).get() as f64), - lit_nil => const_int(0i64), - lit_bool(b) => const_bool(b) - } + Ok(result) } -pub fn compare_const_vals(a: const_val, b: const_val) -> int { - match (&a, &b) { - (&const_int(a), &const_int(b)) => { - if a == b { - 0 - } else if a < b { - -1 - } else { - 1 +fn resolve_trait_associated_const<'a, 'tcx: 'a>(tcx: &'a ty::ctxt<'tcx>, + ti: &'tcx ast::TraitItem, + trait_id: ast::DefId, + rcvr_substs: subst::Substs<'tcx>) + -> Option<&'tcx Expr> +{ + let subst::SeparateVecsPerParamSpace { + types: rcvr_type, + selfs: rcvr_self, + fns: _, + } = rcvr_substs.types.split(); + let trait_substs = + subst::Substs::erased(subst::VecPerParamSpace::new(rcvr_type, + rcvr_self, + Vec::new())); + let trait_substs = tcx.mk_substs(trait_substs); + debug!("resolve_trait_associated_const: trait_substs={:?}", + trait_substs); + let trait_ref = ty::Binder(ty::TraitRef { def_id: trait_id, + substs: trait_substs }); + + ty::populate_implementations_for_trait_if_necessary(tcx, trait_ref.def_id()); + let infcx = infer::new_infer_ctxt(tcx); + + let param_env = ty::empty_parameter_environment(tcx); + let mut selcx = traits::SelectionContext::new(&infcx, ¶m_env); + let obligation = traits::Obligation::new(traits::ObligationCause::dummy(), + trait_ref.to_poly_trait_predicate()); + let selection = match selcx.select(&obligation) { + Ok(Some(vtable)) => vtable, + // Still ambiguous, so give up and let the caller decide whether this + // expression is really needed yet. Some associated constant values + // can't be evaluated until monomorphization is done in trans. + Ok(None) => { + return None } - } - (&const_uint(a), &const_uint(b)) => { - if a == b { - 0 - } else if a < b { - -1 - } else { - 1 + Err(e) => { + tcx.sess.span_bug(ti.span, + &format!("Encountered error `{:?}` when trying \ + to select an implementation for \ + constant trait item reference.", + e)) } - } - (&const_float(a), &const_float(b)) => { - if a == b { - 0 - } else if a < b { - -1 - } else { - 1 + }; + + match selection { + traits::VtableImpl(ref impl_data) => { + match ty::associated_consts(tcx, impl_data.impl_def_id) + .iter().find(|ic| ic.name == ti.ident.name) { + Some(ic) => lookup_const_by_id(tcx, ic.def_id, None), + None => match ti.node { + ast::ConstTraitItem(_, Some(ref expr)) => Some(&*expr), + _ => None, + }, + } } - } - (&const_str(ref a), &const_str(ref b)) => { - if (*a) == (*b) { - 0 - } else if (*a) < (*b) { - -1 - } else { - 1 + _ => { + tcx.sess.span_bug( + ti.span, + &format!("resolve_trait_associated_const: unexpected vtable type")) } } - (&const_bool(a), &const_bool(b)) => { - if a == b { - 0 - } else if a < b { - -1 - } else { - 1 +} + +fn cast_const<'tcx>(tcx: &ty::ctxt<'tcx>, val: ConstVal, ty: Ty) -> CastResult { + macro_rules! convert_val { + ($intermediate_ty:ty, $const_type:ident, $target_ty:ty) => { + match val { + Bool(b) => Ok($const_type(b as u64 as $intermediate_ty as $target_ty)), + Uint(u) => Ok($const_type(u as $intermediate_ty as $target_ty)), + Int(i) => Ok($const_type(i as $intermediate_ty as $target_ty)), + Float(f) => Ok($const_type(f as $intermediate_ty as $target_ty)), + _ => Err(ErrKind::CannotCastTo(stringify!($const_type))), + } } } - _ => fail!(~"compare_const_vals: ill-typed comparison") - } -} -pub fn compare_lit_exprs(tcx: middle::ty::ctxt, a: @expr, b: @expr) -> int { - compare_const_vals(eval_const_expr(tcx, a), eval_const_expr(tcx, b)) -} + // Issue #23890: If isize/usize, then dispatch to appropriate target representation type + match (&ty.sty, tcx.sess.target.int_type, tcx.sess.target.uint_type) { + (&ty::TyInt(ast::TyIs), ast::TyI32, _) => return convert_val!(i32, Int, i64), + (&ty::TyInt(ast::TyIs), ast::TyI64, _) => return convert_val!(i64, Int, i64), + (&ty::TyInt(ast::TyIs), _, _) => panic!("unexpected target.int_type"), + + (&ty::TyUint(ast::TyUs), _, ast::TyU32) => return convert_val!(u32, Uint, u64), + (&ty::TyUint(ast::TyUs), _, ast::TyU64) => return convert_val!(u64, Uint, u64), + (&ty::TyUint(ast::TyUs), _, _) => panic!("unexpected target.uint_type"), + + _ => {} + } -pub fn lit_expr_eq(tcx: middle::ty::ctxt, a: @expr, b: @expr) -> bool { - compare_lit_exprs(tcx, a, b) == 0 + match ty.sty { + ty::TyInt(ast::TyIs) => unreachable!(), + ty::TyUint(ast::TyUs) => unreachable!(), + + ty::TyInt(ast::TyI8) => convert_val!(i8, Int, i64), + ty::TyInt(ast::TyI16) => convert_val!(i16, Int, i64), + ty::TyInt(ast::TyI32) => convert_val!(i32, Int, i64), + ty::TyInt(ast::TyI64) => convert_val!(i64, Int, i64), + + ty::TyUint(ast::TyU8) => convert_val!(u8, Uint, u64), + ty::TyUint(ast::TyU16) => convert_val!(u16, Uint, u64), + ty::TyUint(ast::TyU32) => convert_val!(u32, Uint, u64), + ty::TyUint(ast::TyU64) => convert_val!(u64, Uint, u64), + + ty::TyFloat(ast::TyF32) => convert_val!(f32, Float, f64), + ty::TyFloat(ast::TyF64) => convert_val!(f64, Float, f64), + _ => Err(ErrKind::CannotCast), + } } -pub fn lit_eq(a: @lit, b: @lit) -> bool { - compare_const_vals(lit_to_const(a), lit_to_const(b)) == 0 +fn lit_to_const(lit: &ast::Lit, ty_hint: Option) -> ConstVal { + match lit.node { + ast::LitStr(ref s, _) => Str((*s).clone()), + ast::LitBinary(ref data) => { + Binary(data.clone()) + } + ast::LitByte(n) => Uint(n as u64), + ast::LitChar(n) => Uint(n as u64), + ast::LitInt(n, ast::SignedIntLit(_, ast::Plus)) => Int(n as i64), + ast::LitInt(n, ast::UnsuffixedIntLit(ast::Plus)) => { + match ty_hint.map(|ty| &ty.sty) { + Some(&ty::TyUint(_)) => Uint(n), + _ => Int(n as i64) + } + } + ast::LitInt(n, ast::SignedIntLit(_, ast::Minus)) | + ast::LitInt(n, ast::UnsuffixedIntLit(ast::Minus)) => Int(-(n as i64)), + ast::LitInt(n, ast::UnsignedIntLit(_)) => Uint(n), + ast::LitFloat(ref n, _) | + ast::LitFloatUnsuffixed(ref n) => { + Float(n.parse::().unwrap() as f64) + } + ast::LitBool(b) => Bool(b) + } } +pub fn compare_const_vals(a: &ConstVal, b: &ConstVal) -> Option { + Some(match (a, b) { + (&Int(a), &Int(b)) => a.cmp(&b), + (&Uint(a), &Uint(b)) => a.cmp(&b), + (&Float(a), &Float(b)) => { + // This is pretty bad but it is the existing behavior. + if a == b { + Ordering::Equal + } else if a < b { + Ordering::Less + } else { + Ordering::Greater + } + } + (&Str(ref a), &Str(ref b)) => a.cmp(b), + (&Bool(a), &Bool(b)) => a.cmp(&b), + (&Binary(ref a), &Binary(ref b)) => a.cmp(b), + _ => return None + }) +} -// Local Variables: -// mode: rust -// fill-column: 78; -// indent-tabs-mode: nil -// c-basic-offset: 4 -// buffer-file-coding-system: utf-8-unix -// End: +pub fn compare_lit_exprs<'tcx, S>(tcx: &ty::ctxt<'tcx>, + a: &Expr, + b: &Expr, + ty_hint: Option>, + get_substs: S) -> Option + where S: Fn(ast::NodeId) -> subst::Substs<'tcx> { + let a = match eval_const_expr_with_substs(tcx, a, ty_hint, + |id| {get_substs(id)}) { + Ok(a) => a, + Err(e) => { + tcx.sess.span_err(a.span, &e.description()); + return None; + } + }; + let b = match eval_const_expr_with_substs(tcx, b, ty_hint, get_substs) { + Ok(b) => b, + Err(e) => { + tcx.sess.span_err(b.span, &e.description()); + return None; + } + }; + compare_const_vals(&a, &b) +}