| //! 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, FnDef, Str, Uint, Never, Tuple, Char, Array}; |
| use rustc::ty::adjustment::{Adjustment, Adjust, AllowTwoPhase, AutoBorrow, AutoBorrowMutability}; |
| use rustc::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; |
| use errors::{self,Applicability}; |
| use syntax_pos::Span; |
| use syntax::ast::Ident; |
| use rustc::hir; |
| |
| use rustc_error_codes::*; |
| |
| impl<'a, 'tcx> FnCtxt<'a, 'tcx> { |
| /// Checks a `a <op>= b` |
| pub fn check_binop_assign( |
| &self, |
| expr: &'tcx hir::Expr, |
| op: hir::BinOp, |
| lhs_expr: &'tcx hir::Expr, |
| rhs_expr: &'tcx 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_syntactic_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 |
| } |
| |
| /// Checks a potentially overloaded binary operator. |
| pub fn check_binop( |
| &self, |
| expr: &'tcx hir::Expr, |
| op: hir::BinOp, |
| lhs_expr: &'tcx hir::Expr, |
| rhs_expr: &'tcx hir::Expr, |
| ) -> Ty<'tcx> { |
| let tcx = self.tcx; |
| |
| debug!("check_binop(expr.hir_id={}, expr={:?}, op={:?}, lhs_expr={:?}, rhs_expr={:?})", |
| expr.hir_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: &'tcx hir::Expr, |
| lhs_ty: Ty<'tcx>, |
| rhs_expr: &'tcx 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: &'tcx hir::Expr, |
| lhs_expr: &'tcx hir::Expr, |
| rhs_expr: &'tcx hir::Expr, |
| op: hir::BinOp, |
| is_assign: IsAssign, |
| ) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) { |
| debug!("check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})", |
| expr.hir_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 { |
| kind: TypeVariableOriginKind::MiscVariable, |
| span: 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_vars_with_obligations(lhs_ty); |
| |
| // N.B., 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 { |
| kind: TypeVariableOriginKind::MiscVariable, |
| span: 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_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].kind { |
| let mutbl = match mutbl { |
| hir::Mutability::Immutable => AutoBorrowMutability::Immutable, |
| hir::Mutability::Mutable => 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].kind { |
| let mutbl = match mutbl { |
| hir::Mutability::Immutable => AutoBorrowMutability::Immutable, |
| hir::Mutability::Mutable => 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(_, rty, _) = lhs_ty.kind { |
| if { |
| self.infcx.type_is_copy_modulo_regions(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) { |
| let msg = &format!( |
| "`{}=` can be used on '{}', you can dereference `{}`", |
| op.node.as_str(), |
| rty.peel_refs(), |
| lstring, |
| ); |
| err.span_suggestion( |
| 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( |
| lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, true, op) { |
| // 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.kind { |
| // 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, op.span, E0369, |
| "binary operation `{}` cannot be applied to type `{}`", |
| op.node.as_str(), |
| lhs_ty); |
| |
| let mut involves_fn = false; |
| if !lhs_expr.span.eq(&rhs_expr.span) { |
| involves_fn |= self.add_type_neq_err_label( |
| &mut err, |
| lhs_expr.span, |
| lhs_ty, |
| rhs_ty, |
| op, |
| is_assign |
| ); |
| involves_fn |= self.add_type_neq_err_label( |
| &mut err, |
| rhs_expr.span, |
| rhs_ty, |
| lhs_ty, |
| op, |
| is_assign |
| ); |
| } |
| |
| let mut suggested_deref = false; |
| if let Ref(_, rty, _) = lhs_ty.kind { |
| if { |
| self.infcx.type_is_copy_modulo_regions(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) { |
| err.help(&format!( |
| "`{}` can be used on '{}', you can \ |
| dereference `{2}`: `*{2}`", |
| op.node.as_str(), |
| rty.peel_refs(), |
| lstring |
| )); |
| 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( |
| lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, false, op) { |
| // 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.kind { |
| // FIXME: point to span of param |
| err.note(&format!( |
| "`{}` might need a bound for `{}`", |
| lhs_ty, missing_trait |
| )); |
| } else if !suggested_deref && !involves_fn { |
| 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) |
| } |
| |
| /// If one of the types is an uncalled function and calling it would yield the other type, |
| /// suggest calling the function. Returns wether a suggestion was given. |
| fn add_type_neq_err_label( |
| &self, |
| err: &mut errors::DiagnosticBuilder<'_>, |
| span: Span, |
| ty: Ty<'tcx>, |
| other_ty: Ty<'tcx>, |
| op: hir::BinOp, |
| is_assign: IsAssign, |
| ) -> bool /* did we suggest to call a function because of missing parenthesis? */ { |
| err.span_label(span, ty.to_string()); |
| if let FnDef(def_id, _) = ty.kind { |
| let source_map = self.tcx.sess.source_map(); |
| let hir_id = match self.tcx.hir().as_local_hir_id(def_id) { |
| Some(hir_id) => hir_id, |
| None => return false, |
| }; |
| if self.tcx.has_typeck_tables(def_id) == false { |
| return false; |
| } |
| let fn_sig = { |
| match self.tcx.typeck_tables_of(def_id).liberated_fn_sigs().get(hir_id) { |
| Some(f) => f.clone(), |
| None => { |
| bug!("No fn-sig entry for def_id={:?}", def_id); |
| } |
| } |
| }; |
| |
| let other_ty = if let FnDef(def_id, _) = other_ty.kind { |
| let hir_id = match self.tcx.hir().as_local_hir_id(def_id) { |
| Some(hir_id) => hir_id, |
| None => return false, |
| }; |
| if self.tcx.has_typeck_tables(def_id) == false { |
| return false; |
| } |
| match self.tcx.typeck_tables_of(def_id).liberated_fn_sigs().get(hir_id) { |
| Some(f) => f.clone().output(), |
| None => { |
| bug!("No fn-sig entry for def_id={:?}", def_id); |
| } |
| } |
| } else { |
| other_ty |
| }; |
| |
| if self.lookup_op_method(fn_sig.output(), |
| &[other_ty], |
| Op::Binary(op, is_assign)) |
| .is_ok() { |
| let (variable_snippet, applicability) = if fn_sig.inputs().len() > 0 { |
| (format!("{}( /* arguments */ )", source_map.span_to_snippet(span).unwrap()), |
| Applicability::HasPlaceholders) |
| } else { |
| (format!("{}()", source_map.span_to_snippet(span).unwrap()), |
| Applicability::MaybeIncorrect) |
| }; |
| |
| err.span_suggestion( |
| span, |
| "you might have forgotten to call this function", |
| variable_snippet, |
| applicability, |
| ); |
| return true; |
| } |
| } |
| false |
| } |
| |
| /// Provide actionable suggestions when trying to add two strings with incorrect types, |
| /// like `&str + &str`, `String + String` and `&str + &String`. |
| /// |
| /// 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 |
| fn check_str_addition( |
| &self, |
| lhs_expr: &'tcx hir::Expr, |
| rhs_expr: &'tcx hir::Expr, |
| lhs_ty: Ty<'tcx>, |
| rhs_ty: Ty<'tcx>, |
| err: &mut errors::DiagnosticBuilder<'_>, |
| is_assign: bool, |
| op: hir::BinOp, |
| ) -> bool { |
| let source_map = self.tcx.sess.source_map(); |
| let remove_borrow_msg = "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"; |
| |
| 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"; |
| |
| let is_std_string = |ty| &format!("{:?}", ty) == "std::string::String"; |
| |
| match (&lhs_ty.kind, &rhs_ty.kind) { |
| (&Ref(_, l_ty, _), &Ref(_, r_ty, _)) // &str or &String + &str, &String or &&str |
| if (l_ty.kind == Str || is_std_string(l_ty)) && ( |
| r_ty.kind == Str || is_std_string(r_ty) || |
| &format!("{:?}", rhs_ty) == "&&str" |
| ) => |
| { |
| if !is_assign { // Do not supply this message if `&str += &str` |
| err.span_label( |
| op.span, |
| "`+` cannot be used to concatenate two `&str` strings", |
| ); |
| match source_map.span_to_snippet(lhs_expr.span) { |
| Ok(lstring) => { |
| err.span_suggestion( |
| lhs_expr.span, |
| if lstring.starts_with("&") { |
| remove_borrow_msg |
| } else { |
| msg |
| }, |
| if lstring.starts_with("&") { |
| // let a = String::new(); |
| // let _ = &a + "bar"; |
| format!("{}", &lstring[1..]) |
| } else { |
| format!("{}.to_owned()", lstring) |
| }, |
| Applicability::MachineApplicable, |
| ) |
| } |
| _ => err.help(msg), |
| }; |
| } |
| true |
| } |
| (&Ref(_, l_ty, _), &Adt(..)) // Handle `&str` & `&String` + `String` |
| if (l_ty.kind == Str || is_std_string(l_ty)) && is_std_string(rhs_ty) => |
| { |
| err.span_label( |
| op.span, |
| "`+` cannot 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) => { |
| let to_string = if l.starts_with("&") { |
| // let a = String::new(); let b = String::new(); |
| // let _ = &a + b; |
| format!("{}", &l[1..]) |
| } else { |
| format!("{}.to_owned()", l) |
| }; |
| err.multipart_suggestion( |
| msg, |
| vec![ |
| (lhs_expr.span, to_string), |
| (rhs_expr.span, format!("&{}", r)), |
| ], |
| Applicability::MachineApplicable, |
| ); |
| } |
| _ => { |
| err.help(msg); |
| } |
| }; |
| true |
| } |
| _ => false, |
| } |
| } |
| |
| pub fn check_user_unop( |
| &self, |
| ex: &'tcx 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_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.kind { |
| Uint(_) if op == hir::UnNeg => { |
| err.note("unsigned values cannot be negated"); |
| }, |
| Str | Never | Char | Tuple(_) | Array(_,_) => {}, |
| Ref(_, ref lty, _) if lty.kind == 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() |
| } |
| } |
| } |