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();
            }
        }
Beispiel #2
0
        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);
        }