diff --git a/src/libcore/ops.rs b/src/libcore/ops.rs index 5f0eb63edbc45..350ade22707ca 100644 --- a/src/libcore/ops.rs +++ b/src/libcore/ops.rs @@ -927,6 +927,534 @@ macro_rules! shr_impl_all { shr_impl_all! { u8 u16 u32 u64 usize i8 i16 i32 i64 isize } +/// The `AddAssign` trait is used to specify the functionality of `+=`. +/// +/// # Examples +/// +/// A trivial implementation of `AddAssign`. When `Foo += Foo` happens, it ends up +/// calling `add_assign`, and therefore, `main` prints `Adding!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::AddAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl AddAssign for Foo { +/// fn add_assign(&mut self, _rhs: Foo) { +/// println!("Adding!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo += Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "add_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait AddAssign { + /// The method for the `+=` operator + fn add_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! add_assign_impl { + ($($t:ty)+) => ($( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `SubAssign` trait is used to specify the functionality of `-=`. +/// +/// # Examples +/// +/// A trivial implementation of `SubAssign`. When `Foo -= Foo` happens, it ends up +/// calling `sub_assign`, and therefore, `main` prints `Subtracting!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::SubAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl SubAssign for Foo { +/// fn sub_assign(&mut self, _rhs: Foo) { +/// println!("Subtracting!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo -= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "sub_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait SubAssign { + /// The method for the `-=` operator + fn sub_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! sub_assign_impl { + ($($t:ty)+) => ($( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `MulAssign` trait is used to specify the functionality of `*=`. +/// +/// # Examples +/// +/// A trivial implementation of `MulAssign`. When `Foo *= Foo` happens, it ends up +/// calling `mul_assign`, and therefore, `main` prints `Multiplying!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::MulAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl MulAssign for Foo { +/// fn mul_assign(&mut self, _rhs: Foo) { +/// println!("Multiplying!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo *= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "mul_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait MulAssign { + /// The method for the `*=` operator + fn mul_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! mul_assign_impl { + ($($t:ty)+) => ($( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `DivAssign` trait is used to specify the functionality of `/=`. +/// +/// # Examples +/// +/// A trivial implementation of `DivAssign`. When `Foo /= Foo` happens, it ends up +/// calling `div_assign`, and therefore, `main` prints `Dividing!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::DivAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl DivAssign for Foo { +/// fn div_assign(&mut self, _rhs: Foo) { +/// println!("Dividing!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo /= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "div_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait DivAssign { + /// The method for the `/=` operator + fn div_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! div_assign_impl { + ($($t:ty)+) => ($( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `RemAssign` trait is used to specify the functionality of `%=`. +/// +/// # Examples +/// +/// A trivial implementation of `RemAssign`. When `Foo %= Foo` happens, it ends up +/// calling `rem_assign`, and therefore, `main` prints `Remainder-ing!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::RemAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl RemAssign for Foo { +/// fn rem_assign(&mut self, _rhs: Foo) { +/// println!("Remainder-ing!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo %= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "rem_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait RemAssign { + /// The method for the `%=` operator + fn rem_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! rem_assign_impl { + ($($t:ty)+) => ($( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `BitAndAssign` trait is used to specify the functionality of `&=`. +/// +/// # Examples +/// +/// A trivial implementation of `BitAndAssign`. When `Foo &= Foo` happens, it ends up +/// calling `bitand_assign`, and therefore, `main` prints `Bitwise And-ing!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::BitAndAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl BitAndAssign for Foo { +/// fn bitand_assign(&mut self, _rhs: Foo) { +/// println!("Bitwise And-ing!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo &= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "bitand_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait BitAndAssign { + /// The method for the `&` operator + fn bitand_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! bitand_assign_impl { + ($($t:ty)+) => ($( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `BitOrAssign` trait is used to specify the functionality of `|=`. +/// +/// # Examples +/// +/// A trivial implementation of `BitOrAssign`. When `Foo |= Foo` happens, it ends up +/// calling `bitor_assign`, and therefore, `main` prints `Bitwise Or-ing!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::BitOrAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl BitOrAssign for Foo { +/// fn bitor_assign(&mut self, _rhs: Foo) { +/// println!("Bitwise Or-ing!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo |= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "bitor_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait BitOrAssign { + /// The method for the `|=` operator + fn bitor_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! bitor_assign_impl { + ($($t:ty)+) => ($( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `BitXorAssign` trait is used to specify the functionality of `^=`. +/// +/// # Examples +/// +/// A trivial implementation of `BitXorAssign`. When `Foo ^= Foo` happens, it ends up +/// calling `bitxor_assign`, and therefore, `main` prints `Bitwise Xor-ing!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::BitXorAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl BitXorAssign for Foo { +/// fn bitxor_assign(&mut self, _rhs: Foo) { +/// println!("Bitwise Xor-ing!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo ^= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "bitxor_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait BitXorAssign { + /// The method for the `^=` operator + fn bitxor_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! bitxor_assign_impl { + ($($t:ty)+) => ($( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `ShlAssign` trait is used to specify the functionality of `<<=`. +/// +/// # Examples +/// +/// A trivial implementation of `ShlAssign`. When `Foo <<= Foo` happens, it ends up +/// calling `shl_assign`, and therefore, `main` prints `Shifting left!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::ShlAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl ShlAssign for Foo { +/// fn shl_assign(&mut self, _rhs: Foo) { +/// println!("Shifting left!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo <<= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "shl_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait ShlAssign { + /// The method for the `<<=` operator + fn shl_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! shl_assign_impl { + ($t:ty, $f:ty) => ( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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 } + +/// The `ShrAssign` trait is used to specify the functionality of `>>=`. +/// +/// # Examples +/// +/// A trivial implementation of `ShrAssign`. When `Foo >>= Foo` happens, it ends up +/// calling `shr_assign`, and therefore, `main` prints `Shifting right!`. +/// +/// ``` +/// #![feature(augmented_assignments)] +/// #![feature(op_assign_traits)] +/// +/// use std::ops::ShrAssign; +/// +/// #[derive(Copy, Clone)] +/// struct Foo; +/// +/// impl ShrAssign for Foo { +/// fn shr_assign(&mut self, _rhs: Foo) { +/// println!("Shifting right!"); +/// } +/// } +/// +/// fn main() { +/// let mut foo = Foo; +/// foo >>= Foo; +/// } +/// ``` +#[cfg(not(stage0))] +#[lang = "shr_assign"] +#[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] +pub trait ShrAssign { + /// The method for the `>>=` operator + fn shr_assign(&mut self, Rhs); +} + +#[cfg(not(stage0))] +macro_rules! shr_assign_impl { + ($t:ty, $f:ty) => ( + #[unstable(feature = "op_assign_traits", reason = "recently added", issue = "28235")] + 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/expr_use_visitor.rs b/src/librustc/middle/expr_use_visitor.rs index c067c9b4d6313..297084940486e 100644 --- a/src/librustc/middle/expr_use_visitor.rs +++ b/src/librustc/middle/expr_use_visitor.rs @@ -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) => { diff --git a/src/librustc/middle/lang_items.rs b/src/librustc/middle/lang_items.rs index f0aa824c589d3..b7572d43fbe25 100644 --- a/src/librustc/middle/lang_items.rs +++ b/src/librustc/middle/lang_items.rs @@ -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; diff --git a/src/librustc_trans/trans/expr.rs b/src/librustc_trans/trans/expr.rs index 5b0dae23807a9..83ff766f5da67 100644 --- a/src/librustc_trans/trans/expr.rs +++ b/src/librustc_trans/trans/expr.rs @@ -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) @@ -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( diff --git a/src/librustc_typeck/check/op.rs b/src/librustc_typeck/check/op.rs index 07754e8506b9c..7be61327f81ff 100644 --- a/src/librustc_typeck/check/op.rs +++ b/src/librustc_typeck/check/op.rs @@ -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; @@ -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. @@ -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 @@ -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 @@ -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 } @@ -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) { +fn name_and_trait_def_id(fcx: &FnCtxt, + op: hir::BinOp, + is_assign: IsAssign) + -> (&'static str, Option) { 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") + } } } } @@ -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 diff --git a/src/librustc_typeck/check/regionck.rs b/src/librustc_typeck/check/regionck.rs index 155caaa8cb0fe..b4a99dcb576ca 100644 --- a/src/librustc_typeck/check/regionck.rs +++ b/src/librustc_typeck/check/regionck.rs @@ -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); diff --git a/src/librustc_typeck/check/writeback.rs b/src/librustc_typeck/check/writeback.rs index 6f8e064c9d819..22edd7e2c537f 100644 --- a/src/librustc_typeck/check/writeback.rs +++ b/src/librustc_typeck/check/writeback.rs @@ -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; @@ -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"); + } + } } } + _ => {}, } } } diff --git a/src/librustc_typeck/diagnostics.rs b/src/librustc_typeck/diagnostics.rs index 78eaedfbd1b72..54724f61f86c7 100644 --- a/src/librustc_typeck/diagnostics.rs +++ b/src/librustc_typeck/diagnostics.rs @@ -2862,14 +2862,27 @@ impl Drop for MyStructWrapper { 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: @@ -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##" diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 989977a691755..1e9b244c57cc6 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -194,6 +194,9 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Option, Status // allow empty structs/enum variants with braces ("braced_empty_structs", "1.5.0", None, Active), + + // allow overloading augmented assignment operations like `a += b` + ("augmented_assignments", "1.5.0", None, Active), ]; // (changing above list without updating src/doc/reference.md makes @cmr sad) @@ -457,6 +460,7 @@ pub struct Features { pub default_type_parameter_fallback: bool, pub type_macros: bool, pub cfg_target_feature: bool, + pub augmented_assignments: bool, } impl Features { @@ -485,6 +489,7 @@ impl Features { default_type_parameter_fallback: false, type_macros: false, cfg_target_feature: false, + augmented_assignments: false, } } } @@ -1053,6 +1058,7 @@ fn check_crate_inner(cm: &CodeMap, span_handler: &SpanHandler, default_type_parameter_fallback: cx.has_feature("default_type_parameter_fallback"), type_macros: cx.has_feature("type_macros"), cfg_target_feature: cx.has_feature("cfg_target_feature"), + augmented_assignments: cx.has_feature("augmented_assignments"), } } diff --git a/src/test/auxiliary/augmented_assignments.rs b/src/test/auxiliary/augmented_assignments.rs new file mode 100644 index 0000000000000..9577e1ff0c7d6 --- /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(op_assign_traits)] + +use std::ops::AddAssign; + +pub struct Int(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..0aca9a5c7ee04 --- /dev/null +++ b/src/test/compile-fail/augmented-assignments-feature-gate-cross.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. + +// aux-build:augmented_assignments.rs + +// Test that the feature gate is needed when using augmented assignments that were overloaded 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..66c3014c7a970 --- /dev/null +++ b/src/test/compile-fail/augmented-assignments-feature-gate.rs @@ -0,0 +1,26 @@ +// 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-trait.rs b/src/test/compile-fail/augmented-assignments-trait.rs new file mode 100644 index 0000000000000..83e8d1f3b3870 --- /dev/null +++ b/src/test/compile-fail/augmented-assignments-trait.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. + +use std::ops::AddAssign; +//~^ error: use of unstable library feature 'op_assign_traits' + +struct Int(i32); + +impl AddAssign for Int { + //~^ error: use of unstable library feature 'op_assign_traits' + fn add_assign(&mut self, _: Int) { + //~^ error: use of unstable library feature 'op_assign_traits' + unimplemented!() + } +} + +fn main() {} diff --git a/src/test/compile-fail/augmented-assignments.rs b/src/test/compile-fail/augmented-assignments.rs new file mode 100644 index 0000000000000..ee64171fd8c1e --- /dev/null +++ b/src/test/compile-fail/augmented-assignments.rs @@ -0,0 +1,33 @@ +// 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)] + +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..eb4c1dbb0b7b7 --- /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(op_assign_traits)] + +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; + } + } +}