Skip to content

Make indexers work for interface objects #1246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ details about the cause of the failure
- Fixed a bug where all .NET class instances were considered Iterable
- Fix incorrect choice of method to invoke when using keyword arguments.
- Fix non-delegate types incorrectly appearing as callable.
- Indexers can now be used with interface objects

## [2.5.0][] - 2020-06-14

Expand Down
4 changes: 2 additions & 2 deletions src/runtime/arrayobject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
/// <summary>
/// Implements __getitem__ for array types.
/// </summary>
public static IntPtr mp_subscript(IntPtr ob, IntPtr idx)
public new static IntPtr mp_subscript(IntPtr ob, IntPtr idx)
{
var obj = (CLRObject)GetManagedObject(ob);
var arrObj = (ArrayObject)GetManagedObjectType(ob);
Expand Down Expand Up @@ -133,7 +133,7 @@ public static IntPtr mp_subscript(IntPtr ob, IntPtr idx)
/// <summary>
/// Implements __setitem__ for array types.
/// </summary>
public static int mp_ass_subscript(IntPtr ob, IntPtr idx, IntPtr v)
public static new int mp_ass_subscript(IntPtr ob, IntPtr idx, IntPtr v)
{
var obj = (CLRObject)GetManagedObject(ob);
var items = obj.inst as Array;
Expand Down
124 changes: 124 additions & 0 deletions src/runtime/classbase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,5 +302,129 @@ public static void tp_dealloc(IntPtr ob)
Runtime.XDecref(self.tpHandle);
self.gcHandle.Free();
}


/// <summary>
/// Implements __getitem__ for reflected classes and value types.
/// </summary>
public static IntPtr mp_subscript(IntPtr ob, IntPtr idx)
{
IntPtr tp = Runtime.PyObject_TYPE(ob);
var cls = (ClassBase)GetManagedObject(tp);

if (cls.indexer == null || !cls.indexer.CanGet)
{
Exceptions.SetError(Exceptions.TypeError, "unindexable object");
return IntPtr.Zero;
}

// Arg may be a tuple in the case of an indexer with multiple
// parameters. If so, use it directly, else make a new tuple
// with the index arg (method binders expect arg tuples).
IntPtr args = idx;
var free = false;

if (!Runtime.PyTuple_Check(idx))
{
args = Runtime.PyTuple_New(1);
Runtime.XIncref(idx);
Runtime.PyTuple_SetItem(args, 0, idx);
free = true;
}

IntPtr value;

try
{
value = cls.indexer.GetItem(ob, args);
}
finally
{
if (free)
{
Runtime.XDecref(args);
}
}
return value;
}


/// <summary>
/// Implements __setitem__ for reflected classes and value types.
/// </summary>
public static int mp_ass_subscript(IntPtr ob, IntPtr idx, IntPtr v)
{
IntPtr tp = Runtime.PyObject_TYPE(ob);
var cls = (ClassBase)GetManagedObject(tp);

if (cls.indexer == null || !cls.indexer.CanSet)
{
Exceptions.SetError(Exceptions.TypeError, "object doesn't support item assignment");
return -1;
}

// Arg may be a tuple in the case of an indexer with multiple
// parameters. If so, use it directly, else make a new tuple
// with the index arg (method binders expect arg tuples).
IntPtr args = idx;
var free = false;

if (!Runtime.PyTuple_Check(idx))
{
args = Runtime.PyTuple_New(1);
Runtime.XIncref(idx);
Runtime.PyTuple_SetItem(args, 0, idx);
free = true;
}

// Get the args passed in.
var i = Runtime.PyTuple_Size(args);
IntPtr defaultArgs = cls.indexer.GetDefaultArgs(args);
var numOfDefaultArgs = Runtime.PyTuple_Size(defaultArgs);
var temp = i + numOfDefaultArgs;
IntPtr real = Runtime.PyTuple_New(temp + 1);
for (var n = 0; n < i; n++)
{
IntPtr item = Runtime.PyTuple_GetItem(args, n);
Runtime.XIncref(item);
Runtime.PyTuple_SetItem(real, n, item);
}

// Add Default Args if needed
for (var n = 0; n < numOfDefaultArgs; n++)
{
IntPtr item = Runtime.PyTuple_GetItem(defaultArgs, n);
Runtime.XIncref(item);
Runtime.PyTuple_SetItem(real, n + i, item);
}
// no longer need defaultArgs
Runtime.XDecref(defaultArgs);
i = temp;

// Add value to argument list
Runtime.XIncref(v);
Runtime.PyTuple_SetItem(real, i, v);

try
{
cls.indexer.SetItem(ob, real);
}
finally
{
Runtime.XDecref(real);

if (free)
{
Runtime.XDecref(args);
}
}

if (Exceptions.ErrorOccurred())
{
return -1;
}

return 0;
}
}
}
126 changes: 0 additions & 126 deletions src/runtime/classobject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,131 +152,5 @@ public override IntPtr type_subscript(IntPtr idx)
}
return Exceptions.RaiseTypeError("unsubscriptable object");
}


/// <summary>
/// Implements __getitem__ for reflected classes and value types.
/// </summary>
public static IntPtr mp_subscript(IntPtr ob, IntPtr idx)
{
//ManagedType self = GetManagedObject(ob);
IntPtr tp = Runtime.PyObject_TYPE(ob);
var cls = (ClassBase)GetManagedObject(tp);

if (cls.indexer == null || !cls.indexer.CanGet)
{
Exceptions.SetError(Exceptions.TypeError, "unindexable object");
return IntPtr.Zero;
}

// Arg may be a tuple in the case of an indexer with multiple
// parameters. If so, use it directly, else make a new tuple
// with the index arg (method binders expect arg tuples).
IntPtr args = idx;
var free = false;

if (!Runtime.PyTuple_Check(idx))
{
args = Runtime.PyTuple_New(1);
Runtime.XIncref(idx);
Runtime.PyTuple_SetItem(args, 0, idx);
free = true;
}

IntPtr value;

try
{
value = cls.indexer.GetItem(ob, args);
}
finally
{
if (free)
{
Runtime.XDecref(args);
}
}
return value;
}


/// <summary>
/// Implements __setitem__ for reflected classes and value types.
/// </summary>
public static int mp_ass_subscript(IntPtr ob, IntPtr idx, IntPtr v)
{
//ManagedType self = GetManagedObject(ob);
IntPtr tp = Runtime.PyObject_TYPE(ob);
var cls = (ClassBase)GetManagedObject(tp);

if (cls.indexer == null || !cls.indexer.CanSet)
{
Exceptions.SetError(Exceptions.TypeError, "object doesn't support item assignment");
return -1;
}

// Arg may be a tuple in the case of an indexer with multiple
// parameters. If so, use it directly, else make a new tuple
// with the index arg (method binders expect arg tuples).
IntPtr args = idx;
var free = false;

if (!Runtime.PyTuple_Check(idx))
{
args = Runtime.PyTuple_New(1);
Runtime.XIncref(idx);
Runtime.PyTuple_SetItem(args, 0, idx);
free = true;
}

// Get the args passed in.
var i = Runtime.PyTuple_Size(args);
IntPtr defaultArgs = cls.indexer.GetDefaultArgs(args);
var numOfDefaultArgs = Runtime.PyTuple_Size(defaultArgs);
var temp = i + numOfDefaultArgs;
IntPtr real = Runtime.PyTuple_New(temp + 1);
for (var n = 0; n < i; n++)
{
IntPtr item = Runtime.PyTuple_GetItem(args, n);
Runtime.XIncref(item);
Runtime.PyTuple_SetItem(real, n, item);
}

// Add Default Args if needed
for (var n = 0; n < numOfDefaultArgs; n++)
{
IntPtr item = Runtime.PyTuple_GetItem(defaultArgs, n);
Runtime.XIncref(item);
Runtime.PyTuple_SetItem(real, n + i, item);
}
// no longer need defaultArgs
Runtime.XDecref(defaultArgs);
i = temp;

// Add value to argument list
Runtime.XIncref(v);
Runtime.PyTuple_SetItem(real, i, v);

try
{
cls.indexer.SetItem(ob, real);
}
finally
{
Runtime.XDecref(real);

if (free)
{
Runtime.XDecref(args);
}
}

if (Exceptions.ErrorOccurred())
{
return -1;
}

return 0;
}
}
}
22 changes: 22 additions & 0 deletions src/runtime/typemanager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,28 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType)
Marshal.WriteIntPtr(type, TypeOffset.tp_iter, IntPtr.Zero);
}


// Only set mp_subscript and mp_ass_subscript for types with indexers
if (impl is ClassBase cb)
{
if (!(impl is ArrayObject))
{
if (cb.indexer == null || !cb.indexer.CanGet)
{
Marshal.WriteIntPtr(type, TypeOffset.mp_subscript, IntPtr.Zero);
}
if (cb.indexer == null || !cb.indexer.CanSet)
{
Marshal.WriteIntPtr(type, TypeOffset.mp_ass_subscript, IntPtr.Zero);
}
}
}
else
{
Marshal.WriteIntPtr(type, TypeOffset.mp_subscript, IntPtr.Zero);
Marshal.WriteIntPtr(type, TypeOffset.mp_ass_subscript, IntPtr.Zero);
}

if (base_ != IntPtr.Zero)
{
Marshal.WriteIntPtr(type, TypeOffset.tp_base, base_);
Expand Down
12 changes: 5 additions & 7 deletions src/tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1321,16 +1321,14 @@ def test_array_abuse():
with pytest.raises(TypeError):
Test.PublicArrayTest.__getitem__(0, 0)

with pytest.raises(TypeError):
with pytest.raises(AttributeError):
Test.PublicArrayTest.__setitem__(0, 0, 0)

with pytest.raises(TypeError):
desc = Test.PublicArrayTest.__dict__['__getitem__']
desc(0, 0)
with pytest.raises(KeyError):
Test.PublicArrayTest.__dict__['__getitem__']

with pytest.raises(TypeError):
desc = Test.PublicArrayTest.__dict__['__setitem__']
desc(0, 0, 0)
with pytest.raises(KeyError):
Test.PublicArrayTest.__dict__['__setitem__']


def test_iterator_to_array():
Expand Down
23 changes: 21 additions & 2 deletions src/tests/test_indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_internal_indexer():
with pytest.raises(TypeError):
Test.InternalIndexerTest.__getitem__(ob, 0)

with pytest.raises(TypeError):
with pytest.raises(AttributeError):
ob.__getitem__(0)


Expand All @@ -56,7 +56,7 @@ def test_private_indexer():
with pytest.raises(TypeError):
Test.PrivateIndexerTest.__getitem__(ob, 0)

with pytest.raises(TypeError):
with pytest.raises(AttributeError):
ob.__getitem__(0)


Expand Down Expand Up @@ -595,3 +595,22 @@ def test_indexer_abuse():

with pytest.raises(AttributeError):
del ob.__setitem__


def test_indexer_accessed_through_interface():
"""Test that indexers can be accessed through interfaces"""
from System.Collections.Generic import Dictionary, IDictionary
d = IDictionary[str, str](Dictionary[str, str]())
d["one"] = "1"
assert d["one"] == "1"


def test_using_indexer_on_object_without_indexer():
"""Test using subscript syntax on an object an without indexer raises"""
from System import Object
o = Object()
with pytest.raises(TypeError):
o[0]

with pytest.raises(TypeError):
o[0] = 1