Skip to content

Commit

Permalink
#309: static Equals not used (#310)
Browse files Browse the repository at this point in the history
* TypeEqualityFinder now supports static Equals() methods in base classes.

* Finding an equality comparer on a base type is cached now.

* Type equality info gets stored in PropertyData now
  • Loading branch information
jhartmann123 authored and SimonCropp committed Apr 4, 2018
1 parent dada163 commit 165bf03
Show file tree
Hide file tree
Showing 20 changed files with 464 additions and 14 deletions.
2 changes: 1 addition & 1 deletion PropertyChanged.Fody/EqualityCheckWeaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ void InjectEqualityCheck(Instruction targetInstruction, TypeReference targetType
Instruction.Create(OpCodes.Ret));
return;
}
var typeEqualityMethod = typeEqualityFinder.FindTypeEquality(targetType);
var typeEqualityMethod = propertyData.EqualsMethod;
if (typeEqualityMethod == null)
{
if (targetType.SupportsCeq() && (targetType.IsValueType || !typeEqualityFinder.CheckForEqualityUsingBaseEquals))
Expand Down
1 change: 1 addition & 0 deletions PropertyChanged.Fody/ModuleWeaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public override void Execute()
ResolveOnPropertyNameChangedConfig();
ResolveCheckForEqualityConfig();
ResolveCheckForEqualityUsingBaseEqualsConfig();
ResolveUseStaticEqualsFromBaseConfig();
ResolveEventInvokerName();
FindCoreReferences();
FindInterceptor();
Expand Down
1 change: 1 addition & 0 deletions PropertyChanged.Fody/PropertyData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ public class PropertyData
public FieldReference BackingFieldReference;
public List<PropertyDefinition> AlsoNotifyFor = new List<PropertyDefinition>();
public PropertyDefinition PropertyDefinition;
public MethodReference EqualsMethod;
public List<string> AlreadyNotifies = new List<string>();
}
99 changes: 89 additions & 10 deletions PropertyChanged.Fody/TypeEqualityFinder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Rocks;

public partial class ModuleWeaver
{
Expand All @@ -18,18 +19,29 @@ public void FindComparisonMethods()
.Fields
.First(x => x.Name == "Ordinal")
.Constant;

foreach (var node in NotifyNodes)
{
foreach (var data in node.PropertyDatas)
{
data.EqualsMethod = FindTypeEquality(data);
}
}

methodCache = null;
}

public MethodReference FindTypeEquality(TypeReference typeDefinition)
MethodReference FindTypeEquality(PropertyData propertyData)
{
var typeDefinition = propertyData.PropertyDefinition.PropertyType;
var fullName = typeDefinition.FullName;
if (methodCache.TryGetValue(fullName, out var methodReference))
{
return methodReference;
}

var equality = GetEquality(typeDefinition);
methodCache.Add(fullName, equality);
methodCache[fullName] = equality;
return equality;
}

Expand Down Expand Up @@ -62,12 +74,8 @@ MethodReference GetEquality(TypeReference typeDefinition)
return ModuleDefinition.ImportReference(genericInstanceMethod);
}
}
var equality = GetStaticEquality(typeDefinition);
if (equality == null)
{
return null;
}
return ModuleDefinition.ImportReference(equality);

return GetStaticEquality(typeDefinition);
}

MethodReference GetStaticEquality(TypeReference typeReference)
Expand All @@ -78,7 +86,73 @@ MethodReference GetStaticEquality(TypeReference typeReference)
return null;
}

return FindNamedMethod(typeReference);
MethodReference equality = null;
var typesChecked = new List<string>();

if (UseStaticEqualsFromBase)
{
while (equality == null &&
typeReference != null &&
typeReference.FullName != typeof(object).FullName &&
!methodCache.TryGetValue(typeReference.FullName, out equality))
{
typesChecked.Add(typeReference.FullName);
equality = FindNamedMethod(typeReference);
if (equality == null)
typeReference = GetBaseType(typeReference);
}
}
else
equality = FindNamedMethod(typeReference);

if (equality != null)
equality = ModuleDefinition.ImportReference(equality);

typesChecked.ForEach(typeName => methodCache[typeName] = equality);

return equality;
}

TypeReference GetBaseType(TypeReference typeReference)
{
var typeDef = typeReference as TypeDefinition ?? typeReference.Resolve();
var baseType = typeDef?.BaseType;

if (baseType == null)
return null;

if (baseType.IsGenericInstance && typeReference.IsGenericInstance)
{
//currently we have something like: baseType = BaseClass<T>, typeReference = Class<int> (where the class inherits from BaseClass<T> and int is the parameter for T).
//We want BaseClass<int> -> map generic arguments to the actual parameter types
var genericBaseType = (GenericInstanceType)baseType;
var genericTypeRef = (GenericInstanceType)typeReference;

//create a map from the type reference (child class): generic argument name -> type
var typeRefDict = new Dictionary<string, TypeReference>();
var typeRefParams = genericTypeRef.ElementType.Resolve().GenericParameters;
for (int i = 0; i < typeRefParams.Count; i++)
{
string paramName = typeRefParams[i].FullName;
TypeReference paramType = genericTypeRef.GenericArguments[i];
typeRefDict[paramName] = paramType;
}

//apply to base type
//note: even though the base class may have different argument names in the source code, the argument names of the inheriting class are used in the GenericArguments
//thus we can directly map them.
var baseTypeArgs = genericBaseType.GenericArguments.Select(arg =>
{
if (typeRefDict.TryGetValue(arg.FullName, out TypeReference t))
return t;

return arg;
}).ToArray();

baseType = genericBaseType.ElementType.MakeGenericInstanceType(baseTypeArgs);
}

return baseType;
}

public static MethodReference FindNamedMethod(TypeReference typeReference)
Expand All @@ -103,13 +177,18 @@ public static MethodReference FindNamedMethod(TypeReference typeReference)

static MethodReference FindNamedMethod(TypeDefinition typeDefinition, string methodName, TypeReference parameterType)
{
return typeDefinition.Methods.FirstOrDefault(x => x.Name == methodName &&
MethodReference reference = typeDefinition.Methods.FirstOrDefault(x => x.Name == methodName &&
x.IsStatic &&
x.ReturnType.Name == "Boolean" &&
x.HasParameters &&
x.Parameters.Count == 2 &&
MatchParameter(x.Parameters[0], parameterType) &&
MatchParameter(x.Parameters[1], parameterType));

if (reference == null && typeDefinition != parameterType)
reference = FindNamedMethod(typeDefinition, methodName, typeDefinition);

return reference;
}

static bool MatchParameter(ParameterDefinition parameter, TypeReference typeMatch)
Expand Down
15 changes: 15 additions & 0 deletions PropertyChanged.Fody/UseStaticEqualsFromBaseConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Linq;

public partial class ModuleWeaver
{
public bool UseStaticEqualsFromBase;

public void ResolveUseStaticEqualsFromBaseConfig()
{
var value = Config?.Attributes("UseStaticEqualsFromBase").FirstOrDefault();
if (value != null)
{
UseStaticEqualsFromBase = bool.Parse((string)value);
}
}
}
24 changes: 24 additions & 0 deletions TestAssemblies/AssemblyWithBase/StaticEquals/BaseClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace AssemblyWithBase.StaticEquals
{
public class BaseClass
{
private static bool staticEqualsCalled;
public bool StaticEqualsCalled
{
get => staticEqualsCalled;
set => staticEqualsCalled = value;
}

public static bool Equals(BaseClass first, BaseClass second)
{
staticEqualsCalled = true;

if (ReferenceEquals(first, second))
return true;
if (first == null || second == null)
return false;

return first.Equals(second);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace AssemblyWithBase.StaticEqualsGenericParent
{
public class BaseClass<T>
{
private static bool staticEqualsCalled;
public bool StaticEqualsCalled
{
get => staticEqualsCalled;
set => staticEqualsCalled = value;
}

public static bool Equals(BaseClass<T> first, BaseClass<T> second)
{
staticEqualsCalled = true;

if (ReferenceEquals(first, second))
return true;

if (first == null || second == null)
return false;

return first.Equals(second);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace AssemblyWithBase.StaticEqualsGenericParent
{
public class BaseClass2<T, TSomething> : BaseClass<TSomething>
{
public T SomeProperty { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace AssemblyWithBase.StaticEqualsGenericParent
{
public class BaseClass3<TFoo> : BaseClass2<TFoo, int>
{ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace AssemblyWithBase.StaticEqualsGenericParent
{
public class ClassUsingBaseEquals : BaseClass<int>
{ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace AssemblyWithBase.StaticEqualsGenericParent
{
public class ClassWithOwnEquals : BaseClass<int>
{
private static bool childStaticEqualsCalled;

public bool ChildStaticEqualsCalled
{
get => childStaticEqualsCalled;
set => childStaticEqualsCalled = value;
}

public static bool Equals(ClassWithOwnEquals first, ClassWithOwnEquals second)
{
childStaticEqualsCalled = true;

if (ReferenceEquals(first, second))
return true;

if (first == null || second == null)
return false;

return first.Equals(second);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace AssemblyWithBase.StaticEqualsGenericParent
{
public class ClassWithTwoBaseClasses1 : BaseClass2<int, string>
{ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace AssemblyWithBase.StaticEqualsGenericParent
{
public class ClassWithTwoBaseClasses2 : BaseClass3<string>
{ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using AssemblyWithBase.StaticEquals;

namespace AssemblyWithBaseInDifferentModule.StaticEquals
{
public class StaticEquals : INotifyPropertyChanged
{
private string property1;
public string Property1
{
get => property1;
set
{
property1 = value;
Property2 = new BaseClass();
}
}

public BaseClass Property2 { get; set; }

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using AssemblyWithBase.StaticEqualsGenericParent;

namespace AssemblyWithBaseInDifferentModule.StaticEqualsGenericParent
{
public class ArgsMapping1 : INotifyPropertyChanged
{
private string property1;
public string Property1
{
get => property1;
set
{
property1 = value;
Property2 = new ClassWithTwoBaseClasses1();
}
}

public ClassWithTwoBaseClasses1 Property2 { get; set; }

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using AssemblyWithBase.StaticEqualsGenericParent;

namespace AssemblyWithBaseInDifferentModule.StaticEqualsGenericParent
{
public class ArgsMapping2 : INotifyPropertyChanged
{
private string property1;
public string Property1
{
get => property1;
set
{
property1 = value;
Property2 = new ClassWithTwoBaseClasses2();
}
}

public ClassWithTwoBaseClasses2 Property2 { get; set; }

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Loading

0 comments on commit 165bf03

Please sign in to comment.