From a6746588785516f0f470d01ebbcd14267ad0c17c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 18 Dec 2024 21:11:06 +0000 Subject: [PATCH 1/4] added NixOS FHS shell.nix --- pythonnet.sln | 1 + shell.nix | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 shell.nix diff --git a/pythonnet.sln b/pythonnet.sln index 5bf4a2dbf..cf684c0e1 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F LICENSE = LICENSE README.rst = README.rst version.txt = version.txt + shell.nix = shell.nix EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..fe653deb7 --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +{ pkgs ? import {}}: +let + fhs = pkgs.buildFHSUserEnv { + name = "my-fhs-environment"; + + targetPkgs = _: [ + pkgs.python3 + ]; + }; +in fhs.env From ac605fd3e3ccad28c105ef2d57b955a23f51c2a9 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 18 Dec 2024 21:13:13 +0000 Subject: [PATCH 2/4] index setter was leaking memory --- src/runtime/Types/ClassBase.cs | 10 ++-------- src/runtime/Types/Indexer.cs | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 7296a1900..8b2a98903 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -497,14 +497,8 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo // Add value to argument list Runtime.PyTuple_SetItem(real.Borrow(), i, v); - cls.indexer.SetItem(ob, real.Borrow()); - - if (Exceptions.ErrorOccurred()) - { - return -1; - } - - return 0; + using var result = cls.indexer.SetItem(ob, real.Borrow()); + return result.IsNull() ? -1 : 0; } static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw) diff --git a/src/runtime/Types/Indexer.cs b/src/runtime/Types/Indexer.cs index 4903b6f76..fe6dab4c9 100644 --- a/src/runtime/Types/Indexer.cs +++ b/src/runtime/Types/Indexer.cs @@ -50,9 +50,9 @@ internal NewReference GetItem(BorrowedReference inst, BorrowedReference args) } - internal void SetItem(BorrowedReference inst, BorrowedReference args) + internal NewReference SetItem(BorrowedReference inst, BorrowedReference args) { - SetterBinder.Invoke(inst, args, null); + return SetterBinder.Invoke(inst, args, null); } internal bool NeedsDefaultArgs(BorrowedReference args) From 77bdf6d2bc5b7b6697d51f1b4ed765b01c322e59 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 18 Dec 2024 21:14:40 +0000 Subject: [PATCH 3/4] implemented __delitem__ for IDictionary and IList fixed crash for all other types (now properly throws TypeError) fixes https://github.com/pythonnet/pythonnet/issues/2530 --- CHANGELOG.md | 4 +++ src/runtime/ClassManager.cs | 17 ++++++++++++ src/runtime/MethodBinder.cs | 5 ++++ src/runtime/Types/ArrayObject.cs | 6 ++++ src/runtime/Types/ClassBase.cs | 44 ++++++++++++++++++++++++++++++ src/runtime/Util/ReflectionUtil.cs | 7 +++++ tests/test_indexer.py | 36 ++++++++++++++++++++++++ 7 files changed, 119 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1863a0806..df68fbb39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,13 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ## Unreleased ### Added + +- Support `del obj[...]` for types derived from `IList` and `IDictionary` + ### Changed ### Fixed +- Fixed crash when trying to `del clrObj[...]` for non-arrays - ci: properly exclude job (#2542) ## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index d743bc006..b884bfa92 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -213,6 +213,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p ClassInfo info = GetClassInfo(type, impl); impl.indexer = info.indexer; + impl.del = info.del; impl.richcompare.Clear(); @@ -538,6 +539,21 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) ob = new MethodObject(type, name, mlist); ci.members[name] = ob.AllocObject(); + if (name == nameof(IDictionary.Remove) + && mlist.Any(m => m.DeclaringType?.GetInterfaces() + .Any(i => i.TryGetGenericDefinition() == typeof(IDictionary<,>)) is true)) + { + ci.del = new(); + ci.del.AddRange(mlist.Where(m => !m.IsStatic)); + } + else if (name == nameof(IList.RemoveAt) + && mlist.Any(m => m.DeclaringType?.GetInterfaces() + .Any(i => i.TryGetGenericDefinition() == typeof(IList<>)) is true)) + { + ci.del = new(); + ci.del.AddRange(mlist.Where(m => !m.IsStatic)); + } + if (mlist.Any(OperatorMethod.IsOperatorMethod)) { string pyName = OperatorMethod.GetPyMethodName(name); @@ -581,6 +597,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) private class ClassInfo { public Indexer? indexer; + public MethodBinder? del; public readonly Dictionary members = new(); internal ClassInfo() diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 9a5515c8e..af75a34b4 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -54,6 +54,11 @@ internal void AddMethod(MethodBase m) list.Add(m); } + internal void AddRange(IEnumerable methods) + { + list.AddRange(methods.Select(m => new MaybeMethodBase(m))); + } + /// /// Given a sequence of MethodInfo and a sequence of types, return the /// MethodInfo that matches the signature represented by those types. diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs index b95934baf..f220d53fb 100644 --- a/src/runtime/Types/ArrayObject.cs +++ b/src/runtime/Types/ArrayObject.cs @@ -247,6 +247,12 @@ public static NewReference mp_subscript(BorrowedReference ob, BorrowedReference /// public static int mp_ass_subscript(BorrowedReference ob, BorrowedReference idx, BorrowedReference v) { + if (v.IsNull) + { + Exceptions.RaiseTypeError("'System.Array' object does not support item deletion"); + return -1; + } + var obj = (CLRObject)GetManagedObject(ob)!; var items = (Array)obj.inst; Type itemType = obj.inst.GetType().GetElementType(); diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 8b2a98903..2d6ce8a47 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -25,6 +25,7 @@ internal class ClassBase : ManagedType, IDeserializationCallback [NonSerialized] internal List dotNetMembers = new(); internal Indexer? indexer; + internal MethodBinder? del; internal readonly Dictionary richcompare = new(); internal MaybeType type; @@ -465,6 +466,11 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo // with the index arg (method binders expect arg tuples). NewReference argsTuple = default; + if (v.IsNull) + { + return DelImpl(ob, idx, cls); + } + if (!Runtime.PyTuple_Check(idx)) { argsTuple = Runtime.PyTuple_New(1); @@ -501,6 +507,44 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo return result.IsNull() ? -1 : 0; } + /// Implements __delitem__ (del x[...]) for IList<T> and IDictionary<TKey, TValue>. + private static int DelImpl(BorrowedReference ob, BorrowedReference idx, ClassBase cls) + { + if (cls.del is null) + { + Exceptions.SetError(Exceptions.TypeError, "object does not support item deletion"); + return -1; + } + + if (Runtime.PyTuple_Check(idx)) + { + Exceptions.SetError(Exceptions.TypeError, "multi-index deletion not supported"); + return -1; + } + + using var argsTuple = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx); + using var result = cls.del.Invoke(ob, argsTuple.Borrow(), kw: null); + if (result.IsNull()) + return -1; + + if (Runtime.PyBool_CheckExact(result.Borrow())) + { + if (Runtime.PyObject_IsTrue(result.Borrow()) != 0) + return 0; + + Exceptions.SetError(Exceptions.KeyError, "key not found"); + return -1; + } + + if (!result.IsNone()) + { + Exceptions.warn("unsupported return type for __delitem__", Exceptions.TypeError); + } + + return 0; + } + static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw) { BorrowedReference tp = Runtime.PyObject_TYPE(ob); diff --git a/src/runtime/Util/ReflectionUtil.cs b/src/runtime/Util/ReflectionUtil.cs index 58d0a506e..0fad2d4b2 100644 --- a/src/runtime/Util/ReflectionUtil.cs +++ b/src/runtime/Util/ReflectionUtil.cs @@ -53,4 +53,11 @@ public static BindingFlags GetBindingFlags(this PropertyInfo property) flags |= accessor.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic; return flags; } + + public static Type? TryGetGenericDefinition(this Type type) + { + if (type is null) throw new ArgumentNullException(nameof(type)); + + return type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null; + } } diff --git a/tests/test_indexer.py b/tests/test_indexer.py index 8cf3150ba..108573f0d 100644 --- a/tests/test_indexer.py +++ b/tests/test_indexer.py @@ -668,3 +668,39 @@ def test_public_inherited_overloaded_indexer(): with pytest.raises(TypeError): ob[[]] + +def test_del_indexer_dict(): + """Test deleting indexers (__delitem__).""" + from System.Collections.Generic import Dictionary, KeyNotFoundException + d = Dictionary[str, str]() + d["delme"] = "1" + with pytest.raises(KeyError): + del d["nonexistent"] + del d["delme"] + with pytest.raises(KeyError): + del d["delme"] + +def test_del_indexer_list(): + """Test deleting indexers (__delitem__).""" + from System import ArgumentOutOfRangeException + from System.Collections.Generic import List + l = List[str]() + l.Add("1") + with pytest.raises(ArgumentOutOfRangeException): + del l[3] + del l[0] + assert len(l) == 0 + +def test_del_indexer_array(): + """Test deleting indexers (__delitem__).""" + from System import Array + l = Array[str](0) + with pytest.raises(TypeError): + del l[0] + +def test_del_indexer_absent(): + """Test deleting indexers (__delitem__).""" + from System import Uri + l = Uri("http://www.example.com") + with pytest.raises(TypeError): + del l[0] From a21c7972809500edd6364003c37695193ac1a5d4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 14 Apr 2025 20:12:49 +0200 Subject: [PATCH 4/4] Bump action versions in doc workflow (#2584) --- .github/workflows/docs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5b782c8b4..30163cd14 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,9 +6,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Doxygen Action - uses: mattnotmitt/doxygen-action@1.9.4 + uses: mattnotmitt/doxygen-action@1.12.0 with: working-directory: "doc/" @@ -19,7 +19,7 @@ jobs: - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: doc/build/html/ @@ -37,4 +37,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4