Skip to content

Commit b93cab7

Browse files
committed
Fix binding already defined in c# snake case member
1 parent 10e721b commit b93cab7

File tree

2 files changed

+153
-15
lines changed

2 files changed

+153
-15
lines changed

src/embed_tests/ClassManagerTests.cs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public string JoinToString(string thisIsAStringParameter,
9191
return string.Join("-", thisIsAStringParameter, thisIsACharParameter, thisIsAnIntParameter, thisIsAFloatParameter,
9292
thisIsADoubleParameter, thisIsADecimalParameter ?? 123.456m, thisIsABoolParameter, string.Format("{0:MMddyyyy}", thisIsADateTimeParameter));
9393
}
94-
}
94+
}
9595

9696
[TestCase("AddNumbersAndGetHalf", "add_numbers_and_get_half")]
9797
[TestCase("AddNumbersAndGetHalf_Static", "add_numbers_and_get_half_static")]
@@ -487,6 +487,121 @@ def SetEnumValue3SnakeCase(obj):
487487
}
488488
}
489489

490+
private class AlreadyDefinedSnakeCaseMemberTestBaseClass
491+
{
492+
public virtual int SomeIntProperty { get; set; } = 123;
493+
494+
public int some_int_property { get; set; } = 321;
495+
496+
public virtual int AnotherIntProperty { get; set; } = 456;
497+
498+
public int another_int_property()
499+
{
500+
return 654;
501+
}
502+
}
503+
504+
[Test]
505+
public void DoesntBindSnakeCasedMemberIfAlreadyOriginallyDefinedAsProperty()
506+
{
507+
var obj = new AlreadyDefinedSnakeCaseMemberTestBaseClass();
508+
using var pyObj = obj.ToPython();
509+
510+
Assert.AreEqual(123, pyObj.GetAttr("SomeIntProperty").As<int>());
511+
Assert.AreEqual(321, pyObj.GetAttr("some_int_property").As<int>());
512+
}
513+
514+
[Test]
515+
public void DoesntBindSnakeCasedMemberIfAlreadyOriginallyDefinedAsMethod()
516+
{
517+
var obj = new AlreadyDefinedSnakeCaseMemberTestBaseClass();
518+
using var pyObj = obj.ToPython();
519+
520+
Assert.AreEqual(456, pyObj.GetAttr("AnotherIntProperty").As<int>());
521+
522+
using var method = pyObj.GetAttr("another_int_property");
523+
Assert.IsTrue(method.IsCallable());
524+
Assert.AreEqual(654, method.Invoke().As<int>());
525+
}
526+
527+
private class AlreadyDefinedSnakeCaseMemberTestDerivedClass : AlreadyDefinedSnakeCaseMemberTestBaseClass
528+
{
529+
public int SomeIntProperty { get; set; } = 111;
530+
531+
public int AnotherIntProperty { get; set; } = 222;
532+
}
533+
534+
[Test]
535+
public void DoesntBindSnakeCasedMemberIfAlreadyOriginallyDefinedAsPropertyInBaseClass()
536+
{
537+
var obj = new AlreadyDefinedSnakeCaseMemberTestDerivedClass();
538+
using var pyObj = obj.ToPython();
539+
540+
Assert.AreEqual(111, pyObj.GetAttr("SomeIntProperty").As<int>());
541+
Assert.AreEqual(321, pyObj.GetAttr("some_int_property").As<int>());
542+
}
543+
544+
[Test]
545+
public void DoesntBindSnakeCasedMemberIfAlreadyOriginallyDefinedAsMethodInBaseClass()
546+
{
547+
var obj = new AlreadyDefinedSnakeCaseMemberTestDerivedClass();
548+
using var pyObj = obj.ToPython();
549+
550+
Assert.AreEqual(222, pyObj.GetAttr("AnotherIntProperty").As<int>());
551+
552+
using var method = pyObj.GetAttr("another_int_property");
553+
Assert.IsTrue(method.IsCallable());
554+
Assert.AreEqual(654, method.Invoke().As<int>());
555+
}
556+
557+
private abstract class AlreadyDefinedSnakeCaseMemberTestBaseAbstractClass
558+
{
559+
public abstract int AbstractProperty { get; }
560+
561+
public virtual int SomeIntProperty { get; set; } = 123;
562+
563+
public int some_int_property { get; set; } = 321;
564+
565+
public virtual int AnotherIntProperty { get; set; } = 456;
566+
567+
public int another_int_property()
568+
{
569+
return 654;
570+
}
571+
}
572+
573+
private class AlreadyDefinedSnakeCaseMemberTestDerivedFromAbstractClass : AlreadyDefinedSnakeCaseMemberTestBaseAbstractClass
574+
{
575+
public override int AbstractProperty => 0;
576+
577+
public int SomeIntProperty { get; set; } = 333;
578+
579+
public int AnotherIntProperty { get; set; } = 444;
580+
}
581+
582+
[Test]
583+
public void DoesntBindSnakeCasedMemberIfAlreadyOriginallyDefinedAsPropertyInBaseAbstractClass()
584+
{
585+
var obj = new AlreadyDefinedSnakeCaseMemberTestDerivedFromAbstractClass();
586+
using var pyObj = obj.ToPython();
587+
588+
Assert.AreEqual(333, pyObj.GetAttr("SomeIntProperty").As<int>());
589+
Assert.AreEqual(321, pyObj.GetAttr("some_int_property").As<int>());
590+
}
591+
592+
[Test]
593+
public void DoesntBindSnakeCasedMemberIfAlreadyOriginallyDefinedAsMethodInBaseAbstractClass()
594+
{
595+
var obj = new AlreadyDefinedSnakeCaseMemberTestDerivedFromAbstractClass();
596+
using var pyObj = obj.ToPython();
597+
598+
Assert.AreEqual(444, pyObj.GetAttr("AnotherIntProperty").As<int>());
599+
600+
using var method = pyObj.GetAttr("another_int_property");
601+
Assert.IsTrue(method.IsCallable());
602+
Assert.AreEqual(654, method.Invoke().As<int>());
603+
}
604+
490605
#endregion
491606
}
492607

src/runtime/ClassManager.cs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -343,11 +343,14 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
343343
Type tp;
344344
int i, n;
345345

346-
MemberInfo[] info = type.GetMembers(BindingFlags);
346+
MemberInfo[] info = type.GetMembers(BindingFlags | BindingFlags.FlattenHierarchy);
347347
var local = new HashSet<string>();
348348
var items = new List<MemberInfo>();
349349
MemberInfo m;
350350

351+
var snakeCasedAttributes = new HashSet<string>();
352+
var originalMemberNames = info.Select(mi => mi.Name).ToHashSet();
353+
351354
// Loop through once to find out which names are declared
352355
for (i = 0; i < info.Length; i++)
353356
{
@@ -430,6 +433,28 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
430433
}
431434
}
432435

436+
void CheckForSnakeCasedAttribute(string name)
437+
{
438+
if (snakeCasedAttributes.Remove(name))
439+
{
440+
// If the snake cased attribute is a method, we remove it from the list of methods so that it is not added to the class
441+
methods.Remove(name);
442+
}
443+
}
444+
445+
void AddMember(string name, string snakeCasedName, PyObject obj)
446+
{
447+
CheckForSnakeCasedAttribute(name);
448+
449+
ci.members[name] = obj;
450+
451+
if (!originalMemberNames.Contains(snakeCasedName))
452+
{
453+
ci.members[snakeCasedName] = obj;
454+
snakeCasedAttributes.Add(snakeCasedName);
455+
}
456+
}
457+
433458
for (i = 0; i < items.Count; i++)
434459
{
435460
var mi = (MemberInfo)items[i];
@@ -448,6 +473,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
448473
if (name == "__init__" && !impl.HasCustomNew())
449474
continue;
450475

476+
CheckForSnakeCasedAttribute(name);
451477
if (!methods.TryGetValue(name, out var methodList))
452478
{
453479
methodList = methods[name] = new MethodOverloads(true);
@@ -456,14 +482,15 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
456482

457483
if (!OperatorMethod.IsOperatorMethod(meth))
458484
{
459-
var snakeCasedName = name.ToSnakeCase();
460-
if (snakeCasedName != name)
485+
var snakeCasedMethodName = name.ToSnakeCase();
486+
if (snakeCasedMethodName != name && !originalMemberNames.Contains(snakeCasedMethodName))
461487
{
462-
if (!methods.TryGetValue(snakeCasedName, out methodList))
488+
if (!methods.TryGetValue(snakeCasedMethodName, out methodList))
463489
{
464-
methodList = methods[snakeCasedName] = new MethodOverloads(false);
490+
methodList = methods[snakeCasedMethodName] = new MethodOverloads(false);
465491
}
466492
methodList.Add(meth);
493+
snakeCasedAttributes.Add(snakeCasedMethodName);
467494
}
468495
}
469496
continue;
@@ -506,9 +533,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
506533
}
507534

508535
ob = new PropertyObject(pi);
509-
var allocatedOb = ob.AllocObject();
510-
ci.members[pi.Name] = ob.AllocObject();
511-
ci.members[pi.Name.ToSnakeCase()] = allocatedOb;
536+
AddMember(pi.Name, pi.Name.ToSnakeCase(), ob.AllocObject());
512537
continue;
513538

514539
case MemberTypes.Field:
@@ -518,15 +543,14 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
518543
continue;
519544
}
520545
ob = new FieldObject(fi);
521-
allocatedOb = ob.AllocObject();
522-
ci.members[mi.Name] = allocatedOb;
523546

524547
var pepName = fi.Name.ToSnakeCase();
525548
if (fi.IsLiteral)
526549
{
527550
pepName = pepName.ToUpper();
528551
}
529-
ci.members[pepName] = allocatedOb;
552+
553+
AddMember(fi.Name, pepName, ob.AllocObject());
530554
continue;
531555

532556
case MemberTypes.Event:
@@ -538,9 +562,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
538562
ob = ei.AddMethod.IsStatic
539563
? new EventBinding(ei)
540564
: new EventObject(ei);
541-
allocatedOb = ob.AllocObject();
542-
ci.members[ei.Name] = allocatedOb;
543-
ci.members[ei.Name.ToSnakeCase()] = allocatedOb;
565+
AddMember(ei.Name, ei.Name.ToSnakeCase(), ob.AllocObject());
544566
continue;
545567

546568
case MemberTypes.NestedType:
@@ -552,6 +574,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
552574
}
553575
// Note the given instance might be uninitialized
554576
var pyType = GetClass(tp);
577+
CheckForSnakeCasedAttribute(mi.Name);
555578
// make a copy, that could be disposed later
556579
ci.members[mi.Name] = new ReflectedClrType(pyType);
557580
continue;

0 commit comments

Comments
 (0)