Skip to content

Fixed accessing partially overriden properties from Python #1650

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 1 commit into from
Jan 4, 2022
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
21 changes: 21 additions & 0 deletions src/embed_tests/Inheritance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ public void BaseClearIsCalled()
scope.Set("exn", null);
Assert.AreEqual(1, msg.Refcount);
}

// https://github.com/pythonnet/pythonnet/issues/1455
[Test]
public void PropertyAccessorOverridden()
{
using var derived = new PropertyAccessorDerived().ToPython();
derived.SetAttr(nameof(PropertyAccessorDerived.VirtualProp), "hi".ToPython());
Assert.AreEqual("HI", derived.GetAttr(nameof(PropertyAccessorDerived.VirtualProp)).As<string>());
}
}

class ExtraBaseTypeProvider : IPythonBaseTypeProvider
Expand Down Expand Up @@ -192,6 +201,18 @@ public int XProp
}
}

public class PropertyAccessorBase
{
public virtual string VirtualProp { get; set; }
}

public class PropertyAccessorIntermediate: PropertyAccessorBase { }

public class PropertyAccessorDerived: PropertyAccessorIntermediate
{
public override string VirtualProp { set => base.VirtualProp = value.ToUpperInvariant(); }
}

public class ContainerClass
{
public void BaseMethod() { }
Expand Down
56 changes: 56 additions & 0 deletions src/runtime/ReflectionUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace Python.Runtime;

using System;
using System.Reflection;

static class ReflectionUtil
{
public static MethodInfo? GetBaseGetMethod(this PropertyInfo property, bool nonPublic)
{
if (property is null) throw new ArgumentNullException(nameof(property));

Type baseType = property.DeclaringType.BaseType;
BindingFlags bindingFlags = property.GetBindingFlags();

while (baseType is not null)
{
var baseProperty = baseType.GetProperty(property.Name, bindingFlags | BindingFlags.DeclaredOnly);
var accessor = baseProperty?.GetGetMethod(nonPublic);
if (accessor is not null)
return accessor;

baseType = baseType.BaseType;
}

return null;
}

public static MethodInfo? GetBaseSetMethod(this PropertyInfo property, bool nonPublic)
{
if (property is null) throw new ArgumentNullException(nameof(property));

Type baseType = property.DeclaringType.BaseType;
BindingFlags bindingFlags = property.GetBindingFlags();

while (baseType is not null)
{
var baseProperty = baseType.GetProperty(property.Name, bindingFlags | BindingFlags.DeclaredOnly);
var accessor = baseProperty?.GetSetMethod(nonPublic);
if (accessor is not null)
return accessor;

baseType = baseType.BaseType;
}

return null;
}

public static BindingFlags GetBindingFlags(this PropertyInfo property)
{
var accessor = property.GetMethod ?? property.SetMethod;
BindingFlags flags = default;
flags |= accessor.IsStatic ? BindingFlags.Static : BindingFlags.Instance;
flags |= accessor.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic;
return flags;
}
}
35 changes: 26 additions & 9 deletions src/runtime/propertyobject.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Reflection;
using System.Runtime.Serialization;

namespace Python.Runtime
{
Expand All @@ -8,17 +9,25 @@ namespace Python.Runtime
/// Implements a Python descriptor type that manages CLR properties.
/// </summary>
[Serializable]
internal class PropertyObject : ExtensionType
internal class PropertyObject : ExtensionType, IDeserializationCallback
{
internal MaybeMemberInfo<PropertyInfo> info;
private MaybeMethodInfo getter;
private MaybeMethodInfo setter;
[NonSerialized]
private MethodInfo? getter;
[NonSerialized]
private MethodInfo? setter;

public PropertyObject(PropertyInfo md)
{
getter = md.GetGetMethod(true);
setter = md.GetSetMethod(true);
info = new MaybeMemberInfo<PropertyInfo>(md);
CacheAccessors();
}

void CacheAccessors()
{
PropertyInfo md = info.Value;
getter = md.GetGetMethod(true) ?? md.GetBaseGetMethod(true);
setter = md.GetSetMethod(true) ?? md.GetBaseSetMethod(true);
}


Expand All @@ -35,7 +44,7 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference
return Exceptions.RaiseTypeError(self.info.DeletedMessage);
}
var info = self.info.Value;
MethodInfo getter = self.getter.UnsafeValue;
MethodInfo? getter = self.getter;
object result;


Expand Down Expand Up @@ -70,7 +79,7 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference

try
{
result = info.GetValue(co.inst, null);
result = getter.Invoke(co.inst, Array.Empty<object>());
return Converter.ToPython(result, info.PropertyType);
}
catch (Exception e)
Expand Down Expand Up @@ -100,7 +109,7 @@ public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, Borro
}
var info = self.info.Value;

MethodInfo setter = self.setter.UnsafeValue;
MethodInfo? setter = self.setter;

if (val == null)
{
Expand Down Expand Up @@ -141,7 +150,7 @@ public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, Borro
Exceptions.RaiseTypeError("invalid target");
return -1;
}
info.SetValue(co.inst, newval, null);
setter.Invoke(co.inst, new object?[] { newval });
}
else
{
Expand Down Expand Up @@ -169,5 +178,13 @@ public static NewReference tp_repr(BorrowedReference ob)
var self = (PropertyObject)GetManagedObject(ob)!;
return Runtime.PyString_FromString($"<property '{self.info}'>");
}

void IDeserializationCallback.OnDeserialization(object sender)
{
if (info.Valid)
{
CacheAccessors();
}
}
}
}