File: Binder\Semantics\Conversions\Conversion.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 Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// Summarizes whether a conversion is allowed, and if so, which kind of conversion (and in some cases, the
    /// associated symbol).
    /// </summary>
    public readonly struct Conversion : IEquatable<Conversion>, IConvertibleConversion
    {
        private readonly ConversionKind _kind;
        private readonly UncommonData? _uncommonData;
 
        // most conversions are trivial and do not require additional data besides Kind
        // in uncommon cases an instance of this class is attached to the conversion.
        private abstract class UncommonData
        {
        }
 
        private sealed class MethodUncommonData : UncommonData
        {
            public static readonly MethodUncommonData NoApplicableOperators = new MethodUncommonData(
                isExtensionMethod: false,
                isArrayIndex: false,
                conversionResult: UserDefinedConversionResult.NoApplicableOperators(ImmutableArray<UserDefinedConversionAnalysis>.Empty),
                conversionMethod: null);
 
            public MethodUncommonData(
                bool isExtensionMethod,
                bool isArrayIndex,
                UserDefinedConversionResult conversionResult,
                MethodSymbol? conversionMethod)
            {
                _conversionMethod = conversionMethod;
                _conversionResult = conversionResult;
 
                _flags = isExtensionMethod ? IsExtensionMethodMask : (byte)0;
                if (isArrayIndex)
                {
                    _flags |= IsArrayIndexMask;
                }
            }
 
            internal readonly MethodSymbol? _conversionMethod;
 
            //no effect on Equals/GetHashCode
            internal readonly UserDefinedConversionResult _conversionResult;
 
            private const byte IsExtensionMethodMask = 1 << 0;
            private const byte IsArrayIndexMask = 1 << 1;
            private readonly byte _flags;
 
            internal bool IsExtensionMethod
            {
                get
                {
                    return (_flags & IsExtensionMethodMask) != 0;
                }
            }
 
            // used by dynamic, which needs to know if a conversion is an array index conversion.
            internal bool IsArrayIndex
            {
                get
                {
                    return (_flags & IsArrayIndexMask) != 0;
                }
            }
        }
 
        private class NestedUncommonData : UncommonData
        {
            public NestedUncommonData(ImmutableArray<Conversion> nestedConversions)
            {
                _nestedConversionsOpt = nestedConversions;
            }
 
            internal readonly ImmutableArray<Conversion> _nestedConversionsOpt;
#if DEBUG
            internal bool _nestedConversionsChecked;
#endif
        }
 
        private sealed class DeconstructionUncommonData : UncommonData
        {
            internal DeconstructionUncommonData(DeconstructMethodInfo deconstructMethodInfoOpt, ImmutableArray<(BoundValuePlaceholder? placeholder, BoundExpression? conversion)> deconstructConversionInfo)
            {
                Debug.Assert(!deconstructConversionInfo.IsDefaultOrEmpty);
                DeconstructMethodInfo = deconstructMethodInfoOpt;
                DeconstructConversionInfo = deconstructConversionInfo;
            }
 
            internal readonly DeconstructMethodInfo DeconstructMethodInfo;
            internal readonly ImmutableArray<(BoundValuePlaceholder? placeholder, BoundExpression? conversion)> DeconstructConversionInfo;
        }
 
        private sealed class CollectionExpressionUncommonData : NestedUncommonData
        {
            internal CollectionExpressionUncommonData(
                CollectionExpressionTypeKind collectionExpressionTypeKind, TypeSymbol elementType,
                MethodSymbol? constructor, bool constructorUsedInExpandedForm,
                ImmutableArray<Conversion> elementConversions) :
                base(elementConversions)
            {
                Debug.Assert(collectionExpressionTypeKind != CollectionExpressionTypeKind.None);
                Debug.Assert(elementType is { });
                CollectionExpressionTypeKind = collectionExpressionTypeKind;
                ElementType = elementType;
                Constructor = constructor;
                ConstructorUsedInExpandedForm = constructorUsedInExpandedForm;
            }
 
            internal readonly CollectionExpressionTypeKind CollectionExpressionTypeKind;
            internal readonly TypeSymbol ElementType;
            internal readonly MethodSymbol? Constructor;
            internal readonly bool ConstructorUsedInExpandedForm;
        }
 
        internal static Conversion CreateCollectionExpressionConversion(
            CollectionExpressionTypeKind collectionExpressionTypeKind, TypeSymbol elementType,
            MethodSymbol? constructor, bool constructorUsedInExpandedForm,
            ImmutableArray<Conversion> elementConversions)
        {
            return new Conversion(
                ConversionKind.CollectionExpression,
                new CollectionExpressionUncommonData(collectionExpressionTypeKind, elementType, constructor, constructorUsedInExpandedForm, elementConversions));
        }
 
        private Conversion(
            ConversionKind kind,
            UncommonData? uncommonData = null)
        {
            _kind = kind;
            _uncommonData = uncommonData;
        }
 
        internal Conversion(UserDefinedConversionResult conversionResult, bool isImplicit)
        {
            _kind = conversionResult.Kind == UserDefinedConversionResultKind.NoApplicableOperators
                ? ConversionKind.NoConversion
                : isImplicit ? ConversionKind.ImplicitUserDefined : ConversionKind.ExplicitUserDefined;
 
            _uncommonData = conversionResult.Kind == UserDefinedConversionResultKind.NoApplicableOperators && conversionResult.Results.IsEmpty
                ? MethodUncommonData.NoApplicableOperators
                : new MethodUncommonData(
                    isExtensionMethod: false,
                    isArrayIndex: false,
                    conversionResult: conversionResult,
                    conversionMethod: null);
        }
 
        // For the method group, lambda and anonymous method conversions
        internal Conversion(ConversionKind kind, MethodSymbol conversionMethod, bool isExtensionMethod)
        {
            this._kind = kind;
            _uncommonData = new MethodUncommonData(
                isExtensionMethod: isExtensionMethod,
                isArrayIndex: false,
                conversionResult: default,
                conversionMethod: conversionMethod);
        }
 
        internal Conversion(ConversionKind kind, ImmutableArray<Conversion> nestedConversions)
        {
            this._kind = kind;
            _uncommonData = new NestedUncommonData(
                nestedConversions: nestedConversions);
        }
 
        internal Conversion(ConversionKind kind, DeconstructMethodInfo deconstructMethodInfo, ImmutableArray<(BoundValuePlaceholder? placeholder, BoundExpression? conversion)> deconstructConversionInfo)
        {
            Debug.Assert(kind == ConversionKind.Deconstruction);
 
            this._kind = kind;
            _uncommonData = new DeconstructionUncommonData(deconstructMethodInfo, deconstructConversionInfo);
        }
 
        internal Conversion SetConversionMethod(MethodSymbol conversionMethod)
        {
            // we use this method to patch up the conversion method only in two cases - 
            // 1) when rewriting MethodGroup conversions and the method gets substituted.
            // 2) when lowering IntPtr conversion (a compat-related conversion which becomes a kind of a user-defined conversion)
            // 3) when rewriting user-defined conversions and the method gets substituted
            // in those cases it is ok to ignore existing _uncommonData.
            Debug.Assert(_kind is ConversionKind.MethodGroup or ConversionKind.IntPtr or ConversionKind.ImplicitUserDefined or ConversionKind.ExplicitUserDefined);
 
            return new Conversion(this.Kind, conversionMethod, isExtensionMethod: IsExtensionMethod);
        }
 
        internal Conversion SetArrayIndexConversionForDynamic()
        {
            Debug.Assert(_kind.IsDynamic());
            Debug.Assert(_uncommonData == null);
 
            return new Conversion(
                _kind,
                new MethodUncommonData(
                    isExtensionMethod: false,
                    isArrayIndex: true,
                    conversionResult: default,
                    conversionMethod: null));
        }
 
        [Conditional("DEBUG")]
        private static void AssertTrivialConversion(ConversionKind kind)
        {
            bool isTrivial;
 
            switch (kind)
            {
                case ConversionKind.NoConversion:
                case ConversionKind.Identity:
                case ConversionKind.ImplicitConstant:
                case ConversionKind.ImplicitNumeric:
                case ConversionKind.ImplicitReference:
                case ConversionKind.ImplicitEnumeration:
                case ConversionKind.ImplicitThrow:
                case ConversionKind.AnonymousFunction:
                case ConversionKind.Boxing:
                case ConversionKind.NullLiteral:
                case ConversionKind.DefaultLiteral:
                case ConversionKind.ImplicitNullToPointer:
                case ConversionKind.ImplicitPointerToVoid:
                case ConversionKind.ExplicitPointerToPointer:
                case ConversionKind.ExplicitPointerToInteger:
                case ConversionKind.ExplicitIntegerToPointer:
                case ConversionKind.Unboxing:
                case ConversionKind.ExplicitReference:
                case ConversionKind.IntPtr:
                case ConversionKind.ExplicitEnumeration:
                case ConversionKind.ExplicitNumeric:
                case ConversionKind.ImplicitDynamic:
                case ConversionKind.ExplicitDynamic:
                case ConversionKind.InterpolatedString:
                case ConversionKind.InterpolatedStringHandler:
                case ConversionKind.InlineArray:
                case ConversionKind.ImplicitSpan:
                case ConversionKind.ExplicitSpan:
                    isTrivial = true;
                    break;
 
                default:
                    isTrivial = false;
                    break;
            }
 
            RoslynDebug.Assert(isTrivial, $"this conversion needs additional data: {kind}");
        }
 
        internal static Conversion GetTrivialConversion(ConversionKind kind)
        {
            AssertTrivialConversion(kind);
            return new Conversion(kind);
        }
 
        internal static Conversion UnsetConversion => new Conversion(ConversionKind.UnsetConversionKind);
        internal static Conversion NoConversion => new Conversion(ConversionKind.NoConversion);
        internal static Conversion Identity => new Conversion(ConversionKind.Identity);
        internal static Conversion ImplicitConstant => new Conversion(ConversionKind.ImplicitConstant);
        internal static Conversion ImplicitNumeric => new Conversion(ConversionKind.ImplicitNumeric);
        internal static Conversion ImplicitReference => new Conversion(ConversionKind.ImplicitReference);
        internal static Conversion ImplicitEnumeration => new Conversion(ConversionKind.ImplicitEnumeration);
        internal static Conversion ImplicitThrow => new Conversion(ConversionKind.ImplicitThrow);
        internal static Conversion ObjectCreation => new Conversion(ConversionKind.ObjectCreation);
        internal static Conversion CollectionExpression => new Conversion(ConversionKind.CollectionExpression);
        internal static Conversion AnonymousFunction => new Conversion(ConversionKind.AnonymousFunction);
        internal static Conversion Boxing => new Conversion(ConversionKind.Boxing);
        internal static Conversion NullLiteral => new Conversion(ConversionKind.NullLiteral);
        internal static Conversion DefaultLiteral => new Conversion(ConversionKind.DefaultLiteral);
        internal static Conversion NullToPointer => new Conversion(ConversionKind.ImplicitNullToPointer);
        internal static Conversion PointerToVoid => new Conversion(ConversionKind.ImplicitPointerToVoid);
        internal static Conversion PointerToPointer => new Conversion(ConversionKind.ExplicitPointerToPointer);
        internal static Conversion PointerToInteger => new Conversion(ConversionKind.ExplicitPointerToInteger);
        internal static Conversion IntegerToPointer => new Conversion(ConversionKind.ExplicitIntegerToPointer);
        internal static Conversion Unboxing => new Conversion(ConversionKind.Unboxing);
        internal static Conversion ExplicitReference => new Conversion(ConversionKind.ExplicitReference);
        internal static Conversion IntPtr => new Conversion(ConversionKind.IntPtr);
        internal static Conversion ExplicitEnumeration => new Conversion(ConversionKind.ExplicitEnumeration);
        internal static Conversion ExplicitNumeric => new Conversion(ConversionKind.ExplicitNumeric);
        internal static Conversion ImplicitDynamic => new Conversion(ConversionKind.ImplicitDynamic);
        internal static Conversion ExplicitDynamic => new Conversion(ConversionKind.ExplicitDynamic);
        internal static Conversion InterpolatedString => new Conversion(ConversionKind.InterpolatedString);
        internal static Conversion InterpolatedStringHandler => new Conversion(ConversionKind.InterpolatedStringHandler);
        internal static Conversion Deconstruction => new Conversion(ConversionKind.Deconstruction);
        internal static Conversion PinnedObjectToPointer => new Conversion(ConversionKind.PinnedObjectToPointer);
        internal static Conversion ImplicitPointer => new Conversion(ConversionKind.ImplicitPointer);
        internal static Conversion FunctionType => new Conversion(ConversionKind.FunctionType);
        internal static Conversion InlineArray => new Conversion(ConversionKind.InlineArray);
        internal static Conversion ImplicitSpan => new Conversion(ConversionKind.ImplicitSpan);
        internal static Conversion ExplicitSpan => new Conversion(ConversionKind.ExplicitSpan);
 
        // trivial conversions that could be underlying in nullable conversion
        // NOTE: tuple conversions can be underlying as well, but they are not trivial 
        internal static ImmutableArray<Conversion> IdentityUnderlying => ConversionSingletons.IdentityUnderlying;
        internal static ImmutableArray<Conversion> ImplicitConstantUnderlying => ConversionSingletons.ImplicitConstantUnderlying;
        internal static ImmutableArray<Conversion> ImplicitNumericUnderlying => ConversionSingletons.ImplicitNumericUnderlying;
        internal static ImmutableArray<Conversion> ExplicitNumericUnderlying => ConversionSingletons.ExplicitNumericUnderlying;
        internal static ImmutableArray<Conversion> ExplicitEnumerationUnderlying => ConversionSingletons.ExplicitEnumerationUnderlying;
        internal static ImmutableArray<Conversion> PointerToIntegerUnderlying => ConversionSingletons.PointerToIntegerUnderlying;
 
        // Common combinations to avoid allocations:
        internal static readonly Conversion ExplicitNullableWithExplicitEnumerationUnderlying = new Conversion(ConversionKind.ExplicitNullable, ExplicitEnumerationUnderlying);
        internal static readonly Conversion ExplicitNullableWithPointerToIntegerUnderlying = new Conversion(ConversionKind.ExplicitNullable, PointerToIntegerUnderlying);
        internal static readonly Conversion ExplicitNullableWithIdentityUnderlying = new Conversion(ConversionKind.ExplicitNullable, IdentityUnderlying);
        internal static readonly Conversion ExplicitNullableWithImplicitNumericUnderlying = new Conversion(ConversionKind.ExplicitNullable, ImplicitNumericUnderlying);
        internal static readonly Conversion ExplicitNullableWithExplicitNumericUnderlying = new Conversion(ConversionKind.ExplicitNullable, ExplicitNumericUnderlying);
        internal static readonly Conversion ExplicitNullableWithImplicitConstantUnderlying = new Conversion(ConversionKind.ExplicitNullable, ImplicitConstantUnderlying);
 
        internal static readonly Conversion ImplicitNullableWithExplicitEnumerationUnderlying = new Conversion(ConversionKind.ImplicitNullable, ExplicitEnumerationUnderlying);
        internal static readonly Conversion ImplicitNullableWithPointerToIntegerUnderlying = new Conversion(ConversionKind.ImplicitNullable, PointerToIntegerUnderlying);
        internal static readonly Conversion ImplicitNullableWithIdentityUnderlying = new Conversion(ConversionKind.ImplicitNullable, IdentityUnderlying);
        internal static readonly Conversion ImplicitNullableWithImplicitNumericUnderlying = new Conversion(ConversionKind.ImplicitNullable, ImplicitNumericUnderlying);
        internal static readonly Conversion ImplicitNullableWithExplicitNumericUnderlying = new Conversion(ConversionKind.ImplicitNullable, ExplicitNumericUnderlying);
        internal static readonly Conversion ImplicitNullableWithImplicitConstantUnderlying = new Conversion(ConversionKind.ImplicitNullable, ImplicitConstantUnderlying);
 
        // these static fields are not directly inside the Conversion
        // because that causes CLR loader failure.
        private static class ConversionSingletons
        {
            internal static ImmutableArray<Conversion> IdentityUnderlying = ImmutableArray.Create(Identity);
            internal static ImmutableArray<Conversion> ImplicitConstantUnderlying = ImmutableArray.Create(ImplicitConstant);
            internal static ImmutableArray<Conversion> ImplicitNumericUnderlying = ImmutableArray.Create(ImplicitNumeric);
            internal static ImmutableArray<Conversion> ExplicitNumericUnderlying = ImmutableArray.Create(ExplicitNumeric);
            internal static ImmutableArray<Conversion> ExplicitEnumerationUnderlying = ImmutableArray.Create(ExplicitEnumeration);
            internal static ImmutableArray<Conversion> PointerToIntegerUnderlying = ImmutableArray.Create(PointerToInteger);
        }
 
        internal static Conversion MakeStackAllocToPointerType(Conversion underlyingConversion)
        {
            return new Conversion(ConversionKind.StackAllocToPointerType, ImmutableArray.Create(underlyingConversion));
        }
 
        internal static Conversion MakeStackAllocToSpanType(Conversion underlyingConversion)
        {
            return new Conversion(ConversionKind.StackAllocToSpanType, ImmutableArray.Create(underlyingConversion));
        }
 
        internal static Conversion MakeNullableConversion(ConversionKind kind, Conversion nestedConversion)
        {
            Debug.Assert(kind == ConversionKind.ImplicitNullable || kind == ConversionKind.ExplicitNullable);
 
            return nestedConversion.Kind switch
            {
                ConversionKind.Identity => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithIdentityUnderlying : ExplicitNullableWithIdentityUnderlying,
                ConversionKind.ImplicitConstant => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithImplicitConstantUnderlying : ExplicitNullableWithImplicitConstantUnderlying,
                ConversionKind.ImplicitNumeric => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithImplicitNumericUnderlying : ExplicitNullableWithImplicitNumericUnderlying,
                ConversionKind.ExplicitNumeric => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithExplicitNumericUnderlying : ExplicitNullableWithExplicitNumericUnderlying,
                ConversionKind.ExplicitEnumeration => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithExplicitEnumerationUnderlying : ExplicitNullableWithExplicitEnumerationUnderlying,
                ConversionKind.ExplicitPointerToInteger => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithPointerToIntegerUnderlying : ExplicitNullableWithPointerToIntegerUnderlying,
                _ => new Conversion(kind, ImmutableArray.Create(nestedConversion)),
            };
        }
 
        internal static Conversion MakeSwitchExpression(ImmutableArray<Conversion> innerConversions)
        {
            return new Conversion(ConversionKind.SwitchExpression, innerConversions);
        }
 
        internal static Conversion MakeConditionalExpression(ImmutableArray<Conversion> innerConversions)
        {
            return new Conversion(ConversionKind.ConditionalExpression, innerConversions);
        }
 
        internal ConversionKind Kind
        {
            get
            {
                return _kind;
            }
        }
 
        internal bool IsExtensionMethod
        {
            get
            {
                return _uncommonData is MethodUncommonData { IsExtensionMethod: true };
            }
        }
 
        internal bool IsArrayIndex
        {
            get
            {
                return _uncommonData is MethodUncommonData { IsArrayIndex: true };
            }
        }
 
        internal ImmutableArray<Conversion> UnderlyingConversions
        {
            get
            {
                return _uncommonData is NestedUncommonData { _nestedConversionsOpt: var conversions } ?
                    conversions :
                    default(ImmutableArray<Conversion>);
            }
        }
 
        [Conditional("DEBUG")]
        internal void AssertUnderlyingConversionsChecked()
        {
#if DEBUG
            Debug.Assert((_uncommonData as NestedUncommonData)?._nestedConversionsChecked ?? true);
#endif
        }
 
        [Conditional("DEBUG")]
        internal void AssertUnderlyingConversionsCheckedRecursive()
        {
            AssertUnderlyingConversionsChecked();
 
            var underlyingConversions = UnderlyingConversions;
 
            if (!underlyingConversions.IsDefaultOrEmpty)
            {
                foreach (var underlying in underlyingConversions)
                {
                    underlying.AssertUnderlyingConversionsCheckedRecursive();
                }
            }
 
            if (IsUserDefined)
            {
                UserDefinedFromConversion.AssertUnderlyingConversionsCheckedRecursive();
                UserDefinedToConversion.AssertUnderlyingConversionsCheckedRecursive();
            }
        }
 
        [Conditional("DEBUG")]
        internal void MarkUnderlyingConversionsChecked()
        {
#if DEBUG
            if (_uncommonData is NestedUncommonData nestedUncommonData)
            {
                nestedUncommonData._nestedConversionsChecked = true;
            }
#endif
        }
 
        [Conditional("DEBUG")]
        internal void MarkUnderlyingConversionsCheckedRecursive()
        {
#if DEBUG
            if (_uncommonData is not null)
            {
                if (_uncommonData is NestedUncommonData nestedUncommonData)
                {
                    nestedUncommonData._nestedConversionsChecked = true;
                }
 
                var underlyingConversions = UnderlyingConversions;
 
                if (!underlyingConversions.IsDefaultOrEmpty)
                {
                    foreach (var underlying in underlyingConversions)
                    {
                        underlying.MarkUnderlyingConversionsCheckedRecursive();
                    }
                }
 
                if (IsUserDefined)
                {
                    UserDefinedFromConversion.MarkUnderlyingConversionsCheckedRecursive();
                    UserDefinedToConversion.MarkUnderlyingConversionsCheckedRecursive();
                }
            }
#endif
        }
 
        internal MethodSymbol? Method
        {
            get
            {
                switch (_uncommonData)
                {
                    case MethodUncommonData methodUncommonData:
                        if (methodUncommonData._conversionMethod is { })
                        {
                            return methodUncommonData._conversionMethod;
                        }
 
                        var conversionResult = methodUncommonData._conversionResult;
                        if (conversionResult.Kind == UserDefinedConversionResultKind.Valid)
                        {
                            UserDefinedConversionAnalysis analysis = conversionResult.Results[conversionResult.Best];
                            return analysis.Operator;
                        }
                        break;
 
                    case DeconstructionUncommonData deconstructionUncommonData:
                        if (deconstructionUncommonData.DeconstructMethodInfo.Invocation is BoundCall call)
                        {
                            return call.Method;
                        }
                        break;
                }
 
                return null;
            }
        }
 
        internal TypeParameterSymbol? ConstrainedToTypeOpt
        {
            get
            {
                if (_uncommonData is MethodUncommonData { _conversionMethod: null } methodUncommonData)
                {
                    var conversionResult = methodUncommonData._conversionResult;
                    if (conversionResult.Kind == UserDefinedConversionResultKind.Valid)
                    {
                        UserDefinedConversionAnalysis analysis = conversionResult.Results[conversionResult.Best];
                        return analysis.ConstrainedToTypeOpt;
                    }
                }
 
                return null;
            }
        }
 
        internal DeconstructMethodInfo DeconstructionInfo
        {
            get
            {
                var uncommonData = (DeconstructionUncommonData?)_uncommonData;
                return uncommonData == null ? default : uncommonData.DeconstructMethodInfo;
            }
        }
 
        internal ImmutableArray<(BoundValuePlaceholder? placeholder, BoundExpression? conversion)> DeconstructConversionInfo
        {
            get
            {
                var uncommonData = (DeconstructionUncommonData?)_uncommonData;
                return uncommonData == null ? default : uncommonData.DeconstructConversionInfo;
            }
        }
 
        internal CollectionExpressionTypeKind GetCollectionExpressionTypeKind(out TypeSymbol? elementType, out MethodSymbol? constructor, out bool isExpanded)
        {
            if (_uncommonData is CollectionExpressionUncommonData collectionExpressionData)
            {
                elementType = collectionExpressionData.ElementType;
                constructor = collectionExpressionData.Constructor;
                isExpanded = collectionExpressionData.ConstructorUsedInExpandedForm;
                return collectionExpressionData.CollectionExpressionTypeKind;
            }
 
            elementType = null;
            constructor = null;
            isExpanded = false;
            return CollectionExpressionTypeKind.None;
        }
 
        // CONSIDER: public?
        internal bool IsValid
        {
            get
            {
                if (!this.Exists)
                {
                    return false;
                }
 
                if (_uncommonData is NestedUncommonData { _nestedConversionsOpt: { IsDefault: false } nestedConversions })
                {
                    foreach (var conv in nestedConversions)
                    {
                        if (!conv.IsValid)
                        {
                            return false;
                        }
                    }
 
                    Debug.Assert(!this.IsUserDefined);
                    return true;
                }
 
                return !this.IsUserDefined ||
                    this.Method is object ||
                    (_uncommonData as MethodUncommonData)?._conversionResult.Kind == UserDefinedConversionResultKind.Valid;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion exists, either as an implicit or explicit conversion.
        /// </summary>
        /// <remarks>
        /// The existence of a conversion does not necessarily imply that the conversion is valid.
        /// For example, an ambiguous user-defined conversion may exist but may not be valid.
        /// </remarks>
        public bool Exists
        {
            get
            {
                return Kind != ConversionKind.NoConversion;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is implicit.
        /// </summary>
        /// <remarks>
        /// Implicit conversions are described in section 6.1 of the C# language specification.
        /// </remarks>
        public bool IsImplicit
        {
            get
            {
                return Kind.IsImplicitConversion();
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is explicit.
        /// </summary>
        /// <remarks>
        /// Explicit conversions are described in section 6.2 of the C# language specification.
        /// </remarks>
        public bool IsExplicit
        {
            get
            {
                // All conversions are either implicit or explicit.
                return Exists && !IsImplicit;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an identity conversion.
        /// </summary>
        /// <remarks>
        /// Identity conversions are described in section 6.1.1 of the C# language specification.
        /// </remarks>
        public bool IsIdentity
        {
            get
            {
                return Kind == ConversionKind.Identity;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is a stackalloc conversion.
        /// </summary>
        public bool IsStackAlloc
        {
            get
            {
                return Kind == ConversionKind.StackAllocToPointerType || Kind == ConversionKind.StackAllocToSpanType;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit numeric conversion or explicit numeric conversion. 
        /// </summary>
        /// <remarks>
        /// Implicit and explicit numeric conversions are described in sections 6.1.2 and 6.2.1 of the C# language specification.
        /// </remarks>
        public bool IsNumeric
        {
            get
            {
                return Kind == ConversionKind.ImplicitNumeric || Kind == ConversionKind.ExplicitNumeric;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit enumeration conversion or explicit enumeration conversion.
        /// </summary>
        /// <remarks>
        /// Implicit and explicit enumeration conversions are described in sections 6.1.3 and 6.2.2 of the C# language specification.
        /// </remarks>
        public bool IsEnumeration
        {
            get
            {
                return Kind == ConversionKind.ImplicitEnumeration || Kind == ConversionKind.ExplicitEnumeration;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit throw conversion.
        /// </summary>
        public bool IsThrow
        {
            get
            {
                return Kind == ConversionKind.ImplicitThrow;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit object creation expression conversion.
        /// </summary>
        public bool IsObjectCreation
        {
            get
            {
                return Kind == ConversionKind.ObjectCreation;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit collection expression conversion.
        /// </summary>
        public bool IsCollectionExpression => Kind == ConversionKind.CollectionExpression;
 
        /// <summary>
        /// Returns true if the conversion is an implicit switch expression conversion.
        /// </summary>
        public bool IsSwitchExpression
        {
            get
            {
                return Kind == ConversionKind.SwitchExpression;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit conditional expression conversion.
        /// </summary>
        public bool IsConditionalExpression
        {
            get
            {
                return Kind == ConversionKind.ConditionalExpression;
            }
        }
 
        // TODO: update the language reference section number below.
        /// <summary>
        /// Returns true if the conversion is an interpolated string conversion.
        /// </summary>
        /// <remarks>
        /// The interpolated string conversion described in section 6.1.N of the C# language specification.
        /// </remarks>
        public bool IsInterpolatedString
        {
            get
            {
                return Kind == ConversionKind.InterpolatedString;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an interpolated string builder conversion.
        /// </summary>
        public bool IsInterpolatedStringHandler
        {
            get
            {
                return Kind == ConversionKind.InterpolatedStringHandler;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an inline array conversion.
        /// </summary>
        public bool IsInlineArray
        {
            get
            {
                return Kind == ConversionKind.InlineArray;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit nullable conversion or explicit nullable conversion.
        /// </summary>
        /// <remarks>
        /// Implicit and explicit nullable conversions are described in sections 6.1.4 and 6.2.3 of the C# language specification.
        /// </remarks>
        public bool IsNullable
        {
            get
            {
                return Kind == ConversionKind.ImplicitNullable || Kind == ConversionKind.ExplicitNullable;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit tuple literal conversion or explicit tuple literal conversion.
        /// </summary>
        public bool IsTupleLiteralConversion
        {
            get
            {
                return Kind == ConversionKind.ImplicitTupleLiteral || Kind == ConversionKind.ExplicitTupleLiteral;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit tuple conversion or explicit tuple conversion.
        /// </summary>
        public bool IsTupleConversion
        {
            get
            {
                return Kind == ConversionKind.ImplicitTuple || Kind == ConversionKind.ExplicitTuple;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit reference conversion or explicit reference conversion.
        /// </summary>
        /// <remarks>
        /// Implicit and explicit reference conversions are described in sections 6.1.6 and 6.2.4 of the C# language specification.
        /// </remarks>
        public bool IsReference
        {
            get
            {
                return Kind == ConversionKind.ImplicitReference || Kind == ConversionKind.ExplicitReference;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is a span conversion.
        /// </summary>
        /// <remarks>
        /// Span conversion is available since C# 13 as part of the "first-class Span types" feature.
        /// </remarks>
        public bool IsSpan
        {
            get
            {
                return Kind is ConversionKind.ImplicitSpan or ConversionKind.ExplicitSpan;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit user-defined conversion or explicit user-defined conversion.
        /// </summary>
        /// <remarks>
        /// Implicit and explicit user-defined conversions are described in section 6.4 of the C# language specification.
        /// </remarks>
        public bool IsUserDefined
        {
            get
            {
                return Kind.IsUserDefinedConversion();
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit boxing conversion.
        /// </summary>
        /// <remarks>
        /// Implicit boxing conversions are described in section 6.1.7 of the C# language specification.
        /// </remarks>
        public bool IsBoxing
        {
            get
            {
                return Kind == ConversionKind.Boxing;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an explicit unboxing conversion.
        /// </summary>
        /// <remarks>
        /// Explicit unboxing conversions as described in section 6.2.5 of the C# language specification.
        /// </remarks>
        public bool IsUnboxing
        {
            get
            {
                return Kind == ConversionKind.Unboxing;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit null literal conversion.
        /// </summary>
        /// <remarks>
        /// Null literal conversions are described in section 6.1.5 of the C# language specification.
        /// </remarks>
        public bool IsNullLiteral
        {
            get
            {
                return Kind == ConversionKind.NullLiteral;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit default literal conversion.
        /// </summary>
        public bool IsDefaultLiteral
        {
            get
            {
                return Kind == ConversionKind.DefaultLiteral;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit dynamic conversion. 
        /// </summary>
        /// <remarks>
        /// Implicit dynamic conversions are described in section 6.1.8 of the C# language specification.
        /// </remarks>
        public bool IsDynamic
        {
            get
            {
                return Kind.IsDynamic();
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit constant expression conversion.
        /// </summary>
        /// <remarks>
        /// Implicit constant expression conversions are described in section 6.1.9 of the C# language specification.
        /// </remarks>
        public bool IsConstantExpression
        {
            get
            {
                return Kind == ConversionKind.ImplicitConstant;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit anonymous function conversion.
        /// </summary>
        /// <remarks>
        /// Implicit anonymous function conversions are described in section 6.5 of the C# language specification.
        /// </remarks>
        public bool IsAnonymousFunction
        {
            get
            {
                return Kind == ConversionKind.AnonymousFunction;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is an implicit method group conversion.
        /// </summary>
        /// <remarks>
        /// Implicit method group conversions are described in section 6.6 of the C# language specification.
        /// </remarks>
        public bool IsMethodGroup
        {
            get
            {
                return Kind == ConversionKind.MethodGroup;
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is a pointer conversion 
        /// </summary>
        /// <remarks>
        /// Pointer conversions are described in section 18.4 of the C# language specification.
        /// 
        /// Returns true if the conversion is a conversion 
        ///  a) from a pointer type to void*, 
        ///  b) from a pointer type to another pointer type (other than void*),
        ///  c) from the null literal to a pointer type,
        ///  d) from an integral numeric type to a pointer type,
        ///  e) from a pointer type to an integral numeric type, or
        ///  d) from a function pointer type to a function pointer type.
        /// 
        /// Does not return true for user-defined conversions to/from pointer types.
        /// Does not return true for conversions between pointer types and IntPtr/UIntPtr.
        /// </remarks>
        public bool IsPointer
        {
            get
            {
                return this.Kind.IsPointerConversion();
            }
        }
 
        /// <summary>
        /// Returns true if the conversion is a conversion to or from IntPtr or UIntPtr.
        /// </summary>
        /// <remarks>
        /// Returns true if the conversion is a conversion to or from IntPtr or UIntPtr.
        /// This includes:
        ///   IntPtr to/from int
        ///   IntPtr to/from long
        ///   IntPtr to/from void*
        ///   UIntPtr to/from int
        ///   UIntPtr to/from long
        ///   UIntPtr to/from void*
        /// </remarks>
        public bool IsIntPtr
        {
            get
            {
                return Kind == ConversionKind.IntPtr;
            }
        }
 
        /// <summary>
        /// Returns the method used to create the delegate for a method group conversion if <see cref="IsMethodGroup"/> is true 
        /// or the method used to perform the conversion for a user-defined conversion if <see cref="IsUserDefined"/> is true.
        /// Otherwise, returns null.
        /// </summary>
        /// <remarks>
        /// Method group conversions are described in section 6.6 of the C# language specification.
        /// User-defined conversions are described in section 6.4 of the C# language specification.
        /// </remarks>
        public IMethodSymbol? MethodSymbol
        {
            get
            {
                return this.Method.GetPublicSymbol();
            }
        }
 
        /// <summary>
        /// Type parameter which runtime type will be used to resolve virtual invocation of the <see cref="MethodSymbol" />, if any.
        /// Null if <see cref="MethodSymbol" /> is resolved statically, or is null.
        /// </summary>
        public ITypeSymbol? ConstrainedToType
        {
            get
            {
                return this.ConstrainedToTypeOpt.GetPublicSymbol();
            }
        }
 
        /// <summary>
        /// Gives an indication of how successful the conversion was.
        /// Viable - found a best built-in or user-defined conversion.
        /// Empty - found no applicable built-in or user-defined conversions.
        /// OverloadResolutionFailure - found applicable conversions, but no unique best.
        /// </summary>
        internal LookupResultKind ResultKind
        {
            get
            {
                var conversionResult = (_uncommonData as MethodUncommonData)?._conversionResult ?? default(UserDefinedConversionResult);
 
                switch (conversionResult.Kind)
                {
                    case UserDefinedConversionResultKind.Valid:
                        return LookupResultKind.Viable;
                    case UserDefinedConversionResultKind.Ambiguous:
                    case UserDefinedConversionResultKind.NoBestSourceType:
                    case UserDefinedConversionResultKind.NoBestTargetType:
                        return LookupResultKind.OverloadResolutionFailure;
                    case UserDefinedConversionResultKind.NoApplicableOperators:
                        if (conversionResult.Results.IsDefaultOrEmpty)
                        {
                            return this.Kind == ConversionKind.NoConversion ? LookupResultKind.Empty : LookupResultKind.Viable;
                        }
                        else
                        {
                            // CONSIDER: indicating an overload resolution failure is sufficient,
                            // but it would be nice to indicate lack of accessibility or other
                            // error conditions.
                            return LookupResultKind.OverloadResolutionFailure;
                        }
                    default:
                        throw ExceptionUtilities.UnexpectedValue(conversionResult.Kind);
                }
            }
        }
 
        /// <summary>
        /// Conversion applied to operand of the user-defined conversion.
        /// </summary>
        internal Conversion UserDefinedFromConversion
        {
            get
            {
                UserDefinedConversionAnalysis? best = BestUserDefinedConversionAnalysis;
                return best == null ? Conversion.NoConversion : best.SourceConversion;
            }
        }
 
        /// <summary>
        /// Conversion applied to the result of the user-defined conversion.
        /// </summary>
        internal Conversion UserDefinedToConversion
        {
            get
            {
                UserDefinedConversionAnalysis? best = BestUserDefinedConversionAnalysis;
                return best == null ? Conversion.NoConversion : best.TargetConversion;
            }
        }
 
        /// <summary>
        /// The user-defined operators that were considered when attempting this conversion
        /// (i.e. the arguments to overload resolution).
        /// </summary>
        internal ImmutableArray<MethodSymbol> OriginalUserDefinedConversions
        {
            get
            {
                // If overload resolution has failed then we want to stash away the original methods that we 
                // considered so that the IDE can display tooltips or other information about them.
                // However, if a method group contained a generic method that was type inferred then
                // the IDE wants information about the *inferred* method, not the original unconstructed
                // generic method.
 
                if (_uncommonData is MethodUncommonData { _conversionResult: { Kind: not UserDefinedConversionResultKind.NoApplicableOperators } conversionResult })
                {
                    var builder = ArrayBuilder<MethodSymbol>.GetInstance();
                    foreach (var analysis in conversionResult.Results)
                    {
                        builder.Add(analysis.Operator);
                    }
                    return builder.ToImmutableAndFree();
                }
 
                return ImmutableArray<MethodSymbol>.Empty;
            }
        }
 
        internal UserDefinedConversionAnalysis? BestUserDefinedConversionAnalysis
        {
            get
            {
                if (_uncommonData is MethodUncommonData { _conversionResult: { Kind: UserDefinedConversionResultKind.Valid } conversionResult })
                {
                    UserDefinedConversionAnalysis analysis = conversionResult.Results[conversionResult.Best];
                    return analysis;
                }
 
                return null;
            }
        }
 
        /// <summary>
        /// Creates a <see cref="CommonConversion"/> from this C# conversion.
        /// </summary>
        /// <returns>The <see cref="CommonConversion"/> that represents this conversion.</returns>
        /// <remarks>
        /// This is a lossy conversion; it is not possible to recover the original <see cref="Conversion"/>
        /// from the <see cref="CommonConversion"/> struct.
        /// </remarks>
        public CommonConversion ToCommonConversion()
        {
            // The MethodSymbol of CommonConversion only refers to UserDefined conversions, not method groups
            var (methodSymbol, constrainedToType) = IsUserDefined ? (MethodSymbol, ConstrainedToType) : (null, null);
            return new CommonConversion(Exists, IsIdentity, IsNumeric, IsReference, IsImplicit, IsNullable, methodSymbol, constrainedToType);
        }
 
        /// <summary>
        /// Returns a string that represents the <see cref="Kind"/> of the conversion.
        /// </summary>
        /// <returns>A string that represents the <see cref="Kind"/> of the conversion.</returns>
        public override string ToString()
        {
            return this.Kind.ToString();
        }
 
        /// <summary>
        /// Determines whether the specified <see cref="Conversion"/> object is equal to the current <see cref="Conversion"/> object.
        /// </summary>
        /// <param name="obj">The <see cref="Conversion"/> object to compare with the current <see cref="Conversion"/> object.</param>
        /// <returns>true if the specified <see cref="Conversion"/> object is equal to the current <see cref="Conversion"/> object; otherwise, false.</returns>
        public override bool Equals(object? obj)
        {
            return obj is Conversion && this.Equals((Conversion)obj);
        }
 
        /// <summary>
        /// Determines whether the specified <see cref="Conversion"/> object is equal to the current <see cref="Conversion"/> object.
        /// </summary>
        /// <param name="other">The <see cref="Conversion"/> object to compare with the current <see cref="Conversion"/> object.</param>
        /// <returns>true if the specified <see cref="Conversion"/> object is equal to the current <see cref="Conversion"/> object; otherwise, false.</returns>
        public bool Equals(Conversion other)
        {
            return this.Kind == other.Kind && this.Method == other.Method;
        }
 
        /// <summary>
        /// Returns a hash code for the current <see cref="Conversion"/> object.
        /// </summary>
        /// <returns>A hash code for the current <see cref="Conversion"/> object.</returns>
        public override int GetHashCode()
        {
            return Hash.Combine(this.Method, (int)this.Kind);
        }
 
        /// <summary>
        /// Returns true if the specified <see cref="Conversion"/> objects are equal and false otherwise.
        /// </summary>
        /// <param name="left">The first <see cref="Conversion"/> object.</param>
        /// <param name="right">The second <see cref="Conversion"/> object.</param>
        /// <returns></returns>
        public static bool operator ==(Conversion left, Conversion right)
        {
            return left.Equals(right);
        }
 
        /// <summary>
        /// Returns false if the specified <see cref="Conversion"/> objects are equal and true otherwise.
        /// </summary>
        /// <param name="left">The first <see cref="Conversion"/> object.</param>
        /// <param name="right">The second <see cref="Conversion"/> object.</param>
        /// <returns></returns>
        public static bool operator !=(Conversion left, Conversion right)
        {
            return !(left == right);
        }
 
#if DEBUG
        internal string Dump()
        {
            return TreeDumper.DumpCompact(Dump(this));
 
            TreeDumperNode Dump(Conversion self)
            {
                var sub = new System.Collections.Generic.List<TreeDumperNode>();
 
                if (self.Method is object)
                {
                    sub.Add(new TreeDumperNode("method", self.Method.ToDisplayString(), null));
                }
 
                if (!self.DeconstructionInfo.IsDefault)
                {
                    sub.Add(new TreeDumperNode("deconstructionInfo", null,
                        new[] { BoundTreeDumperNodeProducer.MakeTree(self.DeconstructionInfo.Invocation) }));
                }
 
                var underlyingConversions = self.UnderlyingConversions;
                if (!underlyingConversions.IsDefaultOrEmpty)
                {
                    sub.Add(new TreeDumperNode($"underlyingConversions[{underlyingConversions.Length}]", null,
                        underlyingConversions.SelectAsArray(c => Dump(c))));
                }
 
                return new TreeDumperNode("conversion", self.Kind, sub);
            }
        }
#endif
    }
 
    /// <summary>Stores all the information from binding for calling a Deconstruct method.</summary>
    internal readonly struct DeconstructMethodInfo
    {
        internal DeconstructMethodInfo(BoundExpression invocation, BoundDeconstructValuePlaceholder inputPlaceholder,
            ImmutableArray<BoundDeconstructValuePlaceholder> outputPlaceholders)
        {
            Debug.Assert(invocation is not BoundCall { Expanded: true });
            (Invocation, InputPlaceholder, OutputPlaceholders) = (invocation, inputPlaceholder, outputPlaceholders);
        }
 
        internal readonly BoundExpression Invocation;
        internal readonly BoundDeconstructValuePlaceholder InputPlaceholder;
        internal readonly ImmutableArray<BoundDeconstructValuePlaceholder> OutputPlaceholders;
        internal bool IsDefault => Invocation is null;
    }
}