Repository: disconnect3d/cint

Issue:
Bug?: mutable value
TLDR:
```
In [1]: import cint

In [2]: x = cint.I8(127)

In [3]: y = x

In [4]: y += 1

In [5]: y, x
Out[5]: (I8(-128), I8(-128))
```
while this is correct for Python -  `x` and `y` are the same objects, it is not that intuitive for C-like code.

What I mean is, a similar syntax in C will do sth  different:
```c
int8_t x = 127;
int8_t y = x;
y+= 1;
// x is 127
// y is -128
```
the results are different.

The question is: how should it be handled?

Discussion:

Patch:
diff --git a/cint.py b/cint.py
index 9f520f0..0dd133d 100644
--- a/cint.py
+++ b/cint.py
@@ -133,48 +133,37 @@ class WrappedCint:
     __index__ = __int__  # make indexing work
 
     def __iadd__(self, other):
-        self.value += calc(other)
-        return self
+        return self.__class__(self.value + calc(other))
 
     def __isub__(self, other):
-        self.value -= calc(other)
-        return self
+        return self.__class__(self.value - calc(other))
 
     def __imul__(self, other):
-        self.value *= calc(other)
-        return self
+        return self.__class__(self.value * calc(other))
 
     def __ipow__(self, other):
-        self.value **= calc(other)
-        return self
+        return self.__class__(self.value ** calc(other))
 
     def __itruediv__(self, other):
-        self.value //= calc(other)
-        return self
+        return self.__class__(self.value // calc(other))
 
     def __imod__(self, other):
-        self.value %= calc(other)
-        return self
+        return self.__class__(self.value % calc(other))
 
     def __irshift__(self, other):
-        self.value >>= calc(other)
-        return self
+        return self.__class__(self.value >> calc(other))
 
     def __ilshift__(self, other):
-        self.value <<= calc(other)
-        return self
+        return self.__class__(self.value << calc(other))
 
     def __ixor__(self, other):
-        self.value ^= calc(other)
-        return self
+        return self.__class__(self.value ^ calc(other))
 
     def __iand__(self, other):
-        self.value &= calc(other)
-        return self
+        return self.__class__(self.value & calc(other))
 
     def __ior__(self, other):
-        self.value |= calc(other)
-        return self
+        return self.__class__(self.value | calc(other))
 
 
 def create_immutable(val, type_):


Test patch:
diff --git a/tests/test_cint.py b/tests/test_cint.py
index 9e6ad82..4fece1d 100644
--- a/tests/test_cint.py
+++ b/tests/test_cint.py
@@ -122,3 +122,21 @@ def test_immutable_min_max(ct, op):
 
     with pytest.raises(NotImplementedError):
         op(ct.MAX, 1)
+
+@pytest.mark.parametrize('ct', INTS)
+@pytest.mark.parametrize('op', (iadd, isub, imul, itruediv, ipow, imod, ilshift, irshift, iand, ior, ixor))
+def test_not_mutable_ioperators(ct, op):
+    x = ct(100)
+    y = ct(10)
+
+    result = op(x, y)
+
+    # Just in case, check the value ;)
+    expected_result = ct(int(op(100, 10)))
+    assert result == expected_result
+
+    # Check for mutability - the: `x <op>= y` should not modify x
+    assert x == 100
+    assert y == 10
+    assert id(x) != id(result) != id(y)
+