File: Symbols\AnonymousTypes\AnonymousTypeDescriptor.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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 Microsoft.CodeAnalysis.PooledObjects;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    /// <summary>
    /// Describes anonymous type in terms of fields
    /// </summary>
    internal readonly struct AnonymousTypeDescriptor : IEquatable<AnonymousTypeDescriptor>
    {
        /// <summary> Anonymous type location </summary>
        public readonly Location Location;
 
        /// <summary> Anonymous type fields </summary>
        public readonly ImmutableArray<AnonymousTypeField> Fields;
 
        /// <summary>
        /// Anonymous type descriptor Key 
        /// 
        /// The key is to be used to separate anonymous type templates in an anonymous type symbol cache. 
        /// The type descriptors with the same keys are supposed to map to 'the same' anonymous type 
        /// template in terms of the same generic type being used for their implementation.
        /// </summary>
        public readonly string Key;
 
        public AnonymousTypeDescriptor(ImmutableArray<AnonymousTypeField> fields, Location location)
        {
            this.Fields = fields;
            this.Location = location;
            this.Key = ComputeKey(fields, f => f.Name);
        }
 
        internal static string ComputeKey<T>(ImmutableArray<T> fields, Func<T, string> getName)
        {
            var key = PooledStringBuilder.GetInstance();
            foreach (var field in fields)
            {
                key.Builder.Append('|');
                key.Builder.Append(getName(field));
            }
            return key.ToStringAndFree();
        }
 
        [Conditional("DEBUG")]
        internal void AssertIsGood()
        {
            Debug.Assert(!this.Fields.IsDefault);
 
            foreach (var field in this.Fields)
            {
                field.AssertIsGood();
            }
        }
 
        public bool Equals(AnonymousTypeDescriptor desc)
        {
            return this.Equals(desc, TypeCompareKind.ConsiderEverything);
        }
 
        /// <summary>
        /// Compares two anonymous type descriptors, takes into account fields and types, not locations.
        /// </summary>
        internal bool Equals(AnonymousTypeDescriptor other, TypeCompareKind comparison)
        {
            // Comparing keys ensures field count and field names are the same
            if (this.Key != other.Key)
            {
                return false;
            }
 
            // Compare field types
            return Fields.SequenceEqual(
                other.Fields,
                comparison,
                static (x, y, comparison) => AnonymousTypeField.Equals(x, y, comparison));
        }
 
        /// <summary>
        /// Compares two anonymous type descriptors, takes into account fields and types, not locations.
        /// </summary>
        public override bool Equals(object? obj)
        {
            return obj is AnonymousTypeDescriptor && this.Equals((AnonymousTypeDescriptor)obj, TypeCompareKind.ConsiderEverything);
        }
 
        public override int GetHashCode()
        {
            return this.Key.GetHashCode();
        }
 
        /// <summary>
        /// Creates a new anonymous type descriptor based on 'this' one, 
        /// but having field types passed as an argument.
        /// </summary>
        internal AnonymousTypeDescriptor WithNewFieldsTypes(ImmutableArray<TypeWithAnnotations> newFieldTypes)
        {
            Debug.Assert(!newFieldTypes.IsDefault);
            Debug.Assert(newFieldTypes.Length == this.Fields.Length);
 
            var newFields = Fields.ZipAsArray(newFieldTypes, static (field, type) => field.WithType(type));
            return new AnonymousTypeDescriptor(newFields, this.Location);
        }
 
        internal AnonymousTypeDescriptor SubstituteTypes(AbstractTypeMap map, out bool changed)
        {
            var oldFieldTypes = Fields.SelectAsArray(f => f.TypeWithAnnotations);
            var newFieldTypes = map.SubstituteTypes(oldFieldTypes);
            changed = (oldFieldTypes != newFieldTypes);
            return changed ? WithNewFieldsTypes(newFieldTypes) : this;
        }
    }
}