File: Binder\Binder_Conversions.cs
Web Access
Project: ..\..\..\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.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class Binder
    {
        internal BoundExpression CreateConversion(
            BoundExpression source,
            TypeSymbol destination,
            BindingDiagnosticBag diagnostics)
        {
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            var conversion = Conversions.ClassifyConversionFromExpression(source, destination, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
 
            diagnostics.Add(source.Syntax, useSiteInfo);
            return CreateConversion(source.Syntax, source, conversion, isCast: false, conversionGroupOpt: null, destination: destination, diagnostics: diagnostics);
        }
 
        internal BoundExpression CreateConversion(
            BoundExpression source,
            Conversion conversion,
            TypeSymbol destination,
            BindingDiagnosticBag diagnostics)
        {
            return CreateConversion(source.Syntax, source, conversion, isCast: false, conversionGroupOpt: null, destination: destination, diagnostics: diagnostics);
        }
 
        internal BoundExpression CreateConversion(
            SyntaxNode syntax,
            BoundExpression source,
            Conversion conversion,
            bool isCast,
            ConversionGroup? conversionGroupOpt,
            TypeSymbol destination,
            BindingDiagnosticBag diagnostics)
        {
            return CreateConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt, source.WasCompilerGenerated, destination, diagnostics);
        }
 
        protected BoundExpression CreateConversion(
            SyntaxNode syntax,
            BoundExpression source,
            Conversion conversion,
            bool isCast,
            ConversionGroup? conversionGroupOpt,
            bool wasCompilerGenerated,
            TypeSymbol destination,
            BindingDiagnosticBag diagnostics,
            bool hasErrors = false)
        {
 
            var result = createConversion(syntax, source, conversion, isCast, conversionGroupOpt, wasCompilerGenerated, destination, diagnostics, hasErrors);
 
            Debug.Assert(result is BoundConversion || (conversion.IsIdentity && ((object)result == source) || source.NeedsToBeConverted()) || hasErrors);
 
#if DEBUG
            if (source is BoundValuePlaceholder placeholder1)
            {
                Debug.Assert(filterConversion(conversion));
                Debug.Assert(BoundNode.GetConversion(result, placeholder1) == conversion);
            }
            else if (source.Type is not null && filterConversion(conversion))
            {
                var placeholder2 = new BoundValuePlaceholder(source.Syntax, source.Type);
                var result2 = createConversion(syntax, placeholder2, conversion, isCast, conversionGroupOpt, wasCompilerGenerated, destination, BindingDiagnosticBag.Discarded, hasErrors);
                Debug.Assert(BoundNode.GetConversion(result2, placeholder2) == conversion);
            }
 
            static bool filterConversion(Conversion conversion)
            {
                return !conversion.IsInterpolatedString &&
                       !conversion.IsInterpolatedStringHandler &&
                       !conversion.IsSwitchExpression &&
                       !(conversion.IsTupleLiteralConversion || (conversion.IsNullable && conversion.UnderlyingConversions[0].IsTupleLiteralConversion)) &&
                       (!conversion.IsUserDefined || filterConversion(conversion.UserDefinedFromConversion));
            }
#endif
 
            return result;
 
            BoundExpression createConversion(
                SyntaxNode syntax,
                BoundExpression source,
                Conversion conversion,
                bool isCast,
                ConversionGroup? conversionGroupOpt,
                bool wasCompilerGenerated,
                TypeSymbol destination,
                BindingDiagnosticBag diagnostics,
                bool hasErrors = false)
            {
                RoslynDebug.Assert(source != null);
                RoslynDebug.Assert((object)destination != null);
                RoslynDebug.Assert(!isCast || conversionGroupOpt != null || wasCompilerGenerated);
 
                if (conversion.IsIdentity)
                {
                    if (source is BoundTupleLiteral sourceTuple)
                    {
                        NamedTypeSymbol.ReportTupleNamesMismatchesIfAny(destination, sourceTuple, diagnostics);
                    }
 
                    // identity tuple and switch conversions result in a converted expression
                    // to indicate that such conversions are no longer applicable.
                    source = BindToNaturalType(source, diagnostics);
                    RoslynDebug.Assert(source.Type is object);
 
                    // We need to preserve any conversion that changes the type (even identity conversions, like object->dynamic),
                    // or that was explicitly written in code (so that GetSemanticInfo can find the syntax in the bound tree).
                    if (!isCast && source.Type.Equals(destination, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes))
                    {
                        return source;
                    }
                }
 
                if (conversion.IsMethodGroup)
                {
                    return CreateMethodGroupConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt, destination, diagnostics);
                }
 
                // Obsolete diagnostics for method group are reported as part of creating the method group conversion.
                reportUseSiteDiagnostics(conversion);
 
                if (conversion.IsAnonymousFunction && source.Kind == BoundKind.UnboundLambda)
                {
                    return CreateAnonymousFunctionConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt, destination, diagnostics);
                }
 
                if (conversion.Kind == ConversionKind.FunctionType)
                {
                    return CreateFunctionTypeConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt, destination, diagnostics);
                }
 
                if (conversion.IsStackAlloc)
                {
                    return CreateStackAllocConversion(syntax, source, conversion, isCast, conversionGroupOpt, destination, diagnostics);
                }
 
                if (conversion.IsTupleLiteralConversion ||
                    (conversion.IsNullable && conversion.UnderlyingConversions[0].IsTupleLiteralConversion))
                {
                    return CreateTupleLiteralConversion(syntax, (BoundTupleLiteral)source, conversion, isCast: isCast, conversionGroupOpt, destination, diagnostics);
                }
 
                if (conversion.Kind == ConversionKind.SwitchExpression)
                {
                    var convertedSwitch = ConvertSwitchExpression((BoundUnconvertedSwitchExpression)source, destination, conversionIfTargetTyped: conversion, diagnostics);
                    return new BoundConversion(
                        syntax,
                        convertedSwitch,
                        conversion,
                        CheckOverflowAtRuntime,
                        explicitCastInCode: isCast && !wasCompilerGenerated,
                        conversionGroupOpt,
                        convertedSwitch.ConstantValueOpt,
                        destination,
                        hasErrors);
                }
 
                if (conversion.Kind == ConversionKind.ConditionalExpression)
                {
                    var convertedConditional = ConvertConditionalExpression((BoundUnconvertedConditionalOperator)source, destination, conversionIfTargetTyped: conversion, diagnostics);
                    return new BoundConversion(
                        syntax,
                        convertedConditional,
                        conversion,
                        CheckOverflowAtRuntime,
                        explicitCastInCode: isCast && !wasCompilerGenerated,
                        conversionGroupOpt,
                        convertedConditional.ConstantValueOpt,
                        destination,
                        hasErrors);
                }
 
                if (conversion.Kind == ConversionKind.InterpolatedString)
                {
                    var unconvertedSource = (BoundUnconvertedInterpolatedString)source;
                    source = new BoundInterpolatedString(
                        unconvertedSource.Syntax,
                        interpolationData: null,
                        BindInterpolatedStringParts(unconvertedSource, diagnostics),
                        unconvertedSource.ConstantValueOpt,
                        unconvertedSource.Type,
                        unconvertedSource.HasErrors);
                }
 
                if (conversion.Kind == ConversionKind.InterpolatedStringHandler)
                {
                    return new BoundConversion(
                        syntax,
                        BindUnconvertedInterpolatedExpressionToHandlerType(source, (NamedTypeSymbol)destination, diagnostics),
                        conversion,
                        @checked: CheckOverflowAtRuntime,
                        explicitCastInCode: isCast && !wasCompilerGenerated,
                        conversionGroupOpt,
                        constantValueOpt: null,
                        destination);
                }
 
                if (source.Kind == BoundKind.UnconvertedSwitchExpression)
                {
                    TypeSymbol? type = source.Type;
                    if (type is null)
                    {
                        Debug.Assert(!conversion.Exists);
                        type = CreateErrorType();
                        hasErrors = true;
                    }
 
                    source = ConvertSwitchExpression((BoundUnconvertedSwitchExpression)source, type, conversionIfTargetTyped: null, diagnostics, hasErrors);
                    if (destination.Equals(type, TypeCompareKind.ConsiderEverything) && wasCompilerGenerated)
                    {
                        return source;
                    }
                }
 
                if (conversion.IsObjectCreation)
                {
                    return ConvertObjectCreationExpression(syntax, (BoundUnconvertedObjectCreationExpression)source, conversion, isCast, destination, conversionGroupOpt, wasCompilerGenerated, diagnostics);
                }
 
                if (source.Kind == BoundKind.UnconvertedConditionalOperator)
                {
                    Debug.Assert(source.Type is null);
                    Debug.Assert(!conversion.Exists);
                    hasErrors = true;
 
                    source = ConvertConditionalExpression((BoundUnconvertedConditionalOperator)source, CreateErrorType(), conversionIfTargetTyped: null, diagnostics, hasErrors);
                }
 
                if (conversion.IsUserDefined)
                {
                    // User-defined conversions are likely to be represented as multiple
                    // BoundConversion instances so a ConversionGroup is necessary.
                    return CreateUserDefinedConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt ?? new ConversionGroup(conversion), destination, diagnostics, hasErrors);
                }
 
                ConstantValue? constantValue = this.FoldConstantConversion(syntax, source, conversion, destination, diagnostics);
                if (conversion.Kind == ConversionKind.DefaultLiteral)
                {
                    source = new BoundDefaultExpression(source.Syntax, targetType: null, constantValue, type: destination)
                        .WithSuppression(source.IsSuppressed);
                }
 
                reportUseSiteDiagnosticsForUnderlyingConversions(conversion);
 
                return new BoundConversion(
                    syntax,
                    BindToNaturalType(source, diagnostics),
                    conversion,
                    @checked: CheckOverflowAtRuntime,
                    explicitCastInCode: isCast && !wasCompilerGenerated,
                    conversionGroupOpt,
                    constantValueOpt: constantValue,
                    type: destination,
                    hasErrors: hasErrors)
                { WasCompilerGenerated = wasCompilerGenerated };
 
                void reportUseSiteDiagnostics(Conversion conversion)
                {
                    // Obsolete diagnostics for method group are reported as part of creating the method group conversion.
                    Debug.Assert(!conversion.IsMethodGroup);
                    ReportDiagnosticsIfObsolete(diagnostics, conversion, syntax, hasBaseReceiver: false);
                    if (conversion.Method is not null)
                    {
                        ReportUseSite(conversion.Method, diagnostics, syntax.Location);
                    }
                    CheckConstraintLanguageVersionAndRuntimeSupportForConversion(syntax, conversion, diagnostics);
                }
 
                void reportUseSiteDiagnosticsForUnderlyingConversions(Conversion conversion)
                {
                    var underlyingConversions = conversion.UnderlyingConversions;
 
                    if (!underlyingConversions.IsDefaultOrEmpty)
                    {
                        foreach (var underlying in underlyingConversions)
                        {
                            reportUseSiteDiagnosticsForSelfAndUnderlyingConversions(underlying);
 
                            if (underlying.IsUserDefined)
                            {
                                reportUseSiteDiagnosticsForSelfAndUnderlyingConversions(underlying.UserDefinedFromConversion);
                                reportUseSiteDiagnosticsForSelfAndUnderlyingConversions(underlying.UserDefinedToConversion);
                                underlying.MarkUnderlyingConversionsChecked();
                            }
                        }
 
                        conversion.MarkUnderlyingConversionsChecked();
                    }
 
                    void reportUseSiteDiagnosticsForSelfAndUnderlyingConversions(Conversion conversion)
                    {
                        reportUseSiteDiagnostics(conversion);
                        reportUseSiteDiagnosticsForUnderlyingConversions(conversion);
                    }
                }
            }
        }
 
        internal void CheckConstraintLanguageVersionAndRuntimeSupportForConversion(SyntaxNodeOrToken syntax, Conversion conversion, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(syntax.SyntaxTree is object);
 
            if (conversion.IsUserDefined && conversion.Method is MethodSymbol method && method.IsStatic)
            {
                if (method.IsAbstract || method.IsVirtual)
                {
                    Debug.Assert(conversion.ConstrainedToTypeOpt is TypeParameterSymbol);
 
                    if (Compilation.SourceModule != method.ContainingModule)
                    {
                        CheckFeatureAvailability(syntax.SyntaxTree, MessageID.IDS_FeatureStaticAbstractMembersInInterfaces, diagnostics, syntax.GetLocation()!);
 
                        if (!Compilation.Assembly.RuntimeSupportsStaticAbstractMembersInInterfaces)
                        {
                            Error(diagnostics, ErrorCode.ERR_RuntimeDoesNotSupportStaticAbstractMembersInInterfaces, syntax);
                        }
                    }
                }
 
                if (SyntaxFacts.IsCheckedOperator(method.Name) &&
                    Compilation.SourceModule != method.ContainingModule)
                {
                    CheckFeatureAvailability(syntax.SyntaxTree, MessageID.IDS_FeatureCheckedUserDefinedOperators, diagnostics, syntax.GetLocation()!);
                }
            }
        }
 
        private static BoundExpression ConvertObjectCreationExpression(
            SyntaxNode syntax, BoundUnconvertedObjectCreationExpression node, Conversion conversion, bool isCast, TypeSymbol destination,
            ConversionGroup? conversionGroupOpt, bool wasCompilerGenerated, BindingDiagnosticBag diagnostics)
        {
            var arguments = AnalyzedArguments.GetInstance(node.Arguments, node.ArgumentRefKindsOpt, node.ArgumentNamesOpt);
            BoundExpression expr = bindObjectCreationExpression(node.Syntax, node.InitializerOpt, node.Binder, destination.StrippedType(), arguments, diagnostics);
            arguments.Free();
 
            Debug.Assert(expr is BoundObjectCreationExpressionBase { WasTargetTyped: true } or
                                 BoundDelegateCreationExpression { WasTargetTyped: true } or
                                 BoundBadExpression);
 
            // Assert that the shape of the BoundBadExpression is sound and is not going to confuse NullableWalker for target-typed 'new'.
            Debug.Assert(expr is not BoundBadExpression { ChildBoundNodes: var children } || !children.Any((child, node) => child.Syntax == node.Syntax, node));
 
            if (wasCompilerGenerated)
            {
                expr.MakeCompilerGenerated();
            }
 
            expr = new BoundConversion(
                                  syntax,
                                  expr,
                                  expr is BoundBadExpression ? Conversion.NoConversion : conversion,
                                  node.Binder.CheckOverflowAtRuntime,
                                  explicitCastInCode: isCast && !wasCompilerGenerated,
                                  conversionGroupOpt,
                                  expr.ConstantValueOpt,
                                  destination)
            { WasCompilerGenerated = wasCompilerGenerated };
 
            return expr;
 
            static BoundExpression bindObjectCreationExpression(
                SyntaxNode syntax, InitializerExpressionSyntax? initializerOpt, Binder binder,
                TypeSymbol type, AnalyzedArguments arguments, BindingDiagnosticBag diagnostics)
            {
                switch (type.TypeKind)
                {
                    case TypeKind.Enum:
                    case TypeKind.Struct:
                    case TypeKind.Class when !type.IsAnonymousType: // We don't want to enable object creation with unspeakable types
                        return binder.BindClassCreationExpression(syntax, type.Name, typeNode: syntax, (NamedTypeSymbol)type, arguments, diagnostics, initializerOpt, wasTargetTyped: true);
                    case TypeKind.TypeParameter:
                        return binder.BindTypeParameterCreationExpression(syntax, (TypeParameterSymbol)type, arguments, initializerOpt, typeSyntax: syntax, wasTargetTyped: true, diagnostics);
                    case TypeKind.Delegate:
                        return binder.BindDelegateCreationExpression(syntax, (NamedTypeSymbol)type, arguments, initializerOpt, wasTargetTyped: true, diagnostics);
                    case TypeKind.Interface:
                        return binder.BindInterfaceCreationExpression(syntax, (NamedTypeSymbol)type, diagnostics, typeNode: syntax, arguments, initializerOpt, wasTargetTyped: true);
                    case TypeKind.Array:
                    case TypeKind.Class:
                    case TypeKind.Dynamic:
                        Error(diagnostics, ErrorCode.ERR_ImplicitObjectCreationIllegalTargetType, syntax, type);
                        goto case TypeKind.Error;
                    case TypeKind.Pointer:
                    case TypeKind.FunctionPointer:
                        Error(diagnostics, ErrorCode.ERR_UnsafeTypeInObjectCreation, syntax, type);
                        goto case TypeKind.Error;
                    case TypeKind.Error:
                        return binder.MakeBadExpressionForObjectCreation(syntax, type, arguments, initializerOpt, typeSyntax: syntax, diagnostics);
                    case var v:
                        throw ExceptionUtilities.UnexpectedValue(v);
                }
            }
        }
 
        /// <summary>
        /// Rewrite the subexpressions in a conditional expression to convert the whole thing to the destination type.
        /// </summary>
        private BoundExpression ConvertConditionalExpression(
            BoundUnconvertedConditionalOperator source,
            TypeSymbol destination,
            Conversion? conversionIfTargetTyped,
            BindingDiagnosticBag diagnostics,
            bool hasErrors = false)
        {
            bool targetTyped = conversionIfTargetTyped is { };
            Debug.Assert(targetTyped || destination.IsErrorType() || destination.Equals(source.Type, TypeCompareKind.ConsiderEverything));
            ImmutableArray<Conversion> underlyingConversions = conversionIfTargetTyped.GetValueOrDefault().UnderlyingConversions;
            var condition = source.Condition;
            hasErrors |= source.HasErrors || destination.IsErrorType();
 
            var trueExpr =
                targetTyped
                ? CreateConversion(source.Consequence.Syntax, source.Consequence, underlyingConversions[0], isCast: false, conversionGroupOpt: null, destination, diagnostics)
                : GenerateConversionForAssignment(destination, source.Consequence, diagnostics);
            var falseExpr =
                targetTyped
                ? CreateConversion(source.Alternative.Syntax, source.Alternative, underlyingConversions[1], isCast: false, conversionGroupOpt: null, destination, diagnostics)
                : GenerateConversionForAssignment(destination, source.Alternative, diagnostics);
            var constantValue = FoldConditionalOperator(condition, trueExpr, falseExpr);
            hasErrors |= constantValue?.IsBad == true;
            if (targetTyped && !destination.IsErrorType() && !Compilation.IsFeatureEnabled(MessageID.IDS_FeatureTargetTypedConditional))
            {
                diagnostics.Add(
                    ErrorCode.ERR_NoImplicitConvTargetTypedConditional,
                    source.Syntax.Location,
                    Compilation.LanguageVersion.ToDisplayString(),
                    source.Consequence.Display,
                    source.Alternative.Display,
                    new CSharpRequiredLanguageVersion(MessageID.IDS_FeatureTargetTypedConditional.RequiredVersion()));
            }
 
            return new BoundConditionalOperator(source.Syntax, isRef: false, condition, trueExpr, falseExpr, constantValue, source.Type, wasTargetTyped: targetTyped, destination, hasErrors)
                .WithSuppression(source.IsSuppressed);
        }
 
        /// <summary>
        /// Rewrite the expressions in the switch expression arms to add a conversion to the destination type.
        /// </summary>
        private BoundExpression ConvertSwitchExpression(BoundUnconvertedSwitchExpression source, TypeSymbol destination, Conversion? conversionIfTargetTyped, BindingDiagnosticBag diagnostics, bool hasErrors = false)
        {
            bool targetTyped = conversionIfTargetTyped is { };
            Conversion conversion = conversionIfTargetTyped ?? Conversion.Identity;
            Debug.Assert(targetTyped || destination.IsErrorType() || destination.Equals(source.Type, TypeCompareKind.ConsiderEverything));
            ImmutableArray<Conversion> underlyingConversions = conversion.UnderlyingConversions;
            var builder = ArrayBuilder<BoundSwitchExpressionArm>.GetInstance(source.SwitchArms.Length);
            for (int i = 0, n = source.SwitchArms.Length; i < n; i++)
            {
                var oldCase = source.SwitchArms[i];
                var oldValue = oldCase.Value;
                var newValue =
                    targetTyped
                    ? CreateConversion(oldValue.Syntax, oldValue, underlyingConversions[i], isCast: false, conversionGroupOpt: null, destination, diagnostics)
                    : GenerateConversionForAssignment(destination, oldValue, diagnostics);
                var newCase = (oldValue == newValue) ? oldCase :
                    new BoundSwitchExpressionArm(oldCase.Syntax, oldCase.Locals, oldCase.Pattern, oldCase.WhenClause, newValue, oldCase.Label, oldCase.HasErrors);
                builder.Add(newCase);
            }
 
            var newSwitchArms = builder.ToImmutableAndFree();
            return new BoundConvertedSwitchExpression(
                source.Syntax, source.Type, targetTyped, source.Expression, newSwitchArms, source.ReachabilityDecisionDag,
                source.DefaultLabel, source.ReportedNotExhaustive, destination, hasErrors || source.HasErrors).WithSuppression(source.IsSuppressed);
        }
 
        private BoundExpression CreateUserDefinedConversion(
            SyntaxNode syntax,
            BoundExpression source,
            Conversion conversion,
            bool isCast,
            ConversionGroup conversionGroup,
            TypeSymbol destination,
            BindingDiagnosticBag diagnostics,
            bool hasErrors)
        {
            Debug.Assert(conversionGroup != null);
            Debug.Assert(conversion.IsUserDefined);
 
            conversion.MarkUnderlyingConversionsChecked();
            if (!conversion.IsValid)
            {
                if (!hasErrors)
                    GenerateImplicitConversionError(diagnostics, syntax, conversion, source, destination);
 
                return new BoundConversion(
                    syntax,
                    source,
                    conversion,
                    CheckOverflowAtRuntime,
                    explicitCastInCode: isCast,
                    conversionGroup,
                    constantValueOpt: ConstantValue.NotAvailable,
                    type: destination,
                    hasErrors: true)
                { WasCompilerGenerated = source.WasCompilerGenerated };
            }
 
            // Due to an oddity in the way we create a non-lifted user-defined conversion from A to D? 
            // (required backwards compatibility with the native compiler) we can end up in a situation 
            // where we have:
            // a standard conversion from A to B?
            // then a standard conversion from B? to B
            // then a user-defined  conversion from B to C
            // then a standard conversion from C to C? 
            // then a standard conversion from C? to D?
            //
            // In that scenario, the "from type" of the conversion will be B? and the "from conversion" will be 
            // from A to B?. Similarly the "to type" of the conversion will be C? and the "to conversion"
            // of the conversion will be from C? to D?.
            //
            // Therefore, we might need to introduce an extra conversion on the source side, from B? to B.
            // Now, you might think we should also introduce an extra conversion on the destination side,
            // from C to C?. But that then gives us the following bad situation: If we in fact bind this as
            //
            // (D?)(C?)(C)(B)(B?)(A)x 
            //
            // then what we are in effect doing is saying "convert C? to D? by checking for null, unwrapping,
            // converting C to D, and then wrapping". But we know that the C? will never be null. In this case
            // we should actually generate
            //
            // (D?)(C)(B)(B?)(A)x
            //
            // And thereby skip the unnecessary nullable conversion.
 
            Debug.Assert(conversion.BestUserDefinedConversionAnalysis is object); // All valid user-defined conversions have this populated
 
            // Original expression --> conversion's "from" type
            BoundExpression convertedOperand = CreateConversion(
                syntax: source.Syntax,
                source: source,
                conversion: conversion.UserDefinedFromConversion,
                isCast: false,
                conversionGroupOpt: conversionGroup,
                wasCompilerGenerated: false,
                destination: conversion.BestUserDefinedConversionAnalysis.FromType,
                diagnostics: diagnostics);
 
            TypeSymbol conversionParameterType = conversion.BestUserDefinedConversionAnalysis.Operator.GetParameterType(0);
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
 
            if (conversion.BestUserDefinedConversionAnalysis.Kind == UserDefinedConversionAnalysisKind.ApplicableInNormalForm &&
                !TypeSymbol.Equals(conversion.BestUserDefinedConversionAnalysis.FromType, conversionParameterType, TypeCompareKind.ConsiderEverything2))
            {
                // Conversion's "from" type --> conversion method's parameter type.
                convertedOperand = CreateConversion(
                    syntax: syntax,
                    source: convertedOperand,
                    conversion: Conversions.ClassifyStandardConversion(convertedOperand.Type, conversionParameterType, ref useSiteInfo),
                    isCast: false,
                    conversionGroupOpt: conversionGroup,
                    wasCompilerGenerated: true,
                    destination: conversionParameterType,
                    diagnostics: diagnostics);
            }
 
            BoundExpression userDefinedConversion;
 
            TypeSymbol conversionReturnType = conversion.BestUserDefinedConversionAnalysis.Operator.ReturnType;
            TypeSymbol conversionToType = conversion.BestUserDefinedConversionAnalysis.ToType;
            Conversion toConversion = conversion.UserDefinedToConversion;
 
            if (conversion.BestUserDefinedConversionAnalysis.Kind == UserDefinedConversionAnalysisKind.ApplicableInNormalForm &&
                !TypeSymbol.Equals(conversionToType, conversionReturnType, TypeCompareKind.ConsiderEverything2))
            {
                // Conversion method's parameter type --> conversion method's return type
                // NB: not calling CreateConversion here because this is the recursive base case.
                userDefinedConversion = new BoundConversion(
                    syntax,
                    convertedOperand,
                    conversion,
                    @checked: CheckOverflowAtRuntime,
                    explicitCastInCode: isCast,
                    conversionGroup,
                    constantValueOpt: ConstantValue.NotAvailable,
                    type: conversionReturnType)
                { WasCompilerGenerated = true };
 
                if (conversionToType.IsNullableType() && TypeSymbol.Equals(conversionToType.GetNullableUnderlyingType(), conversionReturnType, TypeCompareKind.ConsiderEverything2))
                {
                    // Skip introducing the conversion from C to C?.  The "to" conversion is now wrong though,
                    // because it will still assume converting C? to D?. 
 
                    toConversion = Conversions.ClassifyConversionFromType(conversionReturnType, destination, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
                    Debug.Assert(toConversion.Exists);
                }
                else
                {
                    // Conversion method's return type --> conversion's "to" type
                    userDefinedConversion = CreateConversion(
                        syntax: syntax,
                        source: userDefinedConversion,
                        conversion: Conversions.ClassifyStandardConversion(conversionReturnType, conversionToType, ref useSiteInfo),
                        isCast: false,
                        conversionGroupOpt: conversionGroup,
                        wasCompilerGenerated: true,
                        destination: conversionToType,
                        diagnostics: diagnostics);
                }
            }
            else
            {
                // Conversion method's parameter type --> conversion method's "to" type
                // NB: not calling CreateConversion here because this is the recursive base case.
                userDefinedConversion = new BoundConversion(
                    syntax,
                    convertedOperand,
                    conversion,
                    @checked: CheckOverflowAtRuntime,
                    explicitCastInCode: isCast,
                    conversionGroup,
                    constantValueOpt: ConstantValue.NotAvailable,
                    type: conversionToType)
                { WasCompilerGenerated = true };
            }
 
            diagnostics.Add(syntax, useSiteInfo);
 
            // Conversion's "to" type --> final type
            BoundExpression finalConversion = CreateConversion(
                syntax: syntax,
                source: userDefinedConversion,
                conversion: toConversion,
                isCast: false,
                conversionGroupOpt: conversionGroup,
                wasCompilerGenerated: true, // NOTE: doesn't necessarily set flag on resulting bound expression.
                destination: destination,
                diagnostics: diagnostics);
 
            finalConversion.ResetCompilerGenerated(source.WasCompilerGenerated);
 
            return finalConversion;
        }
 
        private BoundExpression CreateFunctionTypeConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(conversion.Kind == ConversionKind.FunctionType);
            Debug.Assert(source.Kind is BoundKind.MethodGroup or BoundKind.UnboundLambda);
            Debug.Assert(syntax.IsFeatureEnabled(MessageID.IDS_FeatureInferredDelegateType));
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            var delegateType = source.GetInferredDelegateType(ref useSiteInfo);
            Debug.Assert(delegateType is { });
 
            if (source.Kind == BoundKind.UnboundLambda &&
                destination.IsNonGenericExpressionType())
            {
                delegateType = Compilation.GetWellKnownType(WellKnownType.System_Linq_Expressions_Expression_T).Construct(delegateType);
                delegateType.AddUseSiteInfo(ref useSiteInfo);
            }
 
            conversion = Conversions.ClassifyConversionFromExpression(source, delegateType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
            bool warnOnMethodGroupConversion =
                source.Kind == BoundKind.MethodGroup &&
                !isCast &&
                conversion.Exists &&
                destination.SpecialType == SpecialType.System_Object;
            BoundExpression expr;
            if (!conversion.Exists)
            {
                GenerateImplicitConversionError(diagnostics, syntax, conversion, source, delegateType);
                expr = new BoundConversion(syntax, source, conversion, @checked: false, explicitCastInCode: isCast, conversionGroup, constantValueOpt: ConstantValue.NotAvailable, type: delegateType, hasErrors: true) { WasCompilerGenerated = source.WasCompilerGenerated };
            }
            else
            {
                expr = CreateConversion(syntax, source, conversion, isCast, conversionGroup, delegateType, diagnostics);
            }
 
            conversion = Conversions.ClassifyConversionFromExpression(expr, destination, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
            if (!conversion.Exists)
            {
                GenerateImplicitConversionError(diagnostics, syntax, conversion, source, destination);
            }
            else if (warnOnMethodGroupConversion)
            {
                Error(diagnostics, ErrorCode.WRN_MethGrpToNonDel, syntax, ((BoundMethodGroup)source).Name, destination);
            }
 
            diagnostics.Add(syntax, useSiteInfo);
            return CreateConversion(syntax, expr, conversion, isCast, conversionGroup, destination, diagnostics);
        }
 
        private BoundExpression CreateAnonymousFunctionConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
        {
            // We have a successful anonymous function conversion; rather than producing a node
            // which is a conversion on top of an unbound lambda, replace it with the bound
            // lambda.
 
            // UNDONE: Figure out what to do about the error case, where a lambda
            // UNDONE: is converted to a delegate that does not match. What to surface then?
 
            var unboundLambda = (UnboundLambda)source;
            var boundLambda = unboundLambda.Bind((NamedTypeSymbol)destination, isExpressionTree: destination.IsGenericOrNonGenericExpressionType(out _)).WithInAnonymousFunctionConversion();
            diagnostics.AddRange(boundLambda.Diagnostics);
 
            CheckValidScopedMethodConversion(syntax, boundLambda.Symbol, destination, invokedAsExtensionMethod: false, diagnostics);
            CheckLambdaConversion(boundLambda.Symbol, destination, diagnostics);
            return new BoundConversion(
                syntax,
                boundLambda,
                conversion,
                @checked: false,
                explicitCastInCode: isCast,
                conversionGroup,
                constantValueOpt: ConstantValue.NotAvailable,
                type: destination)
            { WasCompilerGenerated = source.WasCompilerGenerated };
        }
 
        private BoundExpression CreateMethodGroupConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
        {
            var (originalGroup, isAddressOf) = source switch
            {
                BoundMethodGroup m => (m, false),
                BoundUnconvertedAddressOfOperator { Operand: { } m } => (m, true),
                _ => throw ExceptionUtilities.UnexpectedValue(source),
            };
            BoundMethodGroup group = FixMethodGroupWithTypeOrValue(originalGroup, conversion, diagnostics);
            bool hasErrors = false;
 
            if (MethodGroupConversionHasErrors(syntax, conversion, group.ReceiverOpt, conversion.IsExtensionMethod, isAddressOf, destination, diagnostics))
            {
                hasErrors = true;
            }
 
            return new BoundConversion(syntax, group, conversion, @checked: false, explicitCastInCode: isCast, conversionGroup, constantValueOpt: ConstantValue.NotAvailable, type: destination, hasErrors: hasErrors) { WasCompilerGenerated = group.WasCompilerGenerated };
        }
 
        private static void CheckValidScopedMethodConversion(SyntaxNode syntax, MethodSymbol lambdaOrMethod, TypeSymbol targetType, bool invokedAsExtensionMethod, BindingDiagnosticBag diagnostics)
        {
            MethodSymbol? delegateMethod;
            if (targetType.GetDelegateType() is { } delegateType)
            {
                delegateMethod = delegateType.DelegateInvokeMethod;
            }
            else if (targetType is FunctionPointerTypeSymbol functionPointerType)
            {
                delegateMethod = functionPointerType.Signature;
            }
            else
            {
                return;
            }
 
            if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(delegateMethod))
            {
                SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
                    delegateMethod,
                    lambdaOrMethod,
                    diagnostics,
                    static (diagnostics, delegateMethod, lambdaOrMethod, parameter, _, typeAndLocation) =>
                    {
                        diagnostics.Add(
                            SourceMemberContainerTypeSymbol.ReportInvalidScopedOverrideAsError(delegateMethod, lambdaOrMethod) ?
                                ErrorCode.ERR_ScopedMismatchInParameterOfTarget :
                                ErrorCode.WRN_ScopedMismatchInParameterOfTarget,
                            typeAndLocation.Location,
                            new FormattedSymbol(parameter, SymbolDisplayFormat.ShortFormat),
                            typeAndLocation.Type);
                    },
                    (Type: targetType, Location: syntax.Location),
                    allowVariance: true,
                    invokedAsExtensionMethod: invokedAsExtensionMethod);
            }
        }
 
        private static void CheckLambdaConversion(LambdaSymbol lambdaSymbol, TypeSymbol targetType, BindingDiagnosticBag diagnostics)
        {
            var delegateType = targetType.GetDelegateType();
            Debug.Assert(delegateType is not null);
            var isSynthesized = delegateType.DelegateInvokeMethod?.OriginalDefinition is SynthesizedDelegateInvokeMethod;
            var delegateParameters = delegateType.DelegateParameters();
 
            Debug.Assert(lambdaSymbol.ParameterCount == delegateParameters.Length);
            for (int p = 0; p < lambdaSymbol.ParameterCount; p++)
            {
                var lambdaParameter = lambdaSymbol.Parameters[p];
                var delegateParameter = delegateParameters[p];
 
                if (isSynthesized)
                {
                    // If synthesizing a delegate with `decimal`/`DateTime` default value,
                    // check that the corresponding `*ConstantAttribute` is available.
                    if (delegateParameter.ExplicitDefaultConstantValue is { } defaultValue &&
                        // Skip reporting this diagnostic if already reported in `SourceComplexParameterSymbolBase.DefaultSyntaxValue`.
                        lambdaParameter is not SourceComplexParameterSymbolBase
                        {
                            ExplicitDefaultConstantValue.IsDecimal: true,
                            DefaultValueFromAttributes: ConstantValue.NotAvailable
                        })
                    {
                        WellKnownMember? member = defaultValue.SpecialType switch
                        {
                            SpecialType.System_Decimal => WellKnownMember.System_Runtime_CompilerServices_DecimalConstantAttribute__ctor,
                            SpecialType.System_DateTime => WellKnownMember.System_Runtime_CompilerServices_DateTimeConstantAttribute__ctor,
                            _ => null
                        };
                        if (member != null)
                        {
                            reportUseSiteDiagnosticForSynthesizedAttribute(
                                lambdaSymbol,
                                lambdaParameter,
                                member.GetValueOrDefault(),
                                diagnostics);
                        }
                    }
 
                    // If synthesizing a delegate with an [UnscopedRef] parameter, check the attribute is available.
                    if (delegateParameter.HasUnscopedRefAttribute)
                    {
                        reportUseSiteDiagnosticForSynthesizedAttribute(
                            lambdaSymbol,
                            lambdaParameter,
                            WellKnownMember.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor,
                            diagnostics);
                    }
                }
 
                // Warn for defaults/`params` mismatch.
                if (!lambdaSymbol.SyntaxNode.IsKind(SyntaxKind.AnonymousMethodExpression))
                {
                    if (lambdaParameter.HasExplicitDefaultValue &&
                        lambdaParameter.ExplicitDefaultConstantValue is { IsBad: false } lambdaParamDefault)
                    {
                        var delegateParamDefault = delegateParameter.HasExplicitDefaultValue ? delegateParameter.ExplicitDefaultConstantValue : null;
                        if (delegateParamDefault?.IsBad != true && lambdaParamDefault != delegateParamDefault)
                        {
                            // Parameter {0} has default value '{1}' in lambda but '{2}' in target delegate type.
                            Error(diagnostics, ErrorCode.WRN_OptionalParamValueMismatch, lambdaParameter.Locations[0], p + 1, lambdaParamDefault, delegateParamDefault ?? ((object)MessageID.IDS_Missing.Localize()));
                        }
                    }
 
                    if (lambdaParameter.IsParams && !delegateParameter.IsParams && p == lambdaSymbol.ParameterCount - 1 && lambdaParameter.Type.IsSZArray())
                    {
                        // Parameter {0} has params modifier in lambda but not in target delegate type.
                        Error(diagnostics, ErrorCode.WRN_ParamsArrayInLambdaOnly, lambdaParameter.Locations[0], p + 1);
                    }
                }
            }
 
            static void reportUseSiteDiagnosticForSynthesizedAttribute(
                LambdaSymbol lambdaSymbol,
                ParameterSymbol lambdaParameter,
                WellKnownMember member,
                BindingDiagnosticBag diagnostics)
            {
                ReportUseSiteDiagnosticForSynthesizedAttribute(
                    lambdaSymbol.DeclaringCompilation,
                    member,
                    diagnostics,
                    lambdaParameter.Locations.FirstOrDefault() ?? lambdaSymbol.SyntaxNode.Location);
            }
        }
 
        private BoundExpression CreateStackAllocConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(conversion.IsStackAlloc);
 
            var boundStackAlloc = (BoundStackAllocArrayCreation)source;
            var elementType = boundStackAlloc.ElementType;
            TypeSymbol stackAllocType;
 
            switch (conversion.Kind)
            {
                case ConversionKind.StackAllocToPointerType:
                    ReportUnsafeIfNotAllowed(syntax.Location, diagnostics);
                    stackAllocType = new PointerTypeSymbol(TypeWithAnnotations.Create(elementType));
                    break;
                case ConversionKind.StackAllocToSpanType:
                    CheckFeatureAvailability(syntax, MessageID.IDS_FeatureRefStructs, diagnostics);
                    stackAllocType = Compilation.GetWellKnownType(WellKnownType.System_Span_T).Construct(elementType);
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(conversion.Kind);
            }
 
            var convertedNode = new BoundConvertedStackAllocExpression(syntax, elementType, boundStackAlloc.Count, boundStackAlloc.InitializerOpt, stackAllocType, boundStackAlloc.HasErrors);
 
            var underlyingConversion = conversion.UnderlyingConversions.Single();
            return CreateConversion(syntax, convertedNode, underlyingConversion, isCast: isCast, conversionGroup, destination, diagnostics);
        }
 
        private BoundExpression CreateTupleLiteralConversion(SyntaxNode syntax, BoundTupleLiteral sourceTuple, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
        {
            // We have a successful tuple conversion; rather than producing a separate conversion node 
            // which is a conversion on top of a tuple literal, tuple conversion is an element-wise conversion of arguments.
            Debug.Assert(conversion.IsNullable == destination.IsNullableType());
 
            var destinationWithoutNullable = destination;
            var conversionWithoutNullable = conversion;
 
            if (conversion.IsNullable)
            {
                destinationWithoutNullable = destination.GetNullableUnderlyingType();
                conversionWithoutNullable = conversion.UnderlyingConversions[0];
                conversion.MarkUnderlyingConversionsChecked();
            }
 
            Debug.Assert(conversionWithoutNullable.IsTupleLiteralConversion);
 
            NamedTypeSymbol targetType = (NamedTypeSymbol)destinationWithoutNullable;
            if (targetType.IsTupleType)
            {
                NamedTypeSymbol.ReportTupleNamesMismatchesIfAny(targetType, sourceTuple, diagnostics);
 
                // do not lose the original element names and locations in the literal if different from names in the target
                //
                // the tuple has changed the type of elements due to target-typing, 
                // but element names has not changed and locations of their declarations 
                // should not be confused with element locations on the target type.
 
                if (sourceTuple.Type is NamedTypeSymbol { IsTupleType: true } sourceType)
                {
                    targetType = targetType.WithTupleDataFrom(sourceType);
                }
                else
                {
                    var tupleSyntax = (TupleExpressionSyntax)sourceTuple.Syntax;
                    var locationBuilder = ArrayBuilder<Location?>.GetInstance();
 
                    foreach (var argument in tupleSyntax.Arguments)
                    {
                        locationBuilder.Add(argument.NameColon?.Name.Location);
                    }
 
                    targetType = targetType.WithElementNames(sourceTuple.ArgumentNamesOpt!,
                        locationBuilder.ToImmutableAndFree(),
                        errorPositions: default,
                        ImmutableArray.Create(tupleSyntax.Location));
                }
            }
 
            var arguments = sourceTuple.Arguments;
            var convertedArguments = ArrayBuilder<BoundExpression>.GetInstance(arguments.Length);
 
            var targetElementTypes = targetType.TupleElementTypesWithAnnotations;
            Debug.Assert(targetElementTypes.Length == arguments.Length, "converting a tuple literal to incompatible type?");
            var underlyingConversions = conversionWithoutNullable.UnderlyingConversions;
            conversionWithoutNullable.MarkUnderlyingConversionsChecked();
 
            for (int i = 0; i < arguments.Length; i++)
            {
                var argument = arguments[i];
                var destType = targetElementTypes[i];
                var elementConversion = underlyingConversions[i];
                var elementConversionGroup = isCast ? new ConversionGroup(elementConversion, destType) : null;
                convertedArguments.Add(CreateConversion(argument.Syntax, argument, elementConversion, isCast: isCast, elementConversionGroup, destType.Type, diagnostics));
            }
 
            BoundExpression result = new BoundConvertedTupleLiteral(
                sourceTuple.Syntax,
                sourceTuple,
                wasTargetTyped: true,
                convertedArguments.ToImmutableAndFree(),
                sourceTuple.ArgumentNamesOpt,
                sourceTuple.InferredNamesOpt,
                targetType).WithSuppression(sourceTuple.IsSuppressed);
 
            if (!TypeSymbol.Equals(sourceTuple.Type, destination, TypeCompareKind.ConsiderEverything2))
            {
                // literal cast is applied to the literal 
                result = new BoundConversion(
                    sourceTuple.Syntax,
                    result,
                    conversion,
                    @checked: false,
                    explicitCastInCode: isCast,
                    conversionGroup,
                    constantValueOpt: ConstantValue.NotAvailable,
                    type: destination);
            }
 
            // If we had a cast in the code, keep conversion in the tree.
            // even though the literal is already converted to the target type.
            if (isCast)
            {
                result = new BoundConversion(
                    syntax,
                    result,
                    Conversion.Identity,
                    @checked: false,
                    explicitCastInCode: isCast,
                    conversionGroup,
                    constantValueOpt: ConstantValue.NotAvailable,
                    type: destination);
            }
 
            return result;
        }
 
        private static bool IsMethodGroupWithTypeOrValueReceiver(BoundNode node)
        {
            if (node.Kind != BoundKind.MethodGroup)
            {
                return false;
            }
 
            return Binder.IsTypeOrValueExpression(((BoundMethodGroup)node).ReceiverOpt);
        }
 
        private BoundMethodGroup FixMethodGroupWithTypeOrValue(BoundMethodGroup group, Conversion conversion, BindingDiagnosticBag diagnostics)
        {
            if (!IsMethodGroupWithTypeOrValueReceiver(group))
            {
                return group;
            }
 
            BoundExpression? receiverOpt = group.ReceiverOpt;
            RoslynDebug.Assert(receiverOpt != null);
 
            receiverOpt = ReplaceTypeOrValueReceiver(receiverOpt, useType: conversion.Method?.RequiresInstanceReceiver == false && !conversion.IsExtensionMethod, diagnostics);
            return group.Update(
                group.TypeArgumentsOpt,
                group.Name,
                group.Methods,
                group.LookupSymbolOpt,
                group.LookupError,
                group.Flags,
                group.FunctionType,
                receiverOpt, //only change
                group.ResultKind);
        }
 
        /// <summary>
        /// This method implements the algorithm in spec section 7.6.5.1.
        /// 
        /// For method group conversions, there are situations in which the conversion is
        /// considered to exist ("Otherwise the algorithm produces a single best method M having
        /// the same number of parameters as D and the conversion is considered to exist"), but
        /// application of the conversion fails.  These are the "final validation" steps of
        /// overload resolution.
        /// </summary>
        /// <returns>
        /// True if there is any error, except lack of runtime support errors.
        /// </returns>
        private bool MemberGroupFinalValidation(BoundExpression? receiverOpt, MethodSymbol methodSymbol, SyntaxNode node, BindingDiagnosticBag diagnostics, bool invokedAsExtensionMethod)
        {
            if (!IsBadBaseAccess(node, receiverOpt, methodSymbol, diagnostics))
            {
                CheckReceiverAndRuntimeSupportForSymbolAccess(node, receiverOpt, methodSymbol, diagnostics);
            }
 
            if (MemberGroupFinalValidationAccessibilityChecks(receiverOpt, methodSymbol, node, diagnostics, invokedAsExtensionMethod))
            {
                return true;
            }
 
            // SPEC: If the best method is a generic method, the type arguments (supplied or inferred) are checked against the constraints 
            // SPEC: declared on the generic method. If any type argument does not satisfy the corresponding constraint(s) on
            // SPEC: the type parameter, a binding-time error occurs.
 
            // The portion of the overload resolution spec quoted above is subtle and somewhat
            // controversial. The upshot of this is that overload resolution does not consider
            // constraints to be a part of the signature. Overload resolution matches arguments to
            // parameter lists; it does not consider things which are outside of the parameter list.
            // If the best match from the arguments to the formal parameters is not viable then we
            // give an error rather than falling back to a worse match. 
            //
            // Consider the following:
            //
            // void M<T>(T t) where T : Reptile {}
            // void M(object x) {}
            // ...
            // M(new Giraffe());
            //
            // The correct analysis is to determine that the applicable candidates are
            // M<Giraffe>(Giraffe) and M(object). Overload resolution then chooses the former
            // because it is an exact match, over the latter which is an inexact match. Only after
            // the best method is determined do we check the constraints and discover that the
            // constraint on T has been violated.
            // 
            // Note that this is different from the rule that says that during type inference, if an
            // inference violates a constraint then inference fails. For example:
            // 
            // class C<T> where T : struct {}
            // ...
            // void M<U>(U u, C<U> c){}
            // void M(object x, object y) {}
            // ...
            // M("hello", null);
            //
            // Type inference determines that U is string, but since C<string> is not a valid type
            // because of the constraint, type inference fails. M<string> is never added to the
            // applicable candidate set, so the applicable candidate set consists solely of
            // M(object, object) and is therefore the best match.
 
            return !methodSymbol.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(this.Compilation, this.Conversions, includeNullability: false, node.Location, diagnostics));
        }
 
        /// <summary>
        /// Performs the following checks:
        /// 
        /// Spec 7.6.5: Invocation expressions (definition of Final Validation) 
        ///   The method is validated in the context of the method group: If the best method is a static method, 
        ///   the method group must have resulted from a simple-name or a member-access through a type. If the best 
        ///   method is an instance method, the method group must have resulted from a simple-name, a member-access
        ///   through a variable or value, or a base-access. If neither of these requirements is true, a binding-time
        ///   error occurs.
        ///   (Note that the spec omits to mention, in the case of an instance method invoked through a simple name, that
        ///   the invocation must appear within the body of an instance method)
        ///
        /// Spec 7.5.4: Compile-time checking of dynamic overload resolution 
        ///   If F is a static method, the method group must have resulted from a simple-name, a member-access through a type, 
        ///   or a member-access whose receiver can't be classified as a type or value until after overload resolution (see §7.6.4.1). 
        ///   If F is an instance method, the method group must have resulted from a simple-name, a member-access through a variable or value,
        ///   or a member-access whose receiver can't be classified as a type or value until after overload resolution (see §7.6.4.1).
        /// </summary>
        /// <returns>
        /// True if there is any error.
        /// </returns>
        private bool MemberGroupFinalValidationAccessibilityChecks(BoundExpression? receiverOpt, Symbol memberSymbol, SyntaxNode node, BindingDiagnosticBag diagnostics, bool invokedAsExtensionMethod)
        {
            // Perform final validation of the method to be invoked.
 
            Debug.Assert(memberSymbol.Kind != SymbolKind.Method ||
                memberSymbol.CanBeReferencedByName);
            //note that the same assert does not hold for all properties. Some properties and (all indexers) are not referenceable by name, yet
            //their binding brings them through here, perhaps needlessly.
 
            if (IsTypeOrValueExpression(receiverOpt))
            {
                // TypeOrValue expression isn't replaced only if the invocation is late bound, in which case it can't be extension method.
                // None of the checks below apply if the receiver can't be classified as a type or value. 
                Debug.Assert(!invokedAsExtensionMethod);
            }
            else if (!memberSymbol.RequiresInstanceReceiver())
            {
                Debug.Assert(!invokedAsExtensionMethod || (receiverOpt != null));
 
                if (invokedAsExtensionMethod)
                {
                    if (IsMemberAccessedThroughType(receiverOpt))
                    {
                        if (receiverOpt.Kind == BoundKind.QueryClause)
                        {
                            RoslynDebug.Assert(receiverOpt.Type is object);
                            // Could not find an implementation of the query pattern for source type '{0}'.  '{1}' not found.
                            diagnostics.Add(ErrorCode.ERR_QueryNoProvider, node.Location, receiverOpt.Type, memberSymbol.Name);
                        }
                        else
                        {
                            // An object reference is required for the non-static field, method, or property '{0}'
                            diagnostics.Add(ErrorCode.ERR_ObjectRequired, node.Location, memberSymbol);
                        }
                        return true;
                    }
                }
                else if (!WasImplicitReceiver(receiverOpt) && IsMemberAccessedThroughVariableOrValue(receiverOpt))
                {
                    if (this.Flags.Includes(BinderFlags.CollectionInitializerAddMethod))
                    {
                        diagnostics.Add(ErrorCode.ERR_InitializerAddHasWrongSignature, node.Location, memberSymbol);
                    }
                    else if (node.Kind() == SyntaxKind.AwaitExpression && memberSymbol.Name == WellKnownMemberNames.GetAwaiter)
                    {
                        RoslynDebug.Assert(receiverOpt.Type is object);
                        diagnostics.Add(ErrorCode.ERR_BadAwaitArg, node.Location, receiverOpt.Type);
                    }
                    else
                    {
                        diagnostics.Add(ErrorCode.ERR_ObjectProhibited, node.Location, memberSymbol);
                    }
                    return true;
                }
            }
            else if (IsMemberAccessedThroughType(receiverOpt))
            {
                diagnostics.Add(ErrorCode.ERR_ObjectRequired, node.Location, memberSymbol);
                return true;
            }
            else if (WasImplicitReceiver(receiverOpt))
            {
                if (InFieldInitializer && !ContainingType!.IsScriptClass || InConstructorInitializer || InAttributeArgument)
                {
                    SyntaxNode errorNode = node;
                    if (node.Parent != null && node.Parent.Kind() == SyntaxKind.InvocationExpression)
                    {
                        errorNode = node.Parent;
                    }
 
                    ErrorCode code = InFieldInitializer ? ErrorCode.ERR_FieldInitRefNonstatic : ErrorCode.ERR_ObjectRequired;
                    diagnostics.Add(code, errorNode.Location, memberSymbol);
                    return true;
                }
 
                // If we could access the member through implicit "this" the receiver would be a BoundThisReference.
                // If it is null it means that the instance member is inaccessible.
                if (receiverOpt == null || ContainingMember().IsStatic)
                {
                    Error(diagnostics, ErrorCode.ERR_ObjectRequired, node, memberSymbol);
                    return true;
                }
            }
 
            var containingType = this.ContainingType;
            if (containingType is object)
            {
                CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
                bool isAccessible = this.IsSymbolAccessibleConditional(memberSymbol.GetTypeOrReturnType().Type, containingType, ref useSiteInfo);
                diagnostics.Add(node, useSiteInfo);
 
                if (!isAccessible)
                {
                    // In the presence of non-transitive [InternalsVisibleTo] in source, or obnoxious symbols from metadata, it is possible
                    // to select a method through overload resolution in which the type is not accessible.  In this case a method cannot
                    // be called through normal IL, so we give an error.  Neither [InternalsVisibleTo] nor the need for this diagnostic is
                    // described by the language specification.
                    //
                    // Dev11 perform different access checks. See bug #530360 and tests AccessCheckTests.InaccessibleReturnType.
                    Error(diagnostics, ErrorCode.ERR_BadAccess, node, memberSymbol);
                    return true;
                }
            }
 
            return false;
        }
 
        private static bool IsMemberAccessedThroughVariableOrValue(BoundExpression? receiverOpt)
        {
            if (receiverOpt == null)
            {
                return false;
            }
 
            return !IsMemberAccessedThroughType(receiverOpt);
        }
 
        internal static bool IsMemberAccessedThroughType([NotNullWhen(true)] BoundExpression? receiverOpt)
        {
            if (receiverOpt == null)
            {
                return false;
            }
 
            while (receiverOpt.Kind == BoundKind.QueryClause)
            {
                receiverOpt = ((BoundQueryClause)receiverOpt).Value;
            }
 
            return receiverOpt.Kind == BoundKind.TypeExpression;
        }
 
        /// <summary>
        /// Was the receiver expression compiler-generated?
        /// </summary>
        internal static bool WasImplicitReceiver([NotNullWhen(false)] BoundExpression? receiverOpt)
        {
            if (receiverOpt == null) return true;
            if (!receiverOpt.WasCompilerGenerated) return false;
            switch (receiverOpt.Kind)
            {
                case BoundKind.ThisReference:
                case BoundKind.HostObjectMemberReference:
                case BoundKind.PreviousSubmissionReference:
                    return true;
                default:
                    return false;
            }
        }
 
        /// <summary>
        /// This method implements the checks in spec section 15.2.
        /// </summary>
        internal bool MethodIsCompatibleWithDelegateOrFunctionPointer(BoundExpression? receiverOpt, bool isExtensionMethod, MethodSymbol method, TypeSymbol delegateType, Location errorLocation, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(delegateType is NamedTypeSymbol { TypeKind: TypeKind.Delegate, DelegateInvokeMethod: { HasUseSiteError: false } }
                           || delegateType.TypeKind == TypeKind.FunctionPointer,
                         "This method should only be called for valid delegate or function pointer types.");
 
            MethodSymbol delegateOrFuncPtrMethod = delegateType switch
            {
                NamedTypeSymbol { DelegateInvokeMethod: { } invokeMethod } => invokeMethod,
                FunctionPointerTypeSymbol { Signature: { } signature } => signature,
                _ => throw ExceptionUtilities.UnexpectedValue(delegateType),
            };
 
            Debug.Assert(!isExtensionMethod || (receiverOpt != null));
 
            // - Argument types "match", and
            var delegateOrFuncPtrParameters = delegateOrFuncPtrMethod.Parameters;
            var methodParameters = method.Parameters;
            int numParams = delegateOrFuncPtrParameters.Length;
 
            if (methodParameters.Length != numParams + (isExtensionMethod ? 1 : 0))
            {
                // This can happen if "method" has optional parameters.
                Debug.Assert(methodParameters.Length > numParams + (isExtensionMethod ? 1 : 0));
                Error(diagnostics, getMethodMismatchErrorCode(delegateType.TypeKind), errorLocation, method, delegateType);
                return false;
            }
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
 
            // If this is an extension method delegate, the caller should have verified the
            // receiver is compatible with the "this" parameter of the extension method.
            Debug.Assert(!isExtensionMethod ||
                (Conversions.ConvertExtensionMethodThisArg(methodParameters[0].Type, receiverOpt!.Type, ref useSiteInfo).Exists && useSiteInfo.Diagnostics.IsNullOrEmpty()));
 
            useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(useSiteInfo);
 
            for (int i = 0; i < numParams; i++)
            {
                var delegateParameter = delegateOrFuncPtrParameters[i];
                var methodParameter = methodParameters[isExtensionMethod ? i + 1 : i];
 
                // The delegate compatibility checks are stricter than the checks on applicable functions: it's possible
                // to get here with a method that, while all the parameters are applicable, is not actually delegate
                // compatible. This is because the Applicable function member spec requires that:
                //  * Every value parameter (non-ref or similar) from the delegate type has an implicit conversion to the corresponding
                //    target parameter
                //  * Every ref or similar parameter has an identity conversion to the corresponding target parameter
                // However, the delegate compatibility requirements are stricter:
                //  * Every value parameter (non-ref or similar) from the delegate type has an implicit _reference_ conversion to the
                //    corresponding target parameter.
                //  * Every ref or similar parameter has an identity conversion to the corresponding target parameter
                // Note the addition of the reference requirement: it means that for delegate type void D(int i), void M(long l) is
                // _applicable_, but not _compatible_.
                if (!hasConversion(delegateType.TypeKind, Conversions, delegateParameter.Type, methodParameter.Type, delegateParameter.RefKind, methodParameter.RefKind, ref useSiteInfo))
                {
                    // No overload for '{0}' matches delegate '{1}'
                    Error(diagnostics, getMethodMismatchErrorCode(delegateType.TypeKind), errorLocation, method, delegateType);
                    diagnostics.Add(errorLocation, useSiteInfo);
                    return false;
                }
            }
 
            if (delegateOrFuncPtrMethod.RefKind != method.RefKind)
            {
                Error(diagnostics, getRefMismatchErrorCode(delegateType.TypeKind), errorLocation, method, delegateType);
                diagnostics.Add(errorLocation, useSiteInfo);
                return false;
            }
 
            var methodReturnType = method.ReturnType;
            var delegateReturnType = delegateOrFuncPtrMethod.ReturnType;
            bool returnsMatch = delegateOrFuncPtrMethod switch
            {
                { RefKind: RefKind.None, ReturnsVoid: true } => method.ReturnsVoid,
                { RefKind: var destinationRefKind } => hasConversion(delegateType.TypeKind, Conversions, methodReturnType, delegateReturnType, method.RefKind, destinationRefKind, ref useSiteInfo),
            };
 
            if (!returnsMatch)
            {
                Error(diagnostics, ErrorCode.ERR_BadRetType, errorLocation, method, method.ReturnType);
                diagnostics.Add(errorLocation, useSiteInfo);
                return false;
            }
 
            if (delegateType.IsFunctionPointer())
            {
                if (isExtensionMethod)
                {
                    Error(diagnostics, ErrorCode.ERR_CannotUseReducedExtensionMethodInAddressOf, errorLocation);
                    diagnostics.Add(errorLocation, useSiteInfo);
                    return false;
                }
 
                if (!method.IsStatic)
                {
                    // This check is here purely for completeness of implementing the spec. It should
                    // never be hit, as static methods should be eliminated as candidates in overload
                    // resolution and should never make it to this point.
                    Debug.Fail("This method should have been eliminated in overload resolution!");
                    Error(diagnostics, ErrorCode.ERR_FuncPtrMethMustBeStatic, errorLocation, method);
                    diagnostics.Add(errorLocation, useSiteInfo);
                    return false;
                }
            }
 
            diagnostics.Add(errorLocation, useSiteInfo);
            return true;
 
            static bool hasConversion(TypeKind targetKind, Conversions conversions, TypeSymbol source, TypeSymbol destination,
                RefKind sourceRefKind, RefKind destinationRefKind, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
            {
                if (sourceRefKind != destinationRefKind)
                {
                    return false;
                }
 
                if (sourceRefKind != RefKind.None)
                {
                    return ConversionsBase.HasIdentityConversion(source, destination);
                }
 
                if (conversions.HasIdentityOrImplicitReferenceConversion(source, destination, ref useSiteInfo))
                {
                    return true;
                }
 
                return targetKind == TypeKind.FunctionPointer
                       && (ConversionsBase.HasImplicitPointerToVoidConversion(source, destination)
                           || conversions.HasImplicitPointerConversion(source, destination, ref useSiteInfo));
            }
 
            static ErrorCode getMethodMismatchErrorCode(TypeKind type)
                => type switch
                {
                    TypeKind.Delegate => ErrorCode.ERR_MethDelegateMismatch,
                    TypeKind.FunctionPointer => ErrorCode.ERR_MethFuncPtrMismatch,
                    _ => throw ExceptionUtilities.UnexpectedValue(type)
                };
 
            static ErrorCode getRefMismatchErrorCode(TypeKind type)
                => type switch
                {
                    TypeKind.Delegate => ErrorCode.ERR_DelegateRefMismatch,
                    TypeKind.FunctionPointer => ErrorCode.ERR_FuncPtrRefMismatch,
                    _ => throw ExceptionUtilities.UnexpectedValue(type)
                };
        }
 
        /// <summary>
        /// This method combines final validation (section 7.6.5.1) and delegate compatibility (section 15.2).
        /// </summary>
        /// <param name="syntax">CSharpSyntaxNode of the expression requiring method group conversion.</param>
        /// <param name="conversion">Conversion to be performed.</param>
        /// <param name="receiverOpt">Optional receiver.</param>
        /// <param name="isExtensionMethod">Method invoked as extension method.</param>
        /// <param name="delegateOrFuncPtrType">Target delegate type.</param>
        /// <param name="diagnostics">Where diagnostics should be added.</param>
        /// <returns>True if a diagnostic has been added.</returns>
        private bool MethodGroupConversionHasErrors(
            SyntaxNode syntax,
            Conversion conversion,
            BoundExpression? receiverOpt,
            bool isExtensionMethod,
            bool isAddressOf,
            TypeSymbol delegateOrFuncPtrType,
            BindingDiagnosticBag diagnostics)
        {
            var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
            Debug.Assert(Conversions.IsAssignableFromMulticastDelegate(delegateOrFuncPtrType, ref discardedUseSiteInfo) || delegateOrFuncPtrType.TypeKind == TypeKind.Delegate || delegateOrFuncPtrType.TypeKind == TypeKind.FunctionPointer);
            Debug.Assert(conversion.Method is object);
            MethodSymbol selectedMethod = conversion.Method;
 
            var location = syntax.Location;
            if (!Conversions.IsAssignableFromMulticastDelegate(delegateOrFuncPtrType, ref discardedUseSiteInfo))
            {
                if (!MethodIsCompatibleWithDelegateOrFunctionPointer(receiverOpt, isExtensionMethod, selectedMethod, delegateOrFuncPtrType, location, diagnostics) ||
                    MemberGroupFinalValidation(receiverOpt, selectedMethod, syntax, diagnostics, isExtensionMethod))
                {
                    return true;
                }
            }
 
            if (selectedMethod.IsConditional)
            {
                // CS1618: Cannot create delegate with '{0}' because it has a Conditional attribute
                Error(diagnostics, ErrorCode.ERR_DelegateOnConditional, location, selectedMethod);
                return true;
            }
 
            var sourceMethod = selectedMethod as SourceOrdinaryMethodSymbol;
            if (sourceMethod is object && sourceMethod.IsPartialWithoutImplementation)
            {
                // CS0762: Cannot create delegate from method '{0}' because it is a partial method without an implementing declaration
                Error(diagnostics, ErrorCode.ERR_PartialMethodToDelegate, location, selectedMethod);
                return true;
            }
 
            if ((selectedMethod.HasUnsafeParameter() || selectedMethod.ReturnType.IsUnsafe()) && ReportUnsafeIfNotAllowed(syntax, diagnostics))
            {
                return true;
            }
 
            CheckValidScopedMethodConversion(syntax, selectedMethod, delegateOrFuncPtrType, isExtensionMethod, diagnostics);
            if (!isAddressOf)
            {
                ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, selectedMethod, location, isDelegateConversion: true);
            }
            ReportDiagnosticsIfObsolete(diagnostics, selectedMethod, syntax, hasBaseReceiver: false);
 
            // No use site errors, but there could be use site warnings.
            // If there are use site warnings, they were reported during the overload resolution process
            // that chose selectedMethod.
            Debug.Assert(!selectedMethod.HasUseSiteError, "Shouldn't have reached this point if there were use site errors.");
 
            return false;
        }
 
        /// <summary>
        /// This method is a wrapper around MethodGroupConversionHasErrors.  As a preliminary step,
        /// it checks whether a conversion exists.
        /// </summary>
        private bool MethodGroupConversionDoesNotExistOrHasErrors(
            BoundMethodGroup boundMethodGroup,
            NamedTypeSymbol delegateType,
            Location delegateMismatchLocation,
            BindingDiagnosticBag diagnostics,
            out Conversion conversion)
        {
            if (ReportDelegateInvokeUseSiteDiagnostic(diagnostics, delegateType, delegateMismatchLocation))
            {
                conversion = Conversion.NoConversion;
                return true;
            }
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            conversion = Conversions.GetMethodGroupDelegateConversion(boundMethodGroup, delegateType, ref useSiteInfo);
            diagnostics.Add(delegateMismatchLocation, useSiteInfo);
            if (!conversion.Exists)
            {
                if (!Conversions.ReportDelegateOrFunctionPointerMethodGroupDiagnostics(this, boundMethodGroup, delegateType, diagnostics))
                {
                    // No overload for '{0}' matches delegate '{1}'
                    diagnostics.Add(ErrorCode.ERR_MethDelegateMismatch, delegateMismatchLocation, boundMethodGroup.Name, delegateType);
                }
 
                return true;
            }
            else
            {
                Debug.Assert(conversion.IsValid); // i.e. if it exists, then it is valid.
                // Only cares about nullness and type of receiver, so no need to worry about BoundTypeOrValueExpression.
                return this.MethodGroupConversionHasErrors(boundMethodGroup.Syntax, conversion, boundMethodGroup.ReceiverOpt, conversion.IsExtensionMethod, isAddressOf: false, delegateType, diagnostics);
            }
        }
 
        public ConstantValue? FoldConstantConversion(
            SyntaxNode syntax,
            BoundExpression source,
            Conversion conversion,
            TypeSymbol destination,
            BindingDiagnosticBag diagnostics)
        {
            RoslynDebug.Assert(source != null);
            RoslynDebug.Assert((object)destination != null);
 
            // The diagnostics bag can be null in cases where we know ahead of time that the
            // conversion will succeed without error or warning. (For example, if we have a valid
            // implicit numeric conversion on a constant of numeric type.)
 
            // SPEC: A constant expression must be the null literal or a value with one of 
            // SPEC: the following types: sbyte, byte, short, ushort, int, uint, long, 
            // SPEC: ulong, char, float, double, decimal, bool, string, or any enumeration type.
 
            // SPEC: The following conversions are permitted in constant expressions:
            // SPEC: Identity conversions
            // SPEC: Numeric conversions
            // SPEC: Enumeration conversions
            // SPEC: Constant expression conversions
            // SPEC: Implicit and explicit reference conversions, provided that the source of the conversions 
            // SPEC: is a constant expression that evaluates to the null value.
 
            // SPEC VIOLATION: C# has always allowed the following, even though this does violate the rule that
            // SPEC VIOLATION: a constant expression must be either the null literal, or an expression of one 
            // SPEC VIOLATION: of the given types. 
 
            // SPEC VIOLATION: const C c = (C)null;
 
            // TODO: Some conversions can produce errors or warnings depending on checked/unchecked.
            // TODO: Fold conversions on enums and strings too.
 
            var sourceConstantValue = source.ConstantValueOpt;
            if (sourceConstantValue == null)
            {
                if (conversion.Kind == ConversionKind.DefaultLiteral)
                {
                    return destination.GetDefaultValue();
                }
                else
                {
                    return sourceConstantValue;
                }
            }
            else if (sourceConstantValue.IsBad)
            {
                return sourceConstantValue;
            }
 
            if (source.HasAnyErrors)
            {
                return null;
            }
 
            switch (conversion.Kind)
            {
                case ConversionKind.Identity:
                    // An identity conversion to a floating-point type (for example from a cast in
                    // source code) changes the internal representation of the constant value
                    // to precisely the required precision.
                    switch (destination.SpecialType)
                    {
                        case SpecialType.System_Single:
                            return ConstantValue.Create(sourceConstantValue.SingleValue);
                        case SpecialType.System_Double:
                            return ConstantValue.Create(sourceConstantValue.DoubleValue);
                        default:
                            return sourceConstantValue;
                    }
 
                case ConversionKind.NullLiteral:
                    return sourceConstantValue;
 
                case ConversionKind.ImplicitConstant:
                    return FoldConstantNumericConversion(syntax, sourceConstantValue, destination, diagnostics);
 
                case ConversionKind.ExplicitNumeric:
                case ConversionKind.ImplicitNumeric:
                case ConversionKind.ExplicitEnumeration:
                case ConversionKind.ImplicitEnumeration:
                    // The C# specification categorizes conversion from literal zero to nullable enum as 
                    // an Implicit Enumeration Conversion. Such a thing should not be constant folded
                    // because nullable enums are never constants.
 
                    if (destination.IsNullableType())
                    {
                        return null;
                    }
 
                    return FoldConstantNumericConversion(syntax, sourceConstantValue, destination, diagnostics);
 
                case ConversionKind.ExplicitReference:
                case ConversionKind.ImplicitReference:
                    return sourceConstantValue.IsNull ? sourceConstantValue : null;
            }
 
            return null;
        }
 
        private ConstantValue? FoldConstantNumericConversion(
            SyntaxNode syntax,
            ConstantValue sourceValue,
            TypeSymbol destination,
            BindingDiagnosticBag diagnostics)
        {
            RoslynDebug.Assert(sourceValue != null);
            Debug.Assert(!sourceValue.IsBad);
 
            SpecialType destinationType;
            if ((object)destination != null && destination.IsEnumType())
            {
                var underlyingType = ((NamedTypeSymbol)destination).EnumUnderlyingType;
                RoslynDebug.Assert((object)underlyingType != null);
                Debug.Assert(underlyingType.SpecialType != SpecialType.None);
                destinationType = underlyingType.SpecialType;
            }
            else
            {
                destinationType = destination.GetSpecialTypeSafe();
            }
 
            // In an unchecked context we ignore overflowing conversions on conversions from any
            // integral type, float and double to any integral type. "unchecked" actually does not
            // affect conversions from decimal to any integral type; if those are out of bounds then
            // we always give an error regardless.
 
            if (sourceValue.IsDecimal)
            {
                if (!CheckConstantBounds(destinationType, sourceValue, out _))
                {
                    // NOTE: Dev10 puts a suffix, "M", on the constant value.
                    Error(diagnostics, ErrorCode.ERR_ConstOutOfRange, syntax, sourceValue.Value + "M", destination!);
                    return ConstantValue.Bad;
                }
            }
            else if (destinationType == SpecialType.System_Decimal)
            {
                if (!CheckConstantBounds(destinationType, sourceValue, out _))
                {
                    Error(diagnostics, ErrorCode.ERR_ConstOutOfRange, syntax, sourceValue.Value!, destination!);
                    return ConstantValue.Bad;
                }
            }
            else if (CheckOverflowAtCompileTime)
            {
                if (!CheckConstantBounds(destinationType, sourceValue, out bool maySucceedAtRuntime))
                {
                    if (maySucceedAtRuntime)
                    {
                        // Can be calculated at runtime, but is not a compile-time constant.
                        Error(diagnostics, ErrorCode.WRN_ConstOutOfRangeChecked, syntax, sourceValue.Value!, destination!);
                        return null;
                    }
                    else
                    {
                        Error(diagnostics, ErrorCode.ERR_ConstOutOfRangeChecked, syntax, sourceValue.Value!, destination!);
                        return ConstantValue.Bad;
                    }
                }
            }
            else if (destinationType == SpecialType.System_IntPtr || destinationType == SpecialType.System_UIntPtr)
            {
                if (!CheckConstantBounds(destinationType, sourceValue, out _))
                {
                    // Can be calculated at runtime, but is not a compile-time constant.
                    return null;
                }
            }
 
            return ConstantValue.Create(DoUncheckedConversion(destinationType, sourceValue), destinationType);
        }
 
        private static object DoUncheckedConversion(SpecialType destinationType, ConstantValue value)
        {
            // Note that we keep "single" floats as doubles internally to maintain higher precision. However,
            // we do not do so in an entirely "lossless" manner. When *converting* to a float, we do lose 
            // the precision lost due to the conversion. But when doing arithmetic, we do the arithmetic on
            // the double values.
            //
            // An example will help. Suppose we have:
            //
            // const float cf1 = 1.0f;
            // const float cf2 = 1.0e-15f;
            // const double cd3 = cf1 - cf2;
            //
            // We first take the double-precision values for 1.0 and 1.0e-15 and round them to floats,
            // and then turn them back into doubles. Then when we do the subtraction, we do the subtraction
            // in doubles, not in floats. Had we done the subtraction in floats, we'd get 1.0; but instead we
            // do it in doubles and get 0.99999999999999.
            //
            // Similarly, if we have
            //
            // const int i4 = int.MaxValue; // 2147483647
            // const float cf5 = int.MaxValue; //  2147483648.0
            // const double cd6 = cf5; // 2147483648.0
            //
            // The int is converted to float and stored internally as the double 214783648, even though the
            // fully precise int would fit into a double.
 
            unchecked
            {
                switch (value.Discriminator)
                {
                    case ConstantValueTypeDiscriminator.Byte:
                        byte byteValue = value.ByteValue;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)byteValue;
                            case SpecialType.System_Char: return (char)byteValue;
                            case SpecialType.System_UInt16: return (ushort)byteValue;
                            case SpecialType.System_UInt32: return (uint)byteValue;
                            case SpecialType.System_UInt64: return (ulong)byteValue;
                            case SpecialType.System_SByte: return (sbyte)byteValue;
                            case SpecialType.System_Int16: return (short)byteValue;
                            case SpecialType.System_Int32: return (int)byteValue;
                            case SpecialType.System_Int64: return (long)byteValue;
                            case SpecialType.System_IntPtr: return (int)byteValue;
                            case SpecialType.System_UIntPtr: return (uint)byteValue;
                            case SpecialType.System_Single:
                            case SpecialType.System_Double: return (double)byteValue;
                            case SpecialType.System_Decimal: return (decimal)byteValue;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.Char:
                        char charValue = value.CharValue;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)charValue;
                            case SpecialType.System_Char: return (char)charValue;
                            case SpecialType.System_UInt16: return (ushort)charValue;
                            case SpecialType.System_UInt32: return (uint)charValue;
                            case SpecialType.System_UInt64: return (ulong)charValue;
                            case SpecialType.System_SByte: return (sbyte)charValue;
                            case SpecialType.System_Int16: return (short)charValue;
                            case SpecialType.System_Int32: return (int)charValue;
                            case SpecialType.System_Int64: return (long)charValue;
                            case SpecialType.System_IntPtr: return (int)charValue;
                            case SpecialType.System_UIntPtr: return (uint)charValue;
                            case SpecialType.System_Single:
                            case SpecialType.System_Double: return (double)charValue;
                            case SpecialType.System_Decimal: return (decimal)charValue;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.UInt16:
                        ushort uint16Value = value.UInt16Value;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)uint16Value;
                            case SpecialType.System_Char: return (char)uint16Value;
                            case SpecialType.System_UInt16: return (ushort)uint16Value;
                            case SpecialType.System_UInt32: return (uint)uint16Value;
                            case SpecialType.System_UInt64: return (ulong)uint16Value;
                            case SpecialType.System_SByte: return (sbyte)uint16Value;
                            case SpecialType.System_Int16: return (short)uint16Value;
                            case SpecialType.System_Int32: return (int)uint16Value;
                            case SpecialType.System_Int64: return (long)uint16Value;
                            case SpecialType.System_IntPtr: return (int)uint16Value;
                            case SpecialType.System_UIntPtr: return (uint)uint16Value;
                            case SpecialType.System_Single:
                            case SpecialType.System_Double: return (double)uint16Value;
                            case SpecialType.System_Decimal: return (decimal)uint16Value;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.UInt32:
                        uint uint32Value = value.UInt32Value;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)uint32Value;
                            case SpecialType.System_Char: return (char)uint32Value;
                            case SpecialType.System_UInt16: return (ushort)uint32Value;
                            case SpecialType.System_UInt32: return (uint)uint32Value;
                            case SpecialType.System_UInt64: return (ulong)uint32Value;
                            case SpecialType.System_SByte: return (sbyte)uint32Value;
                            case SpecialType.System_Int16: return (short)uint32Value;
                            case SpecialType.System_Int32: return (int)uint32Value;
                            case SpecialType.System_Int64: return (long)uint32Value;
                            case SpecialType.System_IntPtr: return (int)uint32Value;
                            case SpecialType.System_UIntPtr: return (uint)uint32Value;
                            case SpecialType.System_Single: return (double)(float)uint32Value;
                            case SpecialType.System_Double: return (double)uint32Value;
                            case SpecialType.System_Decimal: return (decimal)uint32Value;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.UInt64:
                        ulong uint64Value = value.UInt64Value;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)uint64Value;
                            case SpecialType.System_Char: return (char)uint64Value;
                            case SpecialType.System_UInt16: return (ushort)uint64Value;
                            case SpecialType.System_UInt32: return (uint)uint64Value;
                            case SpecialType.System_UInt64: return (ulong)uint64Value;
                            case SpecialType.System_SByte: return (sbyte)uint64Value;
                            case SpecialType.System_Int16: return (short)uint64Value;
                            case SpecialType.System_Int32: return (int)uint64Value;
                            case SpecialType.System_Int64: return (long)uint64Value;
                            case SpecialType.System_IntPtr: return (int)uint64Value;
                            case SpecialType.System_UIntPtr: return (uint)uint64Value;
                            case SpecialType.System_Single: return (double)(float)uint64Value;
                            case SpecialType.System_Double: return (double)uint64Value;
                            case SpecialType.System_Decimal: return (decimal)uint64Value;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.NUInt:
                        uint nuintValue = value.UInt32Value;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)nuintValue;
                            case SpecialType.System_Char: return (char)nuintValue;
                            case SpecialType.System_UInt16: return (ushort)nuintValue;
                            case SpecialType.System_UInt32: return (uint)nuintValue;
                            case SpecialType.System_UInt64: return (ulong)nuintValue;
                            case SpecialType.System_SByte: return (sbyte)nuintValue;
                            case SpecialType.System_Int16: return (short)nuintValue;
                            case SpecialType.System_Int32: return (int)nuintValue;
                            case SpecialType.System_Int64: return (long)nuintValue;
                            case SpecialType.System_IntPtr: return (int)nuintValue;
                            case SpecialType.System_Single: return (double)(float)nuintValue;
                            case SpecialType.System_Double: return (double)nuintValue;
                            case SpecialType.System_Decimal: return (decimal)nuintValue;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.SByte:
                        sbyte sbyteValue = value.SByteValue;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)sbyteValue;
                            case SpecialType.System_Char: return (char)sbyteValue;
                            case SpecialType.System_UInt16: return (ushort)sbyteValue;
                            case SpecialType.System_UInt32: return (uint)sbyteValue;
                            case SpecialType.System_UInt64: return (ulong)sbyteValue;
                            case SpecialType.System_SByte: return (sbyte)sbyteValue;
                            case SpecialType.System_Int16: return (short)sbyteValue;
                            case SpecialType.System_Int32: return (int)sbyteValue;
                            case SpecialType.System_Int64: return (long)sbyteValue;
                            case SpecialType.System_IntPtr: return (int)sbyteValue;
                            case SpecialType.System_UIntPtr: return (uint)sbyteValue;
                            case SpecialType.System_Single:
                            case SpecialType.System_Double: return (double)sbyteValue;
                            case SpecialType.System_Decimal: return (decimal)sbyteValue;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.Int16:
                        short int16Value = value.Int16Value;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)int16Value;
                            case SpecialType.System_Char: return (char)int16Value;
                            case SpecialType.System_UInt16: return (ushort)int16Value;
                            case SpecialType.System_UInt32: return (uint)int16Value;
                            case SpecialType.System_UInt64: return (ulong)int16Value;
                            case SpecialType.System_SByte: return (sbyte)int16Value;
                            case SpecialType.System_Int16: return (short)int16Value;
                            case SpecialType.System_Int32: return (int)int16Value;
                            case SpecialType.System_Int64: return (long)int16Value;
                            case SpecialType.System_IntPtr: return (int)int16Value;
                            case SpecialType.System_UIntPtr: return (uint)int16Value;
                            case SpecialType.System_Single:
                            case SpecialType.System_Double: return (double)int16Value;
                            case SpecialType.System_Decimal: return (decimal)int16Value;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.Int32:
                        int int32Value = value.Int32Value;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)int32Value;
                            case SpecialType.System_Char: return (char)int32Value;
                            case SpecialType.System_UInt16: return (ushort)int32Value;
                            case SpecialType.System_UInt32: return (uint)int32Value;
                            case SpecialType.System_UInt64: return (ulong)int32Value;
                            case SpecialType.System_SByte: return (sbyte)int32Value;
                            case SpecialType.System_Int16: return (short)int32Value;
                            case SpecialType.System_Int32: return (int)int32Value;
                            case SpecialType.System_Int64: return (long)int32Value;
                            case SpecialType.System_IntPtr: return (int)int32Value;
                            case SpecialType.System_UIntPtr: return (uint)int32Value;
                            case SpecialType.System_Single: return (double)(float)int32Value;
                            case SpecialType.System_Double: return (double)int32Value;
                            case SpecialType.System_Decimal: return (decimal)int32Value;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.Int64:
                        long int64Value = value.Int64Value;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)int64Value;
                            case SpecialType.System_Char: return (char)int64Value;
                            case SpecialType.System_UInt16: return (ushort)int64Value;
                            case SpecialType.System_UInt32: return (uint)int64Value;
                            case SpecialType.System_UInt64: return (ulong)int64Value;
                            case SpecialType.System_SByte: return (sbyte)int64Value;
                            case SpecialType.System_Int16: return (short)int64Value;
                            case SpecialType.System_Int32: return (int)int64Value;
                            case SpecialType.System_Int64: return (long)int64Value;
                            case SpecialType.System_IntPtr: return (int)int64Value;
                            case SpecialType.System_UIntPtr: return (uint)int64Value;
                            case SpecialType.System_Single: return (double)(float)int64Value;
                            case SpecialType.System_Double: return (double)int64Value;
                            case SpecialType.System_Decimal: return (decimal)int64Value;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.NInt:
                        int nintValue = value.Int32Value;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)nintValue;
                            case SpecialType.System_Char: return (char)nintValue;
                            case SpecialType.System_UInt16: return (ushort)nintValue;
                            case SpecialType.System_UInt32: return (uint)nintValue;
                            case SpecialType.System_UInt64: return (ulong)nintValue;
                            case SpecialType.System_SByte: return (sbyte)nintValue;
                            case SpecialType.System_Int16: return (short)nintValue;
                            case SpecialType.System_Int32: return (int)nintValue;
                            case SpecialType.System_Int64: return (long)nintValue;
                            case SpecialType.System_IntPtr: return (int)nintValue;
                            case SpecialType.System_UIntPtr: return (uint)nintValue;
                            case SpecialType.System_Single: return (double)(float)nintValue;
                            case SpecialType.System_Double: return (double)nintValue;
                            case SpecialType.System_Decimal: return (decimal)nintValue;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.Single:
                    case ConstantValueTypeDiscriminator.Double:
                        // When converting from a floating-point type to an integral type, if the checked conversion would
                        // throw an overflow exception, then the unchecked conversion is undefined.  So that we have
                        // identical behavior on every host platform, we yield a result of zero in that case.
                        double doubleValue = CheckConstantBounds(destinationType, value.DoubleValue, out _) ? value.DoubleValue : 0D;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)doubleValue;
                            case SpecialType.System_Char: return (char)doubleValue;
                            case SpecialType.System_UInt16: return (ushort)doubleValue;
                            case SpecialType.System_UInt32: return (uint)doubleValue;
                            case SpecialType.System_UInt64: return (ulong)doubleValue;
                            case SpecialType.System_SByte: return (sbyte)doubleValue;
                            case SpecialType.System_Int16: return (short)doubleValue;
                            case SpecialType.System_Int32: return (int)doubleValue;
                            case SpecialType.System_Int64: return (long)doubleValue;
                            case SpecialType.System_IntPtr: return (int)doubleValue;
                            case SpecialType.System_UIntPtr: return (uint)doubleValue;
                            case SpecialType.System_Single: return (double)(float)doubleValue;
                            case SpecialType.System_Double: return (double)doubleValue;
                            case SpecialType.System_Decimal: return (value.Discriminator == ConstantValueTypeDiscriminator.Single) ? (decimal)(float)doubleValue : (decimal)doubleValue;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    case ConstantValueTypeDiscriminator.Decimal:
                        decimal decimalValue = CheckConstantBounds(destinationType, value.DecimalValue, out _) ? value.DecimalValue : 0m;
                        switch (destinationType)
                        {
                            case SpecialType.System_Byte: return (byte)decimalValue;
                            case SpecialType.System_Char: return (char)decimalValue;
                            case SpecialType.System_UInt16: return (ushort)decimalValue;
                            case SpecialType.System_UInt32: return (uint)decimalValue;
                            case SpecialType.System_UInt64: return (ulong)decimalValue;
                            case SpecialType.System_SByte: return (sbyte)decimalValue;
                            case SpecialType.System_Int16: return (short)decimalValue;
                            case SpecialType.System_Int32: return (int)decimalValue;
                            case SpecialType.System_Int64: return (long)decimalValue;
                            case SpecialType.System_IntPtr: return (int)decimalValue;
                            case SpecialType.System_UIntPtr: return (uint)decimalValue;
                            case SpecialType.System_Single: return (double)(float)decimalValue;
                            case SpecialType.System_Double: return (double)decimalValue;
                            case SpecialType.System_Decimal: return (decimal)decimalValue;
                            default: throw ExceptionUtilities.UnexpectedValue(destinationType);
                        }
                    default:
                        throw ExceptionUtilities.UnexpectedValue(value.Discriminator);
                }
            }
 
            // all cases should have been handled in the switch above.
            // return value.Value;
        }
 
        public static bool CheckConstantBounds(SpecialType destinationType, ConstantValue value, out bool maySucceedAtRuntime)
        {
            if (value.IsBad)
            {
                //assume that the constant was intended to be in bounds
                maySucceedAtRuntime = false;
                return true;
            }
 
            // Compute whether the value fits into the bounds of the given destination type without
            // error. We know that the constant will fit into either a double or a decimal, so
            // convert it to one of those and then check the bounds on that.
            var canonicalValue = CanonicalizeConstant(value);
            return canonicalValue is decimal ?
                CheckConstantBounds(destinationType, (decimal)canonicalValue, out maySucceedAtRuntime) :
                CheckConstantBounds(destinationType, (double)canonicalValue, out maySucceedAtRuntime);
        }
 
        private static bool CheckConstantBounds(SpecialType destinationType, double value, out bool maySucceedAtRuntime)
        {
            maySucceedAtRuntime = false;
 
            // Dev10 checks (minValue - 1) < value < (maxValue + 1).
            // See ExpressionBinder::isConstantInRange.
            switch (destinationType)
            {
                case SpecialType.System_Byte: return (byte.MinValue - 1D) < value && value < (byte.MaxValue + 1D);
                case SpecialType.System_Char: return (char.MinValue - 1D) < value && value < (char.MaxValue + 1D);
                case SpecialType.System_UInt16: return (ushort.MinValue - 1D) < value && value < (ushort.MaxValue + 1D);
                case SpecialType.System_UInt32: return (uint.MinValue - 1D) < value && value < (uint.MaxValue + 1D);
                case SpecialType.System_UInt64: return (ulong.MinValue - 1D) < value && value < (ulong.MaxValue + 1D);
                case SpecialType.System_SByte: return (sbyte.MinValue - 1D) < value && value < (sbyte.MaxValue + 1D);
                case SpecialType.System_Int16: return (short.MinValue - 1D) < value && value < (short.MaxValue + 1D);
                case SpecialType.System_Int32: return (int.MinValue - 1D) < value && value < (int.MaxValue + 1D);
                // Note: Using <= to compare the min value matches the native compiler.
                case SpecialType.System_Int64: return (long.MinValue - 1D) <= value && value < (long.MaxValue + 1D);
                case SpecialType.System_Decimal: return ((double)decimal.MinValue - 1D) < value && value < ((double)decimal.MaxValue + 1D);
                case SpecialType.System_IntPtr:
                    maySucceedAtRuntime = (long.MinValue - 1D) < value && value < (long.MaxValue + 1D);
                    return (int.MinValue - 1D) < value && value < (int.MaxValue + 1D);
                case SpecialType.System_UIntPtr:
                    maySucceedAtRuntime = (ulong.MinValue - 1D) < value && value < (ulong.MaxValue + 1D);
                    return (uint.MinValue - 1D) < value && value < (uint.MaxValue + 1D);
            }
 
            return true;
        }
 
        private static bool CheckConstantBounds(SpecialType destinationType, decimal value, out bool maySucceedAtRuntime)
        {
            maySucceedAtRuntime = false;
 
            // Dev10 checks (minValue - 1) < value < (maxValue + 1).
            // See ExpressionBinder::isConstantInRange.
            switch (destinationType)
            {
                case SpecialType.System_Byte: return (byte.MinValue - 1M) < value && value < (byte.MaxValue + 1M);
                case SpecialType.System_Char: return (char.MinValue - 1M) < value && value < (char.MaxValue + 1M);
                case SpecialType.System_UInt16: return (ushort.MinValue - 1M) < value && value < (ushort.MaxValue + 1M);
                case SpecialType.System_UInt32: return (uint.MinValue - 1M) < value && value < (uint.MaxValue + 1M);
                case SpecialType.System_UInt64: return (ulong.MinValue - 1M) < value && value < (ulong.MaxValue + 1M);
                case SpecialType.System_SByte: return (sbyte.MinValue - 1M) < value && value < (sbyte.MaxValue + 1M);
                case SpecialType.System_Int16: return (short.MinValue - 1M) < value && value < (short.MaxValue + 1M);
                case SpecialType.System_Int32: return (int.MinValue - 1M) < value && value < (int.MaxValue + 1M);
                case SpecialType.System_Int64: return (long.MinValue - 1M) < value && value < (long.MaxValue + 1M);
                case SpecialType.System_IntPtr:
                    maySucceedAtRuntime = (long.MinValue - 1M) < value && value < (long.MaxValue + 1M);
                    return (int.MinValue - 1M) < value && value < (int.MaxValue + 1M);
                case SpecialType.System_UIntPtr:
                    maySucceedAtRuntime = (ulong.MinValue - 1M) < value && value < (ulong.MaxValue + 1M);
                    return (uint.MinValue - 1M) < value && value < (uint.MaxValue + 1M);
            }
 
            return true;
        }
 
        // Takes in a constant of any kind and returns the constant as either a double or decimal
        private static object CanonicalizeConstant(ConstantValue value)
        {
            switch (value.Discriminator)
            {
                case ConstantValueTypeDiscriminator.SByte: return (decimal)value.SByteValue;
                case ConstantValueTypeDiscriminator.Int16: return (decimal)value.Int16Value;
                case ConstantValueTypeDiscriminator.Int32: return (decimal)value.Int32Value;
                case ConstantValueTypeDiscriminator.Int64: return (decimal)value.Int64Value;
                case ConstantValueTypeDiscriminator.NInt: return (decimal)value.Int32Value;
                case ConstantValueTypeDiscriminator.Byte: return (decimal)value.ByteValue;
                case ConstantValueTypeDiscriminator.Char: return (decimal)value.CharValue;
                case ConstantValueTypeDiscriminator.UInt16: return (decimal)value.UInt16Value;
                case ConstantValueTypeDiscriminator.UInt32: return (decimal)value.UInt32Value;
                case ConstantValueTypeDiscriminator.UInt64: return (decimal)value.UInt64Value;
                case ConstantValueTypeDiscriminator.NUInt: return (decimal)value.UInt32Value;
                case ConstantValueTypeDiscriminator.Single:
                case ConstantValueTypeDiscriminator.Double: return value.DoubleValue;
                case ConstantValueTypeDiscriminator.Decimal: return value.DecimalValue;
                default: throw ExceptionUtilities.UnexpectedValue(value.Discriminator);
            }
 
            // all cases handled in the switch, above.
        }
    }
}