1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Code related to processing overloaded binary and unary operators.
15 check_expr_coercable_to_type,
16 check_expr_with_lvalue_pref,
21 structurally_resolved_type,
25 use middle::ty::{self, Ty};
28 use syntax::parse::token;
29 use util::ppaux::{Repr, UserString};
31 /// Check a `a <op>= b`
32 pub fn check_binop_assign<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
33 expr: &'tcx ast::Expr,
35 lhs_expr: &'tcx ast::Expr,
36 rhs_expr: &'tcx ast::Expr)
38 let tcx = fcx.ccx.tcx;
40 check_expr_with_lvalue_pref(fcx, lhs_expr, PreferMutLvalue);
41 check_expr(fcx, rhs_expr);
43 let lhs_ty = structurally_resolved_type(fcx, lhs_expr.span, fcx.expr_ty(lhs_expr));
44 let rhs_ty = structurally_resolved_type(fcx, rhs_expr.span, fcx.expr_ty(rhs_expr));
46 if is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op) {
47 enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
48 fcx.write_nil(expr.id);
50 // error types are considered "builtin"
51 assert!(!ty::type_is_error(lhs_ty) || !ty::type_is_error(rhs_ty));
52 span_err!(tcx.sess, lhs_expr.span, E0368,
53 "binary assignment operation `{}=` cannot be applied to types `{}` and `{}`",
54 ast_util::binop_to_string(op.node),
55 lhs_ty.user_string(fcx.tcx()),
56 rhs_ty.user_string(fcx.tcx()));
57 fcx.write_error(expr.id);
61 if !ty::expr_is_lval(tcx, lhs_expr) {
62 span_err!(tcx.sess, lhs_expr.span, E0067, "illegal left-hand side expression");
65 fcx.require_expr_have_sized_type(lhs_expr, traits::AssignmentLhsSized);
68 /// Check a potentially overloaded binary operator.
69 pub fn check_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
70 expr: &'tcx ast::Expr,
72 lhs_expr: &'tcx ast::Expr,
73 rhs_expr: &'tcx ast::Expr)
75 let tcx = fcx.ccx.tcx;
77 debug!("check_binop(expr.id={}, expr={}, op={:?}, lhs_expr={}, rhs_expr={})",
84 check_expr(fcx, lhs_expr);
85 let lhs_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(lhs_expr));
87 // Annoyingly, SIMD ops don't fit into the PartialEq/PartialOrd
88 // traits, because their return type is not bool. Perhaps this
89 // should change, but for now if LHS is SIMD we go down a
90 // different path that bypassess all traits.
91 if ty::type_is_simd(fcx.tcx(), lhs_ty) {
92 check_expr_coercable_to_type(fcx, rhs_expr, lhs_ty);
93 let rhs_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(lhs_expr));
94 let return_ty = enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
95 fcx.write_ty(expr.id, return_ty);
99 match BinOpCategory::from(op) {
100 BinOpCategory::Shortcircuit => {
101 // && and || are a simple case.
102 demand::suptype(fcx, lhs_expr.span, ty::mk_bool(tcx), lhs_ty);
103 check_expr_coercable_to_type(fcx, rhs_expr, ty::mk_bool(tcx));
104 fcx.write_ty(expr.id, ty::mk_bool(tcx));
107 // Otherwise, we always treat operators as if they are
108 // overloaded. This is the way to be most flexible w/r/t
109 // types that get inferred.
110 let (rhs_ty, return_ty) =
111 check_overloaded_binop(fcx, expr, lhs_expr, lhs_ty, rhs_expr, op);
113 // Supply type inference hints if relevant. Probably these
114 // hints should be enforced during select as part of the
115 // `consider_unification_despite_ambiguity` routine, but this
116 // more convenient for now.
118 // The basic idea is to help type inference by taking
119 // advantage of things we know about how the impls for
120 // scalar types are arranged. This is important in a
121 // scenario like `1_u32 << 2`, because it lets us quickly
122 // deduce that the result type should be `u32`, even
123 // though we don't know yet what type 2 has and hence
124 // can't pin this down to a specific impl.
125 let rhs_ty = fcx.resolve_type_vars_if_possible(rhs_ty);
127 !ty::type_is_ty_var(lhs_ty) &&
128 !ty::type_is_ty_var(rhs_ty) &&
129 is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op)
131 let builtin_return_ty =
132 enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
133 demand::suptype(fcx, expr.span, builtin_return_ty, return_ty);
136 fcx.write_ty(expr.id, return_ty);
141 fn enforce_builtin_binop_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
142 lhs_expr: &'tcx ast::Expr,
144 rhs_expr: &'tcx ast::Expr,
149 debug_assert!(is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op));
152 match BinOpCategory::from(op) {
153 BinOpCategory::Shortcircuit => {
154 demand::suptype(fcx, lhs_expr.span, ty::mk_bool(tcx), lhs_ty);
155 demand::suptype(fcx, rhs_expr.span, ty::mk_bool(tcx), rhs_ty);
159 BinOpCategory::Shift => {
160 // For integers, the shift amount can be of any integral
161 // type. For simd, the type must match exactly.
162 if ty::type_is_simd(tcx, lhs_ty) {
163 demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
166 // result type is same as LHS always
170 BinOpCategory::Math |
171 BinOpCategory::Bitwise => {
172 // both LHS and RHS and result will have the same type
173 demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
177 BinOpCategory::Comparison => {
178 // both LHS and RHS and result will have the same type
179 demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
181 // if this is simd, result is same as lhs, else bool
182 if ty::type_is_simd(tcx, lhs_ty) {
183 let unit_ty = ty::simd_type(tcx, lhs_ty);
184 debug!("enforce_builtin_binop_types: lhs_ty={} unit_ty={}",
187 if !ty::type_is_integral(unit_ty) {
190 &format!("binary comparison operation `{}` not supported \
191 for floating point SIMD vector `{}`",
192 ast_util::binop_to_string(op.node),
193 lhs_ty.user_string(tcx)));
205 fn check_overloaded_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
206 expr: &'tcx ast::Expr,
207 lhs_expr: &'tcx ast::Expr,
209 rhs_expr: &'tcx ast::Expr,
211 -> (Ty<'tcx>, Ty<'tcx>)
213 debug!("check_overloaded_binop(expr.id={}, lhs_ty={})",
215 lhs_ty.repr(fcx.tcx()));
217 let (name, trait_def_id) = name_and_trait_def_id(fcx, op);
219 // NB: As we have not yet type-checked the RHS, we don't have the
220 // type at hand. Make a variable to represent it. The whole reason
221 // for this indirection is so that, below, we can check the expr
222 // using this variable as the expected type, which sometimes lets
223 // us do better coercions than we would be able to do otherwise,
224 // particularly for things like `String + &String`.
225 let rhs_ty_var = fcx.infcx().next_ty_var();
227 let return_ty = match lookup_op_method(fcx, expr, lhs_ty, vec![rhs_ty_var],
228 token::intern(name), trait_def_id,
230 Ok(return_ty) => return_ty,
232 // error types are considered "builtin"
233 if !ty::type_is_error(lhs_ty) {
234 span_err!(fcx.tcx().sess, lhs_expr.span, E0369,
235 "binary operation `{}` cannot be applied to type `{}`",
236 ast_util::binop_to_string(op.node),
237 lhs_ty.user_string(fcx.tcx()));
244 check_expr_coercable_to_type(fcx, rhs_expr, rhs_ty_var);
246 (rhs_ty_var, return_ty)
249 pub fn check_user_unop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
252 trait_did: Option<ast::DefId>,
254 operand_expr: &'tcx ast::Expr,
255 operand_ty: Ty<'tcx>,
259 assert!(ast_util::is_by_value_unop(op));
260 match lookup_op_method(fcx, ex, operand_ty, vec![],
261 token::intern(mname), trait_did,
265 fcx.type_error_message(ex.span, |actual| {
266 format!("cannot apply unary operator `{}` to type `{}`",
268 }, operand_ty, None);
274 fn name_and_trait_def_id(fcx: &FnCtxt, op: ast::BinOp) -> (&'static str, Option<ast::DefId>) {
275 let lang = &fcx.tcx().lang_items;
277 ast::BiAdd => ("add", lang.add_trait()),
278 ast::BiSub => ("sub", lang.sub_trait()),
279 ast::BiMul => ("mul", lang.mul_trait()),
280 ast::BiDiv => ("div", lang.div_trait()),
281 ast::BiRem => ("rem", lang.rem_trait()),
282 ast::BiBitXor => ("bitxor", lang.bitxor_trait()),
283 ast::BiBitAnd => ("bitand", lang.bitand_trait()),
284 ast::BiBitOr => ("bitor", lang.bitor_trait()),
285 ast::BiShl => ("shl", lang.shl_trait()),
286 ast::BiShr => ("shr", lang.shr_trait()),
287 ast::BiLt => ("lt", lang.ord_trait()),
288 ast::BiLe => ("le", lang.ord_trait()),
289 ast::BiGe => ("ge", lang.ord_trait()),
290 ast::BiGt => ("gt", lang.ord_trait()),
291 ast::BiEq => ("eq", lang.eq_trait()),
292 ast::BiNe => ("ne", lang.eq_trait()),
293 ast::BiAnd | ast::BiOr => {
294 fcx.tcx().sess.span_bug(op.span, "&& and || are not overloadable")
299 fn lookup_op_method<'a, 'tcx>(fcx: &'a FnCtxt<'a, 'tcx>,
300 expr: &'tcx ast::Expr,
302 other_tys: Vec<Ty<'tcx>>,
304 trait_did: Option<ast::DefId>,
305 lhs_expr: &'a ast::Expr)
306 -> Result<Ty<'tcx>,()>
308 debug!("lookup_op_method(expr={}, lhs_ty={}, opname={:?}, trait_did={}, lhs_expr={})",
309 expr.repr(fcx.tcx()),
310 lhs_ty.repr(fcx.tcx()),
312 trait_did.repr(fcx.tcx()),
313 lhs_expr.repr(fcx.tcx()));
315 let method = match trait_did {
317 // We do eager coercions to make using operators
320 // - If the input is of type &'a T (resp. &'a mut T),
321 // then reborrow it to &'b T (resp. &'b mut T) where
322 // 'b <= 'a. This makes things like `x == y`, where
323 // `x` and `y` are both region pointers, work. We
324 // could also solve this with variance or different
325 // traits that don't force left and right to have same
327 let (adj_ty, adjustment) = match lhs_ty.sty {
328 ty::ty_rptr(r_in, mt) => {
329 let r_adj = fcx.infcx().next_region_var(infer::Autoref(lhs_expr.span));
330 fcx.mk_subr(infer::Reborrow(lhs_expr.span), r_adj, *r_in);
331 let adjusted_ty = ty::mk_rptr(fcx.tcx(), fcx.tcx().mk_region(r_adj), mt);
332 let autoptr = ty::AutoPtr(r_adj, mt.mutbl, None);
333 let adjustment = ty::AutoDerefRef { autoderefs: 1, autoref: Some(autoptr) };
334 (adjusted_ty, adjustment)
337 (lhs_ty, ty::AutoDerefRef { autoderefs: 0, autoref: None })
341 debug!("adjusted_ty={} adjustment={:?}",
342 adj_ty.repr(fcx.tcx()),
345 method::lookup_in_trait_adjusted(fcx, expr.span, Some(lhs_expr), opname,
346 trait_did, adjustment, adj_ty, Some(other_tys))
353 let method_ty = method.ty;
355 // HACK(eddyb) Fully qualified path to work around a resolve bug.
356 let method_call = ::middle::ty::MethodCall::expr(expr.id);
357 fcx.inh.method_map.borrow_mut().insert(method_call, method);
359 // extract return type for method; all late bound regions
360 // should have been instantiated by now
361 let ret_ty = ty::ty_fn_ret(method_ty);
362 Ok(ty::no_late_bound_regions(fcx.tcx(), &ret_ty).unwrap().unwrap())
370 // Binary operator categories. These categories summarize the behavior
371 // with respect to the builtin operationrs supported.
373 /// &&, || -- cannot be overridden
376 /// <<, >> -- when shifting a single integer, rhs can be any
377 /// integer type. For simd, types must match.
380 /// +, -, etc -- takes equal types, produces same type as input,
381 /// applicable to ints/floats/simd
384 /// &, |, ^ -- takes equal types, produces same type as input,
385 /// applicable to ints/floats/simd/bool
388 /// ==, !=, etc -- takes equal types, produces bools, except for simd,
389 /// which produce the input type
394 fn from(op: ast::BinOp) -> BinOpCategory {
396 ast::BiShl | ast::BiShr =>
397 BinOpCategory::Shift,
409 BinOpCategory::Bitwise,
417 BinOpCategory::Comparison,
421 BinOpCategory::Shortcircuit,
426 /// Returns true if this is a built-in arithmetic operation (e.g. u32
427 /// + u32, i16x4 == i16x4) and false if these types would have to be
428 /// overloaded to be legal. There are two reasons that we distinguish
429 /// builtin operations from overloaded ones (vs trying to drive
430 /// everything uniformly through the trait system and intrinsics or
431 /// something like that):
433 /// 1. Builtin operations can trivially be evaluated in constants.
434 /// 2. For comparison operators applied to SIMD types the result is
435 /// not of type `bool`. For example, `i16x4==i16x4` yields a
436 /// type like `i16x4`. This means that the overloaded trait
437 /// `PartialEq` is not applicable.
439 /// Reason #2 is the killer. I tried for a while to always use
440 /// overloaded logic and just check the types in constants/trans after
441 /// the fact, and it worked fine, except for SIMD types. -nmatsakis
442 fn is_builtin_binop<'tcx>(cx: &ty::ctxt<'tcx>,
448 match BinOpCategory::from(op) {
449 BinOpCategory::Shortcircuit => {
453 BinOpCategory::Shift => {
454 ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
455 ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
456 ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)
459 BinOpCategory::Math => {
460 ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
461 ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
462 ty::type_is_floating_point(lhs) && ty::type_is_floating_point(rhs) ||
463 ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)
466 BinOpCategory::Bitwise => {
467 ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
468 ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
469 ty::type_is_floating_point(lhs) && ty::type_is_floating_point(rhs) ||
470 ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs) ||
471 ty::type_is_bool(lhs) && ty::type_is_bool(rhs)
474 BinOpCategory::Comparison => {
475 ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
476 ty::type_is_scalar(lhs) && ty::type_is_scalar(rhs) ||
477 ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)