Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overloaded augmented assignments #28345

Merged
merged 2 commits into from
Sep 19, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
528 changes: 528 additions & 0 deletions src/libcore/ops.rs

Large diffs are not rendered by default.

13 changes: 8 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,14 @@ 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) => {
// NB All our assignment operations take the RHS by value
assert!(::rustc_front::util::is_by_value_binop(op.node));

if !self.walk_overloaded_operator(expr, lhs, vec![rhs], PassArgs::ByValue) {
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
122 changes: 75 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, IsAssign::Yes);
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, IsAssign::No);

// 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,
is_assign: IsAssign)
-> (Ty<'tcx>, Ty<'tcx>)
{
debug!("check_overloaded_binop(expr.id={}, lhs_ty={:?})",
debug!("check_overloaded_binop(expr.id={}, lhs_ty={:?}, is_assign={:?})",
expr.id,
lhs_ty);
lhs_ty,
is_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, 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
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 let IsAssign::Yes = is_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,
is_assign: IsAssign)
-> (&'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 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 => {
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 Expand Up @@ -362,6 +383,13 @@ impl BinOpCategory {
}
}

/// 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
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: 46 additions & 17 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,53 @@ 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
Loading