public void AddEqualsTo(TypeDefDeclaration enhancedType, StructuralEqualityAttribute config, ISet <FieldDefDeclaration> ignoredFields) { var typedEqualsMethod = this.InjectEqualsType(enhancedType, config, ignoredFields); this.InjectEqualsObject(enhancedType, config, typedEqualsMethod); this.AddEquatable(enhancedType); }
private void InjectEqualsObject(TypeDefDeclaration enhancedType, StructuralEqualityAttribute config, MethodDefDeclaration typedEqualsMethod) { var existingMethod = enhancedType.Methods.FirstOrDefault <IMethod>(declaration => { return(declaration.Name == "Equals" && declaration.IsPublic() && !declaration.IsStatic && declaration.ParameterCount == 1 && declaration.GetParameterType(0).Equals(this.objectType)); }); if (existingMethod != null) { return; } // public virtual bool Equals( object other ) var equalsDeclaration = new MethodDefDeclaration { Name = "Equals", Attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConvention = CallingConvention.HasThis }; enhancedType.Methods.Add(equalsDeclaration); equalsDeclaration.Parameters.Add(new ParameterDeclaration(0, "other", this.objectType)); equalsDeclaration.ReturnParameter = ParameterDeclaration.CreateReturnParameter(this.booleanType); CompilerGeneratedAttributeHelper.AddCompilerGeneratedAttribute(equalsDeclaration); using (var writer = InstructionWriter.GetInstance()) { var methodBody = MethodBodyCreator.CreateModifiableMethodBody(writer, equalsDeclaration); writer.AttachInstructionSequence(methodBody.PrincipalBlock.AddInstructionSequence()); writer.EmitInstruction(OpCodeNumber.Ldc_I4_0); writer.EmitInstructionLocalVariable(OpCodeNumber.Stloc, methodBody.ReturnVariable); this.InjectReferenceEquals(writer, methodBody, enhancedType); var genericTypeInstance = enhancedType.GetCanonicalGenericInstance(); this.EmitTypeCheck(enhancedType, config, writer, genericTypeInstance, methodBody); if (enhancedType.IsValueTypeSafe() == true) { this.EmitEqualsObjectOfValueType(writer, genericTypeInstance, typedEqualsMethod, methodBody); } else { this.EmitEqualsObjectOfReferenceType(writer, genericTypeInstance, typedEqualsMethod, methodBody); } writer.DetachInstructionSequence(); } }
public void ProcessEqualityOperators(TypeDefDeclaration enhancedType, StructuralEqualityAttribute config) { if (config.DoNotAddEqualityOperators) { return; } ProcessOperator(enhancedType, "op_Equality", "==", false); ProcessOperator(enhancedType, "op_Inequality", "!=", true); }
/// <summary> /// Returns information about properties set on an [StructuralEquality] attribute instance. /// </summary> /// <param name="trueAttribute">An attribute instance in user's code.</param> /// <returns>An image of what the user's attribute looked like.</returns> public static StructuralEqualityAttribute ExtractFrom(IAnnotationValue trueAttribute) { StructuralEqualityAttribute equality = new StructuralEqualityAttribute(); MemberValuePairCollection namedArguments = trueAttribute.NamedArguments; equality.DoNotAddEquals = (bool)(namedArguments[nameof(StructuralEqualityAttribute.DoNotAddEquals)]?.Value.Value ?? false); equality.DoNotAddGetHashCode = (bool)(namedArguments[nameof(StructuralEqualityAttribute.DoNotAddGetHashCode)]?.Value.Value ?? false); equality.IgnoreBaseClass = (bool)(namedArguments[nameof(StructuralEqualityAttribute.IgnoreBaseClass)]?.Value.Value ?? false); equality.TypeCheck = (TypeCheck)(namedArguments[nameof(StructuralEqualityAttribute.TypeCheck)]?.Value.Value ?? TypeCheck.ExactlyTheSameTypeAsThis); equality.DoNotAddEqualityOperators = (bool)(namedArguments[nameof(StructuralEqualityAttribute.DoNotAddEqualityOperators)]?.Value.Value ?? false); return(equality); }
/// <summary> /// Gets types for which Equals or GetHashCode should be synthesized, in such an order that base classes come before /// derived classes. This way, when Equals for a derived class is being created, you can be already sure that /// the Equals for the base class was already created (if the base class was target of [StructuralEquality]. /// </summary> private List <EqualsType> GetTypesToEnhance() { IEnumerator <IAnnotationInstance> annotationsOfType = annotationRepositoryService.GetAnnotationsOfType(typeof(StructuralEqualityAttribute), false, false); // TODO: Change the List into a StructuredDeclarationDictionary, because then Visit takes the order of inheritance into // account. But first we would need to make that public in PostSharp.Compiler.Engine. List <EqualsType> toEnhance = new List <EqualsType>(); while (annotationsOfType.MoveNext()) { IAnnotationInstance annotation = annotationsOfType.Current; if (annotation?.TargetElement is TypeDefDeclaration enhancedType) { StructuralEqualityAttribute config = EqualityConfiguration.ExtractFrom(annotation.Value); EqualsType newType = new EqualsType(enhancedType, config); toEnhance.Add(newType); } } toEnhance.Sort((first, second) => { if (first.EnhancedType.IsAssignableTo(second.EnhancedType)) { if (second.EnhancedType.IsAssignableTo(first.EnhancedType)) { return(0); } return(1); } return(-1); }); return(toEnhance); }
private void EmitTypeCheck(TypeDefDeclaration enhancedType, StructuralEqualityAttribute config, InstructionWriter writer, IType genericTypeInstance, CreatedEmptyMethod methodBody) { switch (config.TypeCheck) { case TypeCheck.ExactlyTheSameTypeAsThis: this.InjectExactlyTheSameTypeAsThis(writer, enhancedType, genericTypeInstance); break; case TypeCheck.ExactlyOfType: this.InjectExactlyOfType(writer, genericTypeInstance); break; case TypeCheck.SameTypeOrSubtype: this.InjectSameTypeOrSubtype(writer, genericTypeInstance); break; default: throw new InjectionException("EQU4", $"Unknown TypeCheck value: {config.TypeCheck}"); } // Types are different, return false. writer.EmitBranchingInstruction(OpCodeNumber.Brfalse, methodBody.ReturnSequence); }
public EqualsType(TypeDefDeclaration enhancedType, StructuralEqualityAttribute config) { EnhancedType = enhancedType; Config = config; }
public void AddGetHashCodeTo(TypeDefDeclaration enhancedType, StructuralEqualityAttribute config, ISet <FieldDefDeclaration> ignoredFields) { if (enhancedType.Methods.Any <IMethod>(m => m.Name == "GetHashCode" && m.ParameterCount == 0)) { // GetHashCode already present, just keep it. return; } // Create signature MethodDefDeclaration method = new MethodDefDeclaration { Name = "GetHashCode", CallingConvention = CallingConvention.HasThis, Attributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig }; enhancedType.Methods.Add(method); CompilerGeneratedAttributeHelper.AddCompilerGeneratedAttribute(method); method.ReturnParameter = ParameterDeclaration.CreateReturnParameter(enhancedType.Module.Cache.GetIntrinsic(IntrinsicType.Int32)); // Generate ReSharper-style Fowler–Noll–Vo hash: using (InstructionWriter writer = InstructionWriter.GetInstance()) { CreatedEmptyMethod getHashCodeData = MethodBodyCreator.CreateModifiableMethodBody(writer, method); var resultVariable = getHashCodeData.ReturnVariable; writer.AttachInstructionSequence(getHashCodeData.PrincipalBlock.AddInstructionSequence()); // Start with 0 writer.EmitInstruction(OpCodeNumber.Ldc_I4_0); writer.EmitInstructionLocalVariable(OpCodeNumber.Stloc, resultVariable); bool first = true; // Add base.GetHashCode(): if (!config.IgnoreBaseClass) { bool ignorable = enhancedType.BaseTypeDef.Name == "System.Object" || enhancedType.IsValueTypeSafe() == true; if (!ignorable) { var baseHashCode = project.Module.FindMethod(enhancedType.BaseTypeDef, "GetHashCode", BindingOptions.DontThrowException, 0); // TODO Gael says: using FindOverride would be better if (baseHashCode != null) { writer.EmitInstructionLocalVariable(OpCodeNumber.Ldloc, resultVariable); writer.EmitInstruction(OpCodeNumber.Ldarg_0); // TODO what if it is two steps removed? then we won't call it! writer.EmitInstructionMethod(OpCodeNumber.Call, baseHashCode.GetGenericInstance(enhancedType.BaseType.GetGenericContext())); writer.EmitInstruction(OpCodeNumber.Add); writer.EmitInstructionLocalVariable(OpCodeNumber.Stloc, resultVariable); first = false; } } } // For each field, do "hash = hash * 397 ^ field?.GetHashCode(); foreach (FieldDefDeclaration field in enhancedType.Fields) { if (field.IsConst || field.IsStatic || ignoredFields.Contains(field)) { continue; } this.AddFieldCode(field, first, writer, resultVariable, method, enhancedType); first = false; } // Now custom logic: foreach (var customLogic in enhancedType.Methods) { if (customLogic.CustomAttributes.GetOneByType(typeof(AdditionalGetHashCodeMethodAttribute) .FullName) != null) { writer.EmitInstructionLocalVariable(OpCodeNumber.Ldloc, resultVariable); writer.EmitInstructionInt32(OpCodeNumber.Ldc_I4, magicNumber); writer.EmitInstruction(OpCodeNumber.Mul); AddCustomLogicCall(enhancedType, writer, customLogic); writer.EmitInstruction(OpCodeNumber.Xor); writer.EmitInstructionLocalVariable(OpCodeNumber.Stloc, resultVariable); } } // Return the hash: writer.EmitBranchingInstruction(OpCodeNumber.Br, getHashCodeData.ReturnSequence); writer.DetachInstructionSequence(); } }
private MethodDefDeclaration InjectEqualsType(TypeDefDeclaration enhancedType, StructuralEqualityAttribute config, ICollection <FieldDefDeclaration> ignoredFields) { IType genericTypeInstance = enhancedType.GetCanonicalGenericInstance(); var existingMethod = enhancedType.Methods.FirstOrDefault <IMethod>(declaration => { return(declaration.Name == "Equals" && declaration.IsPublic() && !declaration.IsStatic && declaration.ParameterCount == 1 && declaration.GetParameterType(0).Equals(genericTypeInstance)); }); if (existingMethod != null) { return(existingMethod.GetMethodDefinition()); } // public virtual bool Equals( Typed other ) var equalsDeclaration = new MethodDefDeclaration { Name = "Equals", Attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConvention = CallingConvention.HasThis }; enhancedType.Methods.Add(equalsDeclaration); equalsDeclaration.Parameters.Add(new ParameterDeclaration(0, "other", genericTypeInstance)); equalsDeclaration.ReturnParameter = ParameterDeclaration.CreateReturnParameter(this.booleanType); CompilerGeneratedAttributeHelper.AddCompilerGeneratedAttribute(equalsDeclaration); using (var writer = InstructionWriter.GetInstance()) { var methodBody = MethodBodyCreator.CreateModifiableMethodBody(writer, equalsDeclaration); writer.AttachInstructionSequence(methodBody.PrincipalBlock.AddInstructionSequence()); writer.EmitInstruction(OpCodeNumber.Ldc_I4_0); writer.EmitInstructionLocalVariable(OpCodeNumber.Stloc, methodBody.ReturnVariable); this.InjectReferenceEquals(writer, methodBody, enhancedType); // Writer is either attached to the same sequence (value types) or to a new one which should check the structure. // return base.Equals(other) && this.field1 == other.field1 && ...; if (!config.IgnoreBaseClass && enhancedType.IsValueTypeSafe() != true) { // Find the base method. var baseEqualsMethod = this.instanceEqualsMethod.FindOverride(enhancedType.BaseTypeDef, true) .GetInstance(enhancedType.Module, enhancedType.BaseType.GetGenericContext()); // Do not invoke object.Equals(); if (baseEqualsMethod.DeclaringType.GetTypeDefinition() != this.objectTypeDef) { writer.EmitInstruction(OpCodeNumber.Ldarg_0); writer.EmitInstruction(OpCodeNumber.Ldarg_1); writer.EmitInstructionMethod(OpCodeNumber.Call, baseEqualsMethod); // base.Equals(other) returned false, go to return. writer.EmitBranchingInstruction(OpCodeNumber.Brfalse, methodBody.ReturnSequence); } } foreach (var field in GetFieldsForComparison(enhancedType, ignoredFields)) { this.EmitEqualsField(writer, methodBody, field); } InjectCustomMethods(enhancedType, writer, methodBody); // return true; writer.EmitInstruction(OpCodeNumber.Ldc_I4_1); writer.EmitInstructionLocalVariable(OpCodeNumber.Stloc, methodBody.ReturnVariable); writer.EmitBranchingInstruction(OpCodeNumber.Br, methodBody.ReturnSequence); writer.DetachInstructionSequence(); } return(equalsDeclaration); }