File: Binder\Binder_Operators.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
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
    {
        private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            node.Left.CheckDeconstructionCompatibleArgument(diagnostics);
 
            BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind()));
            ReportSuppressionIfNeeded(left, diagnostics);
            BoundExpression right = BindValue(node.Right, diagnostics, BindValueKind.RValue);
            BinaryOperatorKind kind = SyntaxKindToBinaryOperatorKind(node.Kind());
 
            // If either operand is bad, don't try to do binary operator overload resolution; that will just
            // make cascading errors.
 
            if (left.Kind == BoundKind.EventAccess)
            {
                BinaryOperatorKind kindOperator = kind.Operator();
                switch (kindOperator)
                {
                    case BinaryOperatorKind.Addition:
                    case BinaryOperatorKind.Subtraction:
                        return BindEventAssignment(node, (BoundEventAccess)left, right, kindOperator, diagnostics);
 
                        // fall-through for other operators, if RHS is dynamic we produce dynamic operation, otherwise we'll report an error ...
                }
            }
 
            if (left.HasAnyErrors || right.HasAnyErrors)
            {
                // NOTE: no overload resolution candidates.
                left = BindToTypeForErrorRecovery(left);
                right = BindToTypeForErrorRecovery(right);
                return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right,
                    leftPlaceholder: null, leftConversion: null, finalPlaceholder: null, finalConversion: null, LookupResultKind.Empty, CreateErrorType(), hasErrors: true);
            }
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
 
            if (left.HasDynamicType() || right.HasDynamicType())
            {
                if (IsLegalDynamicOperand(right) && IsLegalDynamicOperand(left) && kind != BinaryOperatorKind.UnsignedRightShift)
                {
                    left = BindToNaturalType(left, diagnostics);
                    right = BindToNaturalType(right, diagnostics);
                    var placeholder = new BoundValuePlaceholder(right.Syntax, left.HasDynamicType() ? left.Type : right.Type).MakeCompilerGenerated();
                    var finalDynamicConversion = this.Compilation.Conversions.ClassifyConversionFromExpression(placeholder, left.Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
                    diagnostics.Add(node, useSiteInfo);
                    var conversion = (BoundConversion)CreateConversion(node, placeholder, finalDynamicConversion, isCast: true, conversionGroupOpt: null, left.Type, diagnostics);
 
                    conversion = conversion.Update(conversion.Operand, conversion.Conversion, conversion.IsBaseConversion, conversion.Checked,
                                                   explicitCastInCode: true, conversion.ConstantValueOpt, conversion.ConversionGroupOpt, conversion.Type);
 
                    return new BoundCompoundAssignmentOperator(
                        node,
                        new BinaryOperatorSignature(
                            kind.WithType(BinaryOperatorKind.Dynamic).WithOverflowChecksIfApplicable(CheckOverflowAtRuntime),
                            left.Type,
                            right.Type,
                            Compilation.DynamicType),
                        left,
                        right,
                        leftPlaceholder: null, leftConversion: null,
                        finalPlaceholder: placeholder,
                        finalConversion: conversion,
                        LookupResultKind.Viable,
                        left.Type,
                        hasErrors: false);
                }
                else
                {
                    Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, node.OperatorToken.Text, left.Display, right.Display);
 
                    // error: operator can't be applied on dynamic and a type that is not convertible to dynamic:
                    left = BindToTypeForErrorRecovery(left);
                    right = BindToTypeForErrorRecovery(right);
                    return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right,
                        leftPlaceholder: null, leftConversion: null, finalPlaceholder: null, finalConversion: null, LookupResultKind.Empty, CreateErrorType(), hasErrors: true);
                }
            }
 
            if (left.Kind == BoundKind.EventAccess && !CheckEventValueKind((BoundEventAccess)left, BindValueKind.Assignable, diagnostics))
            {
                // If we're in a place where the event can be assigned, then continue so that we give errors
                // about the types and operator not lining up.  Otherwise, just report that the event can't
                // be used here.
 
                // NOTE: no overload resolution candidates.
                left = BindToTypeForErrorRecovery(left);
                right = BindToTypeForErrorRecovery(right);
                return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right,
                    leftPlaceholder: null, leftConversion: null, finalPlaceholder: null, finalConversion: null, LookupResultKind.NotAVariable, CreateErrorType(), hasErrors: true);
            }
 
            // A compound operator, say, x |= y, is bound as x = (X)( ((T)x) | ((T)y) ). We must determine
            // the binary operator kind, the type conversions from each side to the types expected by
            // the operator, and the type conversion from the return type of the operand to the left hand side.
            //
            // We can get away with binding the right-hand-side of the operand into its converted form early.
            // This is convenient because first, it is never rewritten into an access to a temporary before
            // the conversion, and second, because that is more convenient for the "d += lambda" case.
            // We want to have the converted (bound) lambda in the bound tree, not the unconverted unbound lambda.
 
            LookupResultKind resultKind;
            ImmutableArray<MethodSymbol> originalUserDefinedOperators;
            BinaryOperatorAnalysisResult best = this.BinaryOperatorOverloadResolution(kind, isChecked: CheckOverflowAtRuntime, left, right, node, diagnostics, out resultKind, out originalUserDefinedOperators);
            if (!best.HasValue)
            {
                ReportAssignmentOperatorError(node, kind, diagnostics, left, right, resultKind);
                left = BindToTypeForErrorRecovery(left);
                right = BindToTypeForErrorRecovery(right);
                return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right,
                    leftPlaceholder: null, leftConversion: null, finalPlaceholder: null, finalConversion: null, resultKind, originalUserDefinedOperators, CreateErrorType(), hasErrors: true);
            }
 
            // The rules in the spec for determining additional errors are bit confusing. In particular
            // this line is misleading:
            //
            // "for predefined operators ... x op= y is permitted if both x op y and x = y are permitted"
            //
            // That's not accurate in many cases. For example, "x += 1" is permitted if x is string or
            // any enum type, but x = 1 is not legal for strings or enums.
            //
            // The correct rules are spelled out in the spec:
            //
            // Spec §7.17.2:
            // An operation of the form x op= y is processed by applying binary operator overload
            // resolution (§7.3.4) as if the operation was written x op y.
            // Let R be the return type of the selected operator, and T the type of x. Then,
            //
            // * If an implicit conversion from an expression of type R to the type T exists,
            //   the operation is evaluated as x = (T)(x op y), except that x is evaluated only once.
            //   [no cast is inserted, unless the conversion is implicit dynamic]
            // * Otherwise, if
            //   (1) the selected operator is a predefined operator,
            //   (2) if R is explicitly convertible to T, and
            //   (3.1) if y is implicitly convertible to T or
            //   (3.2) the operator is a shift operator... [then cast the result to T]
            // * Otherwise ... a binding-time error occurs.
 
            // So let's tease that out. There are two possible errors: the conversion from the
            // operator result type to the left hand type could be bad, and the conversion
            // from the right hand side to the left hand type could be bad.
            //
            // We report the first error under the following circumstances:
            //
            // * The final conversion is bad, or
            // * The final conversion is explicit and the selected operator is not predefined
            //
            // We report the second error under the following circumstances:
            //
            // * The final conversion is explicit, and
            // * The selected operator is predefined, and
            // * the selected operator is not a shift, and
            // * the right-to-left conversion is not implicit
 
            bool hasError = false;
 
            BinaryOperatorSignature bestSignature = best.Signature;
 
            CheckNativeIntegerFeatureAvailability(bestSignature.Kind, node, diagnostics);
            CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, bestSignature.Method,
                isUnsignedRightShift: bestSignature.Kind.Operator() == BinaryOperatorKind.UnsignedRightShift, bestSignature.ConstrainedToTypeOpt, diagnostics);
 
            if (CheckOverflowAtRuntime)
            {
                bestSignature = new BinaryOperatorSignature(
                    bestSignature.Kind.WithOverflowChecksIfApplicable(CheckOverflowAtRuntime),
                    bestSignature.LeftType,
                    bestSignature.RightType,
                    bestSignature.ReturnType,
                    bestSignature.Method,
                    bestSignature.ConstrainedToTypeOpt);
            }
 
            BoundExpression rightConverted = CreateConversion(right, best.RightConversion, bestSignature.RightType, diagnostics);
 
            bool isPredefinedOperator = !bestSignature.Kind.IsUserDefined();
 
            var leftType = left.Type;
 
            var finalPlaceholder = new BoundValuePlaceholder(node, bestSignature.ReturnType);
 
            BoundExpression finalConversion = GenerateConversionForAssignment(leftType, finalPlaceholder, diagnostics,
                            ConversionForAssignmentFlags.CompoundAssignment |
                            (isPredefinedOperator ? ConversionForAssignmentFlags.PredefinedOperator : ConversionForAssignmentFlags.None));
 
            if (finalConversion.HasErrors)
            {
                hasError = true;
            }
 
            if (finalConversion is not BoundConversion final)
            {
                Debug.Assert(finalConversion.HasErrors || (object)finalConversion == finalPlaceholder);
                if ((object)finalConversion != finalPlaceholder)
                {
                    finalPlaceholder = null;
                    finalConversion = null;
                }
            }
            else if (final.Conversion.IsExplicit &&
                isPredefinedOperator &&
                !kind.IsShift())
            {
                Conversion rightToLeftConversion = this.Conversions.ClassifyConversionFromExpression(right, leftType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
                if (!rightToLeftConversion.IsImplicit || !rightToLeftConversion.IsValid)
                {
                    hasError = true;
                    GenerateImplicitConversionError(diagnostics, node, rightToLeftConversion, right, leftType);
                }
            }
 
            diagnostics.Add(node, useSiteInfo);
 
            if (!hasError && leftType.IsVoidPointer())
            {
                Error(diagnostics, ErrorCode.ERR_VoidError, node);
                hasError = true;
            }
 
            // Any events that weren't handled above (by BindEventAssignment) are bad - we just followed this
            // code path for the diagnostics.  Make sure we don't report success.
            Debug.Assert(left.Kind != BoundKind.EventAccess || hasError);
 
            var leftPlaceholder = new BoundValuePlaceholder(left.Syntax, leftType).MakeCompilerGenerated();
            var leftConversion = CreateConversion(node.Left, leftPlaceholder, best.LeftConversion, isCast: false, conversionGroupOpt: null, best.Signature.LeftType, diagnostics);
 
            return new BoundCompoundAssignmentOperator(node, bestSignature, left, rightConverted,
                leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, leftType, hasError);
        }
 
        /// <summary>
        /// For "receiver.event += expr", produce "receiver.add_event(expr)".
        /// For "receiver.event -= expr", produce "receiver.remove_event(expr)".
        /// </summary>
        /// <remarks>
        /// Performs some validation of the accessor that couldn't be done in CheckEventValueKind, because
        /// the specific accessor wasn't known.
        /// </remarks>
        private BoundExpression BindEventAssignment(AssignmentExpressionSyntax node, BoundEventAccess left, BoundExpression right, BinaryOperatorKind opKind, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(opKind == BinaryOperatorKind.Addition || opKind == BinaryOperatorKind.Subtraction);
 
            bool hasErrors = false;
 
            EventSymbol eventSymbol = left.EventSymbol;
            BoundExpression receiverOpt = left.ReceiverOpt;
 
            TypeSymbol delegateType = left.Type;
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            Conversion argumentConversion = this.Conversions.ClassifyConversionFromExpression(right, delegateType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
 
            if (!argumentConversion.IsImplicit || !argumentConversion.IsValid) // NOTE: dev10 appears to allow user-defined conversions here.
            {
                hasErrors = true;
                if (delegateType.IsDelegateType()) // Otherwise, suppress cascading.
                {
                    GenerateImplicitConversionError(diagnostics, node, argumentConversion, right, delegateType);
                }
            }
 
            BoundExpression argument = CreateConversion(right, argumentConversion, delegateType, diagnostics);
 
            bool isAddition = opKind == BinaryOperatorKind.Addition;
            MethodSymbol method = isAddition ? eventSymbol.AddMethod : eventSymbol.RemoveMethod;
 
            TypeSymbol type;
            if ((object)method == null)
            {
                type = this.GetSpecialType(SpecialType.System_Void, diagnostics, node); //we know the return type would have been void
 
                // There will be a diagnostic on the declaration if it is from source.
                if (!eventSymbol.OriginalDefinition.IsFromCompilation(this.Compilation))
                {
                    // CONSIDER: better error code?  ERR_EventNeedsBothAccessors?
                    Error(diagnostics, ErrorCode.ERR_MissingPredefinedMember, node, delegateType, SourceEventSymbol.GetAccessorName(eventSymbol.Name, isAddition));
                }
            }
            else
            {
                CheckImplicitThisCopyInReadOnlyMember(receiverOpt, method, diagnostics);
 
                if (!this.IsAccessible(method, ref useSiteInfo, this.GetAccessThroughType(receiverOpt)))
                {
                    // CONSIDER: depending on the accessibility (e.g. if it's private), dev10 might just report the whole event bogus.
                    Error(diagnostics, ErrorCode.ERR_BadAccess, node, method);
                    hasErrors = true;
                }
                else if (IsBadBaseAccess(node, receiverOpt, method, diagnostics, eventSymbol))
                {
                    hasErrors = true;
                }
                else
                {
                    CheckReceiverAndRuntimeSupportForSymbolAccess(node, receiverOpt, method, diagnostics);
                }
 
                if (eventSymbol.IsWindowsRuntimeEvent)
                {
                    // Return type is actually void because this call will be later encapsulated in a call
                    // to WindowsRuntimeMarshal.AddEventHandler or RemoveEventHandler, which has the return
                    // type of void.
                    type = this.GetSpecialType(SpecialType.System_Void, diagnostics, node);
                }
                else
                {
                    type = method.ReturnType;
                }
            }
 
            diagnostics.Add(node, useSiteInfo);
 
            return new BoundEventAssignmentOperator(
                syntax: node,
                @event: eventSymbol,
                isAddition: isAddition,
                isDynamic: right.HasDynamicType(),
                receiverOpt: receiverOpt,
                argument: argument,
                type: type,
                hasErrors: hasErrors);
        }
 
        private static bool IsLegalDynamicOperand(BoundExpression operand)
        {
            Debug.Assert(operand != null);
 
            TypeSymbol type = operand.Type;
 
            // Literal null is a legal operand to a dynamic operation. The other typeless expressions --
            // method groups, lambdas, anonymous methods -- are not.
 
            // If the operand is of a class, interface, delegate, array, struct, enum, nullable
            // or type param types, it's legal to use in a dynamic expression. In short, the type
            // must be one that is convertible to object.
 
            if ((object)type == null)
            {
                return operand.IsLiteralNull();
            }
 
            // Pointer types and very special types are not convertible to object.
 
            return !type.IsPointerOrFunctionPointer() && !type.IsRestrictedType() && !type.IsVoidType();
        }
 
        private BoundExpression BindDynamicBinaryOperator(
            BinaryExpressionSyntax node,
            BinaryOperatorKind kind,
            BoundExpression left,
            BoundExpression right,
            BindingDiagnosticBag diagnostics)
        {
            // This method binds binary * / % + - << >> < > <= >= == != & ! ^ && || operators where one or both
            // of the operands are dynamic.
            Debug.Assert((object)left.Type != null && left.Type.IsDynamic() || (object)right.Type != null && right.Type.IsDynamic());
 
            bool hasError = false;
            bool leftValidOperand = IsLegalDynamicOperand(left);
            bool rightValidOperand = IsLegalDynamicOperand(right);
 
            if (!leftValidOperand || !rightValidOperand || kind == BinaryOperatorKind.UnsignedRightShift)
            {
                // Operator '{0}' cannot be applied to operands of type '{1}' and '{2}'
                Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, node.OperatorToken.Text, left.Display, right.Display);
                hasError = true;
            }
 
            MethodSymbol userDefinedOperator = null;
 
            if (kind.IsLogical() && leftValidOperand)
            {
                // We need to make sure left is either implicitly convertible to Boolean or has user defined truth operator.
                //   left && right is lowered to {op_False|op_Implicit}(left) ? left : And(left, right)
                //   left || right is lowered to {op_True|!op_Implicit}(left) ? left : Or(left, right)
                if (!IsValidDynamicCondition(left, isNegative: kind == BinaryOperatorKind.LogicalAnd, diagnostics, userDefinedOperator: out userDefinedOperator))
                {
                    // Dev11 reports ERR_MustHaveOpTF. The error was shared between this case and user-defined binary Boolean operators.
                    // We report two distinct more specific error messages.
                    Error(diagnostics, ErrorCode.ERR_InvalidDynamicCondition, node.Left, left.Type, kind == BinaryOperatorKind.LogicalAnd ? "false" : "true");
 
                    hasError = true;
                }
                else
                {
                    Debug.Assert(left.Type is not TypeParameterSymbol);
                    CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, userDefinedOperator, isUnsignedRightShift: false, constrainedToTypeOpt: null, diagnostics);
                }
            }
 
            return new BoundBinaryOperator(
                syntax: node,
                operatorKind: (hasError ? kind : kind.WithType(BinaryOperatorKind.Dynamic)).WithOverflowChecksIfApplicable(CheckOverflowAtRuntime),
                left: BindToNaturalType(left, diagnostics),
                right: BindToNaturalType(right, diagnostics),
                constantValueOpt: ConstantValue.NotAvailable,
                methodOpt: userDefinedOperator,
                constrainedToTypeOpt: null,
                resultKind: LookupResultKind.Viable,
                type: Compilation.DynamicType,
                hasErrors: hasError);
        }
 
        protected static bool IsSimpleBinaryOperator(SyntaxKind kind)
        {
            // We deliberately exclude &&, ||, ??, etc.
            switch (kind)
            {
                case SyntaxKind.AddExpression:
                case SyntaxKind.MultiplyExpression:
                case SyntaxKind.SubtractExpression:
                case SyntaxKind.DivideExpression:
                case SyntaxKind.ModuloExpression:
                case SyntaxKind.EqualsExpression:
                case SyntaxKind.NotEqualsExpression:
                case SyntaxKind.GreaterThanExpression:
                case SyntaxKind.LessThanExpression:
                case SyntaxKind.GreaterThanOrEqualExpression:
                case SyntaxKind.LessThanOrEqualExpression:
                case SyntaxKind.BitwiseAndExpression:
                case SyntaxKind.BitwiseOrExpression:
                case SyntaxKind.ExclusiveOrExpression:
                case SyntaxKind.LeftShiftExpression:
                case SyntaxKind.RightShiftExpression:
                case SyntaxKind.UnsignedRightShiftExpression:
                    return true;
            }
            return false;
        }
 
        private BoundExpression BindSimpleBinaryOperator(BinaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            // The simple binary operators are left-associative, and expressions of the form
            // a + b + c + d .... are relatively common in machine-generated code. The parser can handle
            // creating a deep-on-the-left syntax tree no problem, and then we promptly blow the stack during
            // semantic analysis. Here we build an explicit stack to handle the left-hand recursion.
 
            Debug.Assert(IsSimpleBinaryOperator(node.Kind()));
 
            var syntaxNodes = ArrayBuilder<BinaryExpressionSyntax>.GetInstance();
 
            ExpressionSyntax current = node;
            while (IsSimpleBinaryOperator(current.Kind()))
            {
                var binOp = (BinaryExpressionSyntax)current;
                syntaxNodes.Push(binOp);
                current = binOp.Left;
            }
 
            BoundExpression result = BindExpression(current, diagnostics);
 
            if (node.IsKind(SyntaxKind.SubtractExpression)
                && current.IsKind(SyntaxKind.ParenthesizedExpression))
            {
                if (result.Kind == BoundKind.TypeExpression
                    && !((ParenthesizedExpressionSyntax)current).Expression.IsKind(SyntaxKind.ParenthesizedExpression))
                {
                    Error(diagnostics, ErrorCode.ERR_PossibleBadNegCast, node);
                }
                else if (result.Kind == BoundKind.BadExpression)
                {
                    var parenthesizedExpression = (ParenthesizedExpressionSyntax)current;
 
                    if (parenthesizedExpression.Expression.IsKind(SyntaxKind.IdentifierName)
                        && ((IdentifierNameSyntax)parenthesizedExpression.Expression).Identifier.ValueText == "dynamic")
                    {
                        Error(diagnostics, ErrorCode.ERR_PossibleBadNegCast, node);
                    }
                }
            }
 
            while (syntaxNodes.Count > 0)
            {
                BinaryExpressionSyntax syntaxNode = syntaxNodes.Pop();
                BindValueKind bindValueKind = GetBinaryAssignmentKind(syntaxNode.Kind());
                BoundExpression left = CheckValue(result, bindValueKind, diagnostics);
                BoundExpression right = BindValue(syntaxNode.Right, diagnostics, BindValueKind.RValue);
                BoundExpression boundOp = BindSimpleBinaryOperator(syntaxNode, diagnostics, left, right, leaveUnconvertedIfInterpolatedString: true);
                result = boundOp;
            }
 
            syntaxNodes.Free();
            return result;
        }
 
        private BoundExpression BindSimpleBinaryOperator(BinaryExpressionSyntax node, BindingDiagnosticBag diagnostics,
            BoundExpression left, BoundExpression right, bool leaveUnconvertedIfInterpolatedString)
        {
            BinaryOperatorKind kind = SyntaxKindToBinaryOperatorKind(node.Kind());
 
            // If either operand is bad, don't try to do binary operator overload resolution; that would just
            // make cascading errors.
 
            if (left.HasAnyErrors || right.HasAnyErrors)
            {
                // NOTE: no user-defined conversion candidates
                left = BindToTypeForErrorRecovery(left);
                right = BindToTypeForErrorRecovery(right);
                return new BoundBinaryOperator(node, kind, ConstantValue.NotAvailable, methodOpt: null, constrainedToTypeOpt: null, LookupResultKind.Empty, left, right, GetBinaryOperatorErrorType(kind, diagnostics, node), true);
            }
 
            TypeSymbol leftType = left.Type;
            TypeSymbol rightType = right.Type;
 
            if ((object)leftType != null && leftType.IsDynamic() || (object)rightType != null && rightType.IsDynamic())
            {
                return BindDynamicBinaryOperator(node, kind, left, right, diagnostics);
            }
 
            // SPEC OMISSION: The C# 2.0 spec had a line in it that noted that the expressions "null == null"
            // SPEC OMISSION: and "null != null" were to be automatically treated as the appropriate constant;
            // SPEC OMISSION: overload resolution was to be skipped.  That's because a strict reading
            // SPEC OMISSION: of the overload resolution spec shows that overload resolution would give an
            // SPEC OMISSION: ambiguity error for this case; the expression is ambiguous between the int?,
            // SPEC OMISSION: bool? and string versions of equality.  This line was accidentally edited
            // SPEC OMISSION: out of the C# 3 specification; we should re-insert it.
 
            bool leftNull = left.IsLiteralNull();
            bool rightNull = right.IsLiteralNull();
            bool isEquality = kind == BinaryOperatorKind.Equal || kind == BinaryOperatorKind.NotEqual;
            if (isEquality && leftNull && rightNull)
            {
                return new BoundLiteral(node, ConstantValue.Create(kind == BinaryOperatorKind.Equal), GetSpecialType(SpecialType.System_Boolean, diagnostics, node));
            }
 
            if (IsTupleBinaryOperation(left, right) &&
                (kind == BinaryOperatorKind.Equal || kind == BinaryOperatorKind.NotEqual))
            {
                CheckFeatureAvailability(node, MessageID.IDS_FeatureTupleEquality, diagnostics);
                return BindTupleBinaryOperator(node, kind, left, right, diagnostics);
            }
 
            if (leaveUnconvertedIfInterpolatedString
                && kind == BinaryOperatorKind.Addition
                && left is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }
                && right is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true })
            {
                Debug.Assert(right.Type.SpecialType == SpecialType.System_String);
                var stringConstant = FoldBinaryOperator(node, BinaryOperatorKind.StringConcatenation, left, right, right.Type, diagnostics);
                return new BoundBinaryOperator(node, BinaryOperatorKind.StringConcatenation, BoundBinaryOperator.UncommonData.UnconvertedInterpolatedStringAddition(stringConstant), LookupResultKind.Empty, left, right, right.Type);
            }
 
            // SPEC: For an operation of one of the forms x == null, null == x, x != null, null != x,
            // SPEC: where x is an expression of nullable type, if operator overload resolution
            // SPEC: fails to find an applicable operator, the result is instead computed from
            // SPEC: the HasValue property of x.
 
            // Note that the spec says "fails to find an applicable operator", not "fails to
            // find a unique best applicable operator." For example:
            // struct X {
            //   public static bool operator ==(X? x, double? y) {...}
            //   public static bool operator ==(X? x, decimal? y) {...}
            //
            // The comparison "x == null" should produce an ambiguity error rather
            // that being bound as !x.HasValue.
            //
 
            LookupResultKind resultKind;
            ImmutableArray<MethodSymbol> originalUserDefinedOperators;
            BinaryOperatorSignature signature;
            BinaryOperatorAnalysisResult best;
            bool foundOperator = BindSimpleBinaryOperatorParts(node, diagnostics, left, right, kind,
                out resultKind, out originalUserDefinedOperators, out signature, out best);
 
            BinaryOperatorKind resultOperatorKind = signature.Kind;
            bool hasErrors = false;
            if (!foundOperator)
            {
                ReportBinaryOperatorError(node, diagnostics, node.OperatorToken, left, right, resultKind);
                resultOperatorKind &= ~BinaryOperatorKind.TypeMask;
                hasErrors = true;
            }
 
            switch (node.Kind())
            {
                case SyntaxKind.EqualsExpression:
                case SyntaxKind.NotEqualsExpression:
                case SyntaxKind.LessThanExpression:
                case SyntaxKind.LessThanOrEqualExpression:
                case SyntaxKind.GreaterThanExpression:
                case SyntaxKind.GreaterThanOrEqualExpression:
                    // Function pointer comparisons are defined on `void*` with implicit conversions to `void*` on both sides. So if this is a
                    // pointer comparison operation, and the underlying types of the left and right are both function pointers, then we need to
                    // warn about them because of JIT recompilation. If either side is explicitly cast to void*, that side's type will be void*,
                    // not delegate*, and we won't warn.
                    if ((resultOperatorKind & BinaryOperatorKind.Pointer) == BinaryOperatorKind.Pointer &&
                        leftType?.TypeKind == TypeKind.FunctionPointer && rightType?.TypeKind == TypeKind.FunctionPointer)
                    {
                        // Comparison of function pointers might yield an unexpected result, since pointers to the same function may be distinct.
                        Error(diagnostics, ErrorCode.WRN_DoNotCompareFunctionPointers, node.OperatorToken);
                    }
 
                    break;
                default:
                    if (leftType.IsVoidPointer() || rightType.IsVoidPointer())
                    {
                        // CONSIDER: dev10 cascades this, but roslyn doesn't have to.
                        Error(diagnostics, ErrorCode.ERR_VoidError, node);
                        hasErrors = true;
                    }
                    break;
            }
 
            if (foundOperator)
            {
                CheckNativeIntegerFeatureAvailability(resultOperatorKind, node, diagnostics);
                CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, signature.Method,
                    isUnsignedRightShift: resultOperatorKind.Operator() == BinaryOperatorKind.UnsignedRightShift, signature.ConstrainedToTypeOpt, diagnostics);
            }
 
            TypeSymbol resultType = signature.ReturnType;
            BoundExpression resultLeft = left;
            BoundExpression resultRight = right;
            ConstantValue resultConstant = null;
 
            if (foundOperator && (resultOperatorKind.OperandTypes() != BinaryOperatorKind.NullableNull))
            {
                Debug.Assert((object)signature.LeftType != null);
                Debug.Assert((object)signature.RightType != null);
 
                // If this is an object equality operator, we will suppress Lock conversion warnings.
                var needsFilterDiagnostics =
                    resultOperatorKind is BinaryOperatorKind.ObjectEqual or BinaryOperatorKind.ObjectNotEqual &&
                    diagnostics.AccumulatesDiagnostics;
                var conversionDiagnostics = needsFilterDiagnostics ? BindingDiagnosticBag.GetInstance(template: diagnostics) : diagnostics;
 
                resultLeft = CreateConversion(left, best.LeftConversion, signature.LeftType, conversionDiagnostics);
                resultRight = CreateConversion(right, best.RightConversion, signature.RightType, conversionDiagnostics);
 
                if (needsFilterDiagnostics)
                {
                    Debug.Assert(conversionDiagnostics != diagnostics);
                    diagnostics.AddDependencies(conversionDiagnostics);
 
                    var sourceBag = conversionDiagnostics.DiagnosticBag;
                    Debug.Assert(sourceBag is not null);
 
                    if (!sourceBag.IsEmptyWithoutResolution)
                    {
                        foreach (var diagnostic in sourceBag.AsEnumerableWithoutResolution())
                        {
                            var code = diagnostic is DiagnosticWithInfo { HasLazyInfo: true, LazyInfo.Code: var lazyCode } ? lazyCode : diagnostic.Code;
                            if ((ErrorCode)code is not ErrorCode.WRN_ConvertingLock)
                            {
                                diagnostics.Add(diagnostic);
                            }
                        }
                    }
 
                    conversionDiagnostics.Free();
                }
 
                resultConstant = FoldBinaryOperator(node, resultOperatorKind, resultLeft, resultRight, resultType, diagnostics);
            }
            else
            {
                // If we found an operator, we'll have given the `default` literal a type.
                // Otherwise, we'll have reported the problem in ReportBinaryOperatorError.
                resultLeft = BindToNaturalType(resultLeft, diagnostics, reportNoTargetType: false);
                resultRight = BindToNaturalType(resultRight, diagnostics, reportNoTargetType: false);
            }
 
            hasErrors = hasErrors || resultConstant != null && resultConstant.IsBad;
 
            return new BoundBinaryOperator(
                node,
                resultOperatorKind.WithOverflowChecksIfApplicable(CheckOverflowAtRuntime),
                resultLeft,
                resultRight,
                resultConstant,
                signature.Method,
                signature.ConstrainedToTypeOpt,
                resultKind,
                originalUserDefinedOperators,
                resultType,
                hasErrors);
        }
 
        private bool BindSimpleBinaryOperatorParts(BinaryExpressionSyntax node, BindingDiagnosticBag diagnostics, BoundExpression left, BoundExpression right, BinaryOperatorKind kind,
            out LookupResultKind resultKind, out ImmutableArray<MethodSymbol> originalUserDefinedOperators,
            out BinaryOperatorSignature resultSignature, out BinaryOperatorAnalysisResult best)
        {
            bool foundOperator;
            best = this.BinaryOperatorOverloadResolution(kind, isChecked: CheckOverflowAtRuntime, left, right, node, diagnostics, out resultKind, out originalUserDefinedOperators);
 
            // However, as an implementation detail, we never "fail to find an applicable
            // operator" during overload resolution if we have x == null, x == default, etc. We always
            // find at least the reference conversion object == object; the overload resolution
            // code does not reject that.  Therefore what we should do is only bind
            // "x == null" as a nullable-to-null comparison if overload resolution chooses
            // the reference conversion.
 
            if (!best.HasValue)
            {
                resultSignature = new BinaryOperatorSignature(kind, leftType: null, rightType: null, CreateErrorType());
                foundOperator = false;
            }
            else
            {
                var signature = best.Signature;
 
                bool isObjectEquality = signature.Kind == BinaryOperatorKind.ObjectEqual || signature.Kind == BinaryOperatorKind.ObjectNotEqual;
 
                bool leftNull = left.IsLiteralNull();
                bool rightNull = right.IsLiteralNull();
 
                TypeSymbol leftType = left.Type;
                TypeSymbol rightType = right.Type;
 
                bool isNullableEquality = (object)signature.Method == null &&
                    (signature.Kind.Operator() == BinaryOperatorKind.Equal || signature.Kind.Operator() == BinaryOperatorKind.NotEqual) &&
                    (leftNull && (object)rightType != null && rightType.IsNullableType() ||
                        rightNull && (object)leftType != null && leftType.IsNullableType());
 
                if (isNullableEquality)
                {
                    resultSignature = new BinaryOperatorSignature(kind | BinaryOperatorKind.NullableNull, leftType: null, rightType: null,
                        GetSpecialType(SpecialType.System_Boolean, diagnostics, node));
 
                    foundOperator = true;
                }
                else
                {
                    resultSignature = signature;
                    CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
                    bool leftDefault = left.IsLiteralDefault();
                    bool rightDefault = right.IsLiteralDefault();
                    foundOperator = !isObjectEquality || BuiltInOperators.IsValidObjectEquality(Conversions, leftType, leftNull, leftDefault, rightType, rightNull, rightDefault, ref useSiteInfo);
                    diagnostics.Add(node, useSiteInfo);
                }
            }
            return foundOperator;
        }
 
#nullable enable
        private BoundExpression RebindSimpleBinaryOperatorAsConverted(BoundBinaryOperator unconvertedBinaryOperator, BindingDiagnosticBag diagnostics)
        {
            if (TryBindUnconvertedBinaryOperatorToDefaultInterpolatedStringHandler(unconvertedBinaryOperator, diagnostics, out var convertedBinaryOperator))
            {
                return convertedBinaryOperator;
            }
 
            var result = doRebind(diagnostics, unconvertedBinaryOperator);
            return result;
 
            BoundExpression doRebind(BindingDiagnosticBag diagnostics, BoundBinaryOperator? current)
            {
                var stack = ArrayBuilder<BoundBinaryOperator>.GetInstance();
                while (current != null)
                {
                    Debug.Assert(current.IsUnconvertedInterpolatedStringAddition);
                    stack.Push(current);
                    current = current.Left as BoundBinaryOperator;
                }
 
                Debug.Assert(stack.Count > 0 && stack.Peek().Left is BoundUnconvertedInterpolatedString);
 
                BoundExpression? left = null;
                while (stack.TryPop(out current))
                {
                    var right = current.Right switch
                    {
                        BoundUnconvertedInterpolatedString s => s,
                        BoundBinaryOperator b => doRebind(diagnostics, b),
                        _ => throw ExceptionUtilities.UnexpectedValue(current.Right.Kind)
                    };
                    left = BindSimpleBinaryOperator((BinaryExpressionSyntax)current.Syntax, diagnostics, left ?? current.Left, right, leaveUnconvertedIfInterpolatedString: false);
                }
 
                Debug.Assert(left != null);
                Debug.Assert(stack.Count == 0);
                stack.Free();
                return left;
            }
        }
#nullable disable
 
        private static void ReportUnaryOperatorError(CSharpSyntaxNode node, BindingDiagnosticBag diagnostics, string operatorName, BoundExpression operand, LookupResultKind resultKind)
        {
            if (operand.IsLiteralDefault())
            {
                // We'll have reported an error for not being able to target-type `default` so we can avoid a cascading diagnostic
                return;
            }
 
            ErrorCode errorCode = resultKind == LookupResultKind.Ambiguous ?
                ErrorCode.ERR_AmbigUnaryOp : // Operator '{0}' is ambiguous on an operand of type '{1}'
                ErrorCode.ERR_BadUnaryOp;    // Operator '{0}' cannot be applied to operand of type '{1}'
 
            Error(diagnostics, errorCode, node, operatorName, operand.Display);
        }
 
        private void ReportAssignmentOperatorError(AssignmentExpressionSyntax node, BinaryOperatorKind kind, BindingDiagnosticBag diagnostics, BoundExpression left, BoundExpression right, LookupResultKind resultKind)
        {
            if (IsTypelessExpressionAllowedInBinaryOperator(kind, left, right) &&
                node.OperatorToken.RawKind is (int)SyntaxKind.PlusEqualsToken or (int)SyntaxKind.MinusEqualsToken &&
                (object)left.Type != null && left.Type.TypeKind == TypeKind.Delegate)
            {
                // Special diagnostic for delegate += and -= about wrong right-hand-side
                var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
                var conversion = this.Conversions.ClassifyConversionFromExpression(right, left.Type, isChecked: CheckOverflowAtRuntime, ref discardedUseSiteInfo);
                Debug.Assert(!conversion.IsImplicit);
                GenerateImplicitConversionError(diagnostics, right.Syntax, conversion, right, left.Type);
                // discard use-site diagnostics
            }
            else
            {
                ReportBinaryOperatorError(node, diagnostics, node.OperatorToken, left, right, resultKind);
            }
        }
 
        private void ReportBinaryOperatorError(ExpressionSyntax node, BindingDiagnosticBag diagnostics, SyntaxToken operatorToken, BoundExpression left, BoundExpression right, LookupResultKind resultKind)
        {
            bool isEquality = operatorToken.Kind() == SyntaxKind.EqualsEqualsToken || operatorToken.Kind() == SyntaxKind.ExclamationEqualsToken;
            switch (left.Kind, right.Kind)
            {
                case (BoundKind.DefaultLiteral, _) when !isEquality:
                case (_, BoundKind.DefaultLiteral) when !isEquality:
                    // other than == and !=, binary operators are disallowed on `default` literal
                    Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefaultOrNew, node, operatorToken.Text, "default");
                    return;
                case (BoundKind.DefaultLiteral, BoundKind.DefaultLiteral):
                    Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnDefault, node, operatorToken.Text, left.Display, right.Display);
                    return;
                case (BoundKind.DefaultLiteral, _) when right.Type is TypeParameterSymbol:
                    Debug.Assert(!right.Type.IsReferenceType);
                    Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnUnconstrainedDefault, node, operatorToken.Text, right.Type);
                    return;
                case (_, BoundKind.DefaultLiteral) when left.Type is TypeParameterSymbol:
                    Debug.Assert(!left.Type.IsReferenceType);
                    Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnUnconstrainedDefault, node, operatorToken.Text, left.Type);
                    return;
                case (BoundKind.UnconvertedObjectCreationExpression, _):
                    Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefaultOrNew, node, operatorToken.Text, left.Display);
                    return;
                case (_, BoundKind.UnconvertedObjectCreationExpression):
                    Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefaultOrNew, node, operatorToken.Text, right.Display);
                    return;
            }
 
#nullable enable
            ErrorCode errorCode;
 
            switch (resultKind)
            {
                case LookupResultKind.Ambiguous:
                    errorCode = ErrorCode.ERR_AmbigBinaryOps; // Operator '{0}' is ambiguous on operands of type '{1}' and '{2}'
                    break;
 
                case LookupResultKind.OverloadResolutionFailure when operatorToken.Kind() is SyntaxKind.PlusToken && isReadOnlySpanOfByte(left.Type) && isReadOnlySpanOfByte(right.Type):
                    errorCode = ErrorCode.ERR_BadBinaryReadOnlySpanConcatenation; // Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF-8 byte representations
                    break;
 
                default:
                    errorCode = ErrorCode.ERR_BadBinaryOps;    // Operator '{0}' cannot be applied to operands of type '{1}' and '{2}'
                    break;
            }
 
            Error(diagnostics, errorCode, node, operatorToken.Text, left.Display, right.Display);
 
            bool isReadOnlySpanOfByte(TypeSymbol? type)
            {
                return type is NamedTypeSymbol namedType && Compilation.IsReadOnlySpanType(namedType) &&
                    namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type.SpecialType is SpecialType.System_Byte;
 
            }
#nullable disable
        }
 
        private BoundExpression BindConditionalLogicalOperator(BinaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(node.Kind() == SyntaxKind.LogicalOrExpression || node.Kind() == SyntaxKind.LogicalAndExpression);
 
            // Do not blow the stack due to a deep recursion on the left.
 
            BinaryExpressionSyntax binary = node;
            ExpressionSyntax child;
 
            while (true)
            {
                child = binary.Left;
                var childAsBinary = child as BinaryExpressionSyntax;
 
                if (childAsBinary == null ||
                    (childAsBinary.Kind() != SyntaxKind.LogicalOrExpression && childAsBinary.Kind() != SyntaxKind.LogicalAndExpression))
                {
                    break;
                }
 
                binary = childAsBinary;
            }
 
            BoundExpression left = BindRValueWithoutTargetType(child, diagnostics);
 
            do
            {
                binary = (BinaryExpressionSyntax)child.Parent;
                BoundExpression right = BindRValueWithoutTargetType(binary.Right, diagnostics);
                left = BindConditionalLogicalOperator(binary, left, right, diagnostics);
                child = binary;
            }
            while ((object)child != node);
 
            return left;
        }
 
        private BoundExpression BindConditionalLogicalOperator(BinaryExpressionSyntax node, BoundExpression left, BoundExpression right, BindingDiagnosticBag diagnostics)
        {
            BinaryOperatorKind kind = SyntaxKindToBinaryOperatorKind(node.Kind());
 
            Debug.Assert(kind == BinaryOperatorKind.LogicalAnd || kind == BinaryOperatorKind.LogicalOr);
 
            // Let's take an easy out here. The vast majority of the time the operands will
            // both be bool. This is the only situation in which the expression can be a
            // constant expression, so do the folding now if we can.
 
            if ((object)left.Type != null && left.Type.SpecialType == SpecialType.System_Boolean &&
                (object)right.Type != null && right.Type.SpecialType == SpecialType.System_Boolean)
            {
                var constantValue = FoldBinaryOperator(node, kind | BinaryOperatorKind.Bool, left, right, left.Type, diagnostics);
 
                // NOTE: no candidate user-defined operators.
                return new BoundBinaryOperator(node, kind | BinaryOperatorKind.Bool, constantValue, methodOpt: null, constrainedToTypeOpt: null,
                    resultKind: LookupResultKind.Viable, left, right, type: left.Type, hasErrors: constantValue != null && constantValue.IsBad);
            }
 
            // If either operand is bad, don't try to do binary operator overload resolution; that will just
            // make cascading errors.
 
            if (left.HasAnyErrors || right.HasAnyErrors)
            {
                // NOTE: no candidate user-defined operators.
                return new BoundBinaryOperator(node, kind, ConstantValue.NotAvailable, methodOpt: null, constrainedToTypeOpt: null,
                    resultKind: LookupResultKind.Empty, left, right, type: GetBinaryOperatorErrorType(kind, diagnostics, node), hasErrors: true);
            }
 
            if (left.HasDynamicType() || right.HasDynamicType())
            {
                left = BindToNaturalType(left, diagnostics);
                right = BindToNaturalType(right, diagnostics);
                return BindDynamicBinaryOperator(node, kind, left, right, diagnostics);
            }
 
            LookupResultKind lookupResult;
            ImmutableArray<MethodSymbol> originalUserDefinedOperators;
            var best = this.BinaryOperatorOverloadResolution(kind, isChecked: CheckOverflowAtRuntime, left, right, node, diagnostics, out lookupResult, out originalUserDefinedOperators);
 
            // SPEC: If overload resolution fails to find a single best operator, or if overload
            // SPEC: resolution selects one of the predefined integer logical operators, a binding-
            // SPEC: time error occurs.
            //
            // SPEC OMISSION: We should probably clarify that the enum logical operators count as
            // SPEC OMISSION: integer logical operators. Basically the rule here should actually be:
            // SPEC OMISSION: if overload resolution selects something other than a user-defined
            // SPEC OMISSION: operator or the built in not-lifted operator on bool, an error occurs.
            //
 
            if (!best.HasValue)
            {
                ReportBinaryOperatorError(node, diagnostics, node.OperatorToken, left, right, lookupResult);
            }
            else
            {
                // There are two non-error possibilities. Either both operands are implicitly convertible to
                // bool, or we've got a valid user-defined operator.
                BinaryOperatorSignature signature = best.Signature;
 
                bool bothBool = signature.LeftType.SpecialType == SpecialType.System_Boolean &&
                        signature.RightType.SpecialType == SpecialType.System_Boolean;
 
                MethodSymbol trueOperator = null, falseOperator = null;
 
                if (!bothBool && !signature.Kind.IsUserDefined())
                {
                    ReportBinaryOperatorError(node, diagnostics, node.OperatorToken, left, right, lookupResult);
                }
                else if (bothBool || IsValidUserDefinedConditionalLogicalOperator(node, signature, diagnostics, out trueOperator, out falseOperator))
                {
                    var resultLeft = CreateConversion(left, best.LeftConversion, signature.LeftType, diagnostics);
                    var resultRight = CreateConversion(right, best.RightConversion, signature.RightType, diagnostics);
                    var resultKind = kind | signature.Kind.OperandTypes();
                    if (signature.Kind.IsLifted())
                    {
                        resultKind |= BinaryOperatorKind.Lifted;
                    }
 
                    if (resultKind.IsUserDefined())
                    {
                        Debug.Assert(trueOperator != null && falseOperator != null);
 
                        _ = CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, signature.Method, isUnsignedRightShift: false, signature.ConstrainedToTypeOpt, diagnostics) &&
                            CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, kind == BinaryOperatorKind.LogicalAnd ? falseOperator : trueOperator,
                                isUnsignedRightShift: false, signature.ConstrainedToTypeOpt, diagnostics);
 
                        return new BoundUserDefinedConditionalLogicalOperator(
                            node,
                            resultKind,
                            resultLeft,
                            resultRight,
                            signature.Method,
                            trueOperator,
                            falseOperator,
                            signature.ConstrainedToTypeOpt,
                            lookupResult,
                            originalUserDefinedOperators,
                            signature.ReturnType);
                    }
                    else
                    {
                        Debug.Assert(bothBool);
                        Debug.Assert(!(signature.Method?.ContainingType?.IsInterface ?? false));
 
                        return new BoundBinaryOperator(
                            node,
                            resultKind,
                            resultLeft,
                            resultRight,
                            ConstantValue.NotAvailable,
                            signature.Method,
                            signature.ConstrainedToTypeOpt,
                            lookupResult,
                            originalUserDefinedOperators,
                            signature.ReturnType);
                    }
                }
            }
 
            // We've already reported the error.
            return new BoundBinaryOperator(node, kind, left, right, ConstantValue.NotAvailable, methodOpt: null, constrainedToTypeOpt: null, lookupResult, originalUserDefinedOperators, CreateErrorType(), true);
        }
 
        private bool IsValidDynamicCondition(BoundExpression left, bool isNegative, BindingDiagnosticBag diagnostics, out MethodSymbol userDefinedOperator)
        {
            userDefinedOperator = null;
 
            var type = left.Type;
            if ((object)type == null)
            {
                return false;
            }
 
            if (type.IsDynamic())
            {
                return true;
            }
 
            var booleanType = Compilation.GetSpecialType(SpecialType.System_Boolean);
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            var implicitConversion = Conversions.ClassifyImplicitConversionFromExpression(left, booleanType, ref useSiteInfo);
 
            if (implicitConversion.Exists)
            {
                if (left.Type is not null)
                {
                    var operandPlaceholder = new BoundValuePlaceholder(left.Syntax, left.Type).MakeCompilerGenerated();
                    CreateConversion(left.Syntax, operandPlaceholder, implicitConversion, isCast: false, conversionGroupOpt: null, booleanType, diagnostics);
                }
                else
                {
                    Debug.Assert(left.IsLiteralNull());
                }
 
                diagnostics.Add(left.Syntax, useSiteInfo);
                return true;
            }
 
            if (type.Kind != SymbolKind.NamedType)
            {
                diagnostics.Add(left.Syntax, useSiteInfo);
                return false;
            }
 
            var namedType = type as NamedTypeSymbol;
            var result = HasApplicableBooleanOperator(namedType, isNegative ? WellKnownMemberNames.FalseOperatorName : WellKnownMemberNames.TrueOperatorName, type, ref useSiteInfo, out userDefinedOperator);
            diagnostics.Add(left.Syntax, useSiteInfo);
 
            return result;
        }
 
        private bool IsValidUserDefinedConditionalLogicalOperator(
            CSharpSyntaxNode syntax,
            BinaryOperatorSignature signature,
            BindingDiagnosticBag diagnostics,
            out MethodSymbol trueOperator,
            out MethodSymbol falseOperator)
        {
            Debug.Assert(signature.Kind.OperandTypes() == BinaryOperatorKind.UserDefined);
 
            // SPEC: When the operands of && or || are of types that declare an applicable
            // SPEC: user-defined operator & or |, both of the following must be true, where
            // SPEC: T is the type in which the selected operator is defined:
 
            // SPEC VIOLATION:
            //
            // The native compiler violates the specification, the native compiler allows:
            //
            // public static D? operator &(D? d1, D? d2) { ... }
            // public static bool operator true(D? d) { ... }
            // public static bool operator false(D? d) { ... }
            //
            // to be used as D? && D? or D? || D?. But if you do this:
            //
            // public static D operator &(D d1, D d2) { ... }
            // public static bool operator true(D? d) { ... }
            // public static bool operator false(D? d) { ... }
            //
            // And use the *lifted* form of the operator, this is disallowed.
            //
            // public static D? operator &(D? d1, D d2) { ... }
            // public static bool operator true(D? d) { ... }
            // public static bool operator false(D? d) { ... }
            //
            // Is not allowed because "the return type must be the same as the type of both operands"
            // which is not at all what the spec says.
            //
            // We ought not to break backwards compatibility with the native compiler. The spec
            // is plausibly in error; it is possible that this section of the specification was
            // never updated when nullable types and lifted operators were added to the language.
            // And it seems like the native compiler's behavior of allowing a nullable
            // version but not a lifted version is a bug that should be fixed.
            //
            // Therefore we will do the following in Roslyn:
            //
            // * The return and parameter types of the chosen operator, whether lifted or unlifted,
            //   must be the same.
            // * The return and parameter types must be either the enclosing type, or its corresponding
            //   nullable type.
            // * There must be an operator true/operator false that takes the left hand type of the operator.
 
            // Only classes and structs contain user-defined operators, so we know it is a named type symbol.
            NamedTypeSymbol t = (NamedTypeSymbol)signature.Method.ContainingType;
 
            // SPEC: The return type and the type of each parameter of the selected operator
            // SPEC: must be T.
 
            // As mentioned above, we relax this restriction. The types must all be the same.
 
            bool typesAreSame = TypeSymbol.Equals(signature.LeftType, signature.RightType, TypeCompareKind.ConsiderEverything2) && TypeSymbol.Equals(signature.LeftType, signature.ReturnType, TypeCompareKind.ConsiderEverything2);
            MethodSymbol definition;
            bool typeMatchesContainer = TypeSymbol.Equals(signature.ReturnType.StrippedType(), t, TypeCompareKind.ConsiderEverything2) ||
                                        (t.IsInterface && (signature.Method.IsAbstract || signature.Method.IsVirtual) &&
                                         SourceUserDefinedOperatorSymbol.IsSelfConstrainedTypeParameter((definition = signature.Method.OriginalDefinition).ReturnType.StrippedType(), definition.ContainingType));
 
            if (!typesAreSame || !typeMatchesContainer)
            {
                // CS0217: In order to be applicable as a short circuit operator a user-defined logical
                // operator ('{0}') must have the same return type and parameter types
 
                Error(diagnostics, ErrorCode.ERR_BadBoolOp, syntax, signature.Method);
 
                trueOperator = null;
                falseOperator = null;
                return false;
            }
 
            // SPEC: T must contain declarations of operator true and operator false.
 
            // As mentioned above, we need more than just op true and op false existing; we need
            // to know that the first operand can be passed to it.
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            if (!HasApplicableBooleanOperator(t, WellKnownMemberNames.TrueOperatorName, signature.LeftType, ref useSiteInfo, out trueOperator) ||
                !HasApplicableBooleanOperator(t, WellKnownMemberNames.FalseOperatorName, signature.LeftType, ref useSiteInfo, out falseOperator))
            {
                // I have changed the wording of this error message. The original wording was:
 
                // CS0218: The type ('T') must contain declarations of operator true and operator false
 
                // I have changed that to:
 
                // CS0218: In order to be applicable as a short circuit operator, the declaring type
                // '{1}' of user-defined operator '{0}' must declare operator true and operator false.
 
                Error(diagnostics, ErrorCode.ERR_MustHaveOpTF, syntax, signature.Method, t);
                diagnostics.Add(syntax, useSiteInfo);
 
                trueOperator = null;
                falseOperator = null;
                return false;
            }
 
            diagnostics.Add(syntax, useSiteInfo);
 
            // For the remainder of this method the comments WOLOG assume that we're analyzing an &&. The
            // exact same issues apply to ||.
 
            // Note that the mere *existence* of operator true and operator false is sufficient.  They
            // are already constrained to take either T or T?. Since we know that the applicable
            // T.& takes (T, T), we know that both sides of the && are implicitly convertible
            // to T, and therefore the left side is implicitly convertible to T or T?.
 
            // SPEC: The expression x && y is evaluated as T.false(x) ? x : T.&(x,y) ... except that
            // SPEC: x is only evaluated once.
            //
            // DELIBERATE SPEC VIOLATION: The native compiler does not actually evaluate x&&y in this
            // manner. Suppose X is of type X. The code above is equivalent to:
            //
            // X temp = x, then evaluate:
            // T.false(temp) ? temp : T.&(temp, y)
            //
            // What the native compiler actually evaluates is:
            //
            // T temp = x, then evaluate
            // T.false(temp) ? temp : T.&(temp, y)
            //
            // That is a small difference but it has an observable effect. For example:
            //
            // class V { public static implicit operator T(V v) { ... } }
            // class X : V { public static implicit operator T?(X x) { ... } }
            // struct T {
            //   public static operator false(T? t) { ... }
            //   public static operator true(T? t) { ... }
            //   public static T operator &(T t1, T t2) { ... }
            // }
            //
            // Under the spec'd interpretation, if we had x of type X and y of type T then x && y is
            //
            // X temp = x;
            // T.false(temp) ? temp : T.&(temp, y)
            //
            // which would then be analyzed as:
            //
            // T.false(X.op_Implicit_To_Nullable_T(temp)) ?
            //     V.op_Implicit_To_T(temp) :
            //     T.&(op_Implicit_To_T(temp), y)
            //
            // But the native compiler actually generates:
            //
            // T temp = V.Op_Implicit_To_T(x);
            // T.false(new T?(temp)) ? temp : T.&(temp, y)
            //
            // That is, the native compiler converts the temporary to the type of the declaring operator type
            // regardless of the fact that there is a better conversion for the T.false call.
            //
            // We choose to match the native compiler behavior here; we might consider fixing
            // the spec to match the compiler.
            //
            // With this decision we need not keep track of any extra information in the bound
            // binary operator node; we need to know the left hand side converted to T, the right
            // hand side converted to T, and the method symbol of the chosen T.&(T, T) method.
            // The rewriting pass has enough information to deduce which T.false is to be called,
            // and can convert the T to T? if necessary.
 
            return true;
        }
 
        private bool HasApplicableBooleanOperator(NamedTypeSymbol containingType, string name, TypeSymbol argumentType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out MethodSymbol @operator)
        {
            var operators = ArrayBuilder<MethodSymbol>.GetInstance();
            for (var type = containingType; (object)type != null; type = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
            {
                operators.Clear();
                type.AddOperators(name, operators);
 
                for (var i = 0; i < operators.Count; i++)
                {
                    var op = operators[i];
                    if (op.ParameterCount == 1 && op.DeclaredAccessibility == Accessibility.Public)
                    {
                        var conversion = this.Conversions.ClassifyConversionFromType(argumentType, op.GetParameterType(0), isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
                        if (conversion.IsImplicit)
                        {
                            @operator = op;
                            operators.Free();
                            return true;
                        }
                    }
                }
            }
 
            operators.Free();
            @operator = null;
            return false;
        }
 
        private TypeSymbol GetBinaryOperatorErrorType(BinaryOperatorKind kind, BindingDiagnosticBag diagnostics, CSharpSyntaxNode node)
        {
            switch (kind)
            {
                case BinaryOperatorKind.Equal:
                case BinaryOperatorKind.NotEqual:
                case BinaryOperatorKind.GreaterThan:
                case BinaryOperatorKind.LessThan:
                case BinaryOperatorKind.GreaterThanOrEqual:
                case BinaryOperatorKind.LessThanOrEqual:
                    return GetSpecialType(SpecialType.System_Boolean, diagnostics, node);
                default:
                    return CreateErrorType();
            }
        }
 
        private BinaryOperatorAnalysisResult BinaryOperatorOverloadResolution(
            BinaryOperatorKind kind,
            bool isChecked,
            BoundExpression left,
            BoundExpression right,
            CSharpSyntaxNode node,
            BindingDiagnosticBag diagnostics,
            out LookupResultKind resultKind,
            out ImmutableArray<MethodSymbol> originalUserDefinedOperators)
        {
            if (!IsTypelessExpressionAllowedInBinaryOperator(kind, left, right))
            {
                resultKind = LookupResultKind.OverloadResolutionFailure;
                originalUserDefinedOperators = default(ImmutableArray<MethodSymbol>);
                return default(BinaryOperatorAnalysisResult);
            }
 
            var result = BinaryOperatorOverloadResolutionResult.GetInstance();
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            this.OverloadResolution.BinaryOperatorOverloadResolution(kind, isChecked, left, right, result, ref useSiteInfo);
            diagnostics.Add(node, useSiteInfo);
 
            var possiblyBest = result.Best;
 
            if (result.Results.Any())
            {
                var builder = ArrayBuilder<MethodSymbol>.GetInstance();
                foreach (var analysisResult in result.Results)
                {
                    MethodSymbol method = analysisResult.Signature.Method;
                    if ((object)method != null)
                    {
                        builder.Add(method);
                    }
                }
                originalUserDefinedOperators = builder.ToImmutableAndFree();
 
                if (possiblyBest.HasValue)
                {
                    resultKind = LookupResultKind.Viable;
                }
                else if (result.AnyValid())
                {
                    resultKind = LookupResultKind.Ambiguous;
                }
                else
                {
                    resultKind = LookupResultKind.OverloadResolutionFailure;
                }
            }
            else
            {
                originalUserDefinedOperators = ImmutableArray<MethodSymbol>.Empty;
                resultKind = possiblyBest.HasValue ? LookupResultKind.Viable : LookupResultKind.Empty;
            }
 
            if (possiblyBest is { HasValue: true, Signature: { Method: { } bestMethod } })
            {
                ReportObsoleteAndFeatureAvailabilityDiagnostics(bestMethod, node, diagnostics);
                ReportUseSite(bestMethod, diagnostics, node);
            }
 
            result.Free();
            return possiblyBest;
        }
 
        private void ReportObsoleteAndFeatureAvailabilityDiagnostics(MethodSymbol operatorMethod, CSharpSyntaxNode node, BindingDiagnosticBag diagnostics)
        {
            if ((object)operatorMethod != null)
            {
                ReportDiagnosticsIfObsolete(diagnostics, operatorMethod, node, hasBaseReceiver: false);
 
                if (operatorMethod.ContainingType.IsInterface &&
                    operatorMethod.ContainingModule != Compilation.SourceModule)
                {
                    Binder.CheckFeatureAvailability(node, MessageID.IDS_DefaultInterfaceImplementation, diagnostics);
                }
            }
        }
 
        private bool IsTypelessExpressionAllowedInBinaryOperator(BinaryOperatorKind kind, BoundExpression left, BoundExpression right)
        {
            // The default literal is only allowed with equality operators and both operands cannot be typeless at the same time.
            // Note: we only need to restrict expressions that can be converted to *any* type, in which case the resolution could always succeed.
 
            if (left.IsImplicitObjectCreation() ||
                right.IsImplicitObjectCreation())
            {
                return false;
            }
 
            bool isEquality = kind == BinaryOperatorKind.Equal || kind == BinaryOperatorKind.NotEqual;
            if (isEquality)
            {
                return !left.IsLiteralDefault() || !right.IsLiteralDefault();
            }
            else
            {
                return !left.IsLiteralDefault() && !right.IsLiteralDefault();
            }
        }
 
        private UnaryOperatorAnalysisResult UnaryOperatorOverloadResolution(
            UnaryOperatorKind kind,
            BoundExpression operand,
            CSharpSyntaxNode node,
            BindingDiagnosticBag diagnostics,
            out LookupResultKind resultKind,
            out ImmutableArray<MethodSymbol> originalUserDefinedOperators)
        {
            var result = UnaryOperatorOverloadResolutionResult.GetInstance();
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            this.OverloadResolution.UnaryOperatorOverloadResolution(kind, isChecked: CheckOverflowAtRuntime, operand, result, ref useSiteInfo);
            diagnostics.Add(node, useSiteInfo);
 
            var possiblyBest = result.Best;
 
            if (result.Results.Any())
            {
                var builder = ArrayBuilder<MethodSymbol>.GetInstance();
                foreach (var analysisResult in result.Results)
                {
                    MethodSymbol method = analysisResult.Signature.Method;
                    if ((object)method != null)
                    {
                        builder.Add(method);
                    }
                }
                originalUserDefinedOperators = builder.ToImmutableAndFree();
 
                if (possiblyBest.HasValue)
                {
                    resultKind = LookupResultKind.Viable;
                }
                else if (result.AnyValid())
                {
                    // Special case: If we have the unary minus operator applied to a ulong, technically that should be
                    // an ambiguity. The ulong could be implicitly converted to float, double or decimal, and then
                    // the unary minus operator could be applied to the result. But though float is better than double,
                    // float is neither better nor worse than decimal. However it seems odd to give an ambiguity error
                    // when trying to do something such as applying a unary minus operator to an unsigned long.
                    // The same issue applies to unary minus applied to nuint.
 
                    if (kind == UnaryOperatorKind.UnaryMinus &&
                        (object)operand.Type != null &&
                        (operand.Type.SpecialType == SpecialType.System_UInt64 || isNuint(operand.Type)))
                    {
                        resultKind = LookupResultKind.OverloadResolutionFailure;
                    }
                    else
                    {
                        resultKind = LookupResultKind.Ambiguous;
                    }
                }
                else
                {
                    resultKind = LookupResultKind.OverloadResolutionFailure;
                }
            }
            else
            {
                originalUserDefinedOperators = ImmutableArray<MethodSymbol>.Empty;
                resultKind = possiblyBest.HasValue ? LookupResultKind.Viable : LookupResultKind.Empty;
            }
 
            if (possiblyBest is { HasValue: true, Signature: { Method: { } bestMethod } })
            {
                ReportObsoleteAndFeatureAvailabilityDiagnostics(bestMethod, node, diagnostics);
                ReportUseSite(bestMethod, diagnostics, node);
            }
 
            result.Free();
            return possiblyBest;
 
            static bool isNuint(TypeSymbol type)
            {
                return type.SpecialType == SpecialType.System_UIntPtr
                    && type.IsNativeIntegerType;
            }
        }
 
        private static object FoldDecimalBinaryOperators(BinaryOperatorKind kind, ConstantValue valueLeft, ConstantValue valueRight)
        {
            Debug.Assert(valueLeft != null);
            Debug.Assert(valueRight != null);
 
            // Roslyn uses Decimal.operator+, operator-, etc. for both constant expressions and
            // non-constant expressions. Dev11 uses Decimal.operator+ etc. for non-constant
            // expressions only. This leads to different results between the two compilers
            // for certain constant expressions involving +/-0. (See bug #529730.) For instance,
            // +0 + -0 == +0 in Roslyn and == -0 in Dev11. Similarly, -0 - -0 == -0 in Roslyn, +0 in Dev11.
            // This is a breaking change from the native compiler but seems acceptable since
            // constant and non-constant expressions behave consistently in Roslyn.
            // (In Dev11, (+0 + -0) != (x + y) when x = +0, y = -0.)
 
            switch (kind)
            {
                case BinaryOperatorKind.DecimalAddition:
                    return valueLeft.DecimalValue + valueRight.DecimalValue;
                case BinaryOperatorKind.DecimalSubtraction:
                    return valueLeft.DecimalValue - valueRight.DecimalValue;
                case BinaryOperatorKind.DecimalMultiplication:
                    return valueLeft.DecimalValue * valueRight.DecimalValue;
                case BinaryOperatorKind.DecimalDivision:
                    return valueLeft.DecimalValue / valueRight.DecimalValue;
                case BinaryOperatorKind.DecimalRemainder:
                    return valueLeft.DecimalValue % valueRight.DecimalValue;
            }
 
            return null;
        }
 
        private static object FoldNativeIntegerOverflowingBinaryOperator(BinaryOperatorKind kind, ConstantValue valueLeft, ConstantValue valueRight)
        {
            Debug.Assert(valueLeft != null);
            Debug.Assert(valueRight != null);
 
            checked
            {
                switch (kind)
                {
                    case BinaryOperatorKind.NIntAddition:
                        return valueLeft.Int32Value + valueRight.Int32Value;
                    case BinaryOperatorKind.NUIntAddition:
                        return valueLeft.UInt32Value + valueRight.UInt32Value;
                    case BinaryOperatorKind.NIntSubtraction:
                        return valueLeft.Int32Value - valueRight.Int32Value;
                    case BinaryOperatorKind.NUIntSubtraction:
                        return valueLeft.UInt32Value - valueRight.UInt32Value;
                    case BinaryOperatorKind.NIntMultiplication:
                        return valueLeft.Int32Value * valueRight.Int32Value;
                    case BinaryOperatorKind.NUIntMultiplication:
                        return valueLeft.UInt32Value * valueRight.UInt32Value;
                    case BinaryOperatorKind.NIntDivision:
                        return valueLeft.Int32Value / valueRight.Int32Value;
                    case BinaryOperatorKind.NIntRemainder:
                        return valueLeft.Int32Value % valueRight.Int32Value;
                    case BinaryOperatorKind.NIntLeftShift:
                        {
                            var int32Value = valueLeft.Int32Value << valueRight.Int32Value;
                            var int64Value = valueLeft.Int64Value << valueRight.Int32Value;
                            return (int32Value == int64Value) ? int32Value : null;
                        }
                    case BinaryOperatorKind.NUIntLeftShift:
                        {
                            var uint32Value = valueLeft.UInt32Value << valueRight.Int32Value;
                            var uint64Value = valueLeft.UInt64Value << valueRight.Int32Value;
                            return (uint32Value == uint64Value) ? uint32Value : null;
                        }
                }
 
                return null;
            }
        }
 
        private static object FoldUncheckedIntegralBinaryOperator(BinaryOperatorKind kind, ConstantValue valueLeft, ConstantValue valueRight)
        {
            Debug.Assert(valueLeft != null);
            Debug.Assert(valueRight != null);
 
            unchecked
            {
                switch (kind)
                {
                    case BinaryOperatorKind.IntAddition:
                        return valueLeft.Int32Value + valueRight.Int32Value;
                    case BinaryOperatorKind.LongAddition:
                        return valueLeft.Int64Value + valueRight.Int64Value;
                    case BinaryOperatorKind.UIntAddition:
                        return valueLeft.UInt32Value + valueRight.UInt32Value;
                    case BinaryOperatorKind.ULongAddition:
                        return valueLeft.UInt64Value + valueRight.UInt64Value;
                    case BinaryOperatorKind.IntSubtraction:
                        return valueLeft.Int32Value - valueRight.Int32Value;
                    case BinaryOperatorKind.LongSubtraction:
                        return valueLeft.Int64Value - valueRight.Int64Value;
                    case BinaryOperatorKind.UIntSubtraction:
                        return valueLeft.UInt32Value - valueRight.UInt32Value;
                    case BinaryOperatorKind.ULongSubtraction:
                        return valueLeft.UInt64Value - valueRight.UInt64Value;
                    case BinaryOperatorKind.IntMultiplication:
                        return valueLeft.Int32Value * valueRight.Int32Value;
                    case BinaryOperatorKind.LongMultiplication:
                        return valueLeft.Int64Value * valueRight.Int64Value;
                    case BinaryOperatorKind.UIntMultiplication:
                        return valueLeft.UInt32Value * valueRight.UInt32Value;
                    case BinaryOperatorKind.ULongMultiplication:
                        return valueLeft.UInt64Value * valueRight.UInt64Value;
 
                    // even in unchecked context division may overflow:
                    case BinaryOperatorKind.IntDivision:
                        if (valueLeft.Int32Value == int.MinValue && valueRight.Int32Value == -1)
                        {
                            return int.MinValue;
                        }
 
                        return valueLeft.Int32Value / valueRight.Int32Value;
 
                    case BinaryOperatorKind.LongDivision:
                        if (valueLeft.Int64Value == long.MinValue && valueRight.Int64Value == -1)
                        {
                            return long.MinValue;
                        }
 
                        return valueLeft.Int64Value / valueRight.Int64Value;
                }
 
                return null;
            }
        }
 
        private static object FoldCheckedIntegralBinaryOperator(BinaryOperatorKind kind, ConstantValue valueLeft, ConstantValue valueRight)
        {
            Debug.Assert(valueLeft != null);
            Debug.Assert(valueRight != null);
 
            checked
            {
                switch (kind)
                {
                    case BinaryOperatorKind.IntAddition:
                        return valueLeft.Int32Value + valueRight.Int32Value;
                    case BinaryOperatorKind.LongAddition:
                        return valueLeft.Int64Value + valueRight.Int64Value;
                    case BinaryOperatorKind.UIntAddition:
                        return valueLeft.UInt32Value + valueRight.UInt32Value;
                    case BinaryOperatorKind.ULongAddition:
                        return valueLeft.UInt64Value + valueRight.UInt64Value;
                    case BinaryOperatorKind.IntSubtraction:
                        return valueLeft.Int32Value - valueRight.Int32Value;
                    case BinaryOperatorKind.LongSubtraction:
                        return valueLeft.Int64Value - valueRight.Int64Value;
                    case BinaryOperatorKind.UIntSubtraction:
                        return valueLeft.UInt32Value - valueRight.UInt32Value;
                    case BinaryOperatorKind.ULongSubtraction:
                        return valueLeft.UInt64Value - valueRight.UInt64Value;
                    case BinaryOperatorKind.IntMultiplication:
                        return valueLeft.Int32Value * valueRight.Int32Value;
                    case BinaryOperatorKind.LongMultiplication:
                        return valueLeft.Int64Value * valueRight.Int64Value;
                    case BinaryOperatorKind.UIntMultiplication:
                        return valueLeft.UInt32Value * valueRight.UInt32Value;
                    case BinaryOperatorKind.ULongMultiplication:
                        return valueLeft.UInt64Value * valueRight.UInt64Value;
                    case BinaryOperatorKind.IntDivision:
                        return valueLeft.Int32Value / valueRight.Int32Value;
                    case BinaryOperatorKind.LongDivision:
                        return valueLeft.Int64Value / valueRight.Int64Value;
                }
 
                return null;
            }
        }
 
        internal static TypeSymbol GetEnumType(BinaryOperatorKind kind, BoundExpression left, BoundExpression right)
        {
            switch (kind)
            {
                case BinaryOperatorKind.EnumAndUnderlyingAddition:
                case BinaryOperatorKind.EnumAndUnderlyingSubtraction:
                case BinaryOperatorKind.EnumAnd:
                case BinaryOperatorKind.EnumOr:
                case BinaryOperatorKind.EnumXor:
                case BinaryOperatorKind.EnumEqual:
                case BinaryOperatorKind.EnumGreaterThan:
                case BinaryOperatorKind.EnumGreaterThanOrEqual:
                case BinaryOperatorKind.EnumLessThan:
                case BinaryOperatorKind.EnumLessThanOrEqual:
                case BinaryOperatorKind.EnumNotEqual:
                case BinaryOperatorKind.EnumSubtraction:
                    return left.Type;
                case BinaryOperatorKind.UnderlyingAndEnumAddition:
                case BinaryOperatorKind.UnderlyingAndEnumSubtraction:
                    return right.Type;
                default:
                    throw ExceptionUtilities.UnexpectedValue(kind);
            }
        }
 
        internal static SpecialType GetEnumPromotedType(SpecialType underlyingType)
        {
            switch (underlyingType)
            {
                case SpecialType.System_Byte:
                case SpecialType.System_SByte:
                case SpecialType.System_Int16:
                case SpecialType.System_UInt16:
                    return SpecialType.System_Int32;
 
                case SpecialType.System_Int32:
                case SpecialType.System_UInt32:
                case SpecialType.System_Int64:
                case SpecialType.System_UInt64:
                    return underlyingType;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(underlyingType);
            }
        }
 
#nullable enable
        private ConstantValue? FoldEnumBinaryOperator(
            CSharpSyntaxNode syntax,
            BinaryOperatorKind kind,
            BoundExpression left,
            BoundExpression right,
            TypeSymbol resultTypeSymbol,
            BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(left != null);
            Debug.Assert(right != null);
            Debug.Assert(kind.IsEnum());
            Debug.Assert(!kind.IsLifted());
 
            // A built-in binary operation on constant enum operands is evaluated into an operation on
            // constants of the underlying type U of the enum type E. Comparison operators are lowered as
            // simply computing U<U. All other operators are computed as (E)(U op U) or in the case of
            // E-E, (U)(U-U).
 
            TypeSymbol enumType = GetEnumType(kind, left, right);
            TypeSymbol underlyingType = enumType.GetEnumUnderlyingType()!;
 
            BoundExpression newLeftOperand = CreateConversion(left, underlyingType, diagnostics);
            BoundExpression newRightOperand = CreateConversion(right, underlyingType, diagnostics);
 
            // If the underlying type is byte, sbyte, short, ushort or nullables of those then we'll need
            // to convert it up to int or int? because there are no + - * & | ^ < > <= >= == != operators
            // on byte, sbyte, short or ushort. They all convert to int.
 
            SpecialType operandSpecialType = GetEnumPromotedType(underlyingType.SpecialType);
            TypeSymbol operandType = (operandSpecialType == underlyingType.SpecialType) ?
                underlyingType :
                GetSpecialType(operandSpecialType, diagnostics, syntax);
 
            newLeftOperand = CreateConversion(newLeftOperand, operandType, diagnostics);
            newRightOperand = CreateConversion(newRightOperand, operandType, diagnostics);
 
            BinaryOperatorKind newKind = kind.Operator().WithType(newLeftOperand.Type!.SpecialType);
 
            switch (newKind.Operator())
            {
                case BinaryOperatorKind.Addition:
                case BinaryOperatorKind.Subtraction:
                case BinaryOperatorKind.And:
                case BinaryOperatorKind.Or:
                case BinaryOperatorKind.Xor:
                    resultTypeSymbol = operandType;
                    break;
 
                case BinaryOperatorKind.LessThan:
                case BinaryOperatorKind.LessThanOrEqual:
                case BinaryOperatorKind.GreaterThan:
                case BinaryOperatorKind.GreaterThanOrEqual:
                case BinaryOperatorKind.Equal:
                case BinaryOperatorKind.NotEqual:
                    Debug.Assert(resultTypeSymbol.SpecialType == SpecialType.System_Boolean);
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(newKind.Operator());
            }
 
            var constantValue = FoldBinaryOperator(syntax, newKind, newLeftOperand, newRightOperand, resultTypeSymbol, diagnostics);
 
            if (resultTypeSymbol.SpecialType != SpecialType.System_Boolean && constantValue != null && !constantValue.IsBad)
            {
                TypeSymbol resultType = kind == BinaryOperatorKind.EnumSubtraction ? underlyingType : enumType;
 
                // We might need to convert back to the underlying type.
                return FoldConstantNumericConversion(syntax, constantValue, resultType, diagnostics);
            }
 
            return constantValue;
        }
 
        // Returns null if the operator can't be evaluated at compile time.
        private ConstantValue? FoldBinaryOperator(
            CSharpSyntaxNode syntax,
            BinaryOperatorKind kind,
            BoundExpression left,
            BoundExpression right,
            TypeSymbol resultTypeSymbol,
            BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(left != null);
            Debug.Assert(right != null);
 
            if (left.HasAnyErrors || right.HasAnyErrors)
            {
                return null;
            }
 
            // SPEC VIOLATION: see method definition for details
            ConstantValue? nullableEqualityResult = TryFoldingNullableEquality(kind, left, right);
            if (nullableEqualityResult != null)
            {
                return nullableEqualityResult;
            }
 
            var valueLeft = left.ConstantValueOpt;
            var valueRight = right.ConstantValueOpt;
            if (valueLeft == null || valueRight == null)
            {
                return null;
            }
 
            if (valueLeft.IsBad || valueRight.IsBad)
            {
                return ConstantValue.Bad;
            }
 
            if (kind.IsEnum() && !kind.IsLifted())
            {
                return FoldEnumBinaryOperator(syntax, kind, left, right, resultTypeSymbol, diagnostics);
            }
 
            // Divisions by zero on integral types and decimal always fail even in an unchecked context.
            if (IsDivisionByZero(kind, valueRight))
            {
                Error(diagnostics, ErrorCode.ERR_IntDivByZero, syntax);
                return ConstantValue.Bad;
            }
 
            object? newValue = null;
            SpecialType resultType = resultTypeSymbol.SpecialType;
 
            // Certain binary operations never fail; bool & bool, for example. If we are in one of those
            // cases, simply fold the operation and return.
            //
            // Although remainder and division always overflow at runtime with arguments int.MinValue/long.MinValue and -1
            // (regardless of checked context) the constant folding behavior is different.
            // Remainder never overflows at compile time while division does.
            newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight);
            if (newValue != null)
            {
                return ConstantValue.Create(newValue, resultType);
            }
 
            ConstantValue? concatResult = FoldStringConcatenation(kind, valueLeft, valueRight);
            if (concatResult != null)
            {
                if (concatResult.IsBad)
                {
                    Error(diagnostics, ErrorCode.ERR_ConstantStringTooLong, right.Syntax);
                }
 
                return concatResult;
            }
 
            // Certain binary operations always fail if they overflow even when in an unchecked context;
            // decimal + decimal, for example. If we are in one of those cases, make the attempt. If it
            // succeeds, return the result. If not, give a compile-time error regardless of context.
            try
            {
                newValue = FoldDecimalBinaryOperators(kind, valueLeft, valueRight);
            }
            catch (OverflowException)
            {
                Error(diagnostics, ErrorCode.ERR_DecConstError, syntax);
                return ConstantValue.Bad;
            }
 
            if (newValue != null)
            {
                return ConstantValue.Create(newValue, resultType);
            }
 
            try
            {
                newValue = FoldNativeIntegerOverflowingBinaryOperator(kind, valueLeft, valueRight);
            }
            catch (OverflowException)
            {
                if (CheckOverflowAtCompileTime)
                {
                    Error(diagnostics, ErrorCode.WRN_CompileTimeCheckedOverflow, syntax, resultTypeSymbol);
                }
 
                return null;
            }
 
            if (newValue != null)
            {
                return ConstantValue.Create(newValue, resultType);
            }
 
            if (CheckOverflowAtCompileTime)
            {
                try
                {
                    newValue = FoldCheckedIntegralBinaryOperator(kind, valueLeft, valueRight);
                }
                catch (OverflowException)
                {
                    Error(diagnostics, ErrorCode.ERR_CheckedOverflow, syntax);
                    return ConstantValue.Bad;
                }
            }
            else
            {
                newValue = FoldUncheckedIntegralBinaryOperator(kind, valueLeft, valueRight);
            }
 
            if (newValue != null)
            {
                return ConstantValue.Create(newValue, resultType);
            }
 
            return null;
        }
 
        /// <summary>
        /// If one of the (unconverted) operands has constant value null and the other has
        /// a null constant value other than null, then they are definitely not equal
        /// and we can give a constant value for either == or !=.  This is a spec violation
        /// that we retain from Dev10.
        /// </summary>
        /// <param name="kind">The operator kind.  Nothing will happen if it is not a lifted equality operator.</param>
        /// <param name="left">The left-hand operand of the operation (possibly wrapped in a conversion).</param>
        /// <param name="right">The right-hand operand of the operation (possibly wrapped in a conversion).</param>
        /// <returns>
        /// If the operator represents lifted equality, then constant value true if both arguments have constant
        /// value null, constant value false if exactly one argument has constant value null, and null otherwise.
        /// If the operator represents lifted inequality, then constant value false if both arguments have constant
        /// value null, constant value true if exactly one argument has constant value null, and null otherwise.
        /// </returns>
        /// <remarks>
        /// SPEC VIOLATION: according to the spec (section 7.19) constant expressions cannot
        /// include implicit nullable conversions or nullable subexpressions.  However, Dev10
        /// specifically folds over lifted == and != (see ExpressionBinder::TryFoldingNullableEquality).
        /// Dev 10 does do compile-time evaluation of simple lifted operators, but it does so
        /// in a rewriting pass (see NullableRewriter) - they are not treated as constant values.
        /// </remarks>
        private static ConstantValue? TryFoldingNullableEquality(BinaryOperatorKind kind, BoundExpression left, BoundExpression right)
        {
            if (kind.IsLifted())
            {
                BinaryOperatorKind op = kind.Operator();
                if (op == BinaryOperatorKind.Equal || op == BinaryOperatorKind.NotEqual)
                {
                    if (left.Kind == BoundKind.Conversion && right.Kind == BoundKind.Conversion)
                    {
                        BoundConversion leftConv = (BoundConversion)left;
                        BoundConversion rightConv = (BoundConversion)right;
                        ConstantValue? leftConstant = leftConv.Operand.ConstantValueOpt;
                        ConstantValue? rightConstant = rightConv.Operand.ConstantValueOpt;
 
                        if (leftConstant != null && rightConstant != null)
                        {
                            bool leftIsNull = leftConstant.IsNull;
                            bool rightIsNull = rightConstant.IsNull;
                            if (leftIsNull || rightIsNull)
                            {
                                // IMPL CHANGE: Dev10 raises WRN_NubExprIsConstBool in some cases, but that really doesn't
                                // make sense (why warn that a constant has a constant value?).
                                return (leftIsNull == rightIsNull) == (op == BinaryOperatorKind.Equal) ? ConstantValue.True : ConstantValue.False;
                            }
                        }
                    }
                }
            }
 
            return null;
        }
 
        // Some binary operators on constants never overflow, regardless of whether the context is checked or not.
        private static object? FoldNeverOverflowBinaryOperators(BinaryOperatorKind kind, ConstantValue valueLeft, ConstantValue valueRight)
        {
            Debug.Assert(valueLeft != null);
            Debug.Assert(valueRight != null);
 
            // Note that we *cannot* do folding on single-precision floats as doubles to preserve precision,
            // as that would cause incorrect rounding that would be impossible to correct afterwards.
            switch (kind)
            {
                case BinaryOperatorKind.ObjectEqual:
                    if (valueLeft.IsNull) return valueRight.IsNull;
                    if (valueRight.IsNull) return false;
                    break;
                case BinaryOperatorKind.ObjectNotEqual:
                    if (valueLeft.IsNull) return !valueRight.IsNull;
                    if (valueRight.IsNull) return true;
                    break;
                case BinaryOperatorKind.DoubleAddition:
                    return valueLeft.DoubleValue + valueRight.DoubleValue;
                case BinaryOperatorKind.FloatAddition:
                    return valueLeft.SingleValue + valueRight.SingleValue;
                case BinaryOperatorKind.DoubleSubtraction:
                    return valueLeft.DoubleValue - valueRight.DoubleValue;
                case BinaryOperatorKind.FloatSubtraction:
                    return valueLeft.SingleValue - valueRight.SingleValue;
                case BinaryOperatorKind.DoubleMultiplication:
                    return valueLeft.DoubleValue * valueRight.DoubleValue;
                case BinaryOperatorKind.FloatMultiplication:
                    return valueLeft.SingleValue * valueRight.SingleValue;
                case BinaryOperatorKind.DoubleDivision:
                    return valueLeft.DoubleValue / valueRight.DoubleValue;
                case BinaryOperatorKind.FloatDivision:
                    return valueLeft.SingleValue / valueRight.SingleValue;
                case BinaryOperatorKind.DoubleRemainder:
                    return valueLeft.DoubleValue % valueRight.DoubleValue;
                case BinaryOperatorKind.FloatRemainder:
                    return valueLeft.SingleValue % valueRight.SingleValue;
                case BinaryOperatorKind.IntLeftShift:
                    return valueLeft.Int32Value << valueRight.Int32Value;
                case BinaryOperatorKind.LongLeftShift:
                    return valueLeft.Int64Value << valueRight.Int32Value;
                case BinaryOperatorKind.UIntLeftShift:
                    return valueLeft.UInt32Value << valueRight.Int32Value;
                case BinaryOperatorKind.ULongLeftShift:
                    return valueLeft.UInt64Value << valueRight.Int32Value;
                case BinaryOperatorKind.IntRightShift:
                case BinaryOperatorKind.NIntRightShift:
                    return valueLeft.Int32Value >> valueRight.Int32Value;
                case BinaryOperatorKind.IntUnsignedRightShift:
                    return (int)(((uint)valueLeft.Int32Value) >> valueRight.Int32Value); // Switch to `valueLeft.Int32Value >>> valueRight.Int32Value` once >>> becomes available
                case BinaryOperatorKind.NIntUnsignedRightShift:
                    return (valueLeft.Int32Value >= 0) ? valueLeft.Int32Value >> valueRight.Int32Value : null;
                case BinaryOperatorKind.LongRightShift:
                    return valueLeft.Int64Value >> valueRight.Int32Value;
                case BinaryOperatorKind.LongUnsignedRightShift:
                    return (long)(((ulong)valueLeft.Int64Value) >> valueRight.Int32Value); // Switch to `valueLeft.Int64Value >>> valueRight.Int32Value` once >>> becomes available 
                case BinaryOperatorKind.UIntRightShift:
                case BinaryOperatorKind.NUIntRightShift:
                case BinaryOperatorKind.UIntUnsignedRightShift:
                case BinaryOperatorKind.NUIntUnsignedRightShift:
                    return valueLeft.UInt32Value >> valueRight.Int32Value;
                case BinaryOperatorKind.ULongRightShift:
                case BinaryOperatorKind.ULongUnsignedRightShift:
                    return valueLeft.UInt64Value >> valueRight.Int32Value;
                case BinaryOperatorKind.BoolAnd:
                    return valueLeft.BooleanValue & valueRight.BooleanValue;
                case BinaryOperatorKind.IntAnd:
                case BinaryOperatorKind.NIntAnd:
                    return valueLeft.Int32Value & valueRight.Int32Value;
                case BinaryOperatorKind.LongAnd:
                    return valueLeft.Int64Value & valueRight.Int64Value;
                case BinaryOperatorKind.UIntAnd:
                case BinaryOperatorKind.NUIntAnd:
                    return valueLeft.UInt32Value & valueRight.UInt32Value;
                case BinaryOperatorKind.ULongAnd:
                    return valueLeft.UInt64Value & valueRight.UInt64Value;
                case BinaryOperatorKind.BoolOr:
                    return valueLeft.BooleanValue | valueRight.BooleanValue;
                case BinaryOperatorKind.IntOr:
                case BinaryOperatorKind.NIntOr:
                    return valueLeft.Int32Value | valueRight.Int32Value;
                case BinaryOperatorKind.LongOr:
                    return valueLeft.Int64Value | valueRight.Int64Value;
                case BinaryOperatorKind.UIntOr:
                case BinaryOperatorKind.NUIntOr:
                    return valueLeft.UInt32Value | valueRight.UInt32Value;
                case BinaryOperatorKind.ULongOr:
                    return valueLeft.UInt64Value | valueRight.UInt64Value;
                case BinaryOperatorKind.BoolXor:
                    return valueLeft.BooleanValue ^ valueRight.BooleanValue;
                case BinaryOperatorKind.IntXor:
                case BinaryOperatorKind.NIntXor:
                    return valueLeft.Int32Value ^ valueRight.Int32Value;
                case BinaryOperatorKind.LongXor:
                    return valueLeft.Int64Value ^ valueRight.Int64Value;
                case BinaryOperatorKind.UIntXor:
                case BinaryOperatorKind.NUIntXor:
                    return valueLeft.UInt32Value ^ valueRight.UInt32Value;
                case BinaryOperatorKind.ULongXor:
                    return valueLeft.UInt64Value ^ valueRight.UInt64Value;
                case BinaryOperatorKind.LogicalBoolAnd:
                    return valueLeft.BooleanValue && valueRight.BooleanValue;
                case BinaryOperatorKind.LogicalBoolOr:
                    return valueLeft.BooleanValue || valueRight.BooleanValue;
                case BinaryOperatorKind.BoolEqual:
                    return valueLeft.BooleanValue == valueRight.BooleanValue;
                case BinaryOperatorKind.StringEqual:
                    return valueLeft.StringValue == valueRight.StringValue;
                case BinaryOperatorKind.DecimalEqual:
                    return valueLeft.DecimalValue == valueRight.DecimalValue;
                case BinaryOperatorKind.FloatEqual:
                    return valueLeft.SingleValue == valueRight.SingleValue;
                case BinaryOperatorKind.DoubleEqual:
                    return valueLeft.DoubleValue == valueRight.DoubleValue;
                case BinaryOperatorKind.IntEqual:
                case BinaryOperatorKind.NIntEqual:
                    return valueLeft.Int32Value == valueRight.Int32Value;
                case BinaryOperatorKind.LongEqual:
                    return valueLeft.Int64Value == valueRight.Int64Value;
                case BinaryOperatorKind.UIntEqual:
                case BinaryOperatorKind.NUIntEqual:
                    return valueLeft.UInt32Value == valueRight.UInt32Value;
                case BinaryOperatorKind.ULongEqual:
                    return valueLeft.UInt64Value == valueRight.UInt64Value;
                case BinaryOperatorKind.BoolNotEqual:
                    return valueLeft.BooleanValue != valueRight.BooleanValue;
                case BinaryOperatorKind.StringNotEqual:
                    return valueLeft.StringValue != valueRight.StringValue;
                case BinaryOperatorKind.DecimalNotEqual:
                    return valueLeft.DecimalValue != valueRight.DecimalValue;
                case BinaryOperatorKind.FloatNotEqual:
                    return valueLeft.SingleValue != valueRight.SingleValue;
                case BinaryOperatorKind.DoubleNotEqual:
                    return valueLeft.DoubleValue != valueRight.DoubleValue;
                case BinaryOperatorKind.IntNotEqual:
                case BinaryOperatorKind.NIntNotEqual:
                    return valueLeft.Int32Value != valueRight.Int32Value;
                case BinaryOperatorKind.LongNotEqual:
                    return valueLeft.Int64Value != valueRight.Int64Value;
                case BinaryOperatorKind.UIntNotEqual:
                case BinaryOperatorKind.NUIntNotEqual:
                    return valueLeft.UInt32Value != valueRight.UInt32Value;
                case BinaryOperatorKind.ULongNotEqual:
                    return valueLeft.UInt64Value != valueRight.UInt64Value;
                case BinaryOperatorKind.DecimalLessThan:
                    return valueLeft.DecimalValue < valueRight.DecimalValue;
                case BinaryOperatorKind.FloatLessThan:
                    return valueLeft.SingleValue < valueRight.SingleValue;
                case BinaryOperatorKind.DoubleLessThan:
                    return valueLeft.DoubleValue < valueRight.DoubleValue;
                case BinaryOperatorKind.IntLessThan:
                case BinaryOperatorKind.NIntLessThan:
                    return valueLeft.Int32Value < valueRight.Int32Value;
                case BinaryOperatorKind.LongLessThan:
                    return valueLeft.Int64Value < valueRight.Int64Value;
                case BinaryOperatorKind.UIntLessThan:
                case BinaryOperatorKind.NUIntLessThan:
                    return valueLeft.UInt32Value < valueRight.UInt32Value;
                case BinaryOperatorKind.ULongLessThan:
                    return valueLeft.UInt64Value < valueRight.UInt64Value;
                case BinaryOperatorKind.DecimalGreaterThan:
                    return valueLeft.DecimalValue > valueRight.DecimalValue;
                case BinaryOperatorKind.FloatGreaterThan:
                    return valueLeft.SingleValue > valueRight.SingleValue;
                case BinaryOperatorKind.DoubleGreaterThan:
                    return valueLeft.DoubleValue > valueRight.DoubleValue;
                case BinaryOperatorKind.IntGreaterThan:
                case BinaryOperatorKind.NIntGreaterThan:
                    return valueLeft.Int32Value > valueRight.Int32Value;
                case BinaryOperatorKind.LongGreaterThan:
                    return valueLeft.Int64Value > valueRight.Int64Value;
                case BinaryOperatorKind.UIntGreaterThan:
                case BinaryOperatorKind.NUIntGreaterThan:
                    return valueLeft.UInt32Value > valueRight.UInt32Value;
                case BinaryOperatorKind.ULongGreaterThan:
                    return valueLeft.UInt64Value > valueRight.UInt64Value;
                case BinaryOperatorKind.DecimalLessThanOrEqual:
                    return valueLeft.DecimalValue <= valueRight.DecimalValue;
                case BinaryOperatorKind.FloatLessThanOrEqual:
                    return valueLeft.SingleValue <= valueRight.SingleValue;
                case BinaryOperatorKind.DoubleLessThanOrEqual:
                    return valueLeft.DoubleValue <= valueRight.DoubleValue;
                case BinaryOperatorKind.IntLessThanOrEqual:
                case BinaryOperatorKind.NIntLessThanOrEqual:
                    return valueLeft.Int32Value <= valueRight.Int32Value;
                case BinaryOperatorKind.LongLessThanOrEqual:
                    return valueLeft.Int64Value <= valueRight.Int64Value;
                case BinaryOperatorKind.UIntLessThanOrEqual:
                case BinaryOperatorKind.NUIntLessThanOrEqual:
                    return valueLeft.UInt32Value <= valueRight.UInt32Value;
                case BinaryOperatorKind.ULongLessThanOrEqual:
                    return valueLeft.UInt64Value <= valueRight.UInt64Value;
                case BinaryOperatorKind.DecimalGreaterThanOrEqual:
                    return valueLeft.DecimalValue >= valueRight.DecimalValue;
                case BinaryOperatorKind.FloatGreaterThanOrEqual:
                    return valueLeft.SingleValue >= valueRight.SingleValue;
                case BinaryOperatorKind.DoubleGreaterThanOrEqual:
                    return valueLeft.DoubleValue >= valueRight.DoubleValue;
                case BinaryOperatorKind.IntGreaterThanOrEqual:
                case BinaryOperatorKind.NIntGreaterThanOrEqual:
                    return valueLeft.Int32Value >= valueRight.Int32Value;
                case BinaryOperatorKind.LongGreaterThanOrEqual:
                    return valueLeft.Int64Value >= valueRight.Int64Value;
                case BinaryOperatorKind.UIntGreaterThanOrEqual:
                case BinaryOperatorKind.NUIntGreaterThanOrEqual:
                    return valueLeft.UInt32Value >= valueRight.UInt32Value;
                case BinaryOperatorKind.ULongGreaterThanOrEqual:
                    return valueLeft.UInt64Value >= valueRight.UInt64Value;
                case BinaryOperatorKind.UIntDivision:
                case BinaryOperatorKind.NUIntDivision:
                    return valueLeft.UInt32Value / valueRight.UInt32Value;
                case BinaryOperatorKind.ULongDivision:
                    return valueLeft.UInt64Value / valueRight.UInt64Value;
 
                // MinValue % -1 always overflows at runtime but never at compile time
                case BinaryOperatorKind.IntRemainder:
                    return (valueRight.Int32Value != -1) ? valueLeft.Int32Value % valueRight.Int32Value : 0;
                case BinaryOperatorKind.LongRemainder:
                    return (valueRight.Int64Value != -1) ? valueLeft.Int64Value % valueRight.Int64Value : 0;
                case BinaryOperatorKind.UIntRemainder:
                case BinaryOperatorKind.NUIntRemainder:
                    return valueLeft.UInt32Value % valueRight.UInt32Value;
                case BinaryOperatorKind.ULongRemainder:
                    return valueLeft.UInt64Value % valueRight.UInt64Value;
            }
 
            return null;
        }
 
        /// <summary>
        /// Returns ConstantValue.Bad if, and only if, the resulting string length exceeds <see cref="int.MaxValue"/>.
        /// </summary>
        private static ConstantValue? FoldStringConcatenation(BinaryOperatorKind kind, ConstantValue valueLeft, ConstantValue valueRight)
        {
            Debug.Assert(valueLeft != null);
            Debug.Assert(valueRight != null);
 
            if (kind == BinaryOperatorKind.StringConcatenation)
            {
                Rope leftValue = valueLeft.RopeValue ?? Rope.Empty;
                Rope rightValue = valueRight.RopeValue ?? Rope.Empty;
 
                long newLength = (long)leftValue.Length + (long)rightValue.Length;
                return (newLength > int.MaxValue) ? ConstantValue.Bad : ConstantValue.CreateFromRope(Rope.Concat(leftValue, rightValue));
            }
 
            return null;
        }
#nullable disable
 
        public static BinaryOperatorKind SyntaxKindToBinaryOperatorKind(SyntaxKind kind)
        {
            switch (kind)
            {
                case SyntaxKind.MultiplyAssignmentExpression:
                case SyntaxKind.MultiplyExpression: return BinaryOperatorKind.Multiplication;
                case SyntaxKind.DivideAssignmentExpression:
                case SyntaxKind.DivideExpression: return BinaryOperatorKind.Division;
                case SyntaxKind.ModuloAssignmentExpression:
                case SyntaxKind.ModuloExpression: return BinaryOperatorKind.Remainder;
                case SyntaxKind.AddAssignmentExpression:
                case SyntaxKind.AddExpression: return BinaryOperatorKind.Addition;
                case SyntaxKind.SubtractAssignmentExpression:
                case SyntaxKind.SubtractExpression: return BinaryOperatorKind.Subtraction;
                case SyntaxKind.RightShiftAssignmentExpression:
                case SyntaxKind.RightShiftExpression: return BinaryOperatorKind.RightShift;
                case SyntaxKind.UnsignedRightShiftAssignmentExpression:
                case SyntaxKind.UnsignedRightShiftExpression: return BinaryOperatorKind.UnsignedRightShift;
                case SyntaxKind.LeftShiftAssignmentExpression:
                case SyntaxKind.LeftShiftExpression: return BinaryOperatorKind.LeftShift;
                case SyntaxKind.EqualsExpression: return BinaryOperatorKind.Equal;
                case SyntaxKind.NotEqualsExpression: return BinaryOperatorKind.NotEqual;
                case SyntaxKind.GreaterThanExpression: return BinaryOperatorKind.GreaterThan;
                case SyntaxKind.LessThanExpression: return BinaryOperatorKind.LessThan;
                case SyntaxKind.GreaterThanOrEqualExpression: return BinaryOperatorKind.GreaterThanOrEqual;
                case SyntaxKind.LessThanOrEqualExpression: return BinaryOperatorKind.LessThanOrEqual;
                case SyntaxKind.AndAssignmentExpression:
                case SyntaxKind.BitwiseAndExpression: return BinaryOperatorKind.And;
                case SyntaxKind.OrAssignmentExpression:
                case SyntaxKind.BitwiseOrExpression: return BinaryOperatorKind.Or;
                case SyntaxKind.ExclusiveOrAssignmentExpression:
                case SyntaxKind.ExclusiveOrExpression: return BinaryOperatorKind.Xor;
                case SyntaxKind.LogicalAndExpression: return BinaryOperatorKind.LogicalAnd;
                case SyntaxKind.LogicalOrExpression: return BinaryOperatorKind.LogicalOr;
                default: throw ExceptionUtilities.UnexpectedValue(kind);
            }
        }
 
        private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionSyntax operandSyntax, SyntaxToken operatorToken, BindingDiagnosticBag diagnostics)
        {
            operandSyntax.CheckDeconstructionCompatibleArgument(diagnostics);
 
            BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement), diagnostics);
            UnaryOperatorKind kind = SyntaxKindToUnaryOperatorKind(node.Kind());
 
            // If the operand is bad, avoid generating cascading errors.
            if (operand.HasAnyErrors)
            {
                // NOTE: no candidate user-defined operators.
                return new BoundIncrementOperator(
                    node,
                    kind,
                    operand,
                    methodOpt: null,
                    constrainedToTypeOpt: null,
                    operandPlaceholder: null,
                    operandConversion: null,
                    resultPlaceholder: null,
                    resultConversion: null,
                    LookupResultKind.Empty,
                    CreateErrorType(),
                    hasErrors: true);
            }
 
            // The operand has to be a variable, property or indexer, so it must have a type.
            var operandType = operand.Type;
            Debug.Assert((object)operandType != null);
 
            if (operandType.IsDynamic())
            {
                return new BoundIncrementOperator(
                    node,
                    kind.WithType(UnaryOperatorKind.Dynamic).WithOverflowChecksIfApplicable(CheckOverflowAtRuntime),
                    operand,
                    methodOpt: null,
                    constrainedToTypeOpt: null,
                    operandPlaceholder: null,
                    operandConversion: null,
                    resultPlaceholder: null,
                    resultConversion: null,
                    resultKind: LookupResultKind.Viable,
                    originalUserDefinedOperatorsOpt: default(ImmutableArray<MethodSymbol>),
                    type: operandType,
                    hasErrors: false);
            }
 
            LookupResultKind resultKind;
            ImmutableArray<MethodSymbol> originalUserDefinedOperators;
            var best = this.UnaryOperatorOverloadResolution(kind, operand, node, diagnostics, out resultKind, out originalUserDefinedOperators);
            if (!best.HasValue)
            {
                ReportUnaryOperatorError(node, diagnostics, operatorToken.Text, operand, resultKind);
                return new BoundIncrementOperator(
                    node,
                    kind,
                    operand,
                    methodOpt: null,
                    constrainedToTypeOpt: null,
                    operandPlaceholder: null,
                    operandConversion: null,
                    resultPlaceholder: null,
                    resultConversion: null,
                    resultKind,
                    originalUserDefinedOperators,
                    CreateErrorType(),
                    hasErrors: true);
            }
 
            var signature = best.Signature;
 
            CheckNativeIntegerFeatureAvailability(signature.Kind, node, diagnostics);
            CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, signature.Method, isUnsignedRightShift: false, signature.ConstrainedToTypeOpt, diagnostics);
 
            var resultPlaceholder = new BoundValuePlaceholder(node, signature.ReturnType).MakeCompilerGenerated();
 
            BoundExpression resultConversion = GenerateConversionForAssignment(operandType, resultPlaceholder, diagnostics, ConversionForAssignmentFlags.IncrementAssignment);
 
            bool hasErrors = resultConversion.HasErrors;
 
            if (resultConversion is not BoundConversion)
            {
                Debug.Assert(hasErrors || (object)resultConversion == resultPlaceholder);
                if ((object)resultConversion != resultPlaceholder)
                {
                    resultPlaceholder = null;
                    resultConversion = null;
                }
            }
 
            if (!hasErrors && operandType.IsVoidPointer())
            {
                Error(diagnostics, ErrorCode.ERR_VoidError, node);
                hasErrors = true;
            }
 
            var operandPlaceholder = new BoundValuePlaceholder(operand.Syntax, operand.Type).MakeCompilerGenerated();
            var operandConversion = CreateConversion(node, operandPlaceholder, best.Conversion, isCast: false, conversionGroupOpt: null, best.Signature.OperandType, diagnostics);
 
            return new BoundIncrementOperator(
                node,
                signature.Kind.WithOverflowChecksIfApplicable(CheckOverflowAtRuntime),
                operand,
                signature.Method,
                signature.ConstrainedToTypeOpt,
                operandPlaceholder,
                operandConversion,
                resultPlaceholder,
                resultConversion,
                resultKind,
                originalUserDefinedOperators,
                operandType,
                hasErrors);
        }
 
#nullable enable
        /// <summary>
        /// Returns false if reported an error, true otherwise.
        /// </summary>
        private bool CheckConstraintLanguageVersionAndRuntimeSupportForOperator(SyntaxNode node, MethodSymbol? methodOpt, bool isUnsignedRightShift, TypeSymbol? constrainedToTypeOpt, BindingDiagnosticBag diagnostics)
        {
            bool result = true;
 
            if (methodOpt?.ContainingType?.IsInterface == true && methodOpt.IsStatic)
            {
                if (methodOpt.IsAbstract || methodOpt.IsVirtual)
                {
                    if (constrainedToTypeOpt is not TypeParameterSymbol)
                    {
                        Error(diagnostics, ErrorCode.ERR_BadAbstractStaticMemberAccess, node);
                        return false;
                    }
 
                    if (Compilation.SourceModule != methodOpt.ContainingModule)
                    {
                        result = CheckFeatureAvailability(node, MessageID.IDS_FeatureStaticAbstractMembersInInterfaces, diagnostics);
 
                        if (!Compilation.Assembly.RuntimeSupportsStaticAbstractMembersInInterfaces)
                        {
                            Error(diagnostics, ErrorCode.ERR_RuntimeDoesNotSupportStaticAbstractMembersInInterfaces, node);
                            return false;
                        }
                    }
                }
                else if (methodOpt.Name is WellKnownMemberNames.EqualityOperatorName or WellKnownMemberNames.InequalityOperatorName)
                {
                    result = CheckFeatureAvailability(node, MessageID.IDS_FeatureStaticAbstractMembersInInterfaces, diagnostics);
                }
            }
 
            if (methodOpt is null)
            {
                if (isUnsignedRightShift)
                {
                    result &= CheckFeatureAvailability(node, MessageID.IDS_FeatureUnsignedRightShift, diagnostics);
                }
            }
            else
            {
                Debug.Assert((methodOpt.Name == WellKnownMemberNames.UnsignedRightShiftOperatorName) == isUnsignedRightShift);
 
                if (Compilation.SourceModule != methodOpt.ContainingModule)
                {
                    if (SyntaxFacts.IsCheckedOperator(methodOpt.Name))
                    {
                        result &= CheckFeatureAvailability(node, MessageID.IDS_FeatureCheckedUserDefinedOperators, diagnostics);
                    }
                    else if (isUnsignedRightShift)
                    {
                        result &= CheckFeatureAvailability(node, MessageID.IDS_FeatureUnsignedRightShift, diagnostics);
                    }
                }
            }
 
            return result;
        }
#nullable disable
 
        private BoundExpression BindSuppressNullableWarningExpression(PostfixUnaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeatureNullableReferenceTypes.CheckFeatureAvailability(diagnostics, node.OperatorToken);
 
            var expr = BindExpression(node.Operand, diagnostics);
            switch (expr.Kind)
            {
                case BoundKind.NamespaceExpression:
                case BoundKind.TypeExpression:
                    Error(diagnostics, ErrorCode.ERR_IllegalSuppression, expr.Syntax);
                    break;
                default:
                    if (expr.IsSuppressed)
                    {
                        Debug.Assert(node.Operand.SkipParens().GetLastToken().Kind() == SyntaxKind.ExclamationToken);
                        Error(diagnostics, ErrorCode.ERR_DuplicateNullSuppression, expr.Syntax);
                    }
                    break;
            }
 
            return expr.WithSuppression();
        }
 
        // Based on ExpressionBinder::bindPtrIndirection.
        private BoundExpression BindPointerIndirectionExpression(PrefixUnaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            BoundExpression operand = BindToNaturalType(BindValue(node.Operand, diagnostics, GetUnaryAssignmentKind(node.Kind())), diagnostics);
 
            TypeSymbol pointedAtType;
            bool hasErrors;
            BindPointerIndirectionExpressionInternal(node, operand, diagnostics, out pointedAtType, out hasErrors);
 
            return new BoundPointerIndirectionOperator(node, operand, refersToLocation: false, pointedAtType ?? CreateErrorType(), hasErrors);
        }
 
        private static void BindPointerIndirectionExpressionInternal(CSharpSyntaxNode node, BoundExpression operand, BindingDiagnosticBag diagnostics, out TypeSymbol pointedAtType, out bool hasErrors)
        {
            var operandType = operand.Type as PointerTypeSymbol;
 
            hasErrors = operand.HasAnyErrors; // This would propagate automatically, but by reading it explicitly we can reduce cascading.
 
            if ((object)operandType == null)
            {
                pointedAtType = null;
 
                if (!hasErrors)
                {
                    // NOTE: Dev10 actually reports ERR_BadUnaryOp if the operand has Type == null,
                    // but this seems clearer.
                    Error(diagnostics, ErrorCode.ERR_PtrExpected, node);
                    hasErrors = true;
                }
            }
            else
            {
                pointedAtType = operandType.PointedAtType;
 
                if (pointedAtType.IsVoidType())
                {
                    pointedAtType = null;
 
                    if (!hasErrors)
                    {
                        Error(diagnostics, ErrorCode.ERR_VoidError, node);
                        hasErrors = true;
                    }
                }
            }
        }
 
        // Based on ExpressionBinder::bindPtrAddr.
        private BoundExpression BindAddressOfExpression(PrefixUnaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            BoundExpression operand = BindToNaturalType(BindValue(node.Operand, diagnostics, BindValueKind.AddressOf), diagnostics);
            ReportSuppressionIfNeeded(operand, diagnostics);
 
            bool hasErrors = operand.HasAnyErrors; // This would propagate automatically, but by reading it explicitly we can reduce cascading.
            bool isFixedStatementAddressOfExpression = SyntaxFacts.IsFixedStatementExpression(node);
 
            switch (operand)
            {
                case BoundLambda _:
                case UnboundLambda _:
                    {
                        Debug.Assert(hasErrors);
                        return new BoundAddressOfOperator(node, operand, CreateErrorType(), hasErrors: true);
                    }
 
                case BoundMethodGroup methodGroup:
                    return new BoundUnconvertedAddressOfOperator(node, methodGroup, hasErrors);
            }
 
            TypeSymbol operandType = operand.Type;
            Debug.Assert((object)operandType != null, "BindValue should have caught a null operand type");
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            ManagedKind managedKind = operandType.GetManagedKind(ref useSiteInfo);
            diagnostics.Add(node, useSiteInfo);
 
            if (!hasErrors)
            {
                hasErrors = CheckManagedAddr(Compilation, operandType, managedKind, node.Location, diagnostics);
            }
 
            bool allowManagedAddressOf = Flags.Includes(BinderFlags.AllowMoveableAddressOf);
            if (!hasErrors && !allowManagedAddressOf)
            {
                if (IsMoveableVariable(operand, accessedLocalOrParameterOpt: out _) != isFixedStatementAddressOfExpression)
                {
                    Error(diagnostics, isFixedStatementAddressOfExpression ? ErrorCode.ERR_FixedNotNeeded : ErrorCode.ERR_FixedNeeded, node);
                    hasErrors = true;
                }
            }
 
            TypeSymbol pointerType = new PointerTypeSymbol(TypeWithAnnotations.Create(operandType));
            return new BoundAddressOfOperator(node, operand, pointerType, hasErrors);
        }
 
        /// <summary>
        /// Checks to see whether an expression is a "moveable" variable according to the spec. Moveable
        /// variables have underlying memory which may be moved by the runtime. The spec defines anything
        /// not fixed as moveable and specifies the expressions which are fixed.
        /// </summary>
        internal bool IsMoveableVariable(BoundExpression expr, out Symbol accessedLocalOrParameterOpt)
        {
            accessedLocalOrParameterOpt = null;
 
            while (true)
            {
                BoundKind exprKind = expr.Kind;
                switch (exprKind)
                {
                    case BoundKind.FieldAccess:
                    case BoundKind.EventAccess:
                        {
                            FieldSymbol fieldSymbol;
                            BoundExpression receiver;
                            if (exprKind == BoundKind.FieldAccess)
                            {
                                BoundFieldAccess fieldAccess = (BoundFieldAccess)expr;
                                fieldSymbol = fieldAccess.FieldSymbol;
                                receiver = fieldAccess.ReceiverOpt;
                            }
                            else
                            {
                                BoundEventAccess eventAccess = (BoundEventAccess)expr;
                                if (!eventAccess.IsUsableAsField || eventAccess.EventSymbol.IsWindowsRuntimeEvent)
                                {
                                    return true;
                                }
                                EventSymbol eventSymbol = eventAccess.EventSymbol;
                                fieldSymbol = eventSymbol.AssociatedField;
                                receiver = eventAccess.ReceiverOpt;
                            }
 
                            if ((object)fieldSymbol == null || fieldSymbol.IsStatic || (object)receiver == null)
                            {
                                return true;
                            }
 
                            bool receiverIsLValue = CheckValueKind(receiver.Syntax, receiver, BindValueKind.AddressOf, checkingReceiver: false, diagnostics: BindingDiagnosticBag.Discarded);
 
                            if (!receiverIsLValue)
                            {
                                return true;
                            }
 
                            // NOTE: type parameters will already have been weeded out, since a
                            // variable of type parameter type has to be cast to an effective
                            // base or interface type before its fields can be accessed and a
                            // conversion isn't an lvalue.
                            if (receiver.Type.IsReferenceType)
                            {
                                return true;
                            }
 
                            expr = receiver;
                            continue;
                        }
                    case BoundKind.InlineArrayAccess:
                        {
                            var elementAccess = (BoundInlineArrayAccess)expr;
 
                            if (elementAccess.GetItemOrSliceHelper is WellKnownMember.System_Span_T__get_Item or WellKnownMember.System_ReadOnlySpan_T__get_Item)
                            {
                                expr = elementAccess.Expression;
                                continue;
                            }
 
                            goto default;
                        }
                    case BoundKind.RangeVariable:
                        {
                            // NOTE: there are cases where you can take the address of a range variable.
                            // e.g. from x in new int[3] select *(&x)
                            BoundRangeVariable variableAccess = (BoundRangeVariable)expr;
                            expr = variableAccess.Value; //Check the underlying expression.
                            continue;
                        }
                    case BoundKind.Parameter:
                        {
                            BoundParameter parameterAccess = (BoundParameter)expr;
                            ParameterSymbol parameterSymbol = parameterAccess.ParameterSymbol;
                            accessedLocalOrParameterOpt = parameterSymbol;
 
                            if (parameterSymbol.RefKind != RefKind.None)
                            {
                                return true;
                            }
 
                            if (parameterSymbol.ContainingSymbol is SynthesizedPrimaryConstructor primaryConstructor &&
                                primaryConstructor.GetCapturedParameters().ContainsKey(parameterSymbol))
                            {
                                // See 'case BoundKind.FieldAccess' above. Receiver in our case is 'this' parameter.
                                // If we are in a class, its type is reference type.
                                // If we are in a struct, 'this' RefKind is not None.
                                // Therefore, movable in either case. 
                                return true;
                            }
 
                            return false;
                        }
                    case BoundKind.ThisReference:
                    case BoundKind.BaseReference:
                        {
                            accessedLocalOrParameterOpt = this.ContainingMemberOrLambda.EnclosingThisSymbol();
                            return true;
                        }
                    case BoundKind.Local:
                        {
                            BoundLocal localAccess = (BoundLocal)expr;
                            LocalSymbol localSymbol = localAccess.LocalSymbol;
                            accessedLocalOrParameterOpt = localSymbol;
                            // NOTE: The spec says that this is moveable if it is captured by an anonymous function,
                            // but that will be reported separately and error-recovery is better if we say that
                            // such locals are not moveable.
                            return localSymbol.RefKind != RefKind.None;
                        }
                    case BoundKind.PointerIndirectionOperator: //Covers ->, since the receiver will be one of these.
                    case BoundKind.ConvertedStackAllocExpression:
                        {
                            return false;
                        }
                    case BoundKind.PointerElementAccess:
                        {
                            // C# 7.3:
                            // a variable resulting from a... pointer_element_access of the form P[E] [is fixed] if P
                            // is not a fixed size buffer expression, or if the expression is a fixed size buffer
                            // member_access of the form E.I and E is a fixed variable
                            BoundExpression underlyingExpr = ((BoundPointerElementAccess)expr).Expression;
                            if (underlyingExpr is BoundFieldAccess fieldAccess && fieldAccess.FieldSymbol.IsFixedSizeBuffer)
                            {
                                expr = fieldAccess.ReceiverOpt;
                                continue;
                            }
 
                            return false;
                        }
                    case BoundKind.PropertyAccess: // Never fixed
                    case BoundKind.IndexerAccess: // Never fixed
                    default:
                        {
                            return true;
                        }
                }
            }
        }
 
        private BoundExpression BindUnaryOperator(PrefixUnaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            BoundExpression operand = BindToNaturalType(BindValue(node.Operand, diagnostics, GetUnaryAssignmentKind(node.Kind())), diagnostics);
            BoundLiteral constant = BindIntegralMinValConstants(node, operand, diagnostics);
            return constant ?? BindUnaryOperatorCore(node, node.OperatorToken.Text, operand, diagnostics);
        }
 
        private void ReportSuppressionIfNeeded(BoundExpression expr, BindingDiagnosticBag diagnostics)
        {
            if (expr.IsSuppressed)
            {
                Error(diagnostics, ErrorCode.ERR_IllegalSuppression, expr.Syntax);
            }
        }
 
#nullable enable
        private BoundExpression BindUnaryOperatorCore(CSharpSyntaxNode node, string operatorText, BoundExpression operand, BindingDiagnosticBag diagnostics)
        {
            UnaryOperatorKind kind = SyntaxKindToUnaryOperatorKind(node.Kind());
 
            bool isOperandNullOrNew = operand.IsLiteralNull() || operand.IsImplicitObjectCreation();
            if (isOperandNullOrNew)
            {
                // Dev10 does not allow unary prefix operators to be applied to the null literal
                // (or other typeless expressions).
                Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefaultOrNew, node, operatorText, operand.Display);
            }
 
            // If the operand is bad, avoid generating cascading errors.
            if (isOperandNullOrNew || operand.Type?.IsErrorType() == true)
            {
                // Note: no candidate user-defined operators.
                return new BoundUnaryOperator(node, kind, operand, ConstantValue.NotAvailable,
                    methodOpt: null,
                    constrainedToTypeOpt: null,
                    resultKind: LookupResultKind.Empty,
                    type: CreateErrorType(),
                    hasErrors: true);
            }
 
            // If the operand is dynamic then we do not attempt to do overload resolution at compile
            // time; we defer that until runtime. If we did overload resolution then the dynamic
            // operand would be implicitly convertible to the parameter type of each operator
            // signature, and therefore every operator would be an applicable candidate. Instead
            // of changing overload resolution to handle dynamic, we just handle it here and let
            // overload resolution implement the specification.
 
            if (operand.HasDynamicType())
            {
                return new BoundUnaryOperator(
                    syntax: node,
                    operatorKind: kind.WithType(UnaryOperatorKind.Dynamic).WithOverflowChecksIfApplicable(CheckOverflowAtRuntime),
                    operand: operand,
                    constantValueOpt: ConstantValue.NotAvailable,
                    methodOpt: null,
                    constrainedToTypeOpt: null,
                    resultKind: LookupResultKind.Viable,
                    type: operand.Type!);
            }
 
            LookupResultKind resultKind;
            ImmutableArray<MethodSymbol> originalUserDefinedOperators;
            var best = this.UnaryOperatorOverloadResolution(kind, operand, node, diagnostics, out resultKind, out originalUserDefinedOperators);
            if (!best.HasValue)
            {
                ReportUnaryOperatorError(node, diagnostics, operatorText, operand, resultKind);
                return new BoundUnaryOperator(
                    node,
                    kind,
                    operand,
                    ConstantValue.NotAvailable,
                    methodOpt: null,
                    constrainedToTypeOpt: null,
                    resultKind,
                    originalUserDefinedOperators,
                    CreateErrorType(),
                    hasErrors: true);
            }
 
            var signature = best.Signature;
 
            var resultOperand = CreateConversion(operand.Syntax, operand, best.Conversion, isCast: false, conversionGroupOpt: null, signature.OperandType, diagnostics);
            var resultType = signature.ReturnType;
            UnaryOperatorKind resultOperatorKind = signature.Kind;
            var resultConstant = FoldUnaryOperator(node, resultOperatorKind, resultOperand, resultType, diagnostics);
 
            CheckNativeIntegerFeatureAvailability(resultOperatorKind, node, diagnostics);
            CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, signature.Method, isUnsignedRightShift: false, signature.ConstrainedToTypeOpt, diagnostics);
 
            return new BoundUnaryOperator(
                node,
                resultOperatorKind.WithOverflowChecksIfApplicable(CheckOverflowAtRuntime),
                resultOperand,
                resultConstant,
                signature.Method,
                signature.ConstrainedToTypeOpt,
                resultKind,
                resultType);
        }
 
        private ConstantValue? FoldEnumUnaryOperator(
            CSharpSyntaxNode syntax,
            UnaryOperatorKind kind,
            BoundExpression operand,
            BindingDiagnosticBag diagnostics)
        {
            var underlyingType = operand.Type.GetEnumUnderlyingType()!;
 
            BoundExpression newOperand = CreateConversion(operand, underlyingType, diagnostics);
 
            // We may have to upconvert the type if it is a byte, sbyte, short, ushort
            // or nullable of those, because there is no ~ operator
            var upconvertSpecialType = GetEnumPromotedType(underlyingType.SpecialType);
            var upconvertType = upconvertSpecialType == underlyingType.SpecialType ?
                underlyingType :
                GetSpecialType(upconvertSpecialType, diagnostics, syntax);
 
            newOperand = CreateConversion(newOperand, upconvertType, diagnostics);
 
            UnaryOperatorKind newKind = kind.Operator().WithType(upconvertSpecialType);
 
            var constantValue = FoldUnaryOperator(syntax, newKind, operand, upconvertType, diagnostics);
 
            // Convert back to the underlying type
            if (constantValue != null && !constantValue.IsBad)
            {
                // Do an unchecked conversion if bitwise complement
                var binder = kind.Operator() == UnaryOperatorKind.BitwiseComplement ?
                    this.WithCheckedOrUncheckedRegion(@checked: false) : this;
                return binder.FoldConstantNumericConversion(syntax, constantValue, underlyingType, diagnostics);
            }
 
            return constantValue;
        }
 
        private ConstantValue? FoldUnaryOperator(
            CSharpSyntaxNode syntax,
            UnaryOperatorKind kind,
            BoundExpression operand,
            TypeSymbol resultTypeSymbol,
            BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(operand != null);
            // UNDONE: report errors when in a checked context.
 
            if (operand.HasAnyErrors)
            {
                return null;
            }
 
            var value = operand.ConstantValueOpt;
            if (value == null || value.IsBad)
            {
                return value;
            }
 
            if (kind.IsEnum() && !kind.IsLifted())
            {
                return FoldEnumUnaryOperator(syntax, kind, operand, diagnostics);
            }
 
            SpecialType resultType = resultTypeSymbol.SpecialType;
            var newValue = FoldNeverOverflowUnaryOperator(kind, value);
            if (newValue != null)
            {
                return ConstantValue.Create(newValue, resultType);
            }
 
            try
            {
                newValue = FoldNativeIntegerOverflowingUnaryOperator(kind, value);
            }
            catch (OverflowException)
            {
                if (CheckOverflowAtCompileTime)
                {
                    Error(diagnostics, ErrorCode.WRN_CompileTimeCheckedOverflow, syntax, resultTypeSymbol);
                }
 
                return null;
            }
 
            if (newValue != null)
            {
                return ConstantValue.Create(newValue, resultType);
            }
 
            if (CheckOverflowAtCompileTime)
            {
                try
                {
                    newValue = FoldCheckedIntegralUnaryOperator(kind, value);
                }
                catch (OverflowException)
                {
                    Error(diagnostics, ErrorCode.ERR_CheckedOverflow, syntax);
                    return ConstantValue.Bad;
                }
            }
            else
            {
                newValue = FoldUncheckedIntegralUnaryOperator(kind, value);
            }
 
            if (newValue != null)
            {
                return ConstantValue.Create(newValue, resultType);
            }
 
            return null;
        }
 
        private static object? FoldNeverOverflowUnaryOperator(UnaryOperatorKind kind, ConstantValue value)
        {
            // Note that we do operations on single-precision floats as double-precision.
            switch (kind)
            {
                case UnaryOperatorKind.DecimalUnaryMinus:
                    return -value.DecimalValue;
                case UnaryOperatorKind.DoubleUnaryMinus:
                case UnaryOperatorKind.FloatUnaryMinus:
                    return -value.DoubleValue;
                case UnaryOperatorKind.DecimalUnaryPlus:
                    return +value.DecimalValue;
                case UnaryOperatorKind.FloatUnaryPlus:
                case UnaryOperatorKind.DoubleUnaryPlus:
                    return +value.DoubleValue;
                case UnaryOperatorKind.LongUnaryPlus:
                    return +value.Int64Value;
                case UnaryOperatorKind.ULongUnaryPlus:
                    return +value.UInt64Value;
                case UnaryOperatorKind.IntUnaryPlus:
                case UnaryOperatorKind.NIntUnaryPlus:
                    return +value.Int32Value;
                case UnaryOperatorKind.UIntUnaryPlus:
                case UnaryOperatorKind.NUIntUnaryPlus:
                    return +value.UInt32Value;
                case UnaryOperatorKind.BoolLogicalNegation:
                    return !value.BooleanValue;
                case UnaryOperatorKind.IntBitwiseComplement:
                    return ~value.Int32Value;
                case UnaryOperatorKind.LongBitwiseComplement:
                    return ~value.Int64Value;
                case UnaryOperatorKind.UIntBitwiseComplement:
                    return ~value.UInt32Value;
                case UnaryOperatorKind.ULongBitwiseComplement:
                    return ~value.UInt64Value;
            }
 
            return null;
        }
 
        private static object? FoldUncheckedIntegralUnaryOperator(UnaryOperatorKind kind, ConstantValue value)
        {
            unchecked
            {
                switch (kind)
                {
                    case UnaryOperatorKind.LongUnaryMinus:
                        return -value.Int64Value;
                    case UnaryOperatorKind.IntUnaryMinus:
                        return -value.Int32Value;
                }
            }
 
            return null;
        }
 
        private static object? FoldCheckedIntegralUnaryOperator(UnaryOperatorKind kind, ConstantValue value)
        {
            checked
            {
                switch (kind)
                {
                    case UnaryOperatorKind.LongUnaryMinus:
                        return -value.Int64Value;
                    case UnaryOperatorKind.IntUnaryMinus:
                        return -value.Int32Value;
                }
            }
 
            return null;
        }
 
        private static object? FoldNativeIntegerOverflowingUnaryOperator(UnaryOperatorKind kind, ConstantValue value)
        {
            checked
            {
                switch (kind)
                {
                    case UnaryOperatorKind.NIntUnaryMinus:
                        return -value.Int32Value;
                    case UnaryOperatorKind.NIntBitwiseComplement:
                    case UnaryOperatorKind.NUIntBitwiseComplement:
                        return null;
                }
            }
 
            return null;
        }
 
        public static UnaryOperatorKind SyntaxKindToUnaryOperatorKind(SyntaxKind kind)
        {
            switch (kind)
            {
                case SyntaxKind.PreIncrementExpression: return UnaryOperatorKind.PrefixIncrement;
                case SyntaxKind.PostIncrementExpression: return UnaryOperatorKind.PostfixIncrement;
                case SyntaxKind.PreDecrementExpression: return UnaryOperatorKind.PrefixDecrement;
                case SyntaxKind.PostDecrementExpression: return UnaryOperatorKind.PostfixDecrement;
                case SyntaxKind.UnaryPlusExpression: return UnaryOperatorKind.UnaryPlus;
                case SyntaxKind.UnaryMinusExpression: return UnaryOperatorKind.UnaryMinus;
                case SyntaxKind.LogicalNotExpression: return UnaryOperatorKind.LogicalNegation;
                case SyntaxKind.BitwiseNotExpression: return UnaryOperatorKind.BitwiseComplement;
                default: throw ExceptionUtilities.UnexpectedValue(kind);
            }
        }
 
        private static BindValueKind GetBinaryAssignmentKind(SyntaxKind kind)
        {
            switch (kind)
            {
                case SyntaxKind.SimpleAssignmentExpression:
                    return BindValueKind.Assignable;
                case SyntaxKind.AddAssignmentExpression:
                case SyntaxKind.AndAssignmentExpression:
                case SyntaxKind.DivideAssignmentExpression:
                case SyntaxKind.ExclusiveOrAssignmentExpression:
                case SyntaxKind.LeftShiftAssignmentExpression:
                case SyntaxKind.ModuloAssignmentExpression:
                case SyntaxKind.MultiplyAssignmentExpression:
                case SyntaxKind.OrAssignmentExpression:
                case SyntaxKind.RightShiftAssignmentExpression:
                case SyntaxKind.UnsignedRightShiftAssignmentExpression:
                case SyntaxKind.SubtractAssignmentExpression:
                case SyntaxKind.CoalesceAssignmentExpression:
                    return BindValueKind.CompoundAssignment;
                default:
                    return BindValueKind.RValue;
            }
        }
 
        private static BindValueKind GetUnaryAssignmentKind(SyntaxKind kind)
        {
            switch (kind)
            {
                case SyntaxKind.PreDecrementExpression:
                case SyntaxKind.PreIncrementExpression:
                case SyntaxKind.PostDecrementExpression:
                case SyntaxKind.PostIncrementExpression:
                    return BindValueKind.IncrementDecrement;
                case SyntaxKind.AddressOfExpression:
                    Debug.Assert(false, "Should be handled separately.");
                    goto default;
                default:
                    return BindValueKind.RValue;
            }
        }
#nullable disable
 
        private BoundLiteral BindIntegralMinValConstants(PrefixUnaryExpressionSyntax node, BoundExpression operand, BindingDiagnosticBag diagnostics)
        {
            // SPEC: To permit the smallest possible int and long values to be written as decimal integer
            // SPEC: literals, the following two rules exist:
 
            // SPEC: When a decimal-integer-literal with the value 2147483648 and no integer-type-suffix
            // SPEC: appears as the token immediately following a unary minus operator token, the result is a
            // SPEC: constant of type int with the value −2147483648.
 
            // SPEC: When a decimal-integer-literal with the value 9223372036854775808 and no integer-type-suffix
            // SPEC: or the integer-type-suffix L or l appears as the token immediately following a unary minus
            // SPEC: operator token, the result is a constant of type long with the value −9223372036854775808.
 
            if (node.Kind() != SyntaxKind.UnaryMinusExpression)
            {
                return null;
            }
 
            if (node.Operand != operand.Syntax || operand.Syntax.Kind() != SyntaxKind.NumericLiteralExpression)
            {
                return null;
            }
 
            var literal = (LiteralExpressionSyntax)operand.Syntax;
            var token = literal.Token;
            if (token.Value is uint)
            {
                uint value = (uint)token.Value;
                if (value != 2147483648U)
                {
                    return null;
                }
 
                if (token.Text.Contains("u") || token.Text.Contains("U") || token.Text.Contains("l") || token.Text.Contains("L"))
                {
                    return null;
                }
 
                return new BoundLiteral(node, ConstantValue.Create((int)-2147483648), GetSpecialType(SpecialType.System_Int32, diagnostics, node));
            }
            else if (token.Value is ulong)
            {
                var value = (ulong)token.Value;
                if (value != 9223372036854775808UL)
                {
                    return null;
                }
 
                if (token.Text.Contains("u") || token.Text.Contains("U"))
                {
                    return null;
                }
 
                return new BoundLiteral(node, ConstantValue.Create(-9223372036854775808), GetSpecialType(SpecialType.System_Int64, diagnostics, node));
            }
 
            return null;
        }
 
        private static bool IsDivisionByZero(BinaryOperatorKind kind, ConstantValue valueRight)
        {
            Debug.Assert(valueRight != null);
 
            switch (kind)
            {
                case BinaryOperatorKind.DecimalDivision:
                case BinaryOperatorKind.DecimalRemainder:
                    return valueRight.DecimalValue == 0.0m;
                case BinaryOperatorKind.IntDivision:
                case BinaryOperatorKind.IntRemainder:
                case BinaryOperatorKind.NIntDivision:
                case BinaryOperatorKind.NIntRemainder:
                    return valueRight.Int32Value == 0;
                case BinaryOperatorKind.LongDivision:
                case BinaryOperatorKind.LongRemainder:
                    return valueRight.Int64Value == 0;
                case BinaryOperatorKind.UIntDivision:
                case BinaryOperatorKind.UIntRemainder:
                case BinaryOperatorKind.NUIntDivision:
                case BinaryOperatorKind.NUIntRemainder:
                    return valueRight.UInt32Value == 0;
                case BinaryOperatorKind.ULongDivision:
                case BinaryOperatorKind.ULongRemainder:
                    return valueRight.UInt64Value == 0;
            }
 
            return false;
        }
 
        private bool IsOperandErrors(CSharpSyntaxNode node, ref BoundExpression operand, BindingDiagnosticBag diagnostics)
        {
            switch (operand.Kind)
            {
                case BoundKind.UnboundLambda:
                case BoundKind.Lambda:
                case BoundKind.MethodGroup:  // New in Roslyn - see DevDiv #864740.
                    // operand for an is or as expression cannot be a lambda expression or method group
                    if (!operand.HasAnyErrors)
                    {
                        Error(diagnostics, ErrorCode.ERR_LambdaInIsAs, node);
                    }
 
                    operand = BadExpression(node, operand).MakeCompilerGenerated();
                    return true;
 
                default:
                    if ((object)operand.Type == null && !operand.IsLiteralNull())
                    {
                        if (!operand.HasAnyErrors)
                        {
                            // Operator 'is' cannot be applied to operand of type '(int, <null>)'
                            Error(diagnostics, ErrorCode.ERR_BadUnaryOp, node, SyntaxFacts.GetText(SyntaxKind.IsKeyword), operand.Display);
                        }
 
                        operand = BadExpression(node, operand).MakeCompilerGenerated();
                        return true;
                    }
 
                    break;
            }
 
            return operand.HasAnyErrors;
        }
 
        private bool IsOperatorErrors(CSharpSyntaxNode node, TypeSymbol operandType, BoundTypeExpression typeExpression, BindingDiagnosticBag diagnostics)
        {
            var targetType = typeExpression.Type;
 
            // The native compiler allows "x is C" where C is a static class. This
            // is strictly illegal according to the specification (see the section
            // called "Referencing Static Class Types".) To retain compatibility we
            // allow it, but when /warn:5 or higher we break with the native
            // compiler and turn this into a warning.
            if (targetType.IsStatic)
            {
                Error(diagnostics, ErrorCode.WRN_StaticInAsOrIs, node, targetType);
            }
 
            if ((object)operandType != null && operandType.IsPointerOrFunctionPointer() || targetType.IsPointerOrFunctionPointer())
            {
                // operand for an is or as expression cannot be of pointer type
                Error(diagnostics, ErrorCode.ERR_PointerInAsOrIs, node);
                return true;
            }
 
            return targetType.TypeKind == TypeKind.Error;
        }
 
        protected static bool IsUnderscore(ExpressionSyntax node) =>
            node is IdentifierNameSyntax name && name.Identifier.IsUnderscoreToken();
 
        private BoundExpression BindIsOperator(BinaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            var resultType = (TypeSymbol)GetSpecialType(SpecialType.System_Boolean, diagnostics, node);
            var operand = BindRValueWithoutTargetType(node.Left, diagnostics);
            var operandHasErrors = IsOperandErrors(node, ref operand, diagnostics);
 
            // try binding as a type, but back off to binding as an expression if that does not work.
            bool wasUnderscore = IsUnderscore(node.Right);
            if (!tryBindAsType(node.Right, diagnostics, out BindingDiagnosticBag isTypeDiagnostics, out BoundTypeExpression typeExpression) &&
                !wasUnderscore &&
                ((CSharpParseOptions)node.SyntaxTree.Options).IsFeatureEnabled(MessageID.IDS_FeaturePatternMatching))
            {
                // it did not bind as a type; try binding as a constant expression pattern
                var isPatternDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
                if ((object)operand.Type == null)
                {
                    if (!operandHasErrors)
                    {
                        isPatternDiagnostics.Add(ErrorCode.ERR_BadPatternExpression, node.Left.Location, operand.Display);
                    }
 
                    operand = ToBadExpression(operand);
                }
 
                bool hasErrors = node.Right.HasErrors;
                var convertedExpression = BindExpressionForPattern(operand.Type, node.Right, ref hasErrors, isPatternDiagnostics, out var constantValueOpt, out var wasExpression, out _);
                if (wasExpression)
                {
                    hasErrors |= constantValueOpt is null;
                    isTypeDiagnostics.Free();
                    diagnostics.AddRangeAndFree(isPatternDiagnostics);
                    var boundConstantPattern = new BoundConstantPattern(
                        node.Right, convertedExpression, constantValueOpt ?? ConstantValue.Bad, operand.Type, convertedExpression.Type ?? operand.Type, hasErrors)
#pragma warning disable format
                        { WasCompilerGenerated = true };
#pragma warning restore format
                    return MakeIsPatternExpression(node, operand, boundConstantPattern, resultType, operandHasErrors, diagnostics);
                }
 
                isPatternDiagnostics.Free();
            }
 
            diagnostics.AddRangeAndFree(isTypeDiagnostics);
            var targetTypeWithAnnotations = typeExpression.TypeWithAnnotations;
            var targetType = typeExpression.Type;
            if (targetType.IsReferenceType && targetTypeWithAnnotations.NullableAnnotation.IsAnnotated())
            {
                Error(diagnostics, ErrorCode.ERR_IsNullableType, node.Right, targetType);
                operandHasErrors = true;
            }
 
            var targetTypeKind = targetType.TypeKind;
            if (operandHasErrors || IsOperatorErrors(node, operand.Type, typeExpression, diagnostics))
            {
                return new BoundIsOperator(node, operand, typeExpression, ConversionKind.NoConversion, resultType, hasErrors: true);
            }
 
            if (wasUnderscore && ((CSharpParseOptions)node.SyntaxTree.Options).IsFeatureEnabled(MessageID.IDS_FeatureRecursivePatterns))
            {
                diagnostics.Add(ErrorCode.WRN_IsTypeNamedUnderscore, node.Right.Location, typeExpression.AliasOpt ?? (Symbol)targetType);
            }
 
            // Is and As operator should have null ConstantValue as they are not constant expressions.
            // However we perform analysis of is/as expressions at bind time to detect if the expression
            // will always evaluate to a constant to generate warnings (always true/false/null).
            // We also need this analysis result during rewrite to optimize away redundant isinst instructions.
            // We store the conversion from expression's operand type to target type to enable these
            // optimizations during is/as operator rewrite.
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
 
            if (operand.ConstantValueOpt == ConstantValue.Null ||
                operand.Kind == BoundKind.MethodGroup ||
                operand.Type.IsVoidType())
            {
                // warning for cases where the result is always false:
                // (a) "null is TYPE" OR operand evaluates to null
                // (b) operand is a MethodGroup
                // (c) operand is of void type
 
                // NOTE:    Dev10 violates the SPEC for case (c) above and generates
                // NOTE:    an error ERR_NoExplicitBuiltinConv if the target type
                // NOTE:    is an open type. According to the specification, the result
                // NOTE:    is always false, but no compile time error occurs.
                // NOTE:    We follow the specification and generate WRN_IsAlwaysFalse
                // NOTE:    instead of an error.
                // NOTE:    See Test SyntaxBinderTests.TestIsOperatorWithTypeParameter
 
                Error(diagnostics, ErrorCode.WRN_IsAlwaysFalse, node, targetType);
                Conversion conv = Conversions.ClassifyConversionFromExpression(operand, targetType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
                diagnostics.Add(node, useSiteInfo);
                return new BoundIsOperator(node, operand, typeExpression, conv.Kind, resultType);
            }
 
            if (targetTypeKind == TypeKind.Dynamic)
            {
                // warning for dynamic target type
                Error(diagnostics, ErrorCode.WRN_IsDynamicIsConfusing,
                    node, node.OperatorToken.Text, targetType.Name,
                    GetSpecialType(SpecialType.System_Object, diagnostics, node).Name // a pretty way of getting the string "Object"
                    );
            }
 
            var operandType = operand.Type;
            Debug.Assert((object)operandType != null);
            if (operandType.TypeKind == TypeKind.Dynamic)
            {
                // if operand has a dynamic type, we do the same thing as though it were an object
                operandType = GetSpecialType(SpecialType.System_Object, diagnostics, node);
            }
 
            Conversion conversion = Conversions.ClassifyBuiltInConversion(operandType, targetType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
            diagnostics.Add(node, useSiteInfo);
            ReportIsOperatorDiagnostics(node, diagnostics, operandType, targetType, conversion.Kind, operand.ConstantValueOpt);
            return new BoundIsOperator(node, operand, typeExpression, conversion.Kind, resultType);
 
            bool tryBindAsType(
                ExpressionSyntax possibleType,
                BindingDiagnosticBag diagnostics,
                out BindingDiagnosticBag bindAsTypeDiagnostics,
                out BoundTypeExpression boundType)
            {
                bindAsTypeDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: diagnostics.AccumulatesDependencies);
                TypeWithAnnotations targetTypeWithAnnotations = BindType(possibleType, bindAsTypeDiagnostics, out AliasSymbol alias);
                TypeSymbol targetType = targetTypeWithAnnotations.Type;
                boundType = new BoundTypeExpression(possibleType, alias, targetTypeWithAnnotations);
                return !(targetType?.IsErrorType() == true && bindAsTypeDiagnostics.HasAnyResolvedErrors());
            }
 
        }
 
        private static void ReportIsOperatorDiagnostics(
            CSharpSyntaxNode syntax,
            BindingDiagnosticBag diagnostics,
            TypeSymbol operandType,
            TypeSymbol targetType,
            ConversionKind conversionKind,
            ConstantValue operandConstantValue)
        {
            // NOTE:    Even though BoundIsOperator and BoundAsOperator will always have no ConstantValue
            // NOTE:    (they are non-constant expressions according to Section 7.19 of the specification),
            // NOTE:    we want to perform constant analysis of is/as expressions to generate warnings if the
            // NOTE:    expression will always be true/false/null.
 
            ConstantValue constantValue = GetIsOperatorConstantResult(operandType, targetType, conversionKind, operandConstantValue);
            if (constantValue != null)
            {
                if (constantValue.IsBad)
                {
                    Error(diagnostics, ErrorCode.ERR_BadBinaryOps, syntax, "is", operandType, targetType);
                }
                else
                {
                    Debug.Assert(constantValue == ConstantValue.True || constantValue == ConstantValue.False);
 
                    ErrorCode errorCode = constantValue == ConstantValue.True ? ErrorCode.WRN_IsAlwaysTrue : ErrorCode.WRN_IsAlwaysFalse;
                    Error(diagnostics, errorCode, syntax, targetType);
                }
            }
        }
 
        /// <summary>
        /// Possible return values:
        ///  - <see cref="ConstantValue.False"/>
        ///  - <see cref="ConstantValue.True"/>
        ///  - <see cref="ConstantValue.Bad"/> - compiler doesn't support the type check, i.e. cannot perform it, even at runtime
        ///  - 'null' value - result is not known at compile time    
        /// </summary>
        internal static ConstantValue GetIsOperatorConstantResult(
            TypeSymbol operandType,
            TypeSymbol targetType,
            ConversionKind conversionKind,
            ConstantValue operandConstantValue,
            bool operandCouldBeNull = true)
        {
            Debug.Assert((object)targetType != null);
 
            // SPEC:    The result of the operation depends on D and T as follows:
            // SPEC:    1)      If T is a reference type, the result is true if D and T are the same type, if D is a reference type and
            // SPEC:        an implicit reference conversion from D to T exists, or if D is a value type and a boxing conversion from D to T exists.
            // SPEC:    2)      If T is a nullable type, the result is true if D is the underlying type of T.
            // SPEC:    3)      If T is a non-nullable value type, the result is true if D and T are the same type.
            // SPEC:    4)      Otherwise, the result is false.
 
            // NOTE:    The language specification talks about the runtime evaluation of the is operation.
            // NOTE:    However, we are interested in computing the compile time constant value for the expression.
            // NOTE:    Even though BoundIsOperator and BoundAsOperator will always have no ConstantValue
            // NOTE:    (they are non-constant expressions according to Section 7.19 of the specification),
            // NOTE:    we want to perform constant analysis of is/as expressions during binding to generate warnings
            // NOTE:    (always true/false/null) and during rewriting for optimized codegen.
            // NOTE:
            // NOTE:    Because the heuristic presented here is used to change codegen, it must be conservative. It is acceptable
            // NOTE:    for us to fail to report a warning in cases where humans could logically deduce that the operator will
            // NOTE:    always return false. It is not acceptable to inaccurately warn that the operator will always return false
            // NOTE:    if there are cases where it might succeed.
            // NOTE:
            // NOTE:    These same heuristics are also used in pattern-matching to determine if an expression of the form
            // NOTE:    `e is T x` is permitted. It is an error if `e` cannot be of type `T` according to this method
            // NOTE:    returning ConstantValue.False.
            // NOTE:    The heuristics are also used to determine if a `case T1 x1:` is subsumed by
            // NOTE:    some previous `case T2 x2:` in a switch statement. For that purpose operandType is T1, targetType is T2,
            // NOTE:    and operandCouldBeNull is false; the former subsumes the latter if this method returns ConstantValue.True.
            // NOTE:    Since the heuristic is now used to produce errors in pattern-matching, making it more accurate in the
            // NOTE:    future could be a breaking change.
 
            // To begin our heuristic: if the operand is literal null then we automatically return that the
            // result is false. You might think that we can simply check to see if the conversion is
            // ConversionKind.NullConversion, but "null is T" for a type parameter T is actually classified
            // as an implicit reference conversion if T is constrained to reference types. Rather
            // than deal with all those special cases we can simply bail out here.
 
            if (operandConstantValue == ConstantValue.Null)
            {
                return ConstantValue.False;
            }
 
            Debug.Assert((object)operandType != null);
 
            operandCouldBeNull =
                operandCouldBeNull &&
                operandType.CanContainNull() && // a non-nullable value type is never null
                (operandConstantValue == null || operandConstantValue == ConstantValue.Null); // a non-null constant is never null
 
            switch (conversionKind)
            {
                case ConversionKind.ImplicitSpan:
                case ConversionKind.ExplicitSpan:
                case ConversionKind.NoConversion:
                    // Oddly enough, "x is T" can be true even if there is no conversion from x to T!
                    //
                    // Scenario 1: Type parameter compared to System.Enum.
                    //
                    // bool M1<X>(X x) where X : struct { return x is Enum; }
                    //
                    // There is no conversion from X to Enum, not even an explicit conversion. But
                    // nevertheless, X could be constructed as an enumerated type.
                    // However, we can sometimes know that the result will be false.
                    //
                    // Scenario 2a: Constrained type parameter compared to reference type.
                    //
                    // bool M2a<X>(X x) where X : struct { return x is string; }
                    //
                    // We know that X, constrained to struct, will never be string.
                    //
                    // Scenario 2b: Reference type compared to constrained type parameter.
                    //
                    // bool M2b<X>(string x) where X : struct { return x is X; }
                    //
                    // We know that string will never be X, constrained to struct.
                    //
                    // Scenario 3: Value type compared to type parameter.
                    //
                    // bool M3<T>(int x) { return x is T; }
                    //
                    // There is no conversion from int to T, but T could nevertheless be int.
                    //
                    // Scenario 4: Constructed type compared to open type
                    //
                    // bool M4<T>(C<int> x) { return x is C<T>; }
                    //
                    // There is no conversion from C<int> to C<T>, but nevertheless, T might be int.
                    //
                    // Scenario 5: Open type compared to constructed type:
                    //
                    // bool M5<X>(C<X> x) { return x is C<int>);
                    //
                    // Again, X could be int.
                    //
                    // We could then go on to get more complicated. For example,
                    //
                    // bool M6<X>(C<X> x) where X : struct { return x is C<string>; }
                    //
                    // We know that C<X> is never convertible to C<string> no matter what
                    // X is. Or:
                    //
                    // bool M7<T>(Dictionary<int, int> x) { return x is List<T>; }
                    //
                    // We know that no matter what T is, the conversion will never succeed.
                    //
                    // As noted above, we must be conservative. We follow the lead of the native compiler,
                    // which uses the following algorithm:
                    //
                    // * If neither type is open and there is no conversion then the result is always false:
 
                    if (!operandType.ContainsTypeParameter() && !targetType.ContainsTypeParameter())
                    {
                        return ConstantValue.False;
                    }
 
                    // * Otherwise, at least one of them is of an open type. If the operand is of value type
                    //   and the target is a class type other than System.Enum, or vice versa, then we are
                    //   in scenario 2, not scenario 1, and can correctly deduce that the result is false.
 
                    if (operandType.IsValueType && targetType.IsClassType() && targetType.SpecialType != SpecialType.System_Enum ||
                        targetType.IsValueType && operandType.IsClassType() && operandType.SpecialType != SpecialType.System_Enum)
                    {
                        return ConstantValue.False;
                    }
 
                    // * If either type is a restricted type, the type check isn't supported for some scenarios because
                    //   a restricted type cannot be boxed or unboxed into.
                    if (targetType.IsRestrictedType() || operandType.IsRestrictedType())
                    {
                        if (targetType is TypeParameterSymbol { AllowsRefLikeType: true })
                        {
                            if (!operandType.IsErrorOrRefLikeOrAllowsRefLikeType())
                            {
                                return null;
                            }
                        }
                        else if (operandType is not TypeParameterSymbol { AllowsRefLikeType: true })
                        {
                            if (targetType.IsRefLikeType)
                            {
                                if (operandType is TypeParameterSymbol)
                                {
                                    Debug.Assert(operandType is TypeParameterSymbol { AllowsRefLikeType: false });
                                    return ConstantValue.False;
                                }
                            }
                            else if (operandType.IsRefLikeType)
                            {
                                if (targetType is TypeParameterSymbol)
                                {
                                    Debug.Assert(targetType is TypeParameterSymbol { AllowsRefLikeType: false });
                                    return ConstantValue.False;
                                }
                            }
                        }
 
                        return ConstantValue.Bad;
                    }
 
                    // * Otherwise, we give up. Though there are other situations in which we can deduce that
                    //   the result will always be false, such as scenarios 6 and 7, but we do not attempt
                    //   to deduce this.
 
                    // CONSIDER: we could use TypeUnification.CanUnify to do additional compile-time checking.
 
                    return null;
 
                case ConversionKind.ImplicitNumeric:
                case ConversionKind.ExplicitNumeric:
                case ConversionKind.ImplicitEnumeration:
                // case ConversionKind.ExplicitEnumeration: // Handled separately below.
                case ConversionKind.ImplicitConstant:
                case ConversionKind.ImplicitUserDefined:
                case ConversionKind.ExplicitUserDefined:
                case ConversionKind.IntPtr:
                case ConversionKind.ExplicitTuple:
                case ConversionKind.ImplicitTuple:
 
                    // Consider all the cases where we know that "x is T" must be false just from
                    // the conversion classification.
                    //
                    // If we have "x is T" and the conversion from x to T is numeric or enum then the result must be false.
                    //
                    // If we have "null is T" then obviously that must be false.
                    //
                    // If we have "1 is long" then that must be false. (If we have "1 is int" then it is an identity conversion,
                    // not an implicit constant conversion.
                    //
                    // User-defined and IntPtr conversions are always false for "is".
 
                    return ConstantValue.False;
 
                case ConversionKind.ExplicitEnumeration:
                    // Enum-to-enum conversions should be treated the same as unsuccessful struct-to-struct
                    // conversions (i.e. make allowances for type unification, etc)
                    if (operandType.IsEnumType() && targetType.IsEnumType())
                    {
                        goto case ConversionKind.NoConversion;
                    }
 
                    return ConstantValue.False;
 
                case ConversionKind.ExplicitNullable:
 
                    // An explicit nullable conversion is a conversion of one of the following forms:
                    //
                    // 1) X? --> Y?, where X --> Y is an explicit conversion.  (If X --> Y is an implicit
                    //    conversion then X? --> Y? is an implicit nullable conversion.) In this case we
                    //    know that "X? is Y?" must be false because either X? is null, or we have an
                    //    explicit conversion from struct type X to struct type Y, and so X is never of type Y.)
                    //
                    // 2) X --> Y?, where again, X --> Y is an explicit conversion. By the same reasoning
                    //    as in case 1, this must be false.
 
                    if (targetType.IsNullableType())
                    {
                        return ConstantValue.False;
                    }
 
                    Debug.Assert(operandType.IsNullableType());
 
                    // 3) X? --> X. In this case, this is just a different way of writing "x != null".
                    //    We only know what the result will be if the input is known not to be null.
                    if (Conversions.HasIdentityConversion(operandType.GetNullableUnderlyingType(), targetType))
                    {
                        return operandCouldBeNull ? null : ConstantValue.True;
                    }
 
                    // 4) X? --> Y where the conversion X --> Y is an implicit or explicit value type conversion.
                    //    "X? is Y" again must be false.
 
                    return ConstantValue.False;
 
                case ConversionKind.ImplicitReference:
                    return operandCouldBeNull ? null : ConstantValue.True;
 
                case ConversionKind.ExplicitReference:
                case ConversionKind.Unboxing:
                    // In these three cases, the expression type must be a reference type. Therefore,
                    // the result cannot be determined. The expression could be null or of the wrong type,
                    // resulting in false, or it could be a non-null reference to the appropriate type,
                    // resulting in true.
                    return null;
 
                case ConversionKind.Identity:
                    // The result of "x is T" can be statically determined to be true if x is an expression
                    // of non-nullable value type T. If x is of reference or nullable value type then
                    // we cannot know, because again, the expression value could be null or it could be good.
                    // If it is of pointer type then we have already given an error.
                    return operandCouldBeNull ? null : ConstantValue.True;
 
                case ConversionKind.Boxing:
 
                    // A boxing conversion might be a conversion:
                    //
                    // * From a non-nullable value type to a reference type
                    // * From a nullable value type to a reference type
                    // * From a type parameter that *could* be a value type under construction
                    //   to a reference type
                    //
                    // In the first case we know that the conversion will always succeed and that the
                    // operand is never null, and therefore "is" will always result in true.
                    //
                    // In the second two cases we do not know; either the nullable value type could be
                    // null, or the type parameter could be constructed with a reference type, and it
                    // could be null.
                    return operandCouldBeNull ? null : ConstantValue.True;
 
                case ConversionKind.ImplicitNullable:
                    // We have "x is T" in one of the following situations:
                    // 1) x is of type X and T is X?.  The value is always true.
                    // 2) x is of type X and T is Y? where X is convertible to Y via an implicit numeric conversion. Eg,
                    //    x is of type int and T is decimal?.  The value is always false.
                    // 3) x is of type X? and T is Y? where X is convertible to Y via an implicit numeric conversion.
                    //    The value is always false.
 
                    Debug.Assert(targetType.IsNullableType());
                    return operandType.Equals(targetType.GetNullableUnderlyingType(), TypeCompareKind.AllIgnoreOptions)
                        ? ConstantValue.True : ConstantValue.False;
 
                default:
                case ConversionKind.ImplicitDynamic:
                case ConversionKind.ExplicitDynamic:
                case ConversionKind.ExplicitPointerToInteger:
                case ConversionKind.ExplicitPointerToPointer:
                case ConversionKind.ImplicitPointerToVoid:
                case ConversionKind.ExplicitIntegerToPointer:
                case ConversionKind.ImplicitNullToPointer:
                case ConversionKind.AnonymousFunction:
                case ConversionKind.NullLiteral:
                case ConversionKind.DefaultLiteral:
                case ConversionKind.MethodGroup:
                    // We've either replaced Dynamic with Object, or already bailed out with an error.
                    throw ExceptionUtilities.UnexpectedValue(conversionKind);
            }
        }
 
        private BoundExpression BindAsOperator(BinaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            var operand = BindRValueWithoutTargetType(node.Left, diagnostics);
            AliasSymbol alias;
            TypeWithAnnotations targetTypeWithAnnotations = BindType(node.Right, diagnostics, out alias);
            TypeSymbol targetType = targetTypeWithAnnotations.Type;
            var typeExpression = new BoundTypeExpression(node.Right, alias, targetTypeWithAnnotations);
            var targetTypeKind = targetType.TypeKind;
            var resultType = targetType;
 
            // Is and As operator should have null ConstantValue as they are not constant expressions.
            // However we perform analysis of is/as expressions at bind time to detect if the expression
            // will always evaluate to a constant to generate warnings (always true/false/null).
            // We also need this analysis result during rewrite to optimize away redundant isinst instructions.
            // We store the conversion kind from expression's operand type to target type to enable these
            // optimizations during is/as operator rewrite.
 
            switch (operand.Kind)
            {
                case BoundKind.UnboundLambda:
                case BoundKind.Lambda:
                case BoundKind.MethodGroup:  // New in Roslyn - see DevDiv #864740.
                    // operand for an is or as expression cannot be a lambda expression or method group
                    if (!operand.HasAnyErrors)
                    {
                        Error(diagnostics, ErrorCode.ERR_LambdaInIsAs, node);
                    }
 
                    return new BoundAsOperator(node, operand, typeExpression, operandPlaceholder: null, operandConversion: null, resultType, hasErrors: true);
 
                case BoundKind.TupleLiteral:
                case BoundKind.ConvertedTupleLiteral:
                    if ((object)operand.Type == null)
                    {
                        Error(diagnostics, ErrorCode.ERR_TypelessTupleInAs, node);
                        return new BoundAsOperator(node, operand, typeExpression, operandPlaceholder: null, operandConversion: null, resultType, hasErrors: true);
                    }
                    break;
            }
 
            if (operand.HasAnyErrors || targetTypeKind == TypeKind.Error)
            {
                // If either operand is bad or target type has errors, bail out preventing more cascading errors.
                return new BoundAsOperator(node, operand, typeExpression, operandPlaceholder: null, operandConversion: null, resultType, hasErrors: true);
            }
 
            if (targetType.IsReferenceType && targetTypeWithAnnotations.NullableAnnotation.IsAnnotated())
            {
                Error(diagnostics, ErrorCode.ERR_AsNullableType, node.Right, targetType);
 
                return new BoundAsOperator(node, operand, typeExpression, operandPlaceholder: null, operandConversion: null, resultType, hasErrors: true);
            }
            else if (!targetType.IsReferenceType && !targetType.IsNullableType())
            {
                // SPEC:    In an operation of the form E as T, E must be an expression and T must be a
                // SPEC:    reference type, a type parameter known to be a reference type, or a nullable type.
                if (targetTypeKind == TypeKind.TypeParameter)
                {
                    Error(diagnostics, ErrorCode.ERR_AsWithTypeVar, node, targetType);
                }
                else if (targetTypeKind == TypeKind.Pointer || targetTypeKind == TypeKind.FunctionPointer)
                {
                    Error(diagnostics, ErrorCode.ERR_PointerInAsOrIs, node);
                }
                else
                {
                    Error(diagnostics, ErrorCode.ERR_AsMustHaveReferenceType, node, targetType);
                }
 
                return new BoundAsOperator(node, operand, typeExpression, operandPlaceholder: null, operandConversion: null, resultType, hasErrors: true);
            }
 
            // The C# specification states in the section called
            // "Referencing Static Class Types" that it is always
            // illegal to use "as" with a static type. The
            // native compiler actually allows "null as C" for
            // a static type C to be an expression of type C.
            // It also allows "someObject as C" if "someObject"
            // is of type object. To retain compatibility we
            // allow it, but when /warn:5 or higher we break with the native
            // compiler and turn this into a warning.
            if (targetType.IsStatic)
            {
                Error(diagnostics, ErrorCode.WRN_StaticInAsOrIs, node, targetType);
            }
 
            BoundValuePlaceholder operandPlaceholder;
            BoundExpression operandConversion;
 
            if (operand.IsLiteralNull())
            {
                // We do not want to warn for the case "null as TYPE" where the null
                // is a literal, because the user might be saying it to cause overload resolution
                // to pick a particular method
                Debug.Assert(operand.Type is null);
                operandPlaceholder = new BoundValuePlaceholder(operand.Syntax, operand.Type).MakeCompilerGenerated();
                operandConversion = CreateConversion(node, operandPlaceholder,
                                                     Conversion.NullLiteral,
                                                     isCast: false, conversionGroupOpt: null, resultType, diagnostics);
 
                return new BoundAsOperator(node, operand, typeExpression, operandPlaceholder, operandConversion, resultType);
            }
 
            if (operand.IsLiteralDefault())
            {
                operand = new BoundDefaultExpression(operand.Syntax, targetType: null, constantValueOpt: ConstantValue.Null,
                    type: GetSpecialType(SpecialType.System_Object, diagnostics, node));
            }
 
            var operandType = operand.Type;
            Debug.Assert((object)operandType != null);
            var operandTypeKind = operandType.TypeKind;
 
            Debug.Assert(!targetType.IsPointerOrFunctionPointer(), "Should have been caught above");
            if (operandType.IsPointerOrFunctionPointer())
            {
                // operand for an is or as expression cannot be of pointer type
                Error(diagnostics, ErrorCode.ERR_PointerInAsOrIs, node);
                return new BoundAsOperator(node, operand, typeExpression, operandPlaceholder: null, operandConversion: null, resultType, hasErrors: true);
            }
 
            if (operandTypeKind == TypeKind.Dynamic)
            {
                // if operand has a dynamic type, we do the same thing as though it were an object
                operandType = GetSpecialType(SpecialType.System_Object, diagnostics, node);
                operandTypeKind = operandType.TypeKind;
            }
 
            if (targetTypeKind == TypeKind.Dynamic)
            {
                // for "as dynamic", we do the same thing as though it were an "as object"
                targetType = GetSpecialType(SpecialType.System_Object, diagnostics, node);
                targetTypeKind = targetType.TypeKind;
            }
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            Conversion conversion = Conversions.ClassifyBuiltInConversion(operandType, targetType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
            diagnostics.Add(node, useSiteInfo);
            bool hasErrors = ReportAsOperatorConversionDiagnostics(node, diagnostics, this.Compilation, operandType, targetType, conversion.Kind, operand.ConstantValueOpt);
 
            if (conversion.Exists)
            {
                operandPlaceholder = new BoundValuePlaceholder(operand.Syntax, operand.Type).MakeCompilerGenerated();
                operandConversion = CreateConversion(node, operandPlaceholder,
                                                     conversion,
                                                     isCast: false, conversionGroupOpt: null, resultType, diagnostics);
            }
            else
            {
                operandPlaceholder = null;
                operandConversion = null;
            }
 
            return new BoundAsOperator(node, operand, typeExpression, operandPlaceholder, operandConversion, resultType, hasErrors);
        }
 
        private static bool ReportAsOperatorConversionDiagnostics(
            CSharpSyntaxNode node,
            BindingDiagnosticBag diagnostics,
            CSharpCompilation compilation,
            TypeSymbol operandType,
            TypeSymbol targetType,
            ConversionKind conversionKind,
            ConstantValue operandConstantValue)
        {
            // SPEC:    In an operation of the form E as T, E must be an expression and T must be a reference type,
            // SPEC:    a type parameter known to be a reference type, or a nullable type.
            // SPEC:    Furthermore, at least one of the following must be true, or otherwise a compile-time error occurs:
            // SPEC:    •	An identity (§6.1.1), implicit nullable (§6.1.4), implicit reference (§6.1.6), boxing (§6.1.7),
            // SPEC:        explicit nullable (§6.2.3), explicit reference (§6.2.4), or unboxing (§6.2.5) conversion exists
            // SPEC:        from E to T.
            // SPEC:    •	The type of E or T is an open type.
            // SPEC:    •	E is the null literal.
 
            // SPEC VIOLATION:  The specification contains an error in the list of legal conversions above.
            // SPEC VIOLATION:  If we have "class C<T, U> where T : U where U : class" then there is
            // SPEC VIOLATION:  an implicit conversion from T to U, but it is not an identity, reference or
            // SPEC VIOLATION:  boxing conversion. It will be one of those at runtime, but at compile time
            // SPEC VIOLATION:  we do not know which, and therefore cannot classify it as any of those.
            // SPEC VIOLATION:  See Microsoft.CodeAnalysis.CSharp.UnitTests.SyntaxBinderTests.TestAsOperator_SpecErrorCase() test for an example.
 
            // SPEC VIOLATION:  The specification also unintentionally allows the case where requirement 2 above:
            // SPEC VIOLATION:  "The type of E or T is an open type" is true, but type of E is void type, i.e. T is an open type.
            // SPEC VIOLATION:  Dev10 compiler correctly generates an error for this case and we will maintain compatibility.
 
            bool hasErrors = false;
            switch (conversionKind)
            {
                case ConversionKind.ImplicitReference:
                case ConversionKind.Boxing:
                case ConversionKind.ImplicitNullable:
                case ConversionKind.Identity:
                case ConversionKind.ExplicitNullable:
                case ConversionKind.ExplicitReference:
                case ConversionKind.Unboxing:
                    break;
 
                default:
                    // Generate an error if there is no possible legal conversion and both the operandType
                    // and the targetType are closed types OR operandType is void type, otherwise we need a runtime check
                    if (!operandType.ContainsTypeParameter() && !targetType.ContainsTypeParameter() ||
                        operandType.IsVoidType())
                    {
                        SymbolDistinguisher distinguisher = new SymbolDistinguisher(compilation, operandType, targetType);
                        Error(diagnostics, ErrorCode.ERR_NoExplicitBuiltinConv, node, distinguisher.First, distinguisher.Second);
                        hasErrors = true;
                    }
 
                    break;
            }
 
            if (!hasErrors)
            {
                ReportAsOperatorDiagnostics(node, diagnostics, operandType, targetType, conversionKind, operandConstantValue);
            }
 
            return hasErrors;
        }
 
        private static void ReportAsOperatorDiagnostics(
            CSharpSyntaxNode node,
            BindingDiagnosticBag diagnostics,
            TypeSymbol operandType,
            TypeSymbol targetType,
            ConversionKind conversionKind,
            ConstantValue operandConstantValue)
        {
            // NOTE:    Even though BoundIsOperator and BoundAsOperator will always have no ConstantValue
            // NOTE:    (they are non-constant expressions according to Section 7.19 of the specification),
            // NOTE:    we want to perform constant analysis of is/as expressions to generate warnings if the
            // NOTE:    expression will always be true/false/null.
 
            ConstantValue constantValue = GetAsOperatorConstantResult(operandType, targetType, conversionKind, operandConstantValue);
            if (constantValue != null)
            {
                if (constantValue.IsBad)
                {
                    Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, "as", operandType, targetType);
                }
                else
                {
                    Debug.Assert(constantValue.IsNull);
                    Error(diagnostics, ErrorCode.WRN_AlwaysNull, node, targetType);
                }
            }
        }
 
        /// <summary>
        /// Possible return values:
        ///  - <see cref="ConstantValue.Null"/>
        ///  - <see cref="ConstantValue.Bad"/> - compiler doesn't support the type check, i.e. cannot perform it, even at runtime
        ///  - 'null' value - result is not known at compile time    
        /// </summary>
        internal static ConstantValue GetAsOperatorConstantResult(TypeSymbol operandType, TypeSymbol targetType, ConversionKind conversionKind, ConstantValue operandConstantValue)
        {
            // NOTE:    Even though BoundIsOperator and BoundAsOperator will always have no ConstantValue
            // NOTE:    (they are non-constant expressions according to Section 7.19 of the specification),
            // NOTE:    we want to perform constant analysis of is/as expressions during binding to generate warnings (always true/false/null)
            // NOTE:    and during rewriting for optimized codegen.
 
            ConstantValue isOperatorConstantResult = GetIsOperatorConstantResult(operandType, targetType, conversionKind, operandConstantValue);
            if (isOperatorConstantResult != null)
            {
                if (isOperatorConstantResult.IsBad)
                {
                    return isOperatorConstantResult;
                }
 
                if (!isOperatorConstantResult.BooleanValue)
                {
                    if (operandType?.IsRefLikeType == true)
                    {
                        return ConstantValue.Bad;
                    }
 
                    return ConstantValue.Null;
                }
            }
 
            return null;
        }
 
        private BoundExpression GenerateNullCoalescingBadBinaryOpsError(BinaryExpressionSyntax node, BoundExpression leftOperand, BoundExpression rightOperand, BindingDiagnosticBag diagnostics)
        {
            Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, SyntaxFacts.GetText(node.OperatorToken.Kind()), leftOperand.Display, rightOperand.Display);
 
            leftOperand = BindToTypeForErrorRecovery(leftOperand);
            rightOperand = BindToTypeForErrorRecovery(rightOperand);
            return new BoundNullCoalescingOperator(node, leftOperand, rightOperand,
                leftPlaceholder: null, leftConversion: null, BoundNullCoalescingOperatorResultKind.NoCommonType, @checked: CheckOverflowAtRuntime, CreateErrorType(), hasErrors: true);
        }
 
        private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            var leftOperand = BindValue(node.Left, diagnostics, BindValueKind.RValue);
            leftOperand = BindToNaturalType(leftOperand, diagnostics);
            var rightOperand = BindValue(node.Right, diagnostics, BindValueKind.RValue);
 
            // If either operand is bad, bail out preventing more cascading errors
            if (leftOperand.HasAnyErrors || rightOperand.HasAnyErrors)
            {
                leftOperand = BindToTypeForErrorRecovery(leftOperand);
                rightOperand = BindToTypeForErrorRecovery(rightOperand);
                return new BoundNullCoalescingOperator(node, leftOperand, rightOperand,
                    leftPlaceholder: null, leftConversion: null, BoundNullCoalescingOperatorResultKind.NoCommonType, @checked: CheckOverflowAtRuntime, CreateErrorType(), hasErrors: true);
            }
 
            // The specification does not permit the left hand side to be a default literal
            if (leftOperand.IsLiteralDefault())
            {
                Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefaultOrNew, node, node.OperatorToken.Text, "default");
 
                return new BoundNullCoalescingOperator(node, leftOperand, rightOperand,
                    leftPlaceholder: null, leftConversion: null, BoundNullCoalescingOperatorResultKind.NoCommonType, @checked: CheckOverflowAtRuntime, CreateErrorType(), hasErrors: true);
            }
 
            // SPEC: The type of the expression a ?? b depends on which implicit conversions are available
            // SPEC: between the types of the operands. In order of preference, the type of a ?? b is A0, A, or B,
            // SPEC: where A is the type of a, B is the type of b (provided that b has a type),
            // SPEC: and A0 is the underlying type of A if A is a nullable type, or A otherwise.
 
            TypeSymbol optLeftType = leftOperand.Type;   // "A"
            TypeSymbol optRightType = rightOperand.Type; // "B"
            bool isLeftNullable = (object)optLeftType != null && optLeftType.IsNullableType();
            TypeSymbol optLeftType0 = isLeftNullable ?  // "A0"
                optLeftType.GetNullableUnderlyingType() :
                optLeftType;
 
            // SPEC: The left hand side must be either the null literal or it must have a type. Lambdas and method groups do not have a type,
            // SPEC: so using one is an error.
            if (leftOperand.Kind == BoundKind.UnboundLambda || leftOperand.Kind == BoundKind.MethodGroup)
            {
                return GenerateNullCoalescingBadBinaryOpsError(node, leftOperand, rightOperand, diagnostics);
            }
 
            // SPEC: Otherwise, if A exists and is a non-nullable value type, a compile-time error occurs. First we check for the pre-C# 8.0
            // SPEC: condition, to ensure that we don't allow previously illegal code in old language versions.
            if ((object)optLeftType != null && !optLeftType.IsReferenceType && !isLeftNullable)
            {
                // Prior to C# 8.0, the spec said that the left type must be either a reference type or a nullable value type. This was relaxed
                // with C# 8.0, so if the feature is not enabled then issue a diagnostic and return
                if (!optLeftType.IsValueType)
                {
                    CheckFeatureAvailability(node, MessageID.IDS_FeatureUnconstrainedTypeParameterInNullCoalescingOperator, diagnostics);
                }
                else
                {
                    return GenerateNullCoalescingBadBinaryOpsError(node, leftOperand, rightOperand, diagnostics);
                }
            }
 
            // SPEC:    If b is a dynamic expression, the result is dynamic. At runtime, a is first
            // SPEC:    evaluated. If a is not null, a is converted to a dynamic type, and this becomes
            // SPEC:    the result. Otherwise, b is evaluated, and the outcome becomes the result.
            //
            // Note that there is no runtime dynamic dispatch since comparison with null is not a dynamic operation.
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
 
            if ((object)optRightType != null && optRightType.IsDynamic())
            {
                var leftPlaceholder = new BoundValuePlaceholder(leftOperand.Syntax, optLeftType).MakeCompilerGenerated();
                var objectType = GetSpecialType(SpecialType.System_Object, diagnostics, node);
                var leftConversion = CreateConversion(node, leftPlaceholder,
                                                      Conversions.ClassifyConversionFromExpression(leftOperand, objectType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo),
                                                      isCast: false, conversionGroupOpt: null, objectType, diagnostics);
 
                rightOperand = BindToNaturalType(rightOperand, diagnostics);
                diagnostics.Add(node, useSiteInfo);
                return new BoundNullCoalescingOperator(node, leftOperand, rightOperand,
                    leftPlaceholder, leftConversion, BoundNullCoalescingOperatorResultKind.RightDynamicType, @checked: CheckOverflowAtRuntime, optRightType);
            }
 
            // SPEC:    Otherwise, if A exists and is a nullable type and an implicit conversion exists from b to A0,
            // SPEC:    the result type is A0. At run-time, a is first evaluated. If a is not null,
            // SPEC:    a is unwrapped to type A0, and this becomes the result.
            // SPEC:    Otherwise, b is evaluated and converted to type A0, and this becomes the result.
 
            if (isLeftNullable)
            {
                var rightConversion = Conversions.ClassifyImplicitConversionFromExpression(rightOperand, optLeftType0, ref useSiteInfo);
                if (rightConversion.Exists)
                {
                    var leftPlaceholder = new BoundValuePlaceholder(leftOperand.Syntax, optLeftType0).MakeCompilerGenerated();
                    diagnostics.Add(node, useSiteInfo);
                    var convertedRightOperand = CreateConversion(rightOperand, rightConversion, optLeftType0, diagnostics);
                    // Note: we use an identity conversion for LHS and let lowering get 'a0' from 'a' with GetValueOrDefault
                    return new BoundNullCoalescingOperator(node, leftOperand, convertedRightOperand,
                        leftPlaceholder, leftConversion: leftPlaceholder, BoundNullCoalescingOperatorResultKind.LeftUnwrappedType, @checked: CheckOverflowAtRuntime, optLeftType0);
                }
            }
 
            // SPEC:    Otherwise, if A exists and an implicit conversion exists from b to A, the result type is A.
            // SPEC:    At run-time, a is first evaluated. If a is not null, a becomes the result.
            // SPEC:    Otherwise, b is evaluated and converted to type A, and this becomes the result.
 
            if ((object)optLeftType != null)
            {
                var rightConversion = Conversions.ClassifyImplicitConversionFromExpression(rightOperand, optLeftType, ref useSiteInfo);
                if (rightConversion.Exists)
                {
                    var convertedRightOperand = CreateConversion(rightOperand, rightConversion, optLeftType, diagnostics);
                    var leftPlaceholder = new BoundValuePlaceholder(leftOperand.Syntax, optLeftType).MakeCompilerGenerated();
                    diagnostics.Add(node, useSiteInfo);
                    return new BoundNullCoalescingOperator(node, leftOperand, convertedRightOperand,
                        leftPlaceholder, leftConversion: leftPlaceholder, BoundNullCoalescingOperatorResultKind.LeftType, @checked: CheckOverflowAtRuntime, optLeftType);
                }
            }
 
            // SPEC:    Otherwise, if b has a type B and an implicit conversion exists from a to B,
            // SPEC:    the result type is B. At run-time, a is first evaluated. If a is not null,
            // SPEC:    a is unwrapped to type A0 (if A exists and is nullable) and converted to type B,
            // SPEC:    and this becomes the result. Otherwise, b is evaluated and becomes the result.
 
            // SPEC VIOLATION:  Native compiler violates the specification here and implements this part based on
            // SPEC VIOLATION:  whether A is a nullable type or not.
            // SPEC VIOLATION:  We will maintain compatibility with the native compiler and do the same.
            // SPEC VIOLATION:  Following SPEC PROPOSAL states the current implementations in both compilers:
 
            // SPEC PROPOSAL:    Otherwise, if A exists and is a nullable type and if b has a type B and
            // SPEC PROPOSAL:    an implicit conversion exists from A0 to B, the result type is B.
            // SPEC PROPOSAL:    At run-time, a is first evaluated. If a is not null, a is unwrapped to type A0
            // SPEC PROPOSAL:    and converted to type B, and this becomes the result.
            // SPEC PROPOSAL:    Otherwise, b is evaluated and becomes the result.
 
            // SPEC PROPOSAL:    Otherwise, if A does not exist or is a non-nullable type and if b has a type B and
            // SPEC PROPOSAL:    an implicit conversion exists from a to B, the result type is B.
            // SPEC PROPOSAL:    At run-time, a is first evaluated. If a is not null, a is converted to type B,
            // SPEC PROPOSAL:    and this becomes the result. Otherwise, b is evaluated and becomes the result.
 
            // See test CodeGenTests.TestNullCoalescingOperatorWithNullableConversions for an example.
 
            if ((object)optRightType != null)
            {
                rightOperand = BindToNaturalType(rightOperand, diagnostics);
                Conversion leftConversionClassification;
                BoundNullCoalescingOperatorResultKind resultKind;
 
                if (isLeftNullable)
                {
                    // This is the SPEC VIOLATION case.
                    // Note that at runtime we need two conversions on the left operand:
                    //      1) Explicit nullable conversion from leftOperand to optLeftType0 and
                    //      2) Implicit conversion from optLeftType0 to optRightType.
                    // We just store the second conversion in the bound node and insert the first conversion during rewriting
                    // the null coalescing operator. See method LocalRewriter.GetConvertedLeftForNullCoalescingOperator.
 
                    leftConversionClassification = Conversions.ClassifyImplicitConversionFromType(optLeftType0, optRightType, ref useSiteInfo);
                    resultKind = BoundNullCoalescingOperatorResultKind.LeftUnwrappedRightType;
 
                    if (leftConversionClassification.Exists)
                    {
                        var leftPlaceholder = new BoundValuePlaceholder(leftOperand.Syntax, optLeftType0).MakeCompilerGenerated();
                        var leftConversion = CreateConversion(node, leftPlaceholder, leftConversionClassification, isCast: false, conversionGroupOpt: null, optRightType, diagnostics);
 
                        diagnostics.Add(node, useSiteInfo);
                        return new BoundNullCoalescingOperator(node, leftOperand, rightOperand, leftPlaceholder, leftConversion, resultKind, @checked: CheckOverflowAtRuntime, optRightType);
                    }
                }
                else
                {
                    leftConversionClassification = Conversions.ClassifyImplicitConversionFromExpression(leftOperand, optRightType, ref useSiteInfo);
                    resultKind = BoundNullCoalescingOperatorResultKind.RightType;
 
                    if (leftConversionClassification.Exists)
                    {
                        var leftPlaceholder = new BoundValuePlaceholder(leftOperand.Syntax, optLeftType).MakeCompilerGenerated();
                        var leftConversion = CreateConversion(node, leftPlaceholder, leftConversionClassification, isCast: false, conversionGroupOpt: null, optRightType, diagnostics);
 
                        diagnostics.Add(node, useSiteInfo);
                        return new BoundNullCoalescingOperator(node, leftOperand, rightOperand, leftPlaceholder, leftConversion, resultKind, @checked: CheckOverflowAtRuntime, optRightType);
                    }
                }
            }
 
            // SPEC:    Otherwise, a and b are incompatible, and a compile-time error occurs.
            diagnostics.Add(node, useSiteInfo);
            return GenerateNullCoalescingBadBinaryOpsError(node, leftOperand, rightOperand, diagnostics);
        }
 
        private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeatureCoalesceAssignmentExpression.CheckFeatureAvailability(diagnostics, node.OperatorToken);
 
            BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment);
            ReportSuppressionIfNeeded(leftOperand, diagnostics);
            BoundExpression rightOperand = BindValue(node.Right, diagnostics, BindValueKind.RValue);
 
            // If either operand is bad, bail out preventing more cascading errors
            if (leftOperand.HasAnyErrors || rightOperand.HasAnyErrors)
            {
                leftOperand = BindToTypeForErrorRecovery(leftOperand);
                rightOperand = BindToTypeForErrorRecovery(rightOperand);
                return new BoundNullCoalescingAssignmentOperator(node, leftOperand, rightOperand, CreateErrorType(), hasErrors: true);
            }
 
            // Given a ??= b, the type of a is A, the type of B is b, and if A is a nullable value type, the underlying
            // non-nullable value type of A is A0.
            TypeSymbol leftType = leftOperand.Type;
            Debug.Assert((object)leftType != null);
 
            // If A is a non-nullable value type, a compile-time error occurs
            if (leftType.IsValueType && !leftType.IsNullableType())
            {
                return GenerateNullCoalescingAssignmentBadBinaryOpsError(node, leftOperand, rightOperand, diagnostics);
            }
 
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
 
            // If A0 exists and B is implicitly convertible to A0, then the result type of this expression is A0, except if B is dynamic.
            // This differs from most assignments such that you cannot directly replace a with (a ??= b).
            // The exception for dynamic is called out in the spec, it's the same behavior that ?? has with respect to dynamic.
            if (leftType.IsNullableType())
            {
                var underlyingLeftType = leftType.GetNullableUnderlyingType();
                var underlyingRightConversion = Conversions.ClassifyImplicitConversionFromExpression(rightOperand, underlyingLeftType, ref useSiteInfo);
                if (underlyingRightConversion.Exists && rightOperand.Type?.IsDynamic() != true)
                {
                    diagnostics.Add(node, useSiteInfo);
                    var convertedRightOperand = CreateConversion(rightOperand, underlyingRightConversion, underlyingLeftType, diagnostics);
                    return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, underlyingLeftType);
                }
            }
 
            // If an implicit conversion exists from B to A, we store that conversion. At runtime, a is first evaluated. If
            // a is not null, b is not evaluated. If a is null, b is evaluated and converted to type A, and is stored in a.
            // Reset useSiteDiagnostics because they could have been used populated incorrectly from attempting to bind
            // as the nullable underlying value type case.
            useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(useSiteInfo);
            var rightConversion = Conversions.ClassifyImplicitConversionFromExpression(rightOperand, leftType, ref useSiteInfo);
            diagnostics.Add(node, useSiteInfo);
            if (rightConversion.Exists)
            {
                var convertedRightOperand = CreateConversion(rightOperand, rightConversion, leftType, diagnostics);
                return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, leftType);
            }
 
            // a and b are incompatible and a compile-time error occurs
            return GenerateNullCoalescingAssignmentBadBinaryOpsError(node, leftOperand, rightOperand, diagnostics);
        }
 
        private BoundExpression GenerateNullCoalescingAssignmentBadBinaryOpsError(AssignmentExpressionSyntax node, BoundExpression leftOperand, BoundExpression rightOperand, BindingDiagnosticBag diagnostics)
        {
            Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, SyntaxFacts.GetText(node.OperatorToken.Kind()), leftOperand.Display, rightOperand.Display);
            leftOperand = BindToTypeForErrorRecovery(leftOperand);
            rightOperand = BindToTypeForErrorRecovery(rightOperand);
            return new BoundNullCoalescingAssignmentOperator(node, leftOperand, rightOperand, CreateErrorType(), hasErrors: true);
        }
 
        /// <remarks>
        /// From ExpressionBinder::EnsureQMarkTypesCompatible:
        ///
        /// The v2.0 specification states that the types of the second and third operands T and S of a conditional operator
        /// must be TT and TS such that either (a) TT==TS, or (b), TT->TS or TS->TT but not both.
        ///
        /// Unfortunately that is not what we implemented in v2.0.  Instead, we implemented
        /// that either (a) TT=TS or (b) T->TS or S->TT but not both.  That is, we looked at the
        /// convertibility of the expressions, not the types.
        ///
        ///
        /// Changing that to the algorithm in the standard would be a breaking change.
        ///
        /// b ? (Func&lt;int&gt;)(delegate(){return 1;}) : (delegate(){return 2;})
        ///
        /// and
        ///
        /// b ? 0 : myenum
        ///
        /// would suddenly stop working.  (The first because o2 has no type, the second because 0 goes to
        /// any enum but enum doesn't go to int.)
        ///
        /// It gets worse.  We would like the 3.0 language features which require type inference to use
        /// a consistent algorithm, and that furthermore, the algorithm be smart about choosing the best
        /// of a set of types.  However, the language committee has decided that this algorithm will NOT
        /// consume information about the convertibility of expressions. Rather, it will gather up all
        /// the possible types and then pick the "largest" of them.
        ///
        /// To maintain backwards compatibility while still participating in the spirit of consistency,
        /// we implement an algorithm here which picks the type based on expression convertibility, but
        /// if there is a conflict, then it chooses the larger type rather than producing a type error.
        /// This means that b?0:myshort will have type int rather than producing an error (because 0->short,
        /// myshort->int).
        /// </remarks>
        private BoundExpression BindConditionalOperator(ConditionalExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            var whenTrue = node.WhenTrue.CheckAndUnwrapRefExpression(diagnostics, out var whenTrueRefKind);
            var whenFalse = node.WhenFalse.CheckAndUnwrapRefExpression(diagnostics, out var whenFalseRefKind);
 
            var isRef = whenTrueRefKind == RefKind.Ref && whenFalseRefKind == RefKind.Ref;
            if (!isRef)
            {
                if (whenFalseRefKind == RefKind.Ref)
                {
                    diagnostics.Add(ErrorCode.ERR_RefConditionalNeedsTwoRefs, whenFalse.GetFirstToken().GetLocation());
                }
 
                if (whenTrueRefKind == RefKind.Ref)
                {
                    diagnostics.Add(ErrorCode.ERR_RefConditionalNeedsTwoRefs, whenTrue.GetFirstToken().GetLocation());
                }
            }
            else
            {
                CheckFeatureAvailability(node, MessageID.IDS_FeatureRefConditional, diagnostics);
            }
 
            return isRef ? BindRefConditionalOperator(node, whenTrue, whenFalse, diagnostics) : BindValueConditionalOperator(node, whenTrue, whenFalse, diagnostics);
        }
 
#nullable enable
        private BoundExpression BindValueConditionalOperator(ConditionalExpressionSyntax node, ExpressionSyntax whenTrue, ExpressionSyntax whenFalse, BindingDiagnosticBag diagnostics)
        {
            BoundExpression condition = BindBooleanExpression(node.Condition, diagnostics);
            BoundExpression trueExpr = BindValue(whenTrue, diagnostics, BindValueKind.RValue);
            BoundExpression falseExpr = BindValue(whenFalse, diagnostics, BindValueKind.RValue);
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
            ConstantValue? constantValue = null;
            TypeSymbol? bestType = BestTypeInferrer.InferBestTypeForConditionalOperator(trueExpr, falseExpr, this.Conversions, out bool hadMultipleCandidates, ref useSiteInfo);
            diagnostics.Add(node, useSiteInfo);
 
            if (bestType is null)
            {
                ErrorCode noCommonTypeError = hadMultipleCandidates ? ErrorCode.ERR_AmbigQM : ErrorCode.ERR_InvalidQM;
                constantValue = FoldConditionalOperator(condition, trueExpr, falseExpr);
                return new BoundUnconvertedConditionalOperator(node, condition, trueExpr, falseExpr, constantValue, noCommonTypeError, hasErrors: constantValue?.IsBad == true);
            }
 
            bool hasErrors;
            if (bestType.IsErrorType())
            {
                trueExpr = BindToNaturalType(trueExpr, diagnostics, reportNoTargetType: false);
                falseExpr = BindToNaturalType(falseExpr, diagnostics, reportNoTargetType: false);
                hasErrors = true;
            }
            else
            {
                trueExpr = GenerateConversionForAssignment(bestType, trueExpr, diagnostics);
                falseExpr = GenerateConversionForAssignment(bestType, falseExpr, diagnostics);
                hasErrors = trueExpr.HasAnyErrors || falseExpr.HasAnyErrors;
            }
 
            if (!hasErrors)
            {
                constantValue = FoldConditionalOperator(condition, trueExpr, falseExpr);
                hasErrors = constantValue != null && constantValue.IsBad;
            }
 
            return new BoundConditionalOperator(node, isRef: false, condition, trueExpr, falseExpr, constantValue, naturalTypeOpt: bestType, wasTargetTyped: false, bestType, hasErrors);
        }
#nullable disable
 
        private BoundExpression BindRefConditionalOperator(ConditionalExpressionSyntax node, ExpressionSyntax whenTrue, ExpressionSyntax whenFalse, BindingDiagnosticBag diagnostics)
        {
            BoundExpression condition = BindBooleanExpression(node.Condition, diagnostics);
            BoundExpression trueExpr = BindValue(whenTrue, diagnostics, BindValueKind.RValue | BindValueKind.RefersToLocation);
            BoundExpression falseExpr = BindValue(whenFalse, diagnostics, BindValueKind.RValue | BindValueKind.RefersToLocation);
            bool hasErrors = trueExpr.HasErrors | falseExpr.HasErrors;
            TypeSymbol trueType = trueExpr.Type;
            TypeSymbol falseType = falseExpr.Type;
 
            TypeSymbol type;
            if (!Conversions.HasIdentityConversion(trueType, falseType))
            {
                if (!hasErrors)
                    diagnostics.Add(ErrorCode.ERR_RefConditionalDifferentTypes, falseExpr.Syntax.Location, trueType);
 
                type = CreateErrorType();
                hasErrors = true;
            }
            else
            {
                CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
                type = BestTypeInferrer.InferBestTypeForConditionalOperator(trueExpr, falseExpr, this.Conversions, hadMultipleCandidates: out _, ref useSiteInfo);
                diagnostics.Add(node, useSiteInfo);
 
                Debug.Assert(type is { });
                Debug.Assert(Conversions.HasIdentityConversion(trueType, type));
                Debug.Assert(Conversions.HasIdentityConversion(falseType, type));
            }
 
            trueExpr = BindToNaturalType(trueExpr, diagnostics, reportNoTargetType: false);
            falseExpr = BindToNaturalType(falseExpr, diagnostics, reportNoTargetType: false);
            return new BoundConditionalOperator(node, isRef: true, condition, trueExpr, falseExpr, constantValueOpt: null, type, wasTargetTyped: false, type, hasErrors);
        }
    }
 
    partial class RefSafetyAnalysis
    {
        private void ValidateRefConditionalOperator(SyntaxNode node, BoundExpression trueExpr, BoundExpression falseExpr, BindingDiagnosticBag diagnostics)
        {
            var currentScope = _localScopeDepth;
 
            // val-escape must agree on both branches.
            SafeContext whenTrueEscape = GetValEscape(trueExpr, currentScope);
            SafeContext whenFalseEscape = GetValEscape(falseExpr, currentScope);
 
            if (whenTrueEscape != whenFalseEscape)
            {
                // ask the one with narrower escape, for the wider - hopefully the errors will make the violation easier to fix.
                if (!whenFalseEscape.IsConvertibleTo(whenTrueEscape))
                    CheckValEscape(falseExpr.Syntax, falseExpr, currentScope, whenTrueEscape, checkingReceiver: false, diagnostics: diagnostics);
                else
                    CheckValEscape(trueExpr.Syntax, trueExpr, currentScope, whenFalseEscape, checkingReceiver: false, diagnostics: diagnostics);
 
                diagnostics.Add(_inUnsafeRegion ? ErrorCode.WRN_MismatchedRefEscapeInTernary : ErrorCode.ERR_MismatchedRefEscapeInTernary, node.Location);
            }
        }
    }
 
    partial class Binder
    {
        /// <summary>
        /// Constant folding for conditional (aka ternary) operators.
        /// </summary>
        private static ConstantValue FoldConditionalOperator(BoundExpression condition, BoundExpression trueExpr, BoundExpression falseExpr)
        {
            ConstantValue trueValue = trueExpr.ConstantValueOpt;
            if (trueValue == null || trueValue.IsBad)
            {
                return trueValue;
            }
 
            ConstantValue falseValue = falseExpr.ConstantValueOpt;
            if (falseValue == null || falseValue.IsBad)
            {
                return falseValue;
            }
 
            ConstantValue conditionValue = condition.ConstantValueOpt;
            if (conditionValue == null || conditionValue.IsBad)
            {
                return conditionValue;
            }
            else if (conditionValue == ConstantValue.True)
            {
                return trueValue;
            }
            else if (conditionValue == ConstantValue.False)
            {
                return falseValue;
            }
            else
            {
                return ConstantValue.Bad;
            }
        }
 
        private void CheckNativeIntegerFeatureAvailability(BinaryOperatorKind operatorKind, SyntaxNode syntax, BindingDiagnosticBag diagnostics)
        {
            if (Compilation.Assembly.RuntimeSupportsNumericIntPtr)
            {
                return;
            }
 
            switch (operatorKind & BinaryOperatorKind.TypeMask)
            {
                case BinaryOperatorKind.NInt:
                case BinaryOperatorKind.NUInt:
                    CheckFeatureAvailability(syntax, MessageID.IDS_FeatureNativeInt, diagnostics);
                    break;
            }
        }
 
        private void CheckNativeIntegerFeatureAvailability(UnaryOperatorKind operatorKind, SyntaxNode syntax, BindingDiagnosticBag diagnostics)
        {
            if (Compilation.Assembly.RuntimeSupportsNumericIntPtr)
            {
                return;
            }
 
            switch (operatorKind & UnaryOperatorKind.TypeMask)
            {
                case UnaryOperatorKind.NInt:
                case UnaryOperatorKind.NUInt:
                    CheckFeatureAvailability(syntax, MessageID.IDS_FeatureNativeInt, diagnostics);
                    break;
            }
        }
    }
}