Skip to content

Commit 9d72d87

Browse files
committed
implemented __delitem__ for IDictionary<K,V> and IList<T>
fixed crash for all other types (now properly throws TypeError) fixes pythonnet#2530
1 parent 4eafbfa commit 9d72d87

File tree

7 files changed

+120
-0
lines changed

7 files changed

+120
-0
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
88
## Unreleased
99

1010
### Added
11+
12+
- Support `del obj[...]` for types derived from `IList<T>` and `IDictionary<K, V>`
13+
1114
### Changed
1215
### Fixed
1316

17+
- Fixed crash when trying to `del clrObj[...]` for non-arrays
18+
1419
## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13
1520

1621
### Added

src/runtime/ClassManager.cs

+17
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
213213
ClassInfo info = GetClassInfo(type, impl);
214214

215215
impl.indexer = info.indexer;
216+
impl.del = info.del;
216217
impl.richcompare.Clear();
217218

218219

@@ -538,6 +539,21 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
538539

539540
ob = new MethodObject(type, name, mlist);
540541
ci.members[name] = ob.AllocObject();
542+
if (name == nameof(IDictionary<int, int>.Remove)
543+
&& mlist.Any(m => m.DeclaringType?.GetInterfaces()
544+
.Any(i => i.TryGetGenericDefinition() == typeof(IDictionary<,>)) is true))
545+
{
546+
ci.del = new();
547+
ci.del.AddRange(mlist.Where(m => !m.IsStatic));
548+
}
549+
else if (name == nameof(IList<int>.RemoveAt)
550+
&& mlist.Any(m => m.DeclaringType?.GetInterfaces()
551+
.Any(i => i.TryGetGenericDefinition() == typeof(IList<>)) is true))
552+
{
553+
ci.del = new();
554+
ci.del.AddRange(mlist.Where(m => !m.IsStatic));
555+
}
556+
541557
if (mlist.Any(OperatorMethod.IsOperatorMethod))
542558
{
543559
string pyName = OperatorMethod.GetPyMethodName(name);
@@ -581,6 +597,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
581597
private class ClassInfo
582598
{
583599
public Indexer? indexer;
600+
public MethodBinder? del;
584601
public readonly Dictionary<string, PyObject> members = new();
585602

586603
internal ClassInfo()

src/runtime/MethodBinder.cs

+5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ internal void AddMethod(MethodBase m)
5454
list.Add(m);
5555
}
5656

57+
internal void AddRange(IEnumerable<MethodBase> methods)
58+
{
59+
list.AddRange(methods.Select(m => new MaybeMethodBase(m)));
60+
}
61+
5762
/// <summary>
5863
/// Given a sequence of MethodInfo and a sequence of types, return the
5964
/// MethodInfo that matches the signature represented by those types.

src/runtime/Types/ArrayObject.cs

+6
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ public static NewReference mp_subscript(BorrowedReference ob, BorrowedReference
247247
/// </summary>
248248
public static int mp_ass_subscript(BorrowedReference ob, BorrowedReference idx, BorrowedReference v)
249249
{
250+
if (v.IsNull)
251+
{
252+
Exceptions.RaiseTypeError("'System.Array' object does not support item deletion");
253+
return -1;
254+
}
255+
250256
var obj = (CLRObject)GetManagedObject(ob)!;
251257
var items = (Array)obj.inst;
252258
Type itemType = obj.inst.GetType().GetElementType();

src/runtime/Types/ClassBase.cs

+44
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal class ClassBase : ManagedType, IDeserializationCallback
2525
[NonSerialized]
2626
internal List<string> dotNetMembers = new();
2727
internal Indexer? indexer;
28+
internal MethodBinder? del;
2829
internal readonly Dictionary<int, MethodObject> richcompare = new();
2930
internal MaybeType type;
3031

@@ -465,6 +466,11 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo
465466
// with the index arg (method binders expect arg tuples).
466467
NewReference argsTuple = default;
467468

469+
if (v.IsNull)
470+
{
471+
return DelImpl(ob, idx, cls);
472+
}
473+
468474
if (!Runtime.PyTuple_Check(idx))
469475
{
470476
argsTuple = Runtime.PyTuple_New(1);
@@ -501,6 +507,44 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo
501507
return result.IsNull() ? -1 : 0;
502508
}
503509

510+
/// Implements __delitem__ (del x[...]) for IList&lt;T&gt; and IDictionary&lt;TKey, TValue&gt;.
511+
private static int DelImpl(BorrowedReference ob, BorrowedReference idx, ClassBase cls)
512+
{
513+
if (cls.del is null)
514+
{
515+
Exceptions.SetError(Exceptions.TypeError, "object does not support item deletion");
516+
return -1;
517+
}
518+
519+
if (Runtime.PyTuple_Check(idx))
520+
{
521+
Exceptions.SetError(Exceptions.TypeError, "multi-index deletion not supported");
522+
return -1;
523+
}
524+
525+
using var argsTuple = Runtime.PyTuple_New(1);
526+
Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx);
527+
using var result = cls.del.Invoke(ob, argsTuple.Borrow(), kw: null);
528+
if (result.IsNull())
529+
return -1;
530+
531+
if (Runtime.PyBool_CheckExact(result.Borrow()))
532+
{
533+
if (Runtime.PyObject_IsTrue(result.Borrow()) != 0)
534+
return 0;
535+
536+
Exceptions.SetError(Exceptions.KeyError, "key not found");
537+
return -1;
538+
}
539+
540+
if (!result.IsNone())
541+
{
542+
Exceptions.warn("unsupported return type for __delitem__", Exceptions.TypeError);
543+
}
544+
545+
return 0;
546+
}
547+
504548
static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw)
505549
{
506550
BorrowedReference tp = Runtime.PyObject_TYPE(ob);

src/runtime/Util/ReflectionUtil.cs

+7
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,11 @@ public static BindingFlags GetBindingFlags(this PropertyInfo property)
5353
flags |= accessor.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic;
5454
return flags;
5555
}
56+
57+
public static Type? TryGetGenericDefinition(this Type type)
58+
{
59+
if (type is null) throw new ArgumentNullException(nameof(type));
60+
61+
return type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null;
62+
}
5663
}

tests/test_indexer.py

+36
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,39 @@ def test_public_inherited_overloaded_indexer():
668668

669669
with pytest.raises(TypeError):
670670
ob[[]]
671+
672+
def test_del_indexer_dict():
673+
"""Test deleting indexers (__delitem__)."""
674+
from System.Collections.Generic import Dictionary, KeyNotFoundException
675+
d = Dictionary[str, str]()
676+
d["delme"] = "1"
677+
with pytest.raises(KeyError):
678+
del d["nonexistent"]
679+
del d["delme"]
680+
with pytest.raises(KeyError):
681+
del d["delme"]
682+
683+
def test_del_indexer_list():
684+
"""Test deleting indexers (__delitem__)."""
685+
from System import ArgumentOutOfRangeException
686+
from System.Collections.Generic import List
687+
l = List[str]()
688+
l.Add("1")
689+
with pytest.raises(ArgumentOutOfRangeException):
690+
del l[3]
691+
del l[0]
692+
assert len(l) == 0
693+
694+
def test_del_indexer_array():
695+
"""Test deleting indexers (__delitem__)."""
696+
from System import Array
697+
l = Array[str](0)
698+
with pytest.raises(TypeError):
699+
del l[0]
700+
701+
def test_del_indexer_absent():
702+
"""Test deleting indexers (__delitem__)."""
703+
from System import Uri
704+
l = Uri("http://www.example.com")
705+
with pytest.raises(TypeError):
706+
del l[0]

0 commit comments

Comments
 (0)