From 95e1419633ee4158ee05bc866f4a63863818e165 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Fri, 15 May 2015 00:06:53 -0500 Subject: [PATCH] Overloaded augmented assignments --- src/libcore/ops.rs | 248 ++++++++++++++++++ src/librustc/middle/lang_items.rs | 11 + src/librustc_trans/trans/expr.rs | 20 +- src/librustc_typeck/check/op.rs | 138 ++++++---- src/librustc_typeck/check/regionck.rs | 2 +- src/librustc_typeck/check/writeback.rs | 65 +++-- src/libsyntax/feature_gate.rs | 6 + src/test/auxiliary/augmented_assignments.rs | 22 ++ .../assignment-operator-unimplemented.rs | 2 +- ...ugmented-assignments-feature-gate-cross.rs | 24 ++ .../augmented-assignments-feature-gate.rs | 25 ++ .../compile-fail/augmented-assignments.rs | 35 +++ src/test/run-pass/augmented-assignments.rs | 164 ++++++++++++ 13 files changed, 692 insertions(+), 70 deletions(-) create mode 100644 src/test/auxiliary/augmented_assignments.rs create mode 100644 src/test/compile-fail/augmented-assignments-feature-gate-cross.rs create mode 100644 src/test/compile-fail/augmented-assignments-feature-gate.rs create mode 100644 src/test/compile-fail/augmented-assignments.rs create mode 100644 src/test/run-pass/augmented-assignments.rs diff --git a/src/libcore/ops.rs b/src/libcore/ops.rs index f16614cfd092d..8abe29331505c 100644 --- a/src/libcore/ops.rs +++ b/src/libcore/ops.rs @@ -877,6 +877,254 @@ macro_rules! shr_impl_all { shr_impl_all! { u8 u16 u32 u64 usize i8 i16 i32 i64 isize } +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "add_assign"] +pub trait AddAssign { + /// TODO(japaric) docs + fn add_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! add_assign_impl { + ($($t:ty)+) => ($( + impl AddAssign for $t { + #[inline] + fn add_assign(&mut self, other: $t) { *self += other } + } + )+) +} + +#[cfg(not(stage0))] +add_assign_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "sub_assign"] +pub trait SubAssign { + /// TODO(japaric) docs + fn sub_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! sub_assign_impl { + ($($t:ty)+) => ($( + impl SubAssign for $t { + #[inline] + fn sub_assign(&mut self, other: $t) { *self -= other } + } + )+) +} + +#[cfg(not(stage0))] +sub_assign_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "mul_assign"] +pub trait MulAssign { + /// TODO(japaric) docs + fn mul_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! mul_assign_impl { + ($($t:ty)+) => ($( + impl MulAssign for $t { + #[inline] + fn mul_assign(&mut self, other: $t) { *self *= other } + } + )+) +} + +#[cfg(not(stage0))] +mul_assign_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "div_assign"] +pub trait DivAssign { + /// TODO(japaric) docs + fn div_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! div_assign_impl { + ($($t:ty)+) => ($( + impl DivAssign for $t { + #[inline] + fn div_assign(&mut self, other: $t) { *self /= other } + } + )+) +} + +#[cfg(not(stage0))] +div_assign_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "rem_assign"] +pub trait RemAssign { + /// TODO(japaric) docs + fn rem_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! rem_assign_impl { + ($($t:ty)+) => ($( + impl RemAssign for $t { + #[inline] + fn rem_assign(&mut self, other: $t) { *self %= other } + } + )+) +} + +#[cfg(not(stage0))] +rem_assign_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "bitand_assign"] +pub trait BitAndAssign { + /// TODO(japaric) docs + fn bitand_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! bitand_assign_impl { + ($($t:ty)+) => ($( + impl BitAndAssign for $t { + #[inline] + fn bitand_assign(&mut self, other: $t) { *self &= other } + } + )+) +} + +#[cfg(not(stage0))] +bitand_assign_impl! { bool usize u8 u16 u32 u64 isize i8 i16 i32 i64 } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "bitor_assign"] +pub trait BitOrAssign { + /// TODO(japaric) docs + fn bitor_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! bitor_assign_impl { + ($($t:ty)+) => ($( + impl BitOrAssign for $t { + #[inline] + fn bitor_assign(&mut self, other: $t) { *self |= other } + } + )+) +} + +#[cfg(not(stage0))] +bitor_assign_impl! { bool usize u8 u16 u32 u64 isize i8 i16 i32 i64 } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "bitxor_assign"] +pub trait BitXorAssign { + /// TODO(japaric) docs + fn bitxor_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! bitxor_assign_impl { + ($($t:ty)+) => ($( + impl BitXorAssign for $t { + #[inline] + fn bitxor_assign(&mut self, other: $t) { *self ^= other } + } + )+) +} + +#[cfg(not(stage0))] +bitxor_assign_impl! { bool usize u8 u16 u32 u64 isize i8 i16 i32 i64 } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "shl_assign"] +pub trait ShlAssign { + /// TODO(japaric) docs + fn shl_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! shl_assign_impl { + ($t:ty, $f:ty) => ( + impl ShlAssign<$f> for $t { + #[inline] + fn shl_assign(&mut self, other: $f) { + *self <<= other + } + } + ) +} + +#[cfg(not(stage0))] +macro_rules! shl_assign_impl_all { + ($($t:ty)*) => ($( + shl_assign_impl! { $t, u8 } + shl_assign_impl! { $t, u16 } + shl_assign_impl! { $t, u32 } + shl_assign_impl! { $t, u64 } + shl_assign_impl! { $t, usize } + + shl_assign_impl! { $t, i8 } + shl_assign_impl! { $t, i16 } + shl_assign_impl! { $t, i32 } + shl_assign_impl! { $t, i64 } + shl_assign_impl! { $t, isize } + )*) +} + +#[cfg(not(stage0))] +shl_assign_impl_all! { u8 u16 u32 u64 usize i8 i16 i32 i64 isize } + +/// TODO(japaric) docs +#[cfg(not(stage0))] +#[lang = "shr_assign"] +pub trait ShrAssign { + /// TODO(japaric) docs + fn shr_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! shr_assign_impl { + ($t:ty, $f:ty) => ( + impl ShrAssign<$f> for $t { + #[inline] + fn shr_assign(&mut self, other: $f) { + *self >>= other + } + } + ) +} + +#[cfg(not(stage0))] +macro_rules! shr_assign_impl_all { + ($($t:ty)*) => ($( + shr_assign_impl! { $t, u8 } + shr_assign_impl! { $t, u16 } + shr_assign_impl! { $t, u32 } + shr_assign_impl! { $t, u64 } + shr_assign_impl! { $t, usize } + + shr_assign_impl! { $t, i8 } + shr_assign_impl! { $t, i16 } + shr_assign_impl! { $t, i32 } + shr_assign_impl! { $t, i64 } + shr_assign_impl! { $t, isize } + )*) +} + +#[cfg(not(stage0))] +shr_assign_impl_all! { u8 u16 u32 u64 usize i8 i16 i32 i64 isize } + /// The `Index` trait is used to specify the functionality of indexing operations /// like `arr[idx]` when used in an immutable context. /// diff --git a/src/librustc/middle/lang_items.rs b/src/librustc/middle/lang_items.rs index 273cd6b4f85b4..62d23582067d0 100644 --- a/src/librustc/middle/lang_items.rs +++ b/src/librustc/middle/lang_items.rs @@ -288,6 +288,17 @@ lets_do_this! { RangeToStructLangItem, "range_to", range_to_struct; RangeFullStructLangItem, "range_full", range_full_struct; + AddAssignTraitLangItem, "add_assign", add_assign_trait; + BitAndAssignTraitLangItem, "bitand_assign", bitand_assign_trait; + BitOrAssignTraitLangItem, "bitor_assign", bitor_assign_trait; + BitXorAssignTraitLangItem, "bitxor_assign", bitxor_assign_trait; + DivAssignTraitLangItem, "div_assign", div_assign_trait; + MulAssignTraitLangItem, "mul_assign", mul_assign_trait; + RemAssignTraitLangItem, "rem_assign", rem_assign_trait; + ShlAssignTraitLangItem, "shl_assign", shl_assign_trait; + ShrAssignTraitLangItem, "shr_assign", shr_assign_trait; + SubAssignTraitLangItem, "sub_assign", sub_assign_trait; + UnsafeCellTypeLangItem, "unsafe_cell", unsafe_cell_type; DerefTraitLangItem, "deref", deref_trait; diff --git a/src/librustc_trans/trans/expr.rs b/src/librustc_trans/trans/expr.rs index 270aacfe143df..b630908d5fbcf 100644 --- a/src/librustc_trans/trans/expr.rs +++ b/src/librustc_trans/trans/expr.rs @@ -1009,7 +1009,17 @@ fn trans_rvalue_stmt_unadjusted<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, } } ast::ExprAssignOp(op, ref dst, ref src) => { - trans_assign_op(bcx, expr, op, &**dst, &**src) + let has_method_map = + bcx.tcx().method_map.borrow().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, + vec![(src_datum, src.id)], None, false).bcx + } else { + trans_assign_op(bcx, expr, op, &**dst, &**src) + } } ast::ExprInlineAsm(ref a) => { asm::trans_inline_asm(bcx, a) @@ -1194,8 +1204,12 @@ 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?)") } - ast::ExprAssignOp(op, ref dst, ref src) => { - trans_assign_op(bcx, expr, op, &**dst, &**src) + ast::ExprAssignOp(op, _, _) => { + bcx.tcx().sess.span_bug( + expr.span, + &format!( + "augmented assignment ({}=) should always be a rvalue_stmt", + ast_util::binop_to_string(op.node))); } _ => { bcx.tcx().sess.span_bug( diff --git a/src/librustc_typeck/check/op.rs b/src/librustc_typeck/check/op.rs index b4000788d1998..6c38ed44efd4f 100644 --- a/src/librustc_typeck/check/op.rs +++ b/src/librustc_typeck/check/op.rs @@ -18,9 +18,7 @@ use super::{ method, FnCtxt, PreferMutLvalue, - structurally_resolved_type, }; -use middle::traits; use middle::ty::{self, Ty}; use syntax::ast; use syntax::ast_util; @@ -36,32 +34,42 @@ pub fn check_binop_assign<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>, { let tcx = fcx.ccx.tcx; + debug!("check_binop_assign(expr.id={}, expr={}, op={:?}, lhs_expr={}, rhs_expr={})", + expr.id, + expr.repr(tcx), + op, + lhs_expr.repr(tcx), + rhs_expr.repr(tcx)); + check_expr_with_lvalue_pref(fcx, lhs_expr, PreferMutLvalue); - check_expr(fcx, rhs_expr); + let lhs_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(lhs_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 (rhs_ty, return_ty) = + check_overloaded_binop(fcx, expr, lhs_expr, lhs_ty, rhs_expr, op, true); - if is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op) { + let rhs_ty = fcx.resolve_type_vars_if_possible(rhs_ty); + if + !ty::type_is_ty_var(lhs_ty) && + !ty::type_is_ty_var(rhs_ty) && + is_builtin_binop(fcx.tcx(), 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!(!ty::type_is_error(lhs_ty) || !ty::type_is_error(rhs_ty)); - span_err!(tcx.sess, lhs_expr.span, E0368, - "binary assignment operation `{}=` cannot be applied to types `{}` and `{}`", - ast_util::binop_to_string(op.node), - lhs_ty.user_string(fcx.tcx()), - rhs_ty.user_string(fcx.tcx())); - fcx.write_error(expr.id); + fcx.write_ty(expr.id, return_ty); } - let tcx = fcx.tcx(); - if !ty::expr_is_lval(tcx, lhs_expr) { - span_err!(tcx.sess, lhs_expr.span, E0067, "illegal left-hand side expression"); - } + // XXX(japaric) this forbids mutation via a proxy `y.row_mut(0) += x` + //if !ty::expr_is_lval(tcx, lhs_expr) { + //span_err!(tcx.sess, lhs_expr.span, E0067, "illegal left-hand side expression"); + //} - fcx.require_expr_have_sized_type(lhs_expr, traits::AssignmentLhsSized); + match ty::expr_kind(tcx, lhs_expr) { + ty::LvalueExpr | ty::RvalueDpsExpr => {}, + _ => { + span_err!(tcx.sess, lhs_expr.span, E0067, "illegal left-hand side expression"); + } + } } /// Check a potentially overloaded binary operator. @@ -107,7 +115,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 @@ -206,14 +214,16 @@ fn check_overloaded_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, lhs_expr: &'tcx ast::Expr, lhs_ty: Ty<'tcx>, rhs_expr: &'tcx ast::Expr, - op: ast::BinOp) + op: ast::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.repr(fcx.tcx())); + lhs_ty.repr(fcx.tcx()), + 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 @@ -230,10 +240,17 @@ fn check_overloaded_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, Err(()) => { // error types are considered "builtin" if !ty::type_is_error(lhs_ty) { - span_err!(fcx.tcx().sess, lhs_expr.span, E0369, - "binary operation `{}` cannot be applied to type `{}`", - ast_util::binop_to_string(op.node), - lhs_ty.user_string(fcx.tcx())); + if assign { + span_err!(fcx.tcx().sess, lhs_expr.span, E0368, + "binary assignment operation `{}=` cannot be applied to type `{}`", + ast_util::binop_to_string(op.node), + lhs_ty.user_string(fcx.tcx())); + } else { + span_err!(fcx.tcx().sess, lhs_expr.span, E0369, + "binary operation `{}` cannot be applied to type `{}`", + ast_util::binop_to_string(op.node), + lhs_ty.user_string(fcx.tcx())); + } } fcx.tcx().types.err } @@ -270,27 +287,52 @@ pub fn check_user_unop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, } } -fn name_and_trait_def_id(fcx: &FnCtxt, op: ast::BinOp) -> (&'static str, Option) { +fn name_and_trait_def_id(fcx: &FnCtxt, + op: ast::BinOp, + assign: bool) + -> (&'static str, Option) { let lang = &fcx.tcx().lang_items; - match op.node { - ast::BiAdd => ("add", lang.add_trait()), - ast::BiSub => ("sub", lang.sub_trait()), - ast::BiMul => ("mul", lang.mul_trait()), - ast::BiDiv => ("div", lang.div_trait()), - ast::BiRem => ("rem", lang.rem_trait()), - ast::BiBitXor => ("bitxor", lang.bitxor_trait()), - ast::BiBitAnd => ("bitand", lang.bitand_trait()), - ast::BiBitOr => ("bitor", lang.bitor_trait()), - ast::BiShl => ("shl", lang.shl_trait()), - ast::BiShr => ("shr", lang.shr_trait()), - ast::BiLt => ("lt", lang.ord_trait()), - ast::BiLe => ("le", lang.ord_trait()), - ast::BiGe => ("ge", lang.ord_trait()), - ast::BiGt => ("gt", lang.ord_trait()), - ast::BiEq => ("eq", lang.eq_trait()), - ast::BiNe => ("ne", lang.eq_trait()), - ast::BiAnd | ast::BiOr => { - fcx.tcx().sess.span_bug(op.span, "&& and || are not overloadable") + + if assign { + match op.node { + ast::BiAdd => ("add_assign", lang.add_assign_trait()), + ast::BiSub => ("sub_assign", lang.sub_assign_trait()), + ast::BiMul => ("mul_assign", lang.mul_assign_trait()), + ast::BiDiv => ("div_assign", lang.div_assign_trait()), + ast::BiRem => ("rem_assign", lang.rem_assign_trait()), + ast::BiBitXor => ("bitxor_assign", lang.bitxor_assign_trait()), + ast::BiBitAnd => ("bitand_assign", lang.bitand_assign_trait()), + ast::BiBitOr => ("bitor_assign", lang.bitor_assign_trait()), + ast::BiShl => ("shl_assign", lang.shl_assign_trait()), + ast::BiShr => ("shr_assign", lang.shr_assign_trait()), + ast::BiLt | ast::BiLe | ast::BiGe | ast::BiGt | ast::BiEq | ast::BiNe | ast::BiAnd | + ast::BiOr => { + fcx.tcx().sess.span_bug( + op.span, + &format!("{} is not overloadable", ast_util::binop_to_string(op.node))) + } + } + } else { + match op.node { + ast::BiAdd => ("add", lang.add_trait()), + ast::BiSub => ("sub", lang.sub_trait()), + ast::BiMul => ("mul", lang.mul_trait()), + ast::BiDiv => ("div", lang.div_trait()), + ast::BiRem => ("rem", lang.rem_trait()), + ast::BiBitXor => ("bitxor", lang.bitxor_trait()), + ast::BiBitAnd => ("bitand", lang.bitand_trait()), + ast::BiBitOr => ("bitor", lang.bitor_trait()), + ast::BiShl => ("shl", lang.shl_trait()), + ast::BiShr => ("shr", lang.shr_trait()), + ast::BiLt => ("lt", lang.ord_trait()), + ast::BiLe => ("le", lang.ord_trait()), + ast::BiGe => ("ge", lang.ord_trait()), + ast::BiGt => ("gt", lang.ord_trait()), + ast::BiEq => ("eq", lang.eq_trait()), + ast::BiNe => ("ne", lang.eq_trait()), + ast::BiAnd | ast::BiOr => { + fcx.tcx().sess.span_bug(op.span, "&& and || are not overloadable") + } } } } diff --git a/src/librustc_typeck/check/regionck.rs b/src/librustc_typeck/check/regionck.rs index 090d111b62b89..79d36de5cab61 100644 --- a/src/librustc_typeck/check/regionck.rs +++ b/src/librustc_typeck/check/regionck.rs @@ -606,7 +606,7 @@ fn visit_expr(rcx: &mut Rcx, expr: &ast::Expr) { ast::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); diff --git a/src/librustc_typeck/check/writeback.rs b/src/librustc_typeck/check/writeback.rs index 889975f0eb2f9..cf6063b7de524 100644 --- a/src/librustc_typeck/check/writeback.rs +++ b/src/librustc_typeck/check/writeback.rs @@ -91,24 +91,55 @@ 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: &ast::Expr) { - if let ast::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 ty::type_is_scalar(lhs_ty) && ty::type_is_scalar(rhs_ty) { - self.fcx.inh.method_map.borrow_mut().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 !ast_util::is_by_value_binop(op.node) { - self.fcx.inh.adjustments.borrow_mut().remove(&lhs.id); + match e.node { + ast::ExprBinary(ref op, ref lhs, ref rhs) | + ast::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 ty::type_is_scalar(lhs_ty) && ty::type_is_scalar(rhs_ty) { + self.fcx.inh.method_map.borrow_mut().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 { + ast::ExprBinary(..) => { + if !ast_util::is_by_value_binop(op.node) { + self.fcx.inh.adjustments.borrow_mut().remove(&lhs.id); + } + }, + ast::ExprAssignOp(..) => { + self.fcx.inh.adjustments.borrow_mut().remove(&lhs.id); + }, + _ => {}, + } + } else { + match e.node { + ast::ExprAssignOp(..) if !ty::type_is_error(self.fcx.expr_ty(e)) => { + let tcx = self.tcx(); + + if !tcx.sess.features.borrow().augmented_assignments { + 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" + ) + } + }, + _ => {}, + } } - } + }, + _ => {}, } } } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 28deb4eec3fac..dea83bc0aca4f 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -155,6 +155,9 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Status)] = &[ // Allows the definition of associated constants in `trait` or `impl` // blocks. ("associated_consts", "1.0.0", Active), + + // Overloaded augmented assignment `a += b`; RFC 953 + ("augmented_assignments", "1.1.0", Active) ]; // (changing above list without updating src/doc/reference.md makes @cmr sad) @@ -314,6 +317,7 @@ pub enum AttributeType { /// A set of features to be used by later passes. pub struct Features { + pub augmented_assignments: bool, pub unboxed_closures: bool, pub rustc_diagnostic_macros: bool, pub visible_private_types: bool, @@ -336,6 +340,7 @@ pub struct Features { impl Features { pub fn new() -> Features { Features { + augmented_assignments: false, unboxed_closures: false, rustc_diagnostic_macros: false, visible_private_types: false, @@ -745,6 +750,7 @@ fn check_crate_inner(cm: &CodeMap, span_handler: &SpanHandler, // to a single-pass (instead of N calls to `.has_feature`). Features { + augmented_assignments: cx.has_feature("augmented_assignments"), unboxed_closures: cx.has_feature("unboxed_closures"), rustc_diagnostic_macros: cx.has_feature("rustc_diagnostic_macros"), visible_private_types: cx.has_feature("visible_private_types"), diff --git a/src/test/auxiliary/augmented_assignments.rs b/src/test/auxiliary/augmented_assignments.rs new file mode 100644 index 0000000000000..7b66ff5199a8e --- /dev/null +++ b/src/test/auxiliary/augmented_assignments.rs @@ -0,0 +1,22 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(augmented_assignments)] +#![feature(core)] + +use std::ops::AddAssign; + +pub struct Int(pub i32); + +impl AddAssign for Int { + fn add_assign(&mut self, _: i32) { + unimplemented!(); + } +} diff --git a/src/test/compile-fail/assignment-operator-unimplemented.rs b/src/test/compile-fail/assignment-operator-unimplemented.rs index fef27af59571b..5b24c6bd79f96 100644 --- a/src/test/compile-fail/assignment-operator-unimplemented.rs +++ b/src/test/compile-fail/assignment-operator-unimplemented.rs @@ -13,5 +13,5 @@ struct Foo; fn main() { let mut a = Foo; let ref b = Foo; - a += *b; //~ Error: binary assignment operation `+=` cannot be applied to types `Foo` and `Foo` + a += *b; //~ Error: binary assignment operation `+=` cannot be applied to type `Foo` } diff --git a/src/test/compile-fail/augmented-assignments-feature-gate-cross.rs b/src/test/compile-fail/augmented-assignments-feature-gate-cross.rs new file mode 100644 index 0000000000000..8fffcdc19fd63 --- /dev/null +++ b/src/test/compile-fail/augmented-assignments-feature-gate-cross.rs @@ -0,0 +1,24 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:augmented_assignments.rs + +// Test that the feature gate is needed when using augmented assignments that were implemented in +// another crate + +extern crate augmented_assignments; + +use augmented_assignments::Int; + +fn main() { + let mut x = Int(0); + x += 1; //~ error overloaded augmented assignments are not stable + //~^ help: add `#![feature(augmented_assignments)]` to the crate features to enable +} diff --git a/src/test/compile-fail/augmented-assignments-feature-gate.rs b/src/test/compile-fail/augmented-assignments-feature-gate.rs new file mode 100644 index 0000000000000..20c17305fc567 --- /dev/null +++ b/src/test/compile-fail/augmented-assignments-feature-gate.rs @@ -0,0 +1,25 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::ops::AddAssign; + +struct Int(i32); + +impl AddAssign for Int { + fn add_assign(&mut self, _: i32) { + unimplemented!(); + } +} + +fn main() { + let mut x = Int(0); + x += 1; //~ error overloaded augmented assignments are not stable + // help: add `#![feature(augmented_assignments)]` to the crate features to enable +} diff --git a/src/test/compile-fail/augmented-assignments.rs b/src/test/compile-fail/augmented-assignments.rs new file mode 100644 index 0000000000000..fb23abd88154f --- /dev/null +++ b/src/test/compile-fail/augmented-assignments.rs @@ -0,0 +1,35 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that RHS is moved and that LHS is mutably borrowed + +#![feature(augmented_assignments)] + +use std::ops::AddAssign; + +struct Int(i32); + +impl AddAssign for Int { + fn add_assign(&mut self, _: Int) { + unimplemented!(); + } +} + +fn main() { + let mut x = Int(1); + x //~ error: use of moved value: `x` + += + x; //~ note: `x` moved here because it has type `Int`, which is non-copyable + + let y = Int(2); + y //~ error: cannot borrow immutable local variable `y` as mutable + += + Int(1); +} diff --git a/src/test/run-pass/augmented-assignments.rs b/src/test/run-pass/augmented-assignments.rs new file mode 100644 index 0000000000000..b484466107304 --- /dev/null +++ b/src/test/run-pass/augmented-assignments.rs @@ -0,0 +1,164 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(augmented_assignments)] +#![feature(core)] + +use std::mem; +use std::ops::{ + AddAssign, BitAndAssign, BitOrAssign, BitXorAssign, DivAssign, Index, MulAssign, RemAssign, + ShlAssign, ShrAssign, SubAssign, +}; + +#[derive(Debug, PartialEq)] +struct Int(i32); + +struct Slice([i32]); + +impl Slice { + fn new(slice: &mut [i32]) -> &mut Slice { + unsafe { + mem::transmute(slice) + } + } +} + +fn main() { + let mut x = Int(1); + + x += Int(2); + assert_eq!(x, Int(0b11)); + + x &= Int(0b01); + assert_eq!(x, Int(0b01)); + + x |= Int(0b10); + assert_eq!(x, Int(0b11)); + + x ^= Int(0b01); + assert_eq!(x, Int(0b10)); + + x /= Int(2); + assert_eq!(x, Int(1)); + + x *= Int(3); + assert_eq!(x, Int(3)); + + x %= Int(2); + assert_eq!(x, Int(1)); + + // overloaded RHS + x <<= 1u8; + assert_eq!(x, Int(2)); + + x <<= 1u16; + assert_eq!(x, Int(4)); + + x >>= 1u8; + assert_eq!(x, Int(2)); + + x >>= 1u16; + assert_eq!(x, Int(1)); + + x -= Int(1); + assert_eq!(x, Int(0)); + + // indexed LHS + let mut v = vec![Int(1), Int(2)]; + v[0] += Int(2); + assert_eq!(v[0], Int(3)); + + // unsized RHS + let mut array = [0, 1, 2]; + *Slice::new(&mut array) += 1; + assert_eq!(array[0], 1); + assert_eq!(array[1], 2); + assert_eq!(array[2], 3); +} + +impl AddAssign for Int { + fn add_assign(&mut self, rhs: Int) { + self.0 += rhs.0; + } +} + +impl BitAndAssign for Int { + fn bitand_assign(&mut self, rhs: Int) { + self.0 &= rhs.0; + } +} + +impl BitOrAssign for Int { + fn bitor_assign(&mut self, rhs: Int) { + self.0 |= rhs.0; + } +} + +impl BitXorAssign for Int { + fn bitxor_assign(&mut self, rhs: Int) { + self.0 ^= rhs.0; + } +} + +impl DivAssign for Int { + fn div_assign(&mut self, rhs: Int) { + self.0 /= rhs.0; + } +} + +impl MulAssign for Int { + fn mul_assign(&mut self, rhs: Int) { + self.0 *= rhs.0; + } +} + +impl RemAssign for Int { + fn rem_assign(&mut self, rhs: Int) { + self.0 %= rhs.0; + } +} + +impl ShlAssign for Int { + fn shl_assign(&mut self, rhs: u8) { + self.0 <<= rhs; + } +} + +impl ShlAssign for Int { + fn shl_assign(&mut self, rhs: u16) { + self.0 <<= rhs; + } +} + +impl ShrAssign for Int { + fn shr_assign(&mut self, rhs: u8) { + self.0 >>= rhs; + } +} + +impl ShrAssign for Int { + fn shr_assign(&mut self, rhs: u16) { + self.0 >>= rhs; + } +} + +impl SubAssign for Int { + fn sub_assign(&mut self, rhs: Int) { + self.0 -= rhs.0; + } +} + +impl AddAssign for Slice { + fn add_assign(&mut self, rhs: i32) { + for lhs in &mut self.0 { + *lhs += rhs; + } + } +}