From 7b3bb6175a7d32e1a0ad3ef9a9de38c72535870b Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Mon, 7 Apr 2025 17:33:16 -0700 Subject: [PATCH 1/3] Return subclass from operator + on deque --- src/core/IronPython.Modules/_collections.cs | 16 ++++++--- tests/suite/test_deque.py | 36 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/core/IronPython.Modules/_collections.cs b/src/core/IronPython.Modules/_collections.cs index d58d66f1d..1964fe35a 100644 --- a/src/core/IronPython.Modules/_collections.cs +++ b/src/core/IronPython.Modules/_collections.cs @@ -572,14 +572,20 @@ public deque InPlaceAdd(object other) { #region binary operators - public static deque operator +([NotNone] deque x, object y) { - if (y is deque t) return x + t; + [SpecialName] + public static deque Add(CodeContext context, [NotNone] deque x, object y) { + if (y is deque t) return Add(context, x, t); throw PythonOps.TypeError($"can only concatenate deque (not \"{PythonOps.GetPythonTypeName(y)}\") to deque"); } - public static deque operator +([NotNone] deque x, [NotNone] deque y) { - var d = new deque(x._maxLen); - d.extend(x); + [SpecialName] + public static deque Add(CodeContext context, [NotNone] deque x, [NotNone] deque y) { + var d = (deque)__new__(context, DynamicHelpers.GetPythonType(x), null, null); + if (x._maxLen > 0) { + d.__init__(x, x._maxLen); + } else { + d.__init__(x); + } d.extend(y); return d; } diff --git a/tests/suite/test_deque.py b/tests/suite/test_deque.py index db9325655..e58fd32fc 100644 --- a/tests/suite/test_deque.py +++ b/tests/suite/test_deque.py @@ -52,4 +52,40 @@ def test_maxlen_value(self): self.assertRaises(OverflowError, deque, [], -1<<64) self.assertRaisesMessage(ValueError, "maxlen must be non-negative", deque, [], -1) + + def test_add(self): + d1 = deque([1, 2, 3], maxlen=6) + d2 = deque([4, 5, 6], maxlen=4) + d2 = deque([4, 5, 6]) + d3 = d1 + d2 + self.assertEqual(d3, deque([1, 2, 3, 4, 5, 6])) + self.assertIsInstance(d3, deque) + self.assertEqual(d3.maxlen, 6) + + class Deque(deque): pass + sd1 = Deque([1, 2, 3]) + sd2 = Deque([4, 5, 6]) + sd3 = sd1 + sd2 + self.assertEqual(sd3, deque([1, 2, 3, 4, 5, 6])) + self.assertIsInstance(sd3, Deque) + + + def test_multiply(self): + class Deque(deque): pass + d1 = Deque([1, 2, 3], maxlen=6) + d2 = d1 * 2 + self.assertEqual(d2, deque([1, 2, 3, 1, 2, 3])) + self.assertIsInstance(d2, deque) + + + def test_copy(self): + import copy + class Deque(deque): pass + original = Deque([1, 2, 3], 6) + copy_instance = copy.copy(original) + self.assertEqual(original, copy_instance) + self.assertIsNot(original, copy_instance) + self.assertIsInstance(copy_instance, deque) + + run_test(__name__) From 4c43a435e2c3dd9cfb68a1554720b901a4dc63d8 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Mon, 7 Apr 2025 17:45:39 -0700 Subject: [PATCH 2/3] Optimize deque.GetHashCode --- src/core/IronPython.Modules/_collections.cs | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/core/IronPython.Modules/_collections.cs b/src/core/IronPython.Modules/_collections.cs index 1964fe35a..791db07bb 100644 --- a/src/core/IronPython.Modules/_collections.cs +++ b/src/core/IronPython.Modules/_collections.cs @@ -818,6 +818,27 @@ public int __length_hint__() { #region private members + private object[] GetObjectArray() { + lock (_lockObj) { + if (_itemCnt == 0) return []; + + object[] arr = new object[_itemCnt]; + int cnt1, cnt2; + if (_head >= _tail) { + cnt1 = _data.Length - _head; + cnt2 = _itemCnt - cnt1; + } else { + cnt1 = _itemCnt; + cnt2 = 0; + } + + Array.Copy(_data, _head, arr, 0, cnt1); + Array.Copy(_data, 0, arr, cnt1, cnt2); + return arr; + } + } + + private void GrowArray() { // do nothing if array is already at its max length if (_data.Length == _maxLen) return; @@ -967,7 +988,7 @@ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) { int res; CompareUtil.Push(this); try { - res = ((IStructuralEquatable)new PythonTuple(this)).GetHashCode(comparer); + res = ((IStructuralEquatable)new PythonTuple(GetObjectArray())).GetHashCode(comparer); } finally { CompareUtil.Pop(this); } From da3e04e9280cb7371c734be942b2e030aab7a762 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Thu, 10 Apr 2025 11:47:43 -0700 Subject: [PATCH 3/3] Use `PythonTuple.MakeTuple` iso ctor --- src/core/IronPython.Modules/_collections.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/IronPython.Modules/_collections.cs b/src/core/IronPython.Modules/_collections.cs index 791db07bb..078029a4e 100644 --- a/src/core/IronPython.Modules/_collections.cs +++ b/src/core/IronPython.Modules/_collections.cs @@ -988,7 +988,7 @@ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) { int res; CompareUtil.Push(this); try { - res = ((IStructuralEquatable)new PythonTuple(GetObjectArray())).GetHashCode(comparer); + res = ((IStructuralEquatable)PythonTuple.MakeTuple(GetObjectArray())).GetHashCode(comparer); } finally { CompareUtil.Pop(this); }