about summary refs log tree commit diff
diff options
context:
space:
mode:
authorHavvy (Ryan Scheel) <ryan.havvy@gmail.com>2020-11-22 02:48:14 -0800
committerHavvy (Ryan Scheel) <ryan.havvy@gmail.com>2020-11-22 03:13:27 -0800
commitb6f9705804dd590ef9fa2842bae59ec7d9130f10 (patch)
treeef9947aa0c55ee861677afd8cc7ebfa96f11eb78
parent70090118c281ea7dba2e6093462d8ae0e1fa7195 (diff)
downloadrust-b6f9705804dd590ef9fa2842bae59ec7d9130f10.tar.gz
rust-b6f9705804dd590ef9fa2842bae59ec7d9130f10.zip
Add test for eval order for a+=b
Yes, the order of evaluation *does* change depending on the types of
the operands. Cursed, I know.

I've elected to place this test into `expr/compound-assignment` creating
both the `expr` directory and the `compound-assignment` directory. I
plan in a future PR to also move the `if` directory and the loose `if`
tests into `expr/if` and other similar cleanups of the `test/ui`
directory.

Future work: Test more than just `+=`, but all operators. I don't know
if using a macro to generate these tests cases would be okay or not,
but it'd be boilerplatey without it. I'm also confident you cannot
change the evaluation order of one operator without changing all of
them.

Future work: Additionally, test more than just `i32 += i32` for the
primitive version. I don't actually know the full set of primitive
implementations, but I imagine there's enough to cause a combinatorial
explosion with the previous future work item. Somewhere on the order of
one to two hundred individual functions.
-rw-r--r--src/test/ui/expr/compound-assignment/eval-order.rs76
1 files changed, 76 insertions, 0 deletions
diff --git a/src/test/ui/expr/compound-assignment/eval-order.rs b/src/test/ui/expr/compound-assignment/eval-order.rs
new file mode 100644
index 00000000000..658adae193e
--- /dev/null
+++ b/src/test/ui/expr/compound-assignment/eval-order.rs
@@ -0,0 +1,76 @@
+// Test evaluation order of operands of the compound assignment operators
+
+// run-pass
+
+use std::ops::AddAssign;
+
+enum Side {
+    Lhs,
+    Rhs,
+}
+
+// In the following tests, we place our value into a wrapper type so that we
+// can do an element access as the outer place expression. If we just had the
+// block expression, it'd be a value expression and not compile.
+struct Wrapper<T>(T);
+
+// Evaluation order for `a op= b` where typeof(a) and typeof(b) are primitives
+// is first `b` then `a`.
+fn primitive_compound() {
+    let mut side_order = vec![];
+    let mut int = Wrapper(0);
+
+    {
+        side_order.push(Side::Lhs);
+        int
+    }.0 += {
+        side_order.push(Side::Rhs);
+        0
+    };
+
+    assert!(matches!(side_order[..], [Side::Rhs, Side::Lhs]));
+}
+
+// Evaluation order for `a op=b` otherwise is first `a` then `b`.
+fn generic_compound<T: AddAssign<T> + Default>() {
+    let mut side_order = vec![];
+    let mut add_assignable: Wrapper<T> = Wrapper(Default::default());
+
+    {
+        side_order.push(Side::Lhs);
+        add_assignable
+    }.0 += {
+        side_order.push(Side::Rhs);
+        Default::default()
+    };
+
+    assert!(matches!(side_order[..], [Side::Lhs, Side::Rhs]));
+}
+
+fn custom_compound() {
+    struct Custom;
+
+    impl AddAssign<()> for Custom {
+        fn add_assign(&mut self, _: ()) {
+            // this block purposely left blank
+        }
+    }
+
+    let mut side_order = vec![];
+    let mut custom = Wrapper(Custom);
+
+    {
+        side_order.push(Side::Lhs);
+        custom
+    }.0 += {
+        side_order.push(Side::Rhs);
+    };
+
+    assert!(matches!(side_order[..], [Side::Lhs, Side::Rhs]));
+}
+
+fn main() {
+    primitive_compound();
+    generic_compound::<i32>();
+    custom_compound();
+}