private void AddCustomLogicCall(TypeDefDeclaration enhancedType, InstructionWriter writer, MethodDefDeclaration customMethod) { var parameters = customMethod.Parameters; if (parameters.Count != 0) { Message.Write(enhancedType, SeverityType.Error, "EQU1", "The signature of a method annotated with [" + nameof(AdditionalGetHashCodeMethodAttribute) + "] must be 'int MethodName()'. It can't have parameters."); writer.EmitInstruction(OpCodeNumber.Ldc_I4_0); return; } if (!customMethod.ReturnParameter.ParameterType.IsIntrinsic(IntrinsicType.Int32)) { Message.Write(enhancedType, SeverityType.Error, "EQU2", "The signature of a method annotated with [" + nameof(AdditionalGetHashCodeMethodAttribute) + "] must be 'int MethodName()'. Its return type must be 'int'."); writer.EmitInstruction(OpCodeNumber.Ldc_I4_0); return; } writer.EmitInstruction(OpCodeNumber.Ldarg_0); writer.EmitInstructionMethod( enhancedType.IsValueTypeSafe() == true ? OpCodeNumber.Call : OpCodeNumber.Callvirt, customMethod.GetCanonicalGenericInstance()); }
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 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); }