File: Symbols\Attributes\CommonAttributeDataComparer.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// Used to determine if two <see cref="AttributeData"/> instances are identical,
    /// i.e. they have the same attribute type, attribute constructor and have identical arguments.
    /// </summary>
    internal sealed class CommonAttributeDataComparer : IEqualityComparer<AttributeData>
    {
        public static CommonAttributeDataComparer Instance = new CommonAttributeDataComparer(considerNamedArgumentsOrder: true);
        public static CommonAttributeDataComparer InstanceIgnoringNamedArgumentOrder = new CommonAttributeDataComparer(considerNamedArgumentsOrder: false);
 
        private readonly bool _considerNamedArgumentsOrder;
        private CommonAttributeDataComparer(bool considerNamedArgumentsOrder)
        {
            this._considerNamedArgumentsOrder = considerNamedArgumentsOrder;
        }
 
        public bool Equals(AttributeData attr1, AttributeData attr2)
        {
            Debug.Assert(attr1 != null);
            Debug.Assert(attr2 != null);
 
            var typedConstantComparer = TypedConstantComparer.IgnoreAll;
            var namedArgumentComparer = NamedArgumentComparer.IgnoreAll;
 
            bool equals = attr1.AttributeClass == attr2.AttributeClass &&
                attr1.AttributeConstructor == attr2.AttributeConstructor &&
                attr1.HasErrors == attr2.HasErrors &&
                attr1.IsConditionallyOmitted == attr2.IsConditionallyOmitted &&
                attr1.CommonConstructorArguments.SequenceEqual(attr2.CommonConstructorArguments, typedConstantComparer) &&
                (_considerNamedArgumentsOrder ? attr1.NamedArguments.SequenceEqual(attr2.NamedArguments, namedArgumentComparer) : attr1.NamedArguments.SetEquals(attr2.NamedArguments, namedArgumentComparer));
 
            Debug.Assert(!equals || GetHashCode(attr1) == GetHashCode(attr2), "If attributes are equal for some options, their hashes must be equal for those same options.");
            return equals;
        }
 
        public int GetHashCode(AttributeData attr)
        {
            Debug.Assert(attr != null);
 
            int hash = attr.AttributeClass?.GetHashCode() ?? 0;
            hash = attr.AttributeConstructor != null ? Hash.Combine(attr.AttributeConstructor.GetHashCode(), hash) : hash;
            hash = Hash.Combine(attr.HasErrors, hash);
            hash = Hash.Combine(attr.IsConditionallyOmitted, hash);
            hash = Hash.Combine(GetHashCodeForConstructorArguments(attr.CommonConstructorArguments), hash);
            hash = Hash.Combine(GetHashCodeForNamedArguments(attr.NamedArguments), hash);
 
            return hash;
        }
 
        private static int GetHashCodeForConstructorArguments(ImmutableArray<TypedConstant> constructorArguments)
        {
            int hash = 0;
 
            foreach (var arg in constructorArguments)
            {
                hash = Hash.Combine(arg.GetHashCode(), hash);
            }
 
            return hash;
        }
 
        private int GetHashCodeForNamedArguments(ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments)
        {
            int hash = 0;
 
            foreach (var arg in namedArguments)
            {
                if (arg.Key != null)
                {
                    hash = hashCombine(arg.Key.GetHashCode(), hash, _considerNamedArgumentsOrder);
                }
 
                hash = hashCombine(arg.Value.GetHashCode(), hash, _considerNamedArgumentsOrder);
            }
 
            return hash;
 
            static int hashCombine(int value, int currentHash, bool considerNamedArgumentsOrder)
            {
                // Prefer Hash.Combine for better distribution, unless we are ignoring the order of named arguments (then we use XOR which is commutative).
                if (!considerNamedArgumentsOrder)
                {
                    return value ^ currentHash;
                }
 
                return Hash.Combine(value, currentHash);
            }
        }
 
        private class TypedConstantComparer : IEqualityComparer<TypedConstant>
        {
            public static readonly TypedConstantComparer IgnoreAll = new TypedConstantComparer();
 
            private TypedConstantComparer()
            {
            }
 
            public bool Equals(TypedConstant x, TypedConstant y)
            {
                bool result = equals(x, y);
                Debug.Assert(!result || GetHashCode(x) == GetHashCode(y), "If TypedConstants are equal, their hashes must be equal.");
                return result;
 
                static bool equals(TypedConstant x, TypedConstant y)
                {
                    if (x.Kind == TypedConstantKind.Type && y.Kind == TypedConstantKind.Type)
                    {
                        return x.ValueInternal is ISymbolInternal xType && y.ValueInternal is ISymbolInternal yType && xType.Equals(yType, TypeCompareKind.AllIgnoreOptions);
                    }
 
                    return x.Equals(y);
                }
            }
 
            public int GetHashCode(TypedConstant obj)
            {
                return obj.GetHashCode();
            }
        }
 
        private class NamedArgumentComparer : IEqualityComparer<KeyValuePair<string, TypedConstant>>
        {
            public static readonly NamedArgumentComparer IgnoreAll = new NamedArgumentComparer();
 
            private NamedArgumentComparer()
            {
            }
 
            public bool Equals(KeyValuePair<string, TypedConstant> pair1, KeyValuePair<string, TypedConstant> pair2)
            {
                bool equals = pair1.Key == pair2.Key && TypedConstantComparer.IgnoreAll.Equals(pair1.Value, pair2.Value);
                Debug.Assert(!equals || GetHashCode(pair1) == GetHashCode(pair2), "If named arguments are equal, their hashes must be equal.");
                return equals;
            }
 
            public int GetHashCode(KeyValuePair<string, TypedConstant> pair)
            {
                return pair.GetHashCode();
            }
        }
    }
}