blob: 54f4406b4afc4ed175b4991d1c9b9a55e81be490 [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, Needs};
use super::method::MethodCallee;
use rustc::ty::{self, Ty, TypeFoldable};
use rustc::ty::TyKind::{Ref, Adt, Str, Uint, Never, Tuple, Char, Array};
use rustc::ty::adjustment::{Adjustment, Adjust, AllowTwoPhase, AutoBorrow, AutoBorrowMutability};
use rustc::infer::type_variable::TypeVariableOrigin;
use errors::{self,Applicability};
use syntax_pos::Span;
use syntax::ast::Ident;
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) -> Ty<'tcx>
{
let (lhs_ty, rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs_expr, rhs_expr, op, IsAssign::Yes);
let 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.tcx.mk_unit()
} else {
return_ty
};
if !lhs_expr.is_place_expr() {
struct_span_err!(
self.tcx.sess, lhs_expr.span,
E0067, "invalid left-hand side expression")
.span_label(
lhs_expr.span,
"invalid expression for left-hand side")
.emit();
}
ty
}
/// 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) -> Ty<'tcx>
{
let tcx = self.tcx;
debug!("check_binop(expr.id={}, expr={:?}, op={:?}, lhs_expr={:?}, rhs_expr={:?})",
expr.id,
expr,
op,
lhs_expr,
rhs_expr);
match BinOpCategory::from(op) {
BinOpCategory::Shortcircuit => {
// && and || are a simple case.
self.check_expr_coercable_to_type(lhs_expr, tcx.types.bool);
let lhs_diverges = self.diverges.get();
self.check_expr_coercable_to_type(rhs_expr, tcx.types.bool);
// Depending on the LHS' value, the RHS can never execute.
self.diverges.set(lhs_diverges);
tcx.types.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 (lhs_ty, rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs_expr,
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.
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);
}
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,
rhs_expr: &'gcx hir::Expr,
op: hir::BinOp,
is_assign: IsAssign)
-> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)
{
debug!("check_overloaded_binop(expr.id={}, op={:?}, is_assign={:?})",
expr.id,
op,
is_assign);
let lhs_ty = match is_assign {
IsAssign::No => {
// Find a suitable supertype of the LHS expression's type, by coercing to
// a type variable, to pass as the `Self` to the trait, avoiding invariant
// trait matching creating lifetime constraints that are too strict.
// E.g. adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
// in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
let lhs_ty = self.check_expr_with_needs(lhs_expr, Needs::None);
let fresh_var = self.next_ty_var(TypeVariableOrigin::MiscVariable(lhs_expr.span));
self.demand_coerce(lhs_expr, lhs_ty, fresh_var, AllowTwoPhase::No)
}
IsAssign::Yes => {
// rust-lang/rust#52126: We have to use strict
// equivalence on the LHS of an assign-op like `+=`;
// overwritten or mutably-borrowed places cannot be
// coerced to a supertype.
self.check_expr_with_needs(lhs_expr, Needs::MutPlace)
}
};
let lhs_ty = self.resolve_type_vars_with_obligations(lhs_ty);
// 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(TypeVariableOrigin::MiscVariable(rhs_expr.span));
let result = self.lookup_op_method(lhs_ty, &[rhs_ty_var], Op::Binary(op, is_assign));
// see `NB` above
let rhs_ty = self.check_expr_coercable_to_type(rhs_expr, rhs_ty_var);
let rhs_ty = self.resolve_type_vars_with_obligations(rhs_ty);
let return_ty = match result {
Ok(method) => {
let by_ref_binop = !op.node.is_by_value();
if is_assign == IsAssign::Yes || by_ref_binop {
if let ty::Ref(region, _, mutbl) = method.sig.inputs()[0].sty {
let mutbl = match mutbl {
hir::MutImmutable => AutoBorrowMutability::Immutable,
hir::MutMutable => AutoBorrowMutability::Mutable {
// Allow two-phase borrows for binops in initial deployment
// since they desugar to methods
allow_two_phase_borrow: AllowTwoPhase::Yes,
}
};
let autoref = Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(region, mutbl)),
target: method.sig.inputs()[0]
};
self.apply_adjustments(lhs_expr, vec![autoref]);
}
}
if by_ref_binop {
if let ty::Ref(region, _, mutbl) = method.sig.inputs()[1].sty {
let mutbl = match mutbl {
hir::MutImmutable => AutoBorrowMutability::Immutable,
hir::MutMutable => AutoBorrowMutability::Mutable {
// Allow two-phase borrows for binops in initial deployment
// since they desugar to methods
allow_two_phase_borrow: AllowTwoPhase::Yes,
}
};
let autoref = Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(region, mutbl)),
target: method.sig.inputs()[1]
};
// HACK(eddyb) Bypass checks due to reborrows being in
// some cases applied on the RHS, on top of which we need
// to autoref, which is not allowed by apply_adjustments.
// self.apply_adjustments(rhs_expr, vec![autoref]);
self.tables
.borrow_mut()
.adjustments_mut()
.entry(rhs_expr.hir_id)
.or_default()
.push(autoref);
}
}
self.write_method_call(expr.hir_id, method);
method.sig.output()
}
Err(()) => {
// error types are considered "builtin"
if !lhs_ty.references_error() {
let source_map = self.tcx.sess.source_map();
match is_assign {
IsAssign::Yes => {
let mut err = struct_span_err!(
self.tcx.sess,
expr.span,
E0368,
"binary assignment operation `{}=` cannot be applied to type `{}`",
op.node.as_str(),
lhs_ty,
);
err.span_label(
lhs_expr.span,
format!("cannot use `{}=` on type `{}`",
op.node.as_str(), lhs_ty),
);
let mut suggested_deref = false;
if let Ref(_, mut rty, _) = lhs_ty.sty {
if {
!self.infcx.type_moves_by_default(self.param_env,
rty,
lhs_expr.span) &&
self.lookup_op_method(rty,
&[rhs_ty],
Op::Binary(op, is_assign))
.is_ok()
} {
if let Ok(lstring) = source_map.span_to_snippet(lhs_expr.span) {
while let Ref(_, rty_inner, _) = rty.sty {
rty = rty_inner;
}
let msg = &format!(
"`{}=` can be used on '{}', you can dereference `{}`",
op.node.as_str(),
rty,
lstring,
);
err.span_suggestion_with_applicability(
lhs_expr.span,
msg,
format!("*{}", lstring),
errors::Applicability::MachineApplicable,
);
suggested_deref = true;
}
}
}
let missing_trait = match op.node {
hir::BinOpKind::Add => Some("std::ops::AddAssign"),
hir::BinOpKind::Sub => Some("std::ops::SubAssign"),
hir::BinOpKind::Mul => Some("std::ops::MulAssign"),
hir::BinOpKind::Div => Some("std::ops::DivAssign"),
hir::BinOpKind::Rem => Some("std::ops::RemAssign"),
hir::BinOpKind::BitAnd => Some("std::ops::BitAndAssign"),
hir::BinOpKind::BitXor => Some("std::ops::BitXorAssign"),
hir::BinOpKind::BitOr => Some("std::ops::BitOrAssign"),
hir::BinOpKind::Shl => Some("std::ops::ShlAssign"),
hir::BinOpKind::Shr => Some("std::ops::ShrAssign"),
_ => None
};
if let Some(missing_trait) = missing_trait {
if op.node == hir::BinOpKind::Add &&
self.check_str_addition(expr, lhs_expr, rhs_expr, lhs_ty,
rhs_ty, &mut err, true) {
// This has nothing here because it means we did string
// concatenation (e.g. "Hello " += "World!"). This means
// we don't want the note in the else clause to be emitted
} else if let ty::Param(_) = lhs_ty.sty {
// FIXME: point to span of param
err.note(&format!(
"`{}` might need a bound for `{}`",
lhs_ty, missing_trait
));
} else if !suggested_deref {
err.note(&format!(
"an implementation of `{}` might \
be missing for `{}`",
missing_trait, lhs_ty
));
}
}
err.emit();
}
IsAssign::No => {
let mut err = struct_span_err!(self.tcx.sess, expr.span, E0369,
"binary operation `{}` cannot be applied to type `{}`",
op.node.as_str(),
lhs_ty);
let mut suggested_deref = false;
if let Ref(_, mut rty, _) = lhs_ty.sty {
if {
!self.infcx.type_moves_by_default(self.param_env,
rty,
lhs_expr.span) &&
self.lookup_op_method(rty,
&[rhs_ty],
Op::Binary(op, is_assign))
.is_ok()
} {
if let Ok(lstring) = source_map.span_to_snippet(lhs_expr.span) {
while let Ref(_, rty_inner, _) = rty.sty {
rty = rty_inner;
}
let msg = &format!(
"`{}` can be used on '{}', you can \
dereference `{2}`: `*{2}`",
op.node.as_str(),
rty,
lstring
);
err.help(msg);
suggested_deref = true;
}
}
}
let missing_trait = match op.node {
hir::BinOpKind::Add => Some("std::ops::Add"),
hir::BinOpKind::Sub => Some("std::ops::Sub"),
hir::BinOpKind::Mul => Some("std::ops::Mul"),
hir::BinOpKind::Div => Some("std::ops::Div"),
hir::BinOpKind::Rem => Some("std::ops::Rem"),
hir::BinOpKind::BitAnd => Some("std::ops::BitAnd"),
hir::BinOpKind::BitXor => Some("std::ops::BitXor"),
hir::BinOpKind::BitOr => Some("std::ops::BitOr"),
hir::BinOpKind::Shl => Some("std::ops::Shl"),
hir::BinOpKind::Shr => Some("std::ops::Shr"),
hir::BinOpKind::Eq |
hir::BinOpKind::Ne => Some("std::cmp::PartialEq"),
hir::BinOpKind::Lt |
hir::BinOpKind::Le |
hir::BinOpKind::Gt |
hir::BinOpKind::Ge => Some("std::cmp::PartialOrd"),
_ => None
};
if let Some(missing_trait) = missing_trait {
if op.node == hir::BinOpKind::Add &&
self.check_str_addition(expr, lhs_expr, rhs_expr, lhs_ty,
rhs_ty, &mut err, false) {
// This has nothing here because it means we did string
// concatenation (e.g. "Hello " + "World!"). This means
// we don't want the note in the else clause to be emitted
} else if let ty::Param(_) = lhs_ty.sty {
// FIXME: point to span of param
err.note(&format!(
"`{}` might need a bound for `{}`",
lhs_ty, missing_trait
));
} else if !suggested_deref {
err.note(&format!(
"an implementation of `{}` might \
be missing for `{}`",
missing_trait, lhs_ty
));
}
}
err.emit();
}
}
}
self.tcx.types.err
}
};
(lhs_ty, rhs_ty, return_ty)
}
fn check_str_addition(
&self,
expr: &'gcx hir::Expr,
lhs_expr: &'gcx hir::Expr,
rhs_expr: &'gcx hir::Expr,
lhs_ty: Ty<'tcx>,
rhs_ty: Ty<'tcx>,
err: &mut errors::DiagnosticBuilder,
is_assign: bool,
) -> bool {
let source_map = self.tcx.sess.source_map();
let msg = "`to_owned()` can be used to create an owned `String` \
from a string reference. String concatenation \
appends the string on the right to the string \
on the left and may require reallocation. This \
requires ownership of the string on the left";
// If this function returns true it means a note was printed, so we don't need
// to print the normal "implementation of `std::ops::Add` might be missing" note
match (&lhs_ty.sty, &rhs_ty.sty) {
(&Ref(_, l_ty, _), &Ref(_, r_ty, _))
if l_ty.sty == Str && r_ty.sty == Str => {
if !is_assign {
err.span_label(expr.span,
"`+` can't be used to concatenate two `&str` strings");
match source_map.span_to_snippet(lhs_expr.span) {
Ok(lstring) => err.span_suggestion_with_applicability(
lhs_expr.span,
msg,
format!("{}.to_owned()", lstring),
Applicability::MachineApplicable,
),
_ => err.help(msg),
};
}
true
}
(&Ref(_, l_ty, _), &Adt(..))
if l_ty.sty == Str && &format!("{:?}", rhs_ty) == "std::string::String" => {
err.span_label(expr.span,
"`+` can't be used to concatenate a `&str` with a `String`");
match (
source_map.span_to_snippet(lhs_expr.span),
source_map.span_to_snippet(rhs_expr.span),
is_assign,
) {
(Ok(l), Ok(r), false) => {
err.multipart_suggestion_with_applicability(
msg,
vec![
(lhs_expr.span, format!("{}.to_owned()", l)),
(rhs_expr.span, format!("&{}", r)),
],
Applicability::MachineApplicable,
);
}
_ => {
err.help(msg);
}
};
true
}
_ => false,
}
}
pub fn check_user_unop(&self,
ex: &'gcx hir::Expr,
operand_ty: Ty<'tcx>,
op: hir::UnOp)
-> Ty<'tcx>
{
assert!(op.is_by_value());
match self.lookup_op_method(operand_ty, &[], Op::Unary(op, ex.span)) {
Ok(method) => {
self.write_method_call(ex.hir_id, method);
method.sig.output()
}
Err(()) => {
let actual = self.resolve_type_vars_if_possible(&operand_ty);
if !actual.references_error() {
let mut err = struct_span_err!(self.tcx.sess, ex.span, E0600,
"cannot apply unary operator `{}` to type `{}`",
op.as_str(), actual);
err.span_label(ex.span, format!("cannot apply unary \
operator `{}`", op.as_str()));
match actual.sty {
Uint(_) if op == hir::UnNeg => {
err.note("unsigned values cannot be negated");
},
Str | Never | Char | Tuple(_) | Array(_,_) => {},
Ref(_, ref lty, _) if lty.sty == Str => {},
_ => {
let missing_trait = match op {
hir::UnNeg => "std::ops::Neg",
hir::UnNot => "std::ops::Not",
hir::UnDeref => "std::ops::UnDerf"
};
err.note(&format!("an implementation of `{}` might \
be missing for `{}`",
missing_trait, operand_ty));
}
}
err.emit();
}
self.tcx.types.err
}
}
}
fn lookup_op_method(&self, lhs_ty: Ty<'tcx>, other_tys: &[Ty<'tcx>], op: Op)
-> Result<MethodCallee<'tcx>, ()>
{
let lang = self.tcx.lang_items();
let span = match op {
Op::Binary(op, _) => op.span,
Op::Unary(_, span) => span
};
let (opname, trait_did) = if let Op::Binary(op, IsAssign::Yes) = op {
match op.node {
hir::BinOpKind::Add => ("add_assign", lang.add_assign_trait()),
hir::BinOpKind::Sub => ("sub_assign", lang.sub_assign_trait()),
hir::BinOpKind::Mul => ("mul_assign", lang.mul_assign_trait()),
hir::BinOpKind::Div => ("div_assign", lang.div_assign_trait()),
hir::BinOpKind::Rem => ("rem_assign", lang.rem_assign_trait()),
hir::BinOpKind::BitXor => ("bitxor_assign", lang.bitxor_assign_trait()),
hir::BinOpKind::BitAnd => ("bitand_assign", lang.bitand_assign_trait()),
hir::BinOpKind::BitOr => ("bitor_assign", lang.bitor_assign_trait()),
hir::BinOpKind::Shl => ("shl_assign", lang.shl_assign_trait()),
hir::BinOpKind::Shr => ("shr_assign", lang.shr_assign_trait()),
hir::BinOpKind::Lt | hir::BinOpKind::Le |
hir::BinOpKind::Ge | hir::BinOpKind::Gt |
hir::BinOpKind::Eq | hir::BinOpKind::Ne |
hir::BinOpKind::And | hir::BinOpKind::Or => {
span_bug!(span,
"impossible assignment operation: {}=",
op.node.as_str())
}
}
} else if let Op::Binary(op, IsAssign::No) = op {
match op.node {
hir::BinOpKind::Add => ("add", lang.add_trait()),
hir::BinOpKind::Sub => ("sub", lang.sub_trait()),
hir::BinOpKind::Mul => ("mul", lang.mul_trait()),
hir::BinOpKind::Div => ("div", lang.div_trait()),
hir::BinOpKind::Rem => ("rem", lang.rem_trait()),
hir::BinOpKind::BitXor => ("bitxor", lang.bitxor_trait()),
hir::BinOpKind::BitAnd => ("bitand", lang.bitand_trait()),
hir::BinOpKind::BitOr => ("bitor", lang.bitor_trait()),
hir::BinOpKind::Shl => ("shl", lang.shl_trait()),
hir::BinOpKind::Shr => ("shr", lang.shr_trait()),
hir::BinOpKind::Lt => ("lt", lang.partial_ord_trait()),
hir::BinOpKind::Le => ("le", lang.partial_ord_trait()),
hir::BinOpKind::Ge => ("ge", lang.partial_ord_trait()),
hir::BinOpKind::Gt => ("gt", lang.partial_ord_trait()),
hir::BinOpKind::Eq => ("eq", lang.eq_trait()),
hir::BinOpKind::Ne => ("ne", lang.eq_trait()),
hir::BinOpKind::And | hir::BinOpKind::Or => {
span_bug!(span, "&& and || are not overloadable")
}
}
} else if let Op::Unary(hir::UnNot, _) = op {
("not", lang.not_trait())
} else if let Op::Unary(hir::UnNeg, _) = op {
("neg", lang.neg_trait())
} else {
bug!("lookup_op_method: op not supported: {:?}", op)
};
debug!("lookup_op_method(lhs_ty={:?}, op={:?}, opname={:?}, trait_did={:?})",
lhs_ty,
op,
opname,
trait_did);
let method = trait_did.and_then(|trait_did| {
let opname = Ident::from_str(opname);
self.lookup_method_in_trait(span, opname, trait_did, lhs_ty, Some(other_tys))
});
match method {
Some(ok) => {
let method = self.register_infer_ok_obligations(ok);
self.select_obligations_where_possible(false);
Ok(method)
}
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::BinOpKind::Shl | hir::BinOpKind::Shr =>
BinOpCategory::Shift,
hir::BinOpKind::Add |
hir::BinOpKind::Sub |
hir::BinOpKind::Mul |
hir::BinOpKind::Div |
hir::BinOpKind::Rem =>
BinOpCategory::Math,
hir::BinOpKind::BitXor |
hir::BinOpKind::BitAnd |
hir::BinOpKind::BitOr =>
BinOpCategory::Bitwise,
hir::BinOpKind::Eq |
hir::BinOpKind::Ne |
hir::BinOpKind::Lt |
hir::BinOpKind::Le |
hir::BinOpKind::Ge |
hir::BinOpKind::Gt =>
BinOpCategory::Comparison,
hir::BinOpKind::And |
hir::BinOpKind::Or =>
BinOpCategory::Shortcircuit,
}
}
}
/// Whether the binary operation is an assignment (`a += b`), or not (`a + b`)
#[derive(Clone, Copy, Debug, PartialEq)]
enum IsAssign {
No,
Yes,
}
#[derive(Clone, Copy, Debug)]
enum Op {
Binary(hir::BinOp, IsAssign),
Unary(hir::UnOp, Span),
}
/// 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/codegen 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()
}
}
}