Skip to content

Commit

Permalink
Overloaded augmented assignments
Browse files Browse the repository at this point in the history
  • Loading branch information
Jorge Aparicio committed Sep 19, 2015
1 parent fb5de8c commit 3ae3a5f
Show file tree
Hide file tree
Showing 15 changed files with 979 additions and 85 deletions.
528 changes: 528 additions & 0 deletions src/libcore/ops.rs

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions src/librustc/middle/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,11 +525,17 @@ impl<'d,'t,'a,'tcx> ExprUseVisitor<'d,'t,'a,'tcx> {
self.consume_expr(&**base);
}

hir::ExprAssignOp(_, ref lhs, ref rhs) => {
// This will have to change if/when we support
// overloaded operators for `+=` and so forth.
self.mutate_expr(expr, &**lhs, WriteAndRead);
self.consume_expr(&**rhs);
hir::ExprAssignOp(op, ref lhs, ref rhs) => {
let pass_args = if ::rustc_front::util::is_by_value_binop(op.node) {
PassArgs::ByValue
} else {
PassArgs::ByRef
};

if !self.walk_overloaded_operator(expr, &**lhs, vec![&**rhs], pass_args) {
self.mutate_expr(expr, &**lhs, WriteAndRead);
self.consume_expr(&**rhs);
}
}

hir::ExprRepeat(ref base, ref count) => {
Expand Down
10 changes: 10 additions & 0 deletions src/librustc/middle/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ lets_do_this! {
BitOrTraitLangItem, "bitor", bitor_trait;
ShlTraitLangItem, "shl", shl_trait;
ShrTraitLangItem, "shr", shr_trait;
AddAssignTraitLangItem, "add_assign", add_assign_trait;
SubAssignTraitLangItem, "sub_assign", sub_assign_trait;
MulAssignTraitLangItem, "mul_assign", mul_assign_trait;
DivAssignTraitLangItem, "div_assign", div_assign_trait;
RemAssignTraitLangItem, "rem_assign", rem_assign_trait;
BitXorAssignTraitLangItem, "bitxor_assign", bitxor_assign_trait;
BitAndAssignTraitLangItem, "bitand_assign", bitand_assign_trait;
BitOrAssignTraitLangItem, "bitor_assign", bitor_assign_trait;
ShlAssignTraitLangItem, "shl_assign", shl_assign_trait;
ShrAssignTraitLangItem, "shr_assign", shr_assign_trait;
IndexTraitLangItem, "index", index_trait;
IndexMutTraitLangItem, "index_mut", index_mut_trait;
RangeStructLangItem, "range", range_struct;
Expand Down
22 changes: 19 additions & 3 deletions src/librustc_trans/trans/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,20 @@ fn trans_rvalue_stmt_unadjusted<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
}
}
hir::ExprAssignOp(op, ref dst, ref src) => {
trans_assign_op(bcx, expr, op, &**dst, &**src)
let has_method_map = bcx.tcx()
.tables
.borrow()
.method_map
.contains_key(&MethodCall::expr(expr.id));

if has_method_map {
let dst = unpack_datum!(bcx, trans(bcx, &**dst));
let src_datum = unpack_datum!(bcx, trans(bcx, &**src));
trans_overloaded_op(bcx, expr, MethodCall::expr(expr.id), dst,
Some((src_datum, src.id)), None, false).bcx
} else {
trans_assign_op(bcx, expr, op, &**dst, &**src)
}
}
hir::ExprInlineAsm(ref a) => {
asm::trans_inline_asm(bcx, a)
Expand Down Expand Up @@ -1207,8 +1220,11 @@ fn trans_rvalue_dps_unadjusted<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
// Trait casts used to come this way, now they should be coercions.
bcx.tcx().sess.span_bug(expr.span, "DPS expr_cast (residual trait cast?)")
}
hir::ExprAssignOp(op, ref dst, ref src) => {
trans_assign_op(bcx, expr, op, &**dst, &**src)
hir::ExprAssignOp(op, _, _) => {
bcx.tcx().sess.span_bug(
expr.span,
&format!("augmented assignment `{}=` should always be a rvalue_stmt",
rustc_front::util::binop_to_string(op.node)))
}
_ => {
bcx.tcx().sess.span_bug(
Expand Down
115 changes: 68 additions & 47 deletions src/librustc_typeck/check/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ use super::{
demand,
method,
FnCtxt,
structurally_resolved_type,
};
use middle::def_id::DefId;
use middle::traits;
use middle::ty::{Ty, HasTypeFlags, PreferMutLvalue};
use syntax::ast;
use syntax::parse::token;
Expand All @@ -34,34 +32,24 @@ pub fn check_binop_assign<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
lhs_expr: &'tcx hir::Expr,
rhs_expr: &'tcx hir::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));
let lhs_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(lhs_expr));
let (rhs_ty, return_ty) =
check_overloaded_binop(fcx, expr, lhs_expr, lhs_ty, rhs_expr, op, true);
let rhs_ty = fcx.resolve_type_vars_if_possible(rhs_ty);

if is_builtin_binop(lhs_ty, rhs_ty, op) {
if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(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!(!lhs_ty.references_error() || !rhs_ty.references_error());
span_err!(tcx.sess, lhs_expr.span, E0368,
"binary assignment operation `{}=` cannot be applied to types `{}` and `{}`",
hir_util::binop_to_string(op.node),
lhs_ty,
rhs_ty);
fcx.write_error(expr.id);
fcx.write_ty(expr.id, return_ty);
}

let tcx = fcx.tcx();
if !tcx.expr_is_lval(lhs_expr) {
span_err!(tcx.sess, lhs_expr.span, E0067, "invalid left-hand side expression");
}

fcx.require_expr_have_sized_type(lhs_expr, traits::AssignmentLhsSized);
}

/// Check a potentially overloaded binary operator.
Expand Down Expand Up @@ -95,7 +83,7 @@ pub fn check_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
// 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);
check_overloaded_binop(fcx, expr, lhs_expr, lhs_ty, rhs_expr, op, false);

// Supply type inference hints if relevant. Probably these
// hints should be enforced during select as part of the
Expand Down Expand Up @@ -167,14 +155,16 @@ fn check_overloaded_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
lhs_expr: &'tcx hir::Expr,
lhs_ty: Ty<'tcx>,
rhs_expr: &'tcx hir::Expr,
op: hir::BinOp)
op: hir::BinOp,
assign: bool)
-> (Ty<'tcx>, Ty<'tcx>)
{
debug!("check_overloaded_binop(expr.id={}, lhs_ty={:?})",
debug!("check_overloaded_binop(expr.id={}, lhs_ty={:?}, assign={})",
expr.id,
lhs_ty);
lhs_ty,
assign);

let (name, trait_def_id) = name_and_trait_def_id(fcx, op);
let (name, trait_def_id) = name_and_trait_def_id(fcx, op, 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
Expand All @@ -191,10 +181,17 @@ fn check_overloaded_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
Err(()) => {
// error types are considered "builtin"
if !lhs_ty.references_error() {
span_err!(fcx.tcx().sess, lhs_expr.span, E0369,
"binary operation `{}` cannot be applied to type `{}`",
hir_util::binop_to_string(op.node),
lhs_ty);
if assign {
span_err!(fcx.tcx().sess, lhs_expr.span, E0368,
"binary assignment operation `{}=` cannot be applied to type `{}`",
hir_util::binop_to_string(op.node),
lhs_ty);
} else {
span_err!(fcx.tcx().sess, lhs_expr.span, E0369,
"binary operation `{}` cannot be applied to type `{}`",
hir_util::binop_to_string(op.node),
lhs_ty);
}
}
fcx.tcx().types.err
}
Expand Down Expand Up @@ -231,27 +228,51 @@ pub fn check_user_unop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
}
}

fn name_and_trait_def_id(fcx: &FnCtxt, op: hir::BinOp) -> (&'static str, Option<DefId>) {
fn name_and_trait_def_id(fcx: &FnCtxt,
op: hir::BinOp,
assign: bool)
-> (&'static str, Option<DefId>) {
let lang = &fcx.tcx().lang_items;
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 => {
fcx.tcx().sess.span_bug(op.span, "&& and || are not overloadable")

if 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 => {
fcx.tcx().sess.span_bug(op.span, &format!("impossible assignment operation: {}=",
hir_util::binop_to_string(op.node)))
}
}
} 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 => {
fcx.tcx().sess.span_bug(op.span, "&& and || are not overloadable")
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_typeck/check/regionck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ fn visit_expr(rcx: &mut Rcx, expr: &hir::Expr) {
hir::ExprAssignOp(_, ref lhs, ref rhs) => {
if has_method_map {
constrain_call(rcx, expr, Some(&**lhs),
Some(&**rhs).into_iter(), true);
Some(&**rhs).into_iter(), false);
}

visit::walk_expr(rcx, expr);
Expand Down
63 changes: 45 additions & 18 deletions src/librustc_typeck/check/writeback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use astconv::AstConv;
use check::FnCtxt;
use middle::def_id::DefId;
use middle::pat_util;
use middle::ty::{self, Ty, MethodCall, MethodCallee};
use middle::ty::{self, Ty, MethodCall, MethodCallee, HasTypeFlags};
use middle::ty::adjustment;
use middle::ty::fold::{TypeFolder,TypeFoldable};
use middle::infer;
Expand Down Expand Up @@ -91,24 +91,51 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
// we observe that something like `a+b` is (known to be)
// operating on scalars, we clear the overload.
fn fix_scalar_binary_expr(&mut self, e: &hir::Expr) {
if let hir::ExprBinary(ref op, ref lhs, ref rhs) = e.node {
let lhs_ty = self.fcx.node_ty(lhs.id);
let lhs_ty = self.fcx.infcx().resolve_type_vars_if_possible(&lhs_ty);

let rhs_ty = self.fcx.node_ty(rhs.id);
let rhs_ty = self.fcx.infcx().resolve_type_vars_if_possible(&rhs_ty);

if lhs_ty.is_scalar() && rhs_ty.is_scalar() {
self.fcx.inh.tables.borrow_mut().method_map.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 !hir_util::is_by_value_binop(op.node) {
self.fcx.inh.tables.borrow_mut().adjustments.remove(&lhs.id);
match e.node {
hir::ExprBinary(ref op, ref lhs, ref rhs) |
hir::ExprAssignOp(ref op, ref lhs, ref rhs) => {
let lhs_ty = self.fcx.node_ty(lhs.id);
let lhs_ty = self.fcx.infcx().resolve_type_vars_if_possible(&lhs_ty);

let rhs_ty = self.fcx.node_ty(rhs.id);
let rhs_ty = self.fcx.infcx().resolve_type_vars_if_possible(&rhs_ty);

if lhs_ty.is_scalar() && rhs_ty.is_scalar() {
self.fcx.inh.tables.borrow_mut().method_map.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.
match e.node {
hir::ExprBinary(..) => {
if !hir_util::is_by_value_binop(op.node) {
self.fcx.inh.tables.borrow_mut().adjustments.remove(&lhs.id);
}
},
hir::ExprAssignOp(..) => {
self.fcx.inh.tables.borrow_mut().adjustments.remove(&lhs.id);
},
_ => {},
}
} else {
let tcx = self.tcx();

if let hir::ExprAssignOp(..) = e.node {
if !tcx.sess.features.borrow().augmented_assignments &&
!self.fcx.expr_ty(e).references_error() {
tcx.sess.span_err(
e.span,
"overloaded augmented assignments are not stable");
fileline_help!(
tcx.sess, e.span,
"add #![feature(augmented_assignments)] to the crate features \
to enable");
}
}
}
}
}
_ => {},
}
}
}
Expand Down
30 changes: 20 additions & 10 deletions src/librustc_typeck/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2862,14 +2862,27 @@ impl <T: Foo> Drop for MyStructWrapper<T> {

E0368: r##"
This error indicates that a binary assignment operator like `+=` or `^=` was
applied to the wrong types. For example:
applied to a type that doesn't support it. For example:
```
let mut x: u16 = 5;
x ^= true; // error, `^=` cannot be applied to types `u16` and `bool`
x += (); // error, `+=` cannot be applied to types `u16` and `()`
let mut x = 12f32; // error: binary operation `<<` cannot be applied to
// type `f32`
x <<= 2;
```
To fix this error, please check that this type implements this binary
operation. Example:
```
let x = 12u32; // the `u32` type does implement the `ShlAssign` trait
x <<= 2; // ok!
```
It is also possible to overload most operators for your own type by
implementing the `[OP]Assign` traits from `std::ops`.
Another problem you might be facing is this: suppose you've overloaded the `+`
operator for some type `Foo` by implementing the `std::ops::Add` trait for
`Foo`, but you find that using `+=` does not work, as in this example:
Expand All @@ -2889,15 +2902,12 @@ impl Add for Foo {
fn main() {
let mut x: Foo = Foo(5);
x += Foo(7); // error, `+= cannot be applied to types `Foo` and `Foo`
x += Foo(7); // error, `+= cannot be applied to the type `Foo`
}
```
This is because the binary assignment operators currently do not work off of
traits, so it is not possible to overload them. See [RFC 953] for a proposal
to change this.
[RFC 953]: https://github.com/rust-lang/rfcs/pull/953
This is because `AddAssign` is not automatically implemented, so you need to
manually implement it for your type.
"##,

E0369: r##"
Expand Down
Loading

0 comments on commit 3ae3a5f

Please sign in to comment.