Skip to content

Commit

Permalink
Merge branch 'master-rhino-8.x' into rhino-8.x
Browse files Browse the repository at this point in the history
  • Loading branch information
eirannejad committed Mar 15, 2024
2 parents 3e9eff6 + bafddbb commit 3888585
Show file tree
Hide file tree
Showing 12 changed files with 493 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/runtime/Native/ITypeOffsets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface ITypeOffsets
int tp_dictoffset { get; }
int tp_flags { get; }
int tp_free { get; }
int tp_getattr { get; }
int tp_getattro { get; }
int tp_hash { get; }
int tp_is_gc { get; }
Expand Down
1 change: 1 addition & 0 deletions src/runtime/Native/TypeOffset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ static partial class TypeOffset
internal static int tp_dictoffset { get; private set; }
internal static int tp_flags { get; private set; }
internal static int tp_free { get; private set; }
internal static int tp_getattr { get; private set; }
internal static int tp_getattro { get; private set; }
internal static int tp_hash { get; private set; }
internal static int tp_is_gc { get; private set; }
Expand Down
6 changes: 5 additions & 1 deletion src/runtime/TypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ internal static unsafe PyType CreateType(Type impl)
return type;
}


internal static void InitializeClassCore(Type clrType, PyType pyType, ClassBase impl)
{
if (pyType.BaseReference != null)
Expand Down Expand Up @@ -881,6 +880,11 @@ public static IntPtr GetDefaultSlot(int offset)
// PyType_GenericNew
return Util.ReadIntPtr(Runtime.PySuper_Type, TypeOffset.tp_new);
}
else if (offset == TypeOffset.tp_getattr)
{
// PyObject_GetAttr
return Util.ReadIntPtr(Runtime.PyBaseObjectType, TypeOffset.tp_getattr);
}
else if (offset == TypeOffset.tp_getattro)
{
// PyObject_GenericGetAttr
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/Types/ClassDerived.cs
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,8 @@ class Void { }
pyMethodName = methodName;

using var pyself = new PyObject(self.CheckRun());
using PyObject method = pyself.GetAttr(pyMethodName, Runtime.None);
using var methodNameObj = new PyString(pyMethodName);
using PyObject method = pyself.GetAttr(methodNameObj);
BorrowedReference dt = Runtime.PyObject_TYPE(method);
if (method.Reference != Runtime.PyNone && dt != Runtime.PyMethodWrapperType)
{
Expand Down
31 changes: 25 additions & 6 deletions src/runtime/Types/InterfaceObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,34 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k
}

string? name = Runtime.GetManagedString(key);
if (name == "__implementation__")
if (name != null)
{
return Converter.ToPython(clrObj.inst);
}
else if (name == "__raw_implementation__")
{
return CLRObject.GetReference(clrObj.inst);
if (name == "__implementation__")
{
return Converter.ToPython(clrObj.inst);
}
else if (name == "__raw_implementation__")
{
return CLRObject.GetReference(clrObj.inst);
}
else
{
// try get attr from pure interface wrapper
var value = Runtime.PyObject_GenericGetAttr(ob, key);
if (Exceptions.ErrorOccurred())
{
// if that didn't work, clear errors
// and try get from wrapped object
Exceptions.Clear();

using var pyObj = Converter.ToPython(clrObj.inst);
return Runtime.PyObject_GenericGetAttr(pyObj.Borrow(), key);
}
return value;
}
}


return Runtime.PyObject_GenericGetAttr(ob, key);
}

Expand Down
154 changes: 154 additions & 0 deletions src/runtime/Types/ReflectedClrType.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;

using static Python.Runtime.PythonException;
Expand Down Expand Up @@ -93,6 +94,45 @@ internal static NewReference CreateSubclass(ClassBase baseType, IEnumerable<Type
ThrowIfIsNotZero(Runtime.PyDict_DelItemString(cls_dict, "__classcell__"));
}

const BindingFlags tbFlags = BindingFlags.Public | BindingFlags.Static;
using var clsDict = new PyDict(dict);
using var keys = clsDict.Keys();
foreach (PyObject pyKey in keys)
{
string? keyStr = Runtime.GetManagedString(pyKey);
if (keyStr is null)
{
continue;
}

if (keyStr.StartsWith("__str__"))
{
var tp_str = typeof(ReflectedClrType).GetMethod(nameof(ReflectedClrType.tp_str), tbFlags);
Util.WriteIntPtr(pyTypeObj, TypeOffset.tp_str, Interop.GetThunk(tp_str).Address);
}

if (keyStr.StartsWith("__repr__"))
{
var tp_repr = typeof(ReflectedClrType).GetMethod(nameof(ReflectedClrType.tp_repr), tbFlags);
Util.WriteIntPtr(pyTypeObj, TypeOffset.tp_repr, Interop.GetThunk(tp_repr).Address);
}

if (keyStr.StartsWith("__getattribute__"))
{
var tp_getattro = typeof(ReflectedClrType).GetMethod(nameof(ReflectedClrType.tp_getattro), tbFlags);
Util.WriteIntPtr(pyTypeObj, TypeOffset.tp_getattro, Interop.GetThunk(tp_getattro).Address);
}

if (keyStr.StartsWith("__getattr__"))
{
var tp_getattr = typeof(ReflectedClrType).GetMethod(nameof(ReflectedClrType.tp_getattr), tbFlags);
//Util.WriteIntPtr(pyTypeObj, TypeOffset.tp_getattr, Interop.GetThunk(tp_getattr).Address);
Util.WriteIntPtr(pyTypeObj, TypeOffset.tp_getattro, Interop.GetThunk(tp_getattr).Address);
}

pyKey.Dispose();
}

return new NewReference(pyTypeObj);
}
catch (Exception e)
Expand All @@ -101,6 +141,120 @@ internal static NewReference CreateSubclass(ClassBase baseType, IEnumerable<Type
}
}

public static NewReference tp_str(BorrowedReference ob)
{
CLRObject? clrObj = ManagedType.GetManagedObject(ob) as CLRObject;
if (clrObj is null)
{
return Exceptions.RaiseTypeError("invalid object");
}

if (TryGetBoundMethod0(ob, "__str__", out NewReference result))
{
return result;
}

return ClassObject.tp_str(ob);
}

public static NewReference tp_repr(BorrowedReference ob)
{
CLRObject? clrObj = ManagedType.GetManagedObject(ob) as CLRObject;
if (clrObj is null)
{
return Exceptions.RaiseTypeError("invalid object");
}

if (TryGetBoundMethod0(ob, "__repr__", out NewReference result))
{
return result;
}

return ClassObject.tp_repr(ob);
}

public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key)
{
CLRObject? clrObj = ManagedType.GetManagedObject(ob) as CLRObject;
if (clrObj is null)
{
return Exceptions.RaiseTypeError("invalid object");
}

if (TryGetBoundMethod1(ob, key, "__getattribute__", out NewReference result))
{
if (Exceptions.ErrorOccurred())
{
Exceptions.Clear();
return tp_getattr(ob, key);
}
return result;
}

return Runtime.PyObject_GenericGetAttr(ob, key);
}

public static NewReference tp_getattr(BorrowedReference ob, BorrowedReference key)
{
CLRObject? clrObj = ManagedType.GetManagedObject(ob) as CLRObject;
if (clrObj is null)
{
return Exceptions.RaiseTypeError("invalid object");
}

if (TryGetBoundMethod1(ob, key, "__getattr__", out NewReference result))
{
return result;
}

using var objRepr = Runtime.PyObject_Repr(ob);
using var keyRepr = Runtime.PyObject_Repr(key);
Exceptions.SetError(
Exceptions.AttributeError,
$"object '{Runtime.GetManagedString(objRepr.BorrowOrThrow())}' has no attribute '{Runtime.GetManagedString(keyRepr.BorrowOrThrow())}'"
);
return default;
}

private static bool TryGetBoundMethod0(BorrowedReference ob, string keyName, out NewReference result)
{
result = default;

using var getAttrKey = new PyString(keyName);
using var method = Runtime.PyObject_GenericGetAttr(ob, getAttrKey);
bool foundMethod = !Exceptions.ErrorOccurred();
if (foundMethod && Runtime.PyObject_TypeCheckExact(method.Borrow(), Runtime.PyBoundMethodType))
{
using var args = Runtime.PyTuple_New(0);
result = Runtime.PyObject_Call(method.Borrow(), args.Borrow(), null);
return true;
}
else
Exceptions.Clear();

return false;
}

private static bool TryGetBoundMethod1(BorrowedReference ob, BorrowedReference key, string keyName, out NewReference result)
{
result = default;

using var getAttrKey = new PyString(keyName);
using var method = Runtime.PyObject_GenericGetAttr(ob, getAttrKey);
bool foundMethod = !Exceptions.ErrorOccurred();
if (foundMethod && Runtime.PyObject_TypeCheckExact(method.Borrow(), Runtime.PyBoundMethodType))
{
using var args = Runtime.PyTuple_New(1);
Runtime.PyTuple_SetItem(args.Borrow(), 0, key);
result = Runtime.PyObject_Call(method.Borrow(), args.Borrow(), null);
return true;
}
else
Exceptions.Clear();

return false;
}

static ReflectedClrType AllocateClass(Type clrType)
{
string name = TypeManager.GetPythonTypeName(clrType);
Expand Down
4 changes: 2 additions & 2 deletions tests/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ def test_explicit_cast_to_interface():
assert type(i1).__name__ == 'ISayHello1'
assert hasattr(i1, 'SayHello')
assert i1.SayHello() == 'hello 1'
assert not hasattr(i1, 'HelloProperty')
assert hasattr(i1, 'HelloProperty')
assert i1.__implementation__ == ob
assert i1.__raw_implementation__ == ob

i2 = Test.ISayHello2(ob)
assert type(i2).__name__ == 'ISayHello2'
assert i2.SayHello() == 'hello 2'
assert hasattr(i2, 'SayHello')
assert not hasattr(i2, 'HelloProperty')
assert hasattr(i2, 'HelloProperty')


def test_interface_object_returned_through_method():
Expand Down
46 changes: 46 additions & 0 deletions tests/test_method_getattr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import System
BASE = System.Object
IBASE = System.Collections.IEnumerable


class S1(BASE):
def __getattr__(self, name):
return super().__getattr__(name)

def __getattr__(self, name):
if name == 'answer1':
return 1
return super().__getattr__(name)


class S2(S1):
def __getattr__(self, name):
if name == 'answer2':
return 2
return super().__getattr__(name)


class S3(S2):
def __getattr__(self, name):
if name == 'answer3':
return 3
return super().__getattr__(name)



def test_method_getattr():
s1 = S1()
assert s1.answer1 == 1
try:
assert s1.answer2 == 2
except AttributeError:
pass

s2 = S2()
assert s2.answer1 == 1
assert s2.answer2 == 2

s3 = S3()
assert s2.answer1 == 1
assert s2.answer2 == 2
assert s3.answer3 == 3
45 changes: 45 additions & 0 deletions tests/test_method_getattr_mix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import System
BASE = System.Object
IBASE = System.Collections.IEnumerable


class S1(BASE):
def __getattribute__(self, name):
return super().__getattribute__(name)

def __getattr__(self, name):
if name == 'answer1':
return 1
return super().__getattr__(name)


class S2(S1):
def __getattribute__(self, name):
if name == 'answer2':
return 2
return super().__getattribute__(name)


class S3(S2):
def __getattr__(self, name):
if name == 'answer3':
return 3
return super().__getattr__(name)


def test_method_getattr_mix():
s1 = S1()
assert s1.answer1 == 1
try:
assert s1.answer2 == 2
except AttributeError:
pass

s2 = S2()
assert s2.answer1 == 1
assert s2.answer2 == 2

s3 = S3()
assert s2.answer1 == 1
assert s2.answer2 == 2
assert s3.answer3 == 3
Loading

0 comments on commit 3888585

Please sign in to comment.