blob: 8604dadf46dff2f4011aa22f67fb948f0a634ea0 [file] [log] [blame]
// 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::FnCtxt;
use hir::def_id::DefId;
use rustc::ty::{Ty, TypeFoldable, PreferMutLvalue};
use syntax::ast;
use syntax::parse::token;
use rustc::hir;
impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
/// Check a `a <op>= b`
pub fn check_binop_assign(&self,
expr: &'gcx hir::Expr,
op: hir::BinOp,
lhs_expr: &'gcx hir::Expr,
rhs_expr: &'gcx hir::Expr)
{
self.check_expr_with_lvalue_pref(lhs_expr, PreferMutLvalue);
let lhs_ty = self.resolve_type_vars_with_obligations(self.expr_ty(lhs_expr));
let (rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs_expr, lhs_ty, rhs_expr, op, IsAssign::Yes);
let rhs_ty = self.resolve_type_vars_with_obligations(rhs_ty);
if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) {
self.enforce_builtin_binop_types(lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
self.write_nil(expr.id);
} else {
self.write_ty(expr.id, return_ty);
}
let tcx = self.tcx;
if !tcx.expr_is_lval(lhs_expr) {
span_err!(tcx.sess, lhs_expr.span, E0067, "invalid left-hand side expression");
}
}
/// Check a potentially overloaded binary operator.
pub fn check_binop(&self,
expr: &'gcx hir::Expr,
op: hir::BinOp,
lhs_expr: &'gcx hir::Expr,
rhs_expr: &'gcx hir::Expr)
{
let tcx = self.tcx;
debug!("check_binop(expr.id={}, expr={:?}, op={:?}, lhs_expr={:?}, rhs_expr={:?})",
expr.id,
expr,
op,
lhs_expr,
rhs_expr);
self.check_expr(lhs_expr);
let lhs_ty = self.resolve_type_vars_with_obligations(self.expr_ty(lhs_expr));
match BinOpCategory::from(op) {
BinOpCategory::Shortcircuit => {
// && and || are a simple case.
self.demand_suptype(lhs_expr.span, tcx.mk_bool(), lhs_ty);
self.check_expr_coercable_to_type(rhs_expr, tcx.mk_bool());
self.write_ty(expr.id, tcx.mk_bool());
}
_ => {
// 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) =
self.check_overloaded_binop(expr, lhs_expr, lhs_ty,
rhs_expr, op, IsAssign::No);
// 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 = self.resolve_type_vars_with_obligations(rhs_ty);
if
!lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() &&
is_builtin_binop(lhs_ty, rhs_ty, op)
{
let builtin_return_ty =
self.enforce_builtin_binop_types(lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
self.demand_suptype(expr.span, builtin_return_ty, return_ty);
}
self.write_ty(expr.id, return_ty);
}
}
}
fn enforce_builtin_binop_types(&self,
lhs_expr: &'gcx hir::Expr,
lhs_ty: Ty<'tcx>,
rhs_expr: &'gcx hir::Expr,
rhs_ty: Ty<'tcx>,
op: hir::BinOp)
-> Ty<'tcx>
{
debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, op));
let tcx = self.tcx;
match BinOpCategory::from(op) {
BinOpCategory::Shortcircuit => {
self.demand_suptype(lhs_expr.span, tcx.mk_bool(), lhs_ty);
self.demand_suptype(rhs_expr.span, tcx.mk_bool(), rhs_ty);
tcx.mk_bool()
}
BinOpCategory::Shift => {
// result type is same as LHS always
lhs_ty
}
BinOpCategory::Math |
BinOpCategory::Bitwise => {
// both LHS and RHS and result will have the same type
self.demand_suptype(rhs_expr.span, lhs_ty, rhs_ty);
lhs_ty
}
BinOpCategory::Comparison => {
// both LHS and RHS and result will have the same type
self.demand_suptype(rhs_expr.span, lhs_ty, rhs_ty);
tcx.mk_bool()
}
}
}
fn check_overloaded_binop(&self,
expr: &'gcx hir::Expr,
lhs_expr: &'gcx hir::Expr,
lhs_ty: Ty<'tcx>,
rhs_expr: &'gcx hir::Expr,
op: hir::BinOp,
is_assign: IsAssign)
-> (Ty<'tcx>, Ty<'tcx>)
{
debug!("check_overloaded_binop(expr.id={}, lhs_ty={:?}, is_assign={:?})",
expr.id,
lhs_ty,
is_assign);
let (name, trait_def_id) = self.name_and_trait_def_id(op, is_assign);
// 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 = self.next_ty_var();
let return_ty = match self.lookup_op_method(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 !lhs_ty.references_error() {
if let IsAssign::Yes = is_assign {
span_err!(self.tcx.sess, lhs_expr.span, E0368,
"binary assignment operation `{}=` \
cannot be applied to type `{}`",
op.node.as_str(),
lhs_ty);
} else {
let mut err = struct_span_err!(self.tcx.sess, lhs_expr.span, E0369,
"binary operation `{}` cannot be applied to type `{}`",
op.node.as_str(),
lhs_ty);
let missing_trait = match op.node {
hir::BiAdd => Some("std::ops::Add"),
hir::BiSub => Some("std::ops::Sub"),
hir::BiMul => Some("std::ops::Mul"),
hir::BiDiv => Some("std::ops::Div"),
hir::BiRem => Some("std::ops::Rem"),
hir::BiBitAnd => Some("std::ops::BitAnd"),
hir::BiBitOr => Some("std::ops::BitOr"),
hir::BiShl => Some("std::ops::Shl"),
hir::BiShr => Some("std::ops::Shr"),
hir::BiEq | hir::BiNe => Some("std::cmp::PartialEq"),
hir::BiLt | hir::BiLe | hir::BiGt | hir::BiGe =>
Some("std::cmp::PartialOrd"),
_ => None
};
if let Some(missing_trait) = missing_trait {
span_note!(&mut err, lhs_expr.span,
"an implementation of `{}` might be missing for `{}`",
missing_trait, lhs_ty);
}
err.emit();
}
}
self.tcx.types.err
}
};
// see `NB` above
self.check_expr_coercable_to_type(rhs_expr, rhs_ty_var);
(rhs_ty_var, return_ty)
}
pub fn check_user_unop(&self,
op_str: &str,
mname: &str,
trait_did: Option<DefId>,
ex: &'gcx hir::Expr,
operand_expr: &'gcx hir::Expr,
operand_ty: Ty<'tcx>,
op: hir::UnOp)
-> Ty<'tcx>
{
assert!(op.is_by_value());
match self.lookup_op_method(ex, operand_ty, vec![],
token::intern(mname), trait_did,
operand_expr) {
Ok(t) => t,
Err(()) => {
self.type_error_message(ex.span, |actual| {
format!("cannot apply unary operator `{}` to type `{}`",
op_str, actual)
}, operand_ty, None);
self.tcx.types.err
}
}
}
fn name_and_trait_def_id(&self,
op: hir::BinOp,
is_assign: IsAssign)
-> (&'static str, Option<DefId>) {
let lang = &self.tcx.lang_items;
if let IsAssign::Yes = is_assign {
match op.node {
hir::BiAdd => ("add_assign", lang.add_assign_trait()),
hir::BiSub => ("sub_assign", lang.sub_assign_trait()),
hir::BiMul => ("mul_assign", lang.mul_assign_trait()),
hir::BiDiv => ("div_assign", lang.div_assign_trait()),
hir::BiRem => ("rem_assign", lang.rem_assign_trait()),
hir::BiBitXor => ("bitxor_assign", lang.bitxor_assign_trait()),
hir::BiBitAnd => ("bitand_assign", lang.bitand_assign_trait()),
hir::BiBitOr => ("bitor_assign", lang.bitor_assign_trait()),
hir::BiShl => ("shl_assign", lang.shl_assign_trait()),
hir::BiShr => ("shr_assign", lang.shr_assign_trait()),
hir::BiLt | hir::BiLe |
hir::BiGe | hir::BiGt |
hir::BiEq | hir::BiNe |
hir::BiAnd | hir::BiOr => {
span_bug!(op.span,
"impossible assignment operation: {}=",
op.node.as_str())
}
}
} else {
match op.node {
hir::BiAdd => ("add", lang.add_trait()),
hir::BiSub => ("sub", lang.sub_trait()),
hir::BiMul => ("mul", lang.mul_trait()),
hir::BiDiv => ("div", lang.div_trait()),
hir::BiRem => ("rem", lang.rem_trait()),
hir::BiBitXor => ("bitxor", lang.bitxor_trait()),
hir::BiBitAnd => ("bitand", lang.bitand_trait()),
hir::BiBitOr => ("bitor", lang.bitor_trait()),
hir::BiShl => ("shl", lang.shl_trait()),
hir::BiShr => ("shr", lang.shr_trait()),
hir::BiLt => ("lt", lang.ord_trait()),
hir::BiLe => ("le", lang.ord_trait()),
hir::BiGe => ("ge", lang.ord_trait()),
hir::BiGt => ("gt", lang.ord_trait()),
hir::BiEq => ("eq", lang.eq_trait()),
hir::BiNe => ("ne", lang.eq_trait()),
hir::BiAnd | hir::BiOr => {
span_bug!(op.span, "&& and || are not overloadable")
}
}
}
}
fn lookup_op_method(&self,
expr: &'gcx hir::Expr,
lhs_ty: Ty<'tcx>,
other_tys: Vec<Ty<'tcx>>,
opname: ast::Name,
trait_did: Option<DefId>,
lhs_expr: &'a hir::Expr)
-> Result<Ty<'tcx>,()>
{
debug!("lookup_op_method(expr={:?}, lhs_ty={:?}, opname={:?}, \
trait_did={:?}, lhs_expr={:?})",
expr,
lhs_ty,
opname,
trait_did,
lhs_expr);
let method = match trait_did {
Some(trait_did) => {
self.lookup_method_in_trait_adjusted(expr.span,
Some(lhs_expr),
opname,
trait_did,
0,
false,
lhs_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 = ::rustc::ty::MethodCall::expr(expr.id);
self.tables.borrow_mut().method_map.insert(method_call, method);
// extract return type for method; all late bound regions
// should have been instantiated by now
let ret_ty = method_ty.fn_ret();
Ok(self.tcx.no_late_bound_regions(&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: hir::BinOp) -> BinOpCategory {
match op.node {
hir::BiShl | hir::BiShr =>
BinOpCategory::Shift,
hir::BiAdd |
hir::BiSub |
hir::BiMul |
hir::BiDiv |
hir::BiRem =>
BinOpCategory::Math,
hir::BiBitXor |
hir::BiBitAnd |
hir::BiBitOr =>
BinOpCategory::Bitwise,
hir::BiEq |
hir::BiNe |
hir::BiLt |
hir::BiLe |
hir::BiGe |
hir::BiGt =>
BinOpCategory::Comparison,
hir::BiAnd |
hir::BiOr =>
BinOpCategory::Shortcircuit,
}
}
}
/// Whether the binary operation is an assignment (`a += b`), or not (`a + b`)
#[derive(Clone, Copy, Debug)]
enum IsAssign {
No,
Yes,
}
/// 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(lhs: Ty, rhs: Ty, op: hir::BinOp) -> bool {
match BinOpCategory::from(op) {
BinOpCategory::Shortcircuit => {
true
}
BinOpCategory::Shift => {
lhs.references_error() || rhs.references_error() ||
lhs.is_integral() && rhs.is_integral()
}
BinOpCategory::Math => {
lhs.references_error() || rhs.references_error() ||
lhs.is_integral() && rhs.is_integral() ||
lhs.is_floating_point() && rhs.is_floating_point()
}
BinOpCategory::Bitwise => {
lhs.references_error() || rhs.references_error() ||
lhs.is_integral() && rhs.is_integral() ||
lhs.is_floating_point() && rhs.is_floating_point() ||
lhs.is_bool() && rhs.is_bool()
}
BinOpCategory::Comparison => {
lhs.references_error() || rhs.references_error() ||
lhs.is_scalar() && rhs.is_scalar()
}
}
}