Skip to content

Commit 77bdf6d

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

File tree

7 files changed

+119
-0
lines changed

7 files changed

+119
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ 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
1418
- ci: properly exclude job (#2542)
1519

1620
## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13

src/runtime/ClassManager.cs

Lines changed: 17 additions & 0 deletions
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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 44 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 36 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy