File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Utilities\SymbolEquivalenceComparer.GetHashCodeVisitor.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.
 
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Shared.Utilities;
 
internal sealed partial class SymbolEquivalenceComparer
{
    private sealed class GetHashCodeVisitor
    {
        private readonly SymbolEquivalenceComparer _symbolEquivalenceComparer;
        private readonly bool _compareMethodTypeParametersByIndex;
        private readonly Func<int, IParameterSymbol, int> _parameterAggregator;
        private readonly Func<int, ISymbol, int> _symbolAggregator;
 
        public GetHashCodeVisitor(
            SymbolEquivalenceComparer symbolEquivalenceComparer,
            bool compareMethodTypeParametersByIndex)
        {
            _symbolEquivalenceComparer = symbolEquivalenceComparer;
            _compareMethodTypeParametersByIndex = compareMethodTypeParametersByIndex;
            _parameterAggregator = (acc, sym) => Hash.Combine(symbolEquivalenceComparer.ParameterEquivalenceComparer.GetHashCode(sym), acc);
            _symbolAggregator = (acc, sym) => GetHashCode(sym, acc);
        }
 
        public int GetHashCode(ISymbol? x, int currentHash)
        {
            if (x == null)
                return 0;
 
            x = UnwrapAlias(x);
 
            // Special case.  If we're comparing signatures then we want to compare 'object'
            // and 'dynamic' as the same.  However, since they're different types, we don't
            // want to bail out using the above check.
 
            if (x.Kind == SymbolKind.DynamicType ||
                (_symbolEquivalenceComparer._objectAndDynamicCompareEqually && IsObjectType(x)))
            {
                return Hash.Combine(GetNullableAnnotationsHashCode((ITypeSymbol)x), Hash.Combine(typeof(IDynamicTypeSymbol), currentHash));
            }
 
            return GetHashCodeWorker(x, currentHash);
        }
 
        private int GetNullableAnnotationsHashCode(ITypeSymbol type)
            => _symbolEquivalenceComparer._ignoreNullableAnnotations ? 0 : ((int)type.NullableAnnotation).GetHashCode();
 
        private int GetHashCodeWorker(ISymbol x, int currentHash)
            => x.Kind switch
            {
                SymbolKind.ArrayType => CombineHashCodes((IArrayTypeSymbol)x, currentHash),
                SymbolKind.Assembly => CombineHashCodes((IAssemblySymbol)x, currentHash),
                SymbolKind.Event => CombineHashCodes((IEventSymbol)x, currentHash),
                SymbolKind.Field => CombineHashCodes((IFieldSymbol)x, currentHash),
                SymbolKind.Label => CombineHashCodes((ILabelSymbol)x, currentHash),
                SymbolKind.Local => CombineHashCodes((ILocalSymbol)x, currentHash),
                SymbolKind.Method => CombineHashCodes((IMethodSymbol)x, currentHash),
                SymbolKind.NetModule => CombineHashCodes((IModuleSymbol)x, currentHash),
                SymbolKind.NamedType => CombineHashCodes((INamedTypeSymbol)x, currentHash),
                SymbolKind.Namespace => CombineHashCodes((INamespaceSymbol)x, currentHash),
                SymbolKind.Parameter => CombineHashCodes((IParameterSymbol)x, currentHash),
                SymbolKind.PointerType => CombineHashCodes((IPointerTypeSymbol)x, currentHash),
                SymbolKind.Property => CombineHashCodes((IPropertySymbol)x, currentHash),
                SymbolKind.RangeVariable => CombineHashCodes((IRangeVariableSymbol)x, currentHash),
                SymbolKind.TypeParameter => CombineHashCodes((ITypeParameterSymbol)x, currentHash),
                SymbolKind.Preprocessing => CombineHashCodes((IPreprocessingSymbol)x, currentHash),
                _ => -1,
            };
 
        private int CombineHashCodes(IArrayTypeSymbol x, int currentHash)
        {
            return
                Hash.Combine(GetNullableAnnotationsHashCode(x),
                Hash.Combine(x.Rank,
                GetHashCode(x.ElementType, currentHash)));
        }
 
        private int CombineHashCodes(IAssemblySymbol x, int currentHash)
            => Hash.Combine(_symbolEquivalenceComparer._assemblyComparer?.GetHashCode(x) ?? 0, currentHash);
 
        private int CombineHashCodes(IFieldSymbol x, int currentHash)
        {
            return
                Hash.Combine(x.Name,
                GetHashCode(x.ContainingSymbol, currentHash));
        }
 
        private static int CombineHashCodes(ILabelSymbol x, int currentHash)
        {
            return
                Hash.Combine(x.Name,
                Hash.Combine(x.Locations.FirstOrDefault(), currentHash));
        }
 
        private static int CombineHashCodes(ILocalSymbol x, int currentHash)
            => Hash.Combine(x.Locations.FirstOrDefault(), currentHash);
 
        private static int CombineHashCodes<T>(ImmutableArray<T> array, int currentHash, Func<int, T, int> func)
            => array.Aggregate<int, T>(currentHash, func);
 
        private int CombineHashCodes(IMethodSymbol x, int currentHash)
        {
            currentHash = Hash.Combine(x.MetadataName, currentHash);
            if (x.MethodKind == MethodKind.AnonymousFunction)
            {
                return Hash.Combine(x.Locations.FirstOrDefault(), currentHash);
            }
 
            currentHash =
                Hash.Combine(IsPartialMethodImplementationPart(x),
                Hash.Combine(IsPartialMethodDefinitionPart(x),
                Hash.Combine(x.IsDefinition,
                Hash.Combine(IsConstructedFromSelf(x),
                Hash.Combine(x.Arity,
                Hash.Combine(x.Parameters.Length,
                Hash.Combine(x.Name, currentHash)))))));
 
            var checkContainingType = CheckContainingType(x);
            if (checkContainingType)
            {
                currentHash = GetHashCode(x.ContainingSymbol, currentHash);
            }
 
            currentHash =
                CombineHashCodes(x.Parameters, currentHash, _parameterAggregator);
 
            return IsConstructedFromSelf(x)
                ? currentHash
                : CombineHashCodes(x.TypeArguments, currentHash, _symbolAggregator);
        }
 
        private int CombineHashCodes(IModuleSymbol x, int currentHash)
            => CombineHashCodes(x.ContainingAssembly, Hash.Combine(x.Name, currentHash));
 
        private int CombineHashCodes(INamedTypeSymbol x, int currentHash)
        {
            currentHash = CombineNamedTypeHashCode(x, currentHash);
 
            if (x is IErrorTypeSymbol errorType)
            {
                foreach (var candidate in errorType.CandidateSymbols)
                {
                    if (candidate is INamedTypeSymbol candidateNamedType)
                    {
                        currentHash = CombineNamedTypeHashCode(candidateNamedType, currentHash);
                    }
                }
            }
 
            return currentHash;
        }
 
        private int CombineNamedTypeHashCode(INamedTypeSymbol x, int currentHash)
        {
            if (x.IsTupleType)
            {
                return Hash.Combine(currentHash, Hash.CombineValues(x.TupleElements));
            }
 
            // If we want object and dynamic to be the same, and this is 'object', then return
            // the same hash we do for 'dynamic'.
            currentHash =
                Hash.Combine((int)GetTypeKind(x),
                Hash.Combine(IsConstructedFromSelf(x),
                Hash.Combine(x.Arity,
                Hash.Combine(x.Name,
                Hash.Combine(x.IsAnonymousType,
                Hash.Combine(x.IsUnboundGenericType,
                Hash.Combine(GetNullableAnnotationsHashCode(x),
                GetHashCode(x.ContainingSymbol, currentHash))))))));
 
            if (x.IsAnonymousType)
            {
                return CombineAnonymousTypeHashCode(x, currentHash);
            }
 
            return IsConstructedFromSelf(x) || x.IsUnboundGenericType
                ? currentHash
                : CombineHashCodes(x.TypeArguments, currentHash, _symbolAggregator);
        }
 
        private int CombineAnonymousTypeHashCode(INamedTypeSymbol x, int currentHash)
        {
            if (x.TypeKind == TypeKind.Delegate)
            {
                return GetHashCode(x.DelegateInvokeMethod, currentHash);
            }
            else
            {
                var xMembers = x.GetValidAnonymousTypeProperties();
 
                return xMembers.Aggregate(currentHash, (a, p) =>
                    {
                        return Hash.Combine(p.Name,
                            Hash.Combine(p.IsReadOnly,
                            GetHashCode(p.Type, a)));
                    });
            }
        }
 
        private int CombineHashCodes(INamespaceSymbol x, int currentHash)
        {
            if (x.IsGlobalNamespace && _symbolEquivalenceComparer._assemblyComparer == null)
            {
                // Exclude global namespace's container's hash when assemblies can differ.
                return Hash.Combine(x.Name, currentHash);
            }
 
            return
                Hash.Combine(x.IsGlobalNamespace,
                Hash.Combine(x.Name,
                GetHashCode(x.ContainingSymbol, currentHash)));
        }
 
        private int CombineHashCodes(IParameterSymbol x, int currentHash)
        {
            return
                Hash.Combine(x.IsRefOrOut(),
                Hash.Combine(x.Name,
                GetHashCode(x.Type,
                GetHashCode(x.ContainingSymbol, currentHash))));
        }
 
        private int CombineHashCodes(IPointerTypeSymbol x, int currentHash)
        {
            return
                Hash.Combine(typeof(IPointerTypeSymbol).GetHashCode(),
                GetHashCode(x.PointedAtType, currentHash));
        }
 
        private int CombineHashCodes(IPropertySymbol x, int currentHash)
        {
            currentHash =
                Hash.Combine(x.IsIndexer,
                Hash.Combine(x.Name,
                Hash.Combine(x.Parameters.Length,
                Hash.Combine(IsPartialMethodImplementationPart(x),
                Hash.Combine(IsPartialMethodDefinitionPart(x),
                GetHashCode(x.ContainingSymbol, currentHash))))));
 
            return CombineHashCodes(x.Parameters, currentHash, _parameterAggregator);
        }
 
        private int CombineHashCodes(IEventSymbol x, int currentHash)
        {
            return
                Hash.Combine(x.Name,
                GetHashCode(x.ContainingSymbol, currentHash));
        }
 
        public int CombineHashCodes(ITypeParameterSymbol x, int currentHash)
        {
            Debug.Assert(
                (x.TypeParameterKind == TypeParameterKind.Method && IsConstructedFromSelf(x.DeclaringMethod!)) ||
                (x.TypeParameterKind == TypeParameterKind.Type && IsConstructedFromSelf(x.ContainingType)) ||
                x.TypeParameterKind == TypeParameterKind.Cref);
 
            currentHash =
                Hash.Combine(x.Ordinal,
                Hash.Combine((int)x.TypeParameterKind, currentHash));
 
            if (x.TypeParameterKind == TypeParameterKind.Method && _compareMethodTypeParametersByIndex)
            {
                return currentHash;
            }
 
            if (x.TypeParameterKind == TypeParameterKind.Type && x.ContainingType.IsAnonymousType)
            {
                // Anonymous type type parameters compare by index as well to prevent
                // recursion.
                return currentHash;
            }
 
            if (x.TypeParameterKind == TypeParameterKind.Cref)
            {
                return currentHash;
            }
 
            return
                GetHashCode(x.ContainingSymbol, currentHash);
        }
 
        private static int CombineHashCodes(IRangeVariableSymbol x, int currentHash)
            => Hash.Combine(x.Locations.FirstOrDefault(), currentHash);
 
        private static int CombineHashCodes(IPreprocessingSymbol x, int currentHash)
            => Hash.Combine(x.GetHashCode(), currentHash);
    }
}