Skip to content
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

#309: static Equals not used #310

Merged
merged 3 commits into from
Apr 4, 2018
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
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