mk_tup(cx, Vec::new())
}
+pub fn mk_bool<'tcx>(cx: &ctxt<'tcx>) -> Ty<'tcx> {
+ mk_t(cx, ty_bool)
+}
+
pub fn mk_bare_fn<'tcx>(cx: &ctxt<'tcx>,
opt_def_id: Option<ast::DefId>,
fty: &'tcx BareFnTy<'tcx>) -> Ty<'tcx> {
/// Returns true if this type is a floating point type and false otherwise.
pub fn type_is_floating_point(ty: Ty) -> bool {
match ty.sty {
- ty_float(_) => true,
- _ => false,
+ ty_float(_) |
+ ty_infer(FloatVar(_)) =>
+ true,
+
+ _ =>
+ false,
}
}
}
}
-pub fn is_binopable<'tcx>(cx: &ctxt<'tcx>, ty: Ty<'tcx>, op: ast::BinOp) -> bool {
- #![allow(non_upper_case_globals)]
- const tycat_other: isize = 0;
- const tycat_bool: isize = 1;
- const tycat_char: isize = 2;
- const tycat_int: isize = 3;
- const tycat_float: isize = 4;
- const tycat_raw_ptr: isize = 6;
-
- const opcat_add: isize = 0;
- const opcat_sub: isize = 1;
- const opcat_mult: isize = 2;
- const opcat_shift: isize = 3;
- const opcat_rel: isize = 4;
- const opcat_eq: isize = 5;
- const opcat_bit: isize = 6;
- const opcat_logic: isize = 7;
- const opcat_mod: isize = 8;
-
- fn opcat(op: ast::BinOp) -> isize {
- match op.node {
- ast::BiAdd => opcat_add,
- ast::BiSub => opcat_sub,
- ast::BiMul => opcat_mult,
- ast::BiDiv => opcat_mult,
- ast::BiRem => opcat_mod,
- ast::BiAnd => opcat_logic,
- ast::BiOr => opcat_logic,
- ast::BiBitXor => opcat_bit,
- ast::BiBitAnd => opcat_bit,
- ast::BiBitOr => opcat_bit,
- ast::BiShl => opcat_shift,
- ast::BiShr => opcat_shift,
- ast::BiEq => opcat_eq,
- ast::BiNe => opcat_eq,
- ast::BiLt => opcat_rel,
- ast::BiLe => opcat_rel,
- ast::BiGe => opcat_rel,
- ast::BiGt => opcat_rel
- }
- }
-
- fn tycat<'tcx>(cx: &ctxt<'tcx>, ty: Ty<'tcx>) -> isize {
- if type_is_simd(cx, ty) {
- return tycat(cx, simd_type(cx, ty))
- }
- match ty.sty {
- ty_char => tycat_char,
- ty_bool => tycat_bool,
- ty_int(_) | ty_uint(_) | ty_infer(IntVar(_)) => tycat_int,
- ty_float(_) | ty_infer(FloatVar(_)) => tycat_float,
- ty_ptr(_) => tycat_raw_ptr,
- _ => tycat_other
- }
- }
-
- const t: bool = true;
- const f: bool = false;
-
- let tbl = [
- // +, -, *, shift, rel, ==, bit, logic, mod
- /*other*/ [f, f, f, f, f, f, f, f, f],
- /*bool*/ [f, f, f, f, t, t, t, t, f],
- /*char*/ [f, f, f, f, t, t, f, f, f],
- /*isize*/ [t, t, t, t, t, t, t, f, t],
- /*float*/ [t, t, t, f, t, t, f, f, f],
- /*bot*/ [t, t, t, t, t, t, t, t, t],
- /*raw ptr*/ [f, f, f, f, t, t, f, f, f]];
-
- return tbl[tycat(cx, ty) as usize ][opcat(op) as usize];
-}
-
// Returns the repeat count for a repeating vector expression.
pub fn eval_repeat_count(tcx: &ctxt, count_expr: &ast::Expr) -> usize {
match const_eval::eval_const_expr_partial(tcx, count_expr, Some(tcx.types.usize)) {
_ => bcx.sess().bug("compare_scalar_types: must be a comparison operator")
}
}
- ty::ty_bool | ty::ty_uint(_) | ty::ty_char => {
+ ty::ty_bare_fn(..) | ty::ty_bool | ty::ty_uint(_) | ty::ty_char => {
ICmp(bcx, bin_op_to_icmp_predicate(bcx.ccx(), op, false), lhs, rhs, debug_loc)
}
ty::ty_ptr(mt) if common::type_is_sized(bcx.tcx(), mt.ty) => {
fn const_expr_unadjusted<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
e: &ast::Expr,
ety: Ty<'tcx>,
- param_substs: &'tcx Substs<'tcx>) -> ValueRef {
+ param_substs: &'tcx Substs<'tcx>)
+ -> ValueRef
+{
+ debug!("const_expr_unadjusted(e={}, ety={}, param_substs={})",
+ e.repr(cx.tcx()),
+ ety.repr(cx.tcx()),
+ param_substs.repr(cx.tcx()));
+
let map_list = |exprs: &[P<ast::Expr>]| {
exprs.iter().map(|e| const_expr(cx, &**e, param_substs).0)
.fold(Vec::new(), |mut l, val| { l.push(val); l })
/* Neither type is bottom, and we expect them to be unified
* already, so the following is safe. */
let (te1, ty) = const_expr(cx, &**e1, param_substs);
+ debug!("const_expr_unadjusted: te1={}, ty={}",
+ cx.tn().val_to_string(te1),
+ ty.repr(cx.tcx()));
let is_simd = ty::type_is_simd(cx.tcx(), ty);
let intype = if is_simd {
ty::simd_type(cx.tcx(), ty)
}
}
+#[derive(Debug)]
enum OverflowOp {
Add,
Sub,
enum OverflowOpViaInputCheck { Shl, Shr, }
+#[derive(Debug)]
enum OverflowOpViaIntrinsic { Add, Sub, Mul, }
impl OverflowOpViaIntrinsic {
_ => panic!("unsupported target word size")
},
ref t @ ty_uint(_) | ref t @ ty_int(_) => t.clone(),
- _ => panic!("tried to get overflow intrinsic for non-int type")
+ _ => panic!("tried to get overflow intrinsic for {:?} applied to non-int type",
+ *self)
};
match *self {
// except according to those terms.
use super::autoderef;
-use super::AutorefArgs;
use super::check_argument_types;
use super::check_expr;
use super::check_method_argument_types;
&fn_sig.inputs,
&expected_arg_tys[..],
arg_exprs,
- AutorefArgs::No,
fn_sig.variadic,
TupleArgumentsFlag::DontTupleArguments);
&*fn_sig.inputs,
&*expected_arg_tys,
arg_exprs,
- AutorefArgs::No,
fn_sig.variadic,
TupleArgumentsFlag::TupleArguments);
method_callee.ty,
callee_expr,
arg_exprs,
- AutorefArgs::No,
TupleArgumentsFlag::TupleArguments,
expected);
write_call(fcx, call_expr, output_type);
pub use self::LvaluePreference::*;
pub use self::Expectation::*;
pub use self::compare_method::compare_impl_method;
-use self::IsBinopAssignment::*;
use self::TupleArgumentsFlag::*;
use astconv::{self, ast_region_to_region, ast_ty_to_ty, AstConv, PathParamMode};
mod closure;
mod callee;
mod compare_method;
+mod op;
/// closures defined within the function. For example:
///
}
}
-/// Whether `check_binop` is part of an assignment or not.
-/// Used to know whether we allow user overloads and to print
-/// better messages on error.
-#[derive(PartialEq)]
-enum IsBinopAssignment{
- SimpleBinop,
- BinopAssignment,
-}
-
#[derive(Clone)]
pub struct FnCtxt<'a, 'tcx: 'a> {
body_id: ast::NodeId,
/// version, this version will also select obligations if it seems
/// useful, in an effort to get more type information.
fn resolve_type_vars_if_possible(&self, mut ty: Ty<'tcx>) -> Ty<'tcx> {
+ debug!("resolve_type_vars_if_possible(ty={})", ty.repr(self.tcx()));
+
// No ty::infer()? Nothing needs doing.
if !ty::type_has_ty_infer(ty) {
+ debug!("resolve_type_vars_if_possible: ty={}", ty.repr(self.tcx()));
return ty;
}
// If `ty` is a type variable, see whether we already know what it is.
ty = self.infcx().resolve_type_vars_if_possible(&ty);
if !ty::type_has_ty_infer(ty) {
+ debug!("resolve_type_vars_if_possible: ty={}", ty.repr(self.tcx()));
return ty;
}
vtable::select_new_fcx_obligations(self);
ty = self.infcx().resolve_type_vars_if_possible(&ty);
if !ty::type_has_ty_infer(ty) {
+ debug!("resolve_type_vars_if_possible: ty={}", ty.repr(self.tcx()));
return ty;
}
// indirect dependencies that don't seem worth tracking
// precisely.
vtable::select_fcx_obligations_where_possible(self);
- self.infcx().resolve_type_vars_if_possible(&ty)
+ ty = self.infcx().resolve_type_vars_if_possible(&ty);
+
+ debug!("resolve_type_vars_if_possible: ty={}", ty.repr(self.tcx()));
+ ty
}
/// Resolves all type variables in `t` and then, if any were left
{
match method {
Some(method) => {
- let ref_ty = // invoked methods have all LB regions instantiated
- ty::no_late_bound_regions(
- fcx.tcx(), &ty::ty_fn_ret(method.ty)).unwrap();
- match method_call {
- Some(method_call) => {
- fcx.inh.method_map.borrow_mut().insert(method_call,
- method);
- }
- None => {}
- }
- match ref_ty {
- ty::FnConverging(ref_ty) => {
- ty::deref(ref_ty, true)
- }
- ty::FnDiverging => {
- fcx.tcx().sess.bug("index/deref traits do not define a `!` return")
- }
+ // extract method method return type, which will be &T;
+ // all LB regions should have been instantiated during method lookup
+ let ret_ty = ty::ty_fn_ret(method.ty);
+ let ret_ty = ty::no_late_bound_regions(fcx.tcx(), &ret_ty).unwrap().unwrap();
+
+ if let Some(method_call) = method_call {
+ fcx.inh.method_map.borrow_mut().insert(method_call, method);
}
+
+ // method returns &T, but the type as visible to user is T, so deref
+ ty::deref(ret_ty, true)
}
None => None,
}
method_fn_ty: Ty<'tcx>,
callee_expr: &'tcx ast::Expr,
args_no_rcvr: &'tcx [P<ast::Expr>],
- autoref_args: AutorefArgs,
tuple_arguments: TupleArgumentsFlag,
expected: Expectation<'tcx>)
-> ty::FnOutput<'tcx> {
&err_inputs[..],
&[],
args_no_rcvr,
- autoref_args,
false,
tuple_arguments);
ty::FnConverging(fcx.tcx().types.err)
&fty.sig.0.inputs[1..],
&expected_arg_tys[..],
args_no_rcvr,
- autoref_args,
fty.sig.0.variadic,
tuple_arguments);
fty.sig.0.output
fn_inputs: &[Ty<'tcx>],
expected_arg_tys: &[Ty<'tcx>],
args: &'tcx [P<ast::Expr>],
- autoref_args: AutorefArgs,
variadic: bool,
tuple_arguments: TupleArgumentsFlag) {
let tcx = fcx.ccx.tcx;
if is_block == check_blocks {
debug!("checking the argument");
- let mut formal_ty = formal_tys[i];
-
- match autoref_args {
- AutorefArgs::Yes => {
- match formal_ty.sty {
- ty::ty_rptr(_, mt) => formal_ty = mt.ty,
- ty::ty_err => (),
- _ => {
- // So we hit this case when one implements the
- // operator traits but leaves an argument as
- // just T instead of &T. We'll catch it in the
- // mismatch impl/trait method phase no need to
- // ICE here.
- // See: #11450
- formal_ty = tcx.types.err;
- }
- }
- }
- AutorefArgs::No => {}
- }
+ let formal_ty = formal_tys[i];
// The special-cased logic below has three functions:
// 1. Provide as good of an expected type as possible.
TypeAndSubsts { substs: substs, ty: substd_ty }
}
-// Controls whether the arguments are automatically referenced. This is useful
-// for overloaded binary and unary operators.
-#[derive(Copy, PartialEq)]
-pub enum AutorefArgs {
- Yes,
- No,
-}
-
/// Controls whether the arguments are tupled. This is used for the call
/// operator.
///
fn_ty,
expr,
&args[1..],
- AutorefArgs::No,
DontTupleArguments,
expected);
fcx.write_ty(id, if_ty);
}
- fn lookup_op_method<'a, 'tcx, F>(fcx: &'a FnCtxt<'a, 'tcx>,
- op_ex: &'tcx ast::Expr,
- lhs_ty: Ty<'tcx>,
- opname: ast::Name,
- trait_did: Option<ast::DefId>,
- lhs: &'a ast::Expr,
- rhs: Option<&'tcx P<ast::Expr>>,
- unbound_method: F,
- autoref_args: AutorefArgs) -> Ty<'tcx> where
- F: FnOnce(),
- {
- let method = match trait_did {
- Some(trait_did) => {
- // We do eager coercions to make using operators
- // more ergonomic:
- //
- // - If the input is of type &'a T (resp. &'a mut T),
- // then reborrow it to &'b T (resp. &'b mut T) where
- // 'b <= 'a. This makes things like `x == y`, where
- // `x` and `y` are both region pointers, work. We
- // could also solve this with variance or different
- // traits that don't force left and right to have same
- // type.
- let (adj_ty, adjustment) = match lhs_ty.sty {
- ty::ty_rptr(r_in, mt) => {
- let r_adj = fcx.infcx().next_region_var(infer::Autoref(lhs.span));
- fcx.mk_subr(infer::Reborrow(lhs.span), r_adj, *r_in);
- let adjusted_ty = ty::mk_rptr(fcx.tcx(), fcx.tcx().mk_region(r_adj), mt);
- let autoptr = ty::AutoPtr(r_adj, mt.mutbl, None);
- let adjustment = ty::AutoDerefRef { autoderefs: 1, autoref: Some(autoptr) };
- (adjusted_ty, adjustment)
- }
- _ => {
- (lhs_ty, ty::AutoDerefRef { autoderefs: 0, autoref: None })
- }
- };
-
- debug!("adjusted_ty={} adjustment={:?}",
- adj_ty.repr(fcx.tcx()),
- adjustment);
-
- method::lookup_in_trait_adjusted(fcx, op_ex.span, Some(lhs), opname,
- trait_did, adjustment, adj_ty, None)
- }
- None => None
- };
- let args = match rhs {
- Some(rhs) => slice::ref_slice(rhs),
- None => &[][..]
- };
- match method {
- Some(method) => {
- let method_ty = method.ty;
- // HACK(eddyb) Fully qualified path to work around a resolve bug.
- let method_call = ::middle::ty::MethodCall::expr(op_ex.id);
- fcx.inh.method_map.borrow_mut().insert(method_call, method);
- match check_method_argument_types(fcx,
- op_ex.span,
- method_ty,
- op_ex,
- args,
- autoref_args,
- DontTupleArguments,
- NoExpectation) {
- ty::FnConverging(result_type) => result_type,
- ty::FnDiverging => fcx.tcx().types.err
- }
- }
- None => {
- unbound_method();
- // Check the args anyway
- // so we get all the error messages
- let expected_ty = fcx.tcx().types.err;
- check_method_argument_types(fcx,
- op_ex.span,
- expected_ty,
- op_ex,
- args,
- autoref_args,
- DontTupleArguments,
- NoExpectation);
- fcx.tcx().types.err
- }
- }
- }
-
- // could be either an expr_binop or an expr_assign_binop
- fn check_binop<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
- expr: &'tcx ast::Expr,
- op: ast::BinOp,
- lhs: &'tcx ast::Expr,
- rhs: &'tcx P<ast::Expr>,
- is_binop_assignment: IsBinopAssignment) {
- let tcx = fcx.ccx.tcx;
-
- let lvalue_pref = match is_binop_assignment {
- BinopAssignment => PreferMutLvalue,
- SimpleBinop => NoPreference
- };
- check_expr_with_lvalue_pref(fcx, lhs, lvalue_pref);
-
- // Callee does bot / err checking
- let lhs_t =
- structurally_resolve_type_or_else(fcx, lhs.span, fcx.expr_ty(lhs), || {
- if ast_util::is_symmetric_binop(op.node) {
- // Try RHS first
- check_expr(fcx, &**rhs);
- fcx.expr_ty(&**rhs)
- } else {
- fcx.tcx().types.err
- }
- });
-
- if ty::type_is_integral(lhs_t) && ast_util::is_shift_binop(op.node) {
- // Shift is a special case: rhs must be usize, no matter what lhs is
- check_expr(fcx, &**rhs);
- let rhs_ty = fcx.expr_ty(&**rhs);
- let rhs_ty = structurally_resolved_type(fcx, rhs.span, rhs_ty);
- if ty::type_is_integral(rhs_ty) {
- fcx.write_ty(expr.id, lhs_t);
- } else {
- fcx.type_error_message(
- expr.span,
- |actual| {
- format!(
- "right-hand-side of a shift operation must have integral type, \
- not `{}`",
- actual)
- },
- rhs_ty,
- None);
- fcx.write_ty(expr.id, fcx.tcx().types.err);
- }
- return;
- }
-
- if ty::is_binopable(tcx, lhs_t, op) {
- let tvar = fcx.infcx().next_ty_var();
- demand::suptype(fcx, expr.span, tvar, lhs_t);
- check_expr_has_type(fcx, &**rhs, tvar);
-
- let result_t = match op.node {
- ast::BiEq | ast::BiNe | ast::BiLt | ast::BiLe | ast::BiGe |
- ast::BiGt => {
- if ty::type_is_simd(tcx, lhs_t) {
- if ty::type_is_fp(ty::simd_type(tcx, lhs_t)) {
- fcx.type_error_message(expr.span,
- |actual| {
- format!("binary comparison \
- operation `{}` not \
- supported for floating \
- point SIMD vector `{}`",
- ast_util::binop_to_string(op.node),
- actual)
- },
- lhs_t,
- None
- );
- fcx.tcx().types.err
- } else {
- lhs_t
- }
- } else {
- fcx.tcx().types.bool
- }
- },
- _ => lhs_t,
- };
-
- fcx.write_ty(expr.id, result_t);
- return;
- }
-
- if op.node == ast::BiOr || op.node == ast::BiAnd {
- // This is an error; one of the operands must have the wrong
- // type
- fcx.write_error(expr.id);
- fcx.write_error(rhs.id);
- fcx.type_error_message(expr.span,
- |actual| {
- format!("binary operation `{}` cannot be applied \
- to type `{}`",
- ast_util::binop_to_string(op.node),
- actual)
- },
- lhs_t,
- None)
- }
-
- // Check for overloaded operators if not an assignment.
- let result_t = if is_binop_assignment == SimpleBinop {
- check_user_binop(fcx, expr, lhs, lhs_t, op, rhs)
- } else {
- fcx.type_error_message(expr.span,
- |actual| {
- format!("binary assignment \
- operation `{}=` \
- cannot be applied to \
- type `{}`",
- ast_util::binop_to_string(op.node),
- actual)
- },
- lhs_t,
- None);
- check_expr(fcx, &**rhs);
- fcx.tcx().types.err
- };
-
- fcx.write_ty(expr.id, result_t);
- if ty::type_is_error(result_t) {
- fcx.write_ty(rhs.id, result_t);
- }
- }
-
- fn check_user_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
- ex: &'tcx ast::Expr,
- lhs_expr: &'tcx ast::Expr,
- lhs_resolved_t: Ty<'tcx>,
- op: ast::BinOp,
- rhs: &'tcx P<ast::Expr>) -> Ty<'tcx> {
- let tcx = fcx.ccx.tcx;
- let lang = &tcx.lang_items;
- let (name, trait_did) = match op.node {
- ast::BiAdd => ("add", lang.add_trait()),
- ast::BiSub => ("sub", lang.sub_trait()),
- ast::BiMul => ("mul", lang.mul_trait()),
- ast::BiDiv => ("div", lang.div_trait()),
- ast::BiRem => ("rem", lang.rem_trait()),
- ast::BiBitXor => ("bitxor", lang.bitxor_trait()),
- ast::BiBitAnd => ("bitand", lang.bitand_trait()),
- ast::BiBitOr => ("bitor", lang.bitor_trait()),
- ast::BiShl => ("shl", lang.shl_trait()),
- ast::BiShr => ("shr", lang.shr_trait()),
- ast::BiLt => ("lt", lang.ord_trait()),
- ast::BiLe => ("le", lang.ord_trait()),
- ast::BiGe => ("ge", lang.ord_trait()),
- ast::BiGt => ("gt", lang.ord_trait()),
- ast::BiEq => ("eq", lang.eq_trait()),
- ast::BiNe => ("ne", lang.eq_trait()),
- ast::BiAnd | ast::BiOr => {
- check_expr(fcx, &**rhs);
- return tcx.types.err;
- }
- };
- lookup_op_method(fcx, ex, lhs_resolved_t, token::intern(name),
- trait_did, lhs_expr, Some(rhs), || {
- fcx.type_error_message(ex.span, |actual| {
- format!("binary operation `{}` cannot be applied to type `{}`",
- ast_util::binop_to_string(op.node),
- actual)
- }, lhs_resolved_t, None)
- }, if ast_util::is_by_value_binop(op.node) { AutorefArgs::No } else { AutorefArgs::Yes })
- }
-
- fn check_user_unop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
- op_str: &str,
- mname: &str,
- trait_did: Option<ast::DefId>,
- ex: &'tcx ast::Expr,
- rhs_expr: &'tcx ast::Expr,
- rhs_t: Ty<'tcx>,
- op: ast::UnOp) -> Ty<'tcx> {
- lookup_op_method(fcx, ex, rhs_t, token::intern(mname),
- trait_did, rhs_expr, None, || {
- fcx.type_error_message(ex.span, |actual| {
- format!("cannot apply unary operator `{}` to type `{}`",
- op_str, actual)
- }, rhs_t, None);
- }, if ast_util::is_by_value_unop(op) { AutorefArgs::No } else { AutorefArgs::Yes })
- }
-
// Check field access expressions
fn check_field<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
expr: &'tcx ast::Expr,
fcx.write_ty(id, typ);
}
ast::ExprBinary(op, ref lhs, ref rhs) => {
- check_binop(fcx, expr, op, &**lhs, rhs, SimpleBinop);
-
- let lhs_ty = fcx.expr_ty(&**lhs);
- let rhs_ty = fcx.expr_ty(&**rhs);
- if ty::type_is_error(lhs_ty) ||
- ty::type_is_error(rhs_ty) {
- fcx.write_error(id);
- }
+ op::check_binop(fcx, expr, op, lhs, rhs);
}
ast::ExprAssignOp(op, ref lhs, ref rhs) => {
- check_binop(fcx, expr, op, &**lhs, rhs, BinopAssignment);
-
- let lhs_t = fcx.expr_ty(&**lhs);
- let result_t = fcx.expr_ty(expr);
- demand::suptype(fcx, expr.span, result_t, lhs_t);
-
- let tcx = fcx.tcx();
- if !ty::expr_is_lval(tcx, &**lhs) {
- span_err!(tcx.sess, lhs.span, E0067, "illegal left-hand side expression");
- }
-
- fcx.require_expr_have_sized_type(&**lhs, traits::AssignmentLhsSized);
-
- // Overwrite result of check_binop...this preserves existing behavior
- // but seems quite dubious with regard to user-defined methods
- // and so forth. - Niko
- if !ty::type_is_error(result_t) {
- fcx.write_nil(expr.id);
- }
+ op::check_binop_assign(fcx, expr, op, lhs, rhs);
}
ast::ExprUnary(unop, ref oprnd) => {
let expected_inner = expected.to_option(fcx).map_or(NoExpectation, |ty| {
oprnd_t);
if !(ty::type_is_integral(oprnd_t) ||
oprnd_t.sty == ty::ty_bool) {
- oprnd_t = check_user_unop(fcx, "!", "not",
- tcx.lang_items.not_trait(),
- expr, &**oprnd, oprnd_t, unop);
+ oprnd_t = op::check_user_unop(fcx, "!", "not",
+ tcx.lang_items.not_trait(),
+ expr, &**oprnd, oprnd_t, unop);
}
}
ast::UnNeg => {
oprnd_t);
if !(ty::type_is_integral(oprnd_t) ||
ty::type_is_fp(oprnd_t)) {
- oprnd_t = check_user_unop(fcx, "-", "neg",
- tcx.lang_items.neg_trait(),
- expr, &**oprnd, oprnd_t, unop);
+ oprnd_t = op::check_user_unop(fcx, "-", "neg",
+ tcx.lang_items.neg_trait(),
+ expr, &**oprnd, oprnd_t, unop);
}
}
}
match result {
Some((index_ty, element_ty)) => {
- // FIXME: we've already checked idx above, we should
- // probably just demand subtype or something here.
- check_expr_has_type(fcx, &**idx, index_ty);
+ let idx_expr_ty = fcx.expr_ty(idx);
+ demand::eqtype(fcx, expr.span, index_ty, idx_expr_ty);
fcx.write_ty(id, element_ty);
}
_ => {
--- /dev/null
+// Copyright 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.
+
+//! Code related to processing overloaded binary and unary operators.
+
+use super::{
+ check_expr,
+ check_expr_coercable_to_type,
+ check_expr_with_lvalue_pref,
+ demand,
+ method,
+ FnCtxt,
+ PreferMutLvalue,
+ structurally_resolved_type,
+};
+use middle::infer;
+use middle::traits;
+use middle::ty::{self, Ty};
+use syntax::ast;
+use syntax::ast_util;
+use syntax::parse::token;
+use util::ppaux::{Repr, UserString};
+
+/// Check a `a <op>= b`
+pub fn check_binop_assign<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
+ expr: &'tcx ast::Expr,
+ op: ast::BinOp,
+ lhs_expr: &'tcx ast::Expr,
+ rhs_expr: &'tcx ast::Expr)
+{
+ let tcx = fcx.ccx.tcx;
+
+ check_expr_with_lvalue_pref(fcx, lhs_expr, PreferMutLvalue);
+ check_expr(fcx, rhs_expr);
+
+ let lhs_ty = structurally_resolved_type(fcx, lhs_expr.span, fcx.expr_ty(lhs_expr));
+ let rhs_ty = structurally_resolved_type(fcx, rhs_expr.span, fcx.expr_ty(rhs_expr));
+
+ if is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op) {
+ enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
+ fcx.write_nil(expr.id);
+ } else {
+ // error types are considered "builtin"
+ assert!(!ty::type_is_error(lhs_ty) || !ty::type_is_error(rhs_ty));
+ span_err!(tcx.sess, lhs_expr.span, E0368,
+ "binary assignment operation `{}=` cannot be applied to types `{}` and `{}`",
+ ast_util::binop_to_string(op.node),
+ lhs_ty.user_string(fcx.tcx()),
+ rhs_ty.user_string(fcx.tcx()));
+ fcx.write_error(expr.id);
+ }
+
+ let tcx = fcx.tcx();
+ if !ty::expr_is_lval(tcx, lhs_expr) {
+ span_err!(tcx.sess, lhs_expr.span, E0067, "illegal left-hand side expression");
+ }
+
+ fcx.require_expr_have_sized_type(lhs_expr, traits::AssignmentLhsSized);
+}
+
+/// Check a potentially overloaded binary operator.
+pub fn check_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
+ expr: &'tcx ast::Expr,
+ op: ast::BinOp,
+ lhs_expr: &'tcx ast::Expr,
+ rhs_expr: &'tcx ast::Expr)
+{
+ let tcx = fcx.ccx.tcx;
+
+ debug!("check_binop(expr.id={}, expr={}, op={:?}, lhs_expr={}, rhs_expr={})",
+ expr.id,
+ expr.repr(tcx),
+ op,
+ lhs_expr.repr(tcx),
+ rhs_expr.repr(tcx));
+
+ check_expr(fcx, lhs_expr);
+ let lhs_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(lhs_expr));
+
+ // Annoyingly, SIMD ops don't fit into the PartialEq/PartialOrd
+ // traits, because their return type is not bool. Perhaps this
+ // should change, but for now if LHS is SIMD we go down a
+ // different path that bypassess all traits.
+ if ty::type_is_simd(fcx.tcx(), lhs_ty) {
+ check_expr_coercable_to_type(fcx, rhs_expr, lhs_ty);
+ let rhs_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(lhs_expr));
+ let return_ty = enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
+ fcx.write_ty(expr.id, return_ty);
+ return;
+ }
+
+ match BinOpCategory::from(op) {
+ BinOpCategory::Shortcircuit => {
+ // && and || are a simple case.
+ demand::suptype(fcx, lhs_expr.span, ty::mk_bool(tcx), lhs_ty);
+ check_expr_coercable_to_type(fcx, rhs_expr, ty::mk_bool(tcx));
+ fcx.write_ty(expr.id, ty::mk_bool(tcx));
+ }
+ _ => {
+ // Otherwise, we always treat operators as if they are
+ // overloaded. This is the way to be most flexible w/r/t
+ // types that get inferred.
+ let (rhs_ty, return_ty) =
+ check_overloaded_binop(fcx, expr, lhs_expr, lhs_ty, rhs_expr, op);
+
+ // Supply type inference hints if relevant. Probably these
+ // hints should be enforced during select as part of the
+ // `consider_unification_despite_ambiguity` routine, but this
+ // more convenient for now.
+ //
+ // The basic idea is to help type inference by taking
+ // advantage of things we know about how the impls for
+ // scalar types are arranged. This is important in a
+ // scenario like `1_u32 << 2`, because it lets us quickly
+ // deduce that the result type should be `u32`, even
+ // though we don't know yet what type 2 has and hence
+ // can't pin this down to a specific impl.
+ let rhs_ty = fcx.resolve_type_vars_if_possible(rhs_ty);
+ if
+ !ty::type_is_ty_var(lhs_ty) &&
+ !ty::type_is_ty_var(rhs_ty) &&
+ is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op)
+ {
+ let builtin_return_ty =
+ enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
+ demand::suptype(fcx, expr.span, builtin_return_ty, return_ty);
+ }
+
+ fcx.write_ty(expr.id, return_ty);
+ }
+ }
+}
+
+fn enforce_builtin_binop_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
+ lhs_expr: &'tcx ast::Expr,
+ lhs_ty: Ty<'tcx>,
+ rhs_expr: &'tcx ast::Expr,
+ rhs_ty: Ty<'tcx>,
+ op: ast::BinOp)
+ -> Ty<'tcx>
+{
+ debug_assert!(is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op));
+
+ let tcx = fcx.tcx();
+ match BinOpCategory::from(op) {
+ BinOpCategory::Shortcircuit => {
+ demand::suptype(fcx, lhs_expr.span, ty::mk_bool(tcx), lhs_ty);
+ demand::suptype(fcx, rhs_expr.span, ty::mk_bool(tcx), rhs_ty);
+ ty::mk_bool(tcx)
+ }
+
+ BinOpCategory::Shift => {
+ // For integers, the shift amount can be of any integral
+ // type. For simd, the type must match exactly.
+ if ty::type_is_simd(tcx, lhs_ty) {
+ demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
+ }
+
+ // result type is same as LHS always
+ lhs_ty
+ }
+
+ BinOpCategory::Math |
+ BinOpCategory::Bitwise => {
+ // both LHS and RHS and result will have the same type
+ demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
+ lhs_ty
+ }
+
+ BinOpCategory::Comparison => {
+ // both LHS and RHS and result will have the same type
+ demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
+
+ // if this is simd, result is same as lhs, else bool
+ if ty::type_is_simd(tcx, lhs_ty) {
+ let unit_ty = ty::simd_type(tcx, lhs_ty);
+ debug!("enforce_builtin_binop_types: lhs_ty={} unit_ty={}",
+ lhs_ty.repr(tcx),
+ unit_ty.repr(tcx));
+ if !ty::type_is_integral(unit_ty) {
+ tcx.sess.span_err(
+ lhs_expr.span,
+ &format!("binary comparison operation `{}` not supported \
+ for floating point SIMD vector `{}`",
+ ast_util::binop_to_string(op.node),
+ lhs_ty.user_string(tcx)));
+ tcx.types.err
+ } else {
+ lhs_ty
+ }
+ } else {
+ ty::mk_bool(tcx)
+ }
+ }
+ }
+}
+
+fn check_overloaded_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
+ expr: &'tcx ast::Expr,
+ lhs_expr: &'tcx ast::Expr,
+ lhs_ty: Ty<'tcx>,
+ rhs_expr: &'tcx ast::Expr,
+ op: ast::BinOp)
+ -> (Ty<'tcx>, Ty<'tcx>)
+{
+ debug!("check_overloaded_binop(expr.id={}, lhs_ty={})",
+ expr.id,
+ lhs_ty.repr(fcx.tcx()));
+
+ let (name, trait_def_id) = name_and_trait_def_id(fcx, op);
+
+ // NB: As we have not yet type-checked the RHS, we don't have the
+ // type at hand. Make a variable to represent it. The whole reason
+ // for this indirection is so that, below, we can check the expr
+ // using this variable as the expected type, which sometimes lets
+ // us do better coercions than we would be able to do otherwise,
+ // particularly for things like `String + &String`.
+ let rhs_ty_var = fcx.infcx().next_ty_var();
+
+ let return_ty = match lookup_op_method(fcx, expr, lhs_ty, vec![rhs_ty_var],
+ token::intern(name), trait_def_id,
+ lhs_expr) {
+ Ok(return_ty) => return_ty,
+ Err(()) => {
+ // error types are considered "builtin"
+ if !ty::type_is_error(lhs_ty) {
+ span_err!(fcx.tcx().sess, lhs_expr.span, E0369,
+ "binary operation `{}` cannot be applied to type `{}`",
+ ast_util::binop_to_string(op.node),
+ lhs_ty.user_string(fcx.tcx()));
+ }
+ fcx.tcx().types.err
+ }
+ };
+
+ // see `NB` above
+ check_expr_coercable_to_type(fcx, rhs_expr, rhs_ty_var);
+
+ (rhs_ty_var, return_ty)
+}
+
+pub fn check_user_unop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
+ op_str: &str,
+ mname: &str,
+ trait_did: Option<ast::DefId>,
+ ex: &'tcx ast::Expr,
+ operand_expr: &'tcx ast::Expr,
+ operand_ty: Ty<'tcx>,
+ op: ast::UnOp)
+ -> Ty<'tcx>
+{
+ assert!(ast_util::is_by_value_unop(op));
+ match lookup_op_method(fcx, ex, operand_ty, vec![],
+ token::intern(mname), trait_did,
+ operand_expr) {
+ Ok(t) => t,
+ Err(()) => {
+ fcx.type_error_message(ex.span, |actual| {
+ format!("cannot apply unary operator `{}` to type `{}`",
+ op_str, actual)
+ }, operand_ty, None);
+ fcx.tcx().types.err
+ }
+ }
+}
+
+fn name_and_trait_def_id(fcx: &FnCtxt, op: ast::BinOp) -> (&'static str, Option<ast::DefId>) {
+ let lang = &fcx.tcx().lang_items;
+ match op.node {
+ ast::BiAdd => ("add", lang.add_trait()),
+ ast::BiSub => ("sub", lang.sub_trait()),
+ ast::BiMul => ("mul", lang.mul_trait()),
+ ast::BiDiv => ("div", lang.div_trait()),
+ ast::BiRem => ("rem", lang.rem_trait()),
+ ast::BiBitXor => ("bitxor", lang.bitxor_trait()),
+ ast::BiBitAnd => ("bitand", lang.bitand_trait()),
+ ast::BiBitOr => ("bitor", lang.bitor_trait()),
+ ast::BiShl => ("shl", lang.shl_trait()),
+ ast::BiShr => ("shr", lang.shr_trait()),
+ ast::BiLt => ("lt", lang.ord_trait()),
+ ast::BiLe => ("le", lang.ord_trait()),
+ ast::BiGe => ("ge", lang.ord_trait()),
+ ast::BiGt => ("gt", lang.ord_trait()),
+ ast::BiEq => ("eq", lang.eq_trait()),
+ ast::BiNe => ("ne", lang.eq_trait()),
+ ast::BiAnd | ast::BiOr => {
+ fcx.tcx().sess.span_bug(op.span, "&& and || are not overloadable")
+ }
+ }
+}
+
+fn lookup_op_method<'a, 'tcx>(fcx: &'a FnCtxt<'a, 'tcx>,
+ expr: &'tcx ast::Expr,
+ lhs_ty: Ty<'tcx>,
+ other_tys: Vec<Ty<'tcx>>,
+ opname: ast::Name,
+ trait_did: Option<ast::DefId>,
+ lhs_expr: &'a ast::Expr)
+ -> Result<Ty<'tcx>,()>
+{
+ debug!("lookup_op_method(expr={}, lhs_ty={}, opname={:?}, trait_did={}, lhs_expr={})",
+ expr.repr(fcx.tcx()),
+ lhs_ty.repr(fcx.tcx()),
+ opname,
+ trait_did.repr(fcx.tcx()),
+ lhs_expr.repr(fcx.tcx()));
+
+ let method = match trait_did {
+ Some(trait_did) => {
+ // We do eager coercions to make using operators
+ // more ergonomic:
+ //
+ // - If the input is of type &'a T (resp. &'a mut T),
+ // then reborrow it to &'b T (resp. &'b mut T) where
+ // 'b <= 'a. This makes things like `x == y`, where
+ // `x` and `y` are both region pointers, work. We
+ // could also solve this with variance or different
+ // traits that don't force left and right to have same
+ // type.
+ let (adj_ty, adjustment) = match lhs_ty.sty {
+ ty::ty_rptr(r_in, mt) => {
+ let r_adj = fcx.infcx().next_region_var(infer::Autoref(lhs_expr.span));
+ fcx.mk_subr(infer::Reborrow(lhs_expr.span), r_adj, *r_in);
+ let adjusted_ty = ty::mk_rptr(fcx.tcx(), fcx.tcx().mk_region(r_adj), mt);
+ let autoptr = ty::AutoPtr(r_adj, mt.mutbl, None);
+ let adjustment = ty::AutoDerefRef { autoderefs: 1, autoref: Some(autoptr) };
+ (adjusted_ty, adjustment)
+ }
+ _ => {
+ (lhs_ty, ty::AutoDerefRef { autoderefs: 0, autoref: None })
+ }
+ };
+
+ debug!("adjusted_ty={} adjustment={:?}",
+ adj_ty.repr(fcx.tcx()),
+ adjustment);
+
+ method::lookup_in_trait_adjusted(fcx, expr.span, Some(lhs_expr), opname,
+ trait_did, adjustment, adj_ty, Some(other_tys))
+ }
+ None => None
+ };
+
+ match method {
+ Some(method) => {
+ let method_ty = method.ty;
+
+ // HACK(eddyb) Fully qualified path to work around a resolve bug.
+ let method_call = ::middle::ty::MethodCall::expr(expr.id);
+ fcx.inh.method_map.borrow_mut().insert(method_call, method);
+
+ // extract return type for method; all late bound regions
+ // should have been instantiated by now
+ let ret_ty = ty::ty_fn_ret(method_ty);
+ Ok(ty::no_late_bound_regions(fcx.tcx(), &ret_ty).unwrap().unwrap())
+ }
+ None => {
+ Err(())
+ }
+ }
+}
+
+// Binary operator categories. These categories summarize the behavior
+// with respect to the builtin operationrs supported.
+enum BinOpCategory {
+ /// &&, || -- cannot be overridden
+ Shortcircuit,
+
+ /// <<, >> -- when shifting a single integer, rhs can be any
+ /// integer type. For simd, types must match.
+ Shift,
+
+ /// +, -, etc -- takes equal types, produces same type as input,
+ /// applicable to ints/floats/simd
+ Math,
+
+ /// &, |, ^ -- takes equal types, produces same type as input,
+ /// applicable to ints/floats/simd/bool
+ Bitwise,
+
+ /// ==, !=, etc -- takes equal types, produces bools, except for simd,
+ /// which produce the input type
+ Comparison,
+}
+
+impl BinOpCategory {
+ fn from(op: ast::BinOp) -> BinOpCategory {
+ match op.node {
+ ast::BiShl | ast::BiShr =>
+ BinOpCategory::Shift,
+
+ ast::BiAdd |
+ ast::BiSub |
+ ast::BiMul |
+ ast::BiDiv |
+ ast::BiRem =>
+ BinOpCategory::Math,
+
+ ast::BiBitXor |
+ ast::BiBitAnd |
+ ast::BiBitOr =>
+ BinOpCategory::Bitwise,
+
+ ast::BiEq |
+ ast::BiNe |
+ ast::BiLt |
+ ast::BiLe |
+ ast::BiGe |
+ ast::BiGt =>
+ BinOpCategory::Comparison,
+
+ ast::BiAnd |
+ ast::BiOr =>
+ BinOpCategory::Shortcircuit,
+ }
+ }
+}
+
+/// Returns true if this is a built-in arithmetic operation (e.g. u32
+/// + u32, i16x4 == i16x4) and false if these types would have to be
+/// overloaded to be legal. There are two reasons that we distinguish
+/// builtin operations from overloaded ones (vs trying to drive
+/// everything uniformly through the trait system and intrinsics or
+/// something like that):
+///
+/// 1. Builtin operations can trivially be evaluated in constants.
+/// 2. For comparison operators applied to SIMD types the result is
+/// not of type `bool`. For example, `i16x4==i16x4` yields a
+/// type like `i16x4`. This means that the overloaded trait
+/// `PartialEq` is not applicable.
+///
+/// Reason #2 is the killer. I tried for a while to always use
+/// overloaded logic and just check the types in constants/trans after
+/// the fact, and it worked fine, except for SIMD types. -nmatsakis
+fn is_builtin_binop<'tcx>(cx: &ty::ctxt<'tcx>,
+ lhs: Ty<'tcx>,
+ rhs: Ty<'tcx>,
+ op: ast::BinOp)
+ -> bool
+{
+ match BinOpCategory::from(op) {
+ BinOpCategory::Shortcircuit => {
+ true
+ }
+
+ BinOpCategory::Shift => {
+ ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
+ ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
+ ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)
+ }
+
+ BinOpCategory::Math => {
+ ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
+ ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
+ ty::type_is_floating_point(lhs) && ty::type_is_floating_point(rhs) ||
+ ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)
+ }
+
+ BinOpCategory::Bitwise => {
+ ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
+ ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
+ ty::type_is_floating_point(lhs) && ty::type_is_floating_point(rhs) ||
+ ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs) ||
+ ty::type_is_bool(lhs) && ty::type_is_bool(rhs)
+ }
+
+ BinOpCategory::Comparison => {
+ ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
+ ty::type_is_scalar(lhs) && ty::type_is_scalar(rhs) ||
+ ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)
+ }
+ }
+}
+
use std::cell::Cell;
use syntax::ast;
+use syntax::ast_util;
use syntax::codemap::{DUMMY_SP, Span};
use syntax::print::pprust::pat_to_string;
use syntax::visit;
return;
}
+ // Hacky hack: During type-checking, we treat *all* operators
+ // as potentially overloaded. But then, during writeback, if
+ // we observe that something like `a+b` is (known to be)
+ // operating on scalars, we clear the overload.
+ match e.node {
+ ast::ExprBinary(ref op, ref lhs, ref rhs) => {
+ let lhs_ty = self.fcx.expr_ty(lhs);
+ let lhs_ty = self.fcx.infcx().resolve_type_vars_if_possible(&lhs_ty);
+ let rhs_ty = self.fcx.expr_ty(rhs);
+ let rhs_ty = self.fcx.infcx().resolve_type_vars_if_possible(&rhs_ty);
+ if ty::type_is_scalar(lhs_ty) && ty::type_is_scalar(rhs_ty) {
+ self.fcx.inh.method_map.borrow_mut().remove(&MethodCall::expr(e.id));
+
+ // weird but true: the by-ref binops put an
+ // adjustment on the lhs but not the rhs; the
+ // adjustment for rhs is kind of baked into the
+ // system.
+ if !ast_util::is_by_value_binop(op.node) {
+ self.fcx.inh.adjustments.borrow_mut().remove(&lhs.id);
+ }
+ }
+ }
+ _ => { }
+ }
+
self.visit_node_id(ResolvingExpr(e.span), e.id);
self.visit_method_map_entry(ResolvingExpr(e.span),
MethodCall::expr(e.id));
E0321, // extended coherence rules for defaulted traits violated
E0322, // cannot implement Sized explicitly
E0366, // dropck forbid specialization to concrete type or region
- E0367 // dropck forbid specialization to predicate not in struct/enum
+ E0367, // dropck forbid specialization to predicate not in struct/enum
+ E0368, // binary operation `<op>=` cannot be applied to types
+ E0369 // binary operation `<op>` cannot be applied to types
}
__build_diagnostic_array! { DIAGNOSTICS }
pub fn is_comparison_binop(b: BinOp_) -> bool {
match b {
- BiEq | BiLt | BiLe | BiNe | BiGt | BiGe => true,
- _ => false
+ BiEq | BiLt | BiLe | BiNe | BiGt | BiGe =>
+ true,
+ BiAnd | BiOr | BiAdd | BiSub | BiMul | BiDiv | BiRem |
+ BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr =>
+ false,
}
}
/// Returns `true` if the binary operator takes its arguments by value
pub fn is_by_value_binop(b: BinOp_) -> bool {
- match b {
- BiAdd | BiSub | BiMul | BiDiv | BiRem | BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr => {
- true
- }
- _ => false
- }
-}
-
-/// Returns `true` if the binary operator is symmetric in the sense that LHS
-/// and RHS must have the same type. So the type of LHS can serve as an hint
-/// for the type of RHS and vice versa.
-pub fn is_symmetric_binop(b: BinOp_) -> bool {
- match b {
- BiAdd | BiSub | BiMul | BiDiv | BiRem |
- BiBitXor | BiBitAnd | BiBitOr |
- BiEq | BiLt | BiLe | BiNe | BiGt | BiGe => {
- true
- }
- _ => false
- }
+ !is_comparison_binop(b)
}
/// Returns `true` if the unary operator takes its argument by value