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();
            }
        }
Example #3
0
        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);
        }
Example #7
0
 public EqualsType(TypeDefDeclaration enhancedType, StructuralEqualityAttribute config)
 {
     EnhancedType = enhancedType;
     Config       = config;
 }
Example #8
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);
        }