// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// Internal cache of built-in operators.
    /// Cache is compilation-specific because it uses compilation-specific SpecialTypes.
    /// </summary>
    internal class BuiltInOperators
    {
        private readonly CSharpCompilation _compilation;

        //actual lazily-constructed caches of built-in operators.
        private ImmutableArray<UnaryOperatorSignature>[] _builtInUnaryOperators;
        private ImmutableArray<BinaryOperatorSignature>[][] _builtInOperators;
        private SingleInitNullable<BinaryOperatorSignature> _builtInUtf8Concatenation;

        internal BuiltInOperators(CSharpCompilation compilation)
        {
            _compilation = compilation;
        }

        // PERF: Use int instead of UnaryOperatorKind so the compiler can use array literal initialization.
        //       The most natural type choice, Enum arrays, are not blittable due to a CLR limitation.
        private ImmutableArray<UnaryOperatorSignature> GetSignaturesFromUnaryOperatorKinds(int[] operatorKinds)
        {
            var builder = ArrayBuilder<UnaryOperatorSignature>.GetInstance();
            foreach (var kind in operatorKinds)
            {
                builder.Add(GetSignature((UnaryOperatorKind)kind));
            }

            return builder.ToImmutableAndFree();
        }

        internal void GetSimpleBuiltInOperators(UnaryOperatorKind kind, ArrayBuilder<UnaryOperatorSignature> operators, bool skipNativeIntegerOperators)
        {
            if (_builtInUnaryOperators == null)
            {
                var allOperators = new ImmutableArray<UnaryOperatorSignature>[]
                {
                    GetSignaturesFromUnaryOperatorKinds(new []
                    {
                        (int)UnaryOperatorKind.SBytePostfixIncrement,
                        (int)UnaryOperatorKind.BytePostfixIncrement,
                        (int)UnaryOperatorKind.ShortPostfixIncrement,
                        (int)UnaryOperatorKind.UShortPostfixIncrement,
                        (int)UnaryOperatorKind.IntPostfixIncrement,
                        (int)UnaryOperatorKind.UIntPostfixIncrement,
                        (int)UnaryOperatorKind.LongPostfixIncrement,
                        (int)UnaryOperatorKind.ULongPostfixIncrement,
                        (int)UnaryOperatorKind.NIntPostfixIncrement,
                        (int)UnaryOperatorKind.NUIntPostfixIncrement,
                        (int)UnaryOperatorKind.CharPostfixIncrement,
                        (int)UnaryOperatorKind.FloatPostfixIncrement,
                        (int)UnaryOperatorKind.DoublePostfixIncrement,
                        (int)UnaryOperatorKind.DecimalPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedSBytePostfixIncrement,
                        (int)UnaryOperatorKind.LiftedBytePostfixIncrement,
                        (int)UnaryOperatorKind.LiftedShortPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedUShortPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedIntPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedUIntPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedLongPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedULongPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedNIntPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedNUIntPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedCharPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedFloatPostfixIncrement,
                        (int)UnaryOperatorKind.LiftedDoublePostfixIncrement,
                        (int)UnaryOperatorKind.LiftedDecimalPostfixIncrement,
                    }),
                    GetSignaturesFromUnaryOperatorKinds(new []
                    {
                        (int)UnaryOperatorKind.SBytePostfixDecrement,
                        (int)UnaryOperatorKind.BytePostfixDecrement,
                        (int)UnaryOperatorKind.ShortPostfixDecrement,
                        (int)UnaryOperatorKind.UShortPostfixDecrement,
                        (int)UnaryOperatorKind.IntPostfixDecrement,
                        (int)UnaryOperatorKind.UIntPostfixDecrement,
                        (int)UnaryOperatorKind.LongPostfixDecrement,
                        (int)UnaryOperatorKind.ULongPostfixDecrement,
                        (int)UnaryOperatorKind.NIntPostfixDecrement,
                        (int)UnaryOperatorKind.NUIntPostfixDecrement,
                        (int)UnaryOperatorKind.CharPostfixDecrement,
                        (int)UnaryOperatorKind.FloatPostfixDecrement,
                        (int)UnaryOperatorKind.DoublePostfixDecrement,
                        (int)UnaryOperatorKind.DecimalPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedSBytePostfixDecrement,
                        (int)UnaryOperatorKind.LiftedBytePostfixDecrement,
                        (int)UnaryOperatorKind.LiftedShortPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedUShortPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedIntPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedUIntPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedLongPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedULongPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedNIntPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedNUIntPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedCharPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedFloatPostfixDecrement,
                        (int)UnaryOperatorKind.LiftedDoublePostfixDecrement,
                        (int)UnaryOperatorKind.LiftedDecimalPostfixDecrement,
                    }),
                    GetSignaturesFromUnaryOperatorKinds(new []
                    {
                        (int)UnaryOperatorKind.SBytePrefixIncrement,
                        (int)UnaryOperatorKind.BytePrefixIncrement,
                        (int)UnaryOperatorKind.ShortPrefixIncrement,
                        (int)UnaryOperatorKind.UShortPrefixIncrement,
                        (int)UnaryOperatorKind.IntPrefixIncrement,
                        (int)UnaryOperatorKind.UIntPrefixIncrement,
                        (int)UnaryOperatorKind.LongPrefixIncrement,
                        (int)UnaryOperatorKind.ULongPrefixIncrement,
                        (int)UnaryOperatorKind.NIntPrefixIncrement,
                        (int)UnaryOperatorKind.NUIntPrefixIncrement,
                        (int)UnaryOperatorKind.CharPrefixIncrement,
                        (int)UnaryOperatorKind.FloatPrefixIncrement,
                        (int)UnaryOperatorKind.DoublePrefixIncrement,
                        (int)UnaryOperatorKind.DecimalPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedSBytePrefixIncrement,
                        (int)UnaryOperatorKind.LiftedBytePrefixIncrement,
                        (int)UnaryOperatorKind.LiftedShortPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedUShortPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedIntPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedUIntPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedLongPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedULongPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedNIntPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedNUIntPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedCharPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedFloatPrefixIncrement,
                        (int)UnaryOperatorKind.LiftedDoublePrefixIncrement,
                        (int)UnaryOperatorKind.LiftedDecimalPrefixIncrement,
                    }),
                    GetSignaturesFromUnaryOperatorKinds(new []
                    {
                        (int)UnaryOperatorKind.SBytePrefixDecrement,
                        (int)UnaryOperatorKind.BytePrefixDecrement,
                        (int)UnaryOperatorKind.ShortPrefixDecrement,
                        (int)UnaryOperatorKind.UShortPrefixDecrement,
                        (int)UnaryOperatorKind.IntPrefixDecrement,
                        (int)UnaryOperatorKind.UIntPrefixDecrement,
                        (int)UnaryOperatorKind.NIntPrefixDecrement,
                        (int)UnaryOperatorKind.NUIntPrefixDecrement,
                        (int)UnaryOperatorKind.LongPrefixDecrement,
                        (int)UnaryOperatorKind.ULongPrefixDecrement,
                        (int)UnaryOperatorKind.CharPrefixDecrement,
                        (int)UnaryOperatorKind.FloatPrefixDecrement,
                        (int)UnaryOperatorKind.DoublePrefixDecrement,
                        (int)UnaryOperatorKind.DecimalPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedSBytePrefixDecrement,
                        (int)UnaryOperatorKind.LiftedBytePrefixDecrement,
                        (int)UnaryOperatorKind.LiftedShortPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedUShortPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedIntPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedUIntPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedLongPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedULongPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedNIntPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedNUIntPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedCharPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedFloatPrefixDecrement,
                        (int)UnaryOperatorKind.LiftedDoublePrefixDecrement,
                        (int)UnaryOperatorKind.LiftedDecimalPrefixDecrement,
                    }),
                    GetSignaturesFromUnaryOperatorKinds(new []
                    {
                        (int)UnaryOperatorKind.IntUnaryPlus,
                        (int)UnaryOperatorKind.UIntUnaryPlus,
                        (int)UnaryOperatorKind.LongUnaryPlus,
                        (int)UnaryOperatorKind.ULongUnaryPlus,
                        (int)UnaryOperatorKind.NIntUnaryPlus,
                        (int)UnaryOperatorKind.NUIntUnaryPlus,
                        (int)UnaryOperatorKind.FloatUnaryPlus,
                        (int)UnaryOperatorKind.DoubleUnaryPlus,
                        (int)UnaryOperatorKind.DecimalUnaryPlus,
                        (int)UnaryOperatorKind.LiftedIntUnaryPlus,
                        (int)UnaryOperatorKind.LiftedUIntUnaryPlus,
                        (int)UnaryOperatorKind.LiftedLongUnaryPlus,
                        (int)UnaryOperatorKind.LiftedULongUnaryPlus,
                        (int)UnaryOperatorKind.LiftedNIntUnaryPlus,
                        (int)UnaryOperatorKind.LiftedNUIntUnaryPlus,
                        (int)UnaryOperatorKind.LiftedFloatUnaryPlus,
                        (int)UnaryOperatorKind.LiftedDoubleUnaryPlus,
                        (int)UnaryOperatorKind.LiftedDecimalUnaryPlus,
                    }),
                    GetSignaturesFromUnaryOperatorKinds(new []
                    {
                        (int)UnaryOperatorKind.IntUnaryMinus,
                        (int)UnaryOperatorKind.LongUnaryMinus,
                        (int)UnaryOperatorKind.NIntUnaryMinus,
                        (int)UnaryOperatorKind.FloatUnaryMinus,
                        (int)UnaryOperatorKind.DoubleUnaryMinus,
                        (int)UnaryOperatorKind.DecimalUnaryMinus,
                        (int)UnaryOperatorKind.LiftedIntUnaryMinus,
                        (int)UnaryOperatorKind.LiftedLongUnaryMinus,
                        (int)UnaryOperatorKind.LiftedNIntUnaryMinus,
                        (int)UnaryOperatorKind.LiftedFloatUnaryMinus,
                        (int)UnaryOperatorKind.LiftedDoubleUnaryMinus,
                        (int)UnaryOperatorKind.LiftedDecimalUnaryMinus,
                    }),
                    GetSignaturesFromUnaryOperatorKinds(new []
                    {
                        (int)UnaryOperatorKind.BoolLogicalNegation,
                        (int)UnaryOperatorKind.LiftedBoolLogicalNegation,
                    }),
                    GetSignaturesFromUnaryOperatorKinds(new []
                    {
                        (int)UnaryOperatorKind.IntBitwiseComplement,
                        (int)UnaryOperatorKind.UIntBitwiseComplement,
                        (int)UnaryOperatorKind.LongBitwiseComplement,
                        (int)UnaryOperatorKind.ULongBitwiseComplement,
                        (int)UnaryOperatorKind.NIntBitwiseComplement,
                        (int)UnaryOperatorKind.NUIntBitwiseComplement,
                        (int)UnaryOperatorKind.LiftedIntBitwiseComplement,
                        (int)UnaryOperatorKind.LiftedUIntBitwiseComplement,
                        (int)UnaryOperatorKind.LiftedLongBitwiseComplement,
                        (int)UnaryOperatorKind.LiftedULongBitwiseComplement,
                        (int)UnaryOperatorKind.LiftedNIntBitwiseComplement,
                        (int)UnaryOperatorKind.LiftedNUIntBitwiseComplement,
                    }),
                    // No built-in operator true or operator false
                    ImmutableArray<UnaryOperatorSignature>.Empty,
                    ImmutableArray<UnaryOperatorSignature>.Empty,
                };

                Interlocked.CompareExchange(ref _builtInUnaryOperators, allOperators, null);
            }

            foreach (var op in _builtInUnaryOperators[kind.OperatorIndex()])
            {
                if (skipNativeIntegerOperators)
                {
                    switch (op.Kind.OperandTypes())
                    {
                        case UnaryOperatorKind.NInt:
                        case UnaryOperatorKind.NUInt:
                            continue;
                    }
                }
                operators.Add(op);
            }
        }

        internal UnaryOperatorSignature GetSignature(UnaryOperatorKind kind)
        {
            TypeSymbol opType;
            switch (kind.OperandTypes())
            {
                case UnaryOperatorKind.SByte: opType = _compilation.GetSpecialType(SpecialType.System_SByte); break;
                case UnaryOperatorKind.Byte: opType = _compilation.GetSpecialType(SpecialType.System_Byte); break;
                case UnaryOperatorKind.Short: opType = _compilation.GetSpecialType(SpecialType.System_Int16); break;
                case UnaryOperatorKind.UShort: opType = _compilation.GetSpecialType(SpecialType.System_UInt16); break;
                case UnaryOperatorKind.Int: opType = _compilation.GetSpecialType(SpecialType.System_Int32); break;
                case UnaryOperatorKind.UInt: opType = _compilation.GetSpecialType(SpecialType.System_UInt32); break;
                case UnaryOperatorKind.Long: opType = _compilation.GetSpecialType(SpecialType.System_Int64); break;
                case UnaryOperatorKind.ULong: opType = _compilation.GetSpecialType(SpecialType.System_UInt64); break;
                case UnaryOperatorKind.NInt: opType = _compilation.CreateNativeIntegerTypeSymbol(signed: true); break;
                case UnaryOperatorKind.NUInt: opType = _compilation.CreateNativeIntegerTypeSymbol(signed: false); break;
                case UnaryOperatorKind.Char: opType = _compilation.GetSpecialType(SpecialType.System_Char); break;
                case UnaryOperatorKind.Float: opType = _compilation.GetSpecialType(SpecialType.System_Single); break;
                case UnaryOperatorKind.Double: opType = _compilation.GetSpecialType(SpecialType.System_Double); break;
                case UnaryOperatorKind.Decimal: opType = _compilation.GetSpecialType(SpecialType.System_Decimal); break;
                case UnaryOperatorKind.Bool: opType = _compilation.GetSpecialType(SpecialType.System_Boolean); break;
                default: throw ExceptionUtilities.UnexpectedValue(kind.OperandTypes());
            }

            if (kind.IsLifted())
            {
                opType = _compilation.GetOrCreateNullableType(opType);
            }

            return new UnaryOperatorSignature(kind, opType, opType);
        }

        // PERF: Use int instead of BinaryOperatorKind so the compiler can use array literal initialization.
        //       The most natural type choice, Enum arrays, are not blittable due to a CLR limitation.
        private ImmutableArray<BinaryOperatorSignature> GetSignaturesFromBinaryOperatorKinds(int[] operatorKinds)
        {
            var builder = ArrayBuilder<BinaryOperatorSignature>.GetInstance();
            foreach (var kind in operatorKinds)
            {
                builder.Add(GetSignature((BinaryOperatorKind)kind));
            }

            return builder.ToImmutableAndFree();
        }

        internal void GetSimpleBuiltInOperators(BinaryOperatorKind kind, ArrayBuilder<BinaryOperatorSignature> operators, bool skipNativeIntegerOperators)
        {
            if (_builtInOperators == null)
            {
                var logicalOperators = new ImmutableArray<BinaryOperatorSignature>[]
                {
                    ImmutableArray<BinaryOperatorSignature>.Empty, //multiplication
                    ImmutableArray<BinaryOperatorSignature>.Empty, //addition
                    ImmutableArray<BinaryOperatorSignature>.Empty, //subtraction
                    ImmutableArray<BinaryOperatorSignature>.Empty, //division
                    ImmutableArray<BinaryOperatorSignature>.Empty, //remainder
                    ImmutableArray<BinaryOperatorSignature>.Empty, //left shift
                    ImmutableArray<BinaryOperatorSignature>.Empty, //right shift
                    ImmutableArray<BinaryOperatorSignature>.Empty, //equal
                    ImmutableArray<BinaryOperatorSignature>.Empty, //not equal
                    ImmutableArray<BinaryOperatorSignature>.Empty, //greater than
                    ImmutableArray<BinaryOperatorSignature>.Empty, //less than
                    ImmutableArray<BinaryOperatorSignature>.Empty, //greater than or equal
                    ImmutableArray<BinaryOperatorSignature>.Empty, //less than or equal
                    ImmutableArray.Create<BinaryOperatorSignature>(GetSignature(BinaryOperatorKind.LogicalBoolAnd)), //and
                    ImmutableArray<BinaryOperatorSignature>.Empty, //xor
                    ImmutableArray.Create<BinaryOperatorSignature>(GetSignature(BinaryOperatorKind.LogicalBoolOr)), //or
                    ImmutableArray<BinaryOperatorSignature>.Empty, //unsigned right shift
                };

                var nonLogicalOperators = new ImmutableArray<BinaryOperatorSignature>[]
                {
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntMultiplication,
                        (int)BinaryOperatorKind.UIntMultiplication,
                        (int)BinaryOperatorKind.LongMultiplication,
                        (int)BinaryOperatorKind.ULongMultiplication,
                        (int)BinaryOperatorKind.NIntMultiplication,
                        (int)BinaryOperatorKind.NUIntMultiplication,
                        (int)BinaryOperatorKind.FloatMultiplication,
                        (int)BinaryOperatorKind.DoubleMultiplication,
                        (int)BinaryOperatorKind.DecimalMultiplication,
                        (int)BinaryOperatorKind.LiftedIntMultiplication,
                        (int)BinaryOperatorKind.LiftedUIntMultiplication,
                        (int)BinaryOperatorKind.LiftedLongMultiplication,
                        (int)BinaryOperatorKind.LiftedULongMultiplication,
                        (int)BinaryOperatorKind.LiftedNIntMultiplication,
                        (int)BinaryOperatorKind.LiftedNUIntMultiplication,
                        (int)BinaryOperatorKind.LiftedFloatMultiplication,
                        (int)BinaryOperatorKind.LiftedDoubleMultiplication,
                        (int)BinaryOperatorKind.LiftedDecimalMultiplication,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntAddition,
                        (int)BinaryOperatorKind.UIntAddition,
                        (int)BinaryOperatorKind.LongAddition,
                        (int)BinaryOperatorKind.ULongAddition,
                        (int)BinaryOperatorKind.NIntAddition,
                        (int)BinaryOperatorKind.NUIntAddition,
                        (int)BinaryOperatorKind.FloatAddition,
                        (int)BinaryOperatorKind.DoubleAddition,
                        (int)BinaryOperatorKind.DecimalAddition,
                        (int)BinaryOperatorKind.LiftedIntAddition,
                        (int)BinaryOperatorKind.LiftedUIntAddition,
                        (int)BinaryOperatorKind.LiftedLongAddition,
                        (int)BinaryOperatorKind.LiftedULongAddition,
                        (int)BinaryOperatorKind.LiftedNIntAddition,
                        (int)BinaryOperatorKind.LiftedNUIntAddition,
                        (int)BinaryOperatorKind.LiftedFloatAddition,
                        (int)BinaryOperatorKind.LiftedDoubleAddition,
                        (int)BinaryOperatorKind.LiftedDecimalAddition,
                        (int)BinaryOperatorKind.StringConcatenation,
                        (int)BinaryOperatorKind.StringAndObjectConcatenation,
                        (int)BinaryOperatorKind.ObjectAndStringConcatenation,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntSubtraction,
                        (int)BinaryOperatorKind.UIntSubtraction,
                        (int)BinaryOperatorKind.LongSubtraction,
                        (int)BinaryOperatorKind.ULongSubtraction,
                        (int)BinaryOperatorKind.NIntSubtraction,
                        (int)BinaryOperatorKind.NUIntSubtraction,
                        (int)BinaryOperatorKind.FloatSubtraction,
                        (int)BinaryOperatorKind.DoubleSubtraction,
                        (int)BinaryOperatorKind.DecimalSubtraction,
                        (int)BinaryOperatorKind.LiftedIntSubtraction,
                        (int)BinaryOperatorKind.LiftedUIntSubtraction,
                        (int)BinaryOperatorKind.LiftedLongSubtraction,
                        (int)BinaryOperatorKind.LiftedULongSubtraction,
                        (int)BinaryOperatorKind.LiftedNIntSubtraction,
                        (int)BinaryOperatorKind.LiftedNUIntSubtraction,
                        (int)BinaryOperatorKind.LiftedFloatSubtraction,
                        (int)BinaryOperatorKind.LiftedDoubleSubtraction,
                        (int)BinaryOperatorKind.LiftedDecimalSubtraction,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntDivision,
                        (int)BinaryOperatorKind.UIntDivision,
                        (int)BinaryOperatorKind.LongDivision,
                        (int)BinaryOperatorKind.ULongDivision,
                        (int)BinaryOperatorKind.NIntDivision,
                        (int)BinaryOperatorKind.NUIntDivision,
                        (int)BinaryOperatorKind.FloatDivision,
                        (int)BinaryOperatorKind.DoubleDivision,
                        (int)BinaryOperatorKind.DecimalDivision,
                        (int)BinaryOperatorKind.LiftedIntDivision,
                        (int)BinaryOperatorKind.LiftedUIntDivision,
                        (int)BinaryOperatorKind.LiftedLongDivision,
                        (int)BinaryOperatorKind.LiftedULongDivision,
                        (int)BinaryOperatorKind.LiftedNIntDivision,
                        (int)BinaryOperatorKind.LiftedNUIntDivision,
                        (int)BinaryOperatorKind.LiftedFloatDivision,
                        (int)BinaryOperatorKind.LiftedDoubleDivision,
                        (int)BinaryOperatorKind.LiftedDecimalDivision,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntRemainder,
                        (int)BinaryOperatorKind.UIntRemainder,
                        (int)BinaryOperatorKind.LongRemainder,
                        (int)BinaryOperatorKind.ULongRemainder,
                        (int)BinaryOperatorKind.NIntRemainder,
                        (int)BinaryOperatorKind.NUIntRemainder,
                        (int)BinaryOperatorKind.FloatRemainder,
                        (int)BinaryOperatorKind.DoubleRemainder,
                        (int)BinaryOperatorKind.DecimalRemainder,
                        (int)BinaryOperatorKind.LiftedIntRemainder,
                        (int)BinaryOperatorKind.LiftedUIntRemainder,
                        (int)BinaryOperatorKind.LiftedLongRemainder,
                        (int)BinaryOperatorKind.LiftedULongRemainder,
                        (int)BinaryOperatorKind.LiftedNIntRemainder,
                        (int)BinaryOperatorKind.LiftedNUIntRemainder,
                        (int)BinaryOperatorKind.LiftedFloatRemainder,
                        (int)BinaryOperatorKind.LiftedDoubleRemainder,
                        (int)BinaryOperatorKind.LiftedDecimalRemainder,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntLeftShift,
                        (int)BinaryOperatorKind.UIntLeftShift,
                        (int)BinaryOperatorKind.LongLeftShift,
                        (int)BinaryOperatorKind.ULongLeftShift,
                        (int)BinaryOperatorKind.NIntLeftShift,
                        (int)BinaryOperatorKind.NUIntLeftShift,
                        (int)BinaryOperatorKind.LiftedIntLeftShift,
                        (int)BinaryOperatorKind.LiftedUIntLeftShift,
                        (int)BinaryOperatorKind.LiftedLongLeftShift,
                        (int)BinaryOperatorKind.LiftedULongLeftShift,
                        (int)BinaryOperatorKind.LiftedNIntLeftShift,
                        (int)BinaryOperatorKind.LiftedNUIntLeftShift,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntRightShift,
                        (int)BinaryOperatorKind.UIntRightShift,
                        (int)BinaryOperatorKind.LongRightShift,
                        (int)BinaryOperatorKind.ULongRightShift,
                        (int)BinaryOperatorKind.NIntRightShift,
                        (int)BinaryOperatorKind.NUIntRightShift,
                        (int)BinaryOperatorKind.LiftedIntRightShift,
                        (int)BinaryOperatorKind.LiftedUIntRightShift,
                        (int)BinaryOperatorKind.LiftedLongRightShift,
                        (int)BinaryOperatorKind.LiftedULongRightShift,
                        (int)BinaryOperatorKind.LiftedNIntRightShift,
                        (int)BinaryOperatorKind.LiftedNUIntRightShift,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntEqual,
                        (int)BinaryOperatorKind.UIntEqual,
                        (int)BinaryOperatorKind.LongEqual,
                        (int)BinaryOperatorKind.ULongEqual,
                        (int)BinaryOperatorKind.NIntEqual,
                        (int)BinaryOperatorKind.NUIntEqual,
                        (int)BinaryOperatorKind.FloatEqual,
                        (int)BinaryOperatorKind.DoubleEqual,
                        (int)BinaryOperatorKind.DecimalEqual,
                        (int)BinaryOperatorKind.BoolEqual,
                        (int)BinaryOperatorKind.LiftedIntEqual,
                        (int)BinaryOperatorKind.LiftedUIntEqual,
                        (int)BinaryOperatorKind.LiftedLongEqual,
                        (int)BinaryOperatorKind.LiftedULongEqual,
                        (int)BinaryOperatorKind.LiftedNIntEqual,
                        (int)BinaryOperatorKind.LiftedNUIntEqual,
                        (int)BinaryOperatorKind.LiftedFloatEqual,
                        (int)BinaryOperatorKind.LiftedDoubleEqual,
                        (int)BinaryOperatorKind.LiftedDecimalEqual,
                        (int)BinaryOperatorKind.LiftedBoolEqual,
                        (int)BinaryOperatorKind.ObjectEqual,
                        (int)BinaryOperatorKind.StringEqual,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntNotEqual,
                        (int)BinaryOperatorKind.UIntNotEqual,
                        (int)BinaryOperatorKind.LongNotEqual,
                        (int)BinaryOperatorKind.ULongNotEqual,
                        (int)BinaryOperatorKind.NIntNotEqual,
                        (int)BinaryOperatorKind.NUIntNotEqual,
                        (int)BinaryOperatorKind.FloatNotEqual,
                        (int)BinaryOperatorKind.DoubleNotEqual,
                        (int)BinaryOperatorKind.DecimalNotEqual,
                        (int)BinaryOperatorKind.BoolNotEqual,
                        (int)BinaryOperatorKind.LiftedIntNotEqual,
                        (int)BinaryOperatorKind.LiftedUIntNotEqual,
                        (int)BinaryOperatorKind.LiftedLongNotEqual,
                        (int)BinaryOperatorKind.LiftedULongNotEqual,
                        (int)BinaryOperatorKind.LiftedNIntNotEqual,
                        (int)BinaryOperatorKind.LiftedNUIntNotEqual,
                        (int)BinaryOperatorKind.LiftedFloatNotEqual,
                        (int)BinaryOperatorKind.LiftedDoubleNotEqual,
                        (int)BinaryOperatorKind.LiftedDecimalNotEqual,
                        (int)BinaryOperatorKind.LiftedBoolNotEqual,
                        (int)BinaryOperatorKind.ObjectNotEqual,
                        (int)BinaryOperatorKind.StringNotEqual,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntGreaterThan,
                        (int)BinaryOperatorKind.UIntGreaterThan,
                        (int)BinaryOperatorKind.LongGreaterThan,
                        (int)BinaryOperatorKind.ULongGreaterThan,
                        (int)BinaryOperatorKind.NIntGreaterThan,
                        (int)BinaryOperatorKind.NUIntGreaterThan,
                        (int)BinaryOperatorKind.FloatGreaterThan,
                        (int)BinaryOperatorKind.DoubleGreaterThan,
                        (int)BinaryOperatorKind.DecimalGreaterThan,
                        (int)BinaryOperatorKind.LiftedIntGreaterThan,
                        (int)BinaryOperatorKind.LiftedUIntGreaterThan,
                        (int)BinaryOperatorKind.LiftedLongGreaterThan,
                        (int)BinaryOperatorKind.LiftedULongGreaterThan,
                        (int)BinaryOperatorKind.LiftedNIntGreaterThan,
                        (int)BinaryOperatorKind.LiftedNUIntGreaterThan,
                        (int)BinaryOperatorKind.LiftedFloatGreaterThan,
                        (int)BinaryOperatorKind.LiftedDoubleGreaterThan,
                        (int)BinaryOperatorKind.LiftedDecimalGreaterThan,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntLessThan,
                        (int)BinaryOperatorKind.UIntLessThan,
                        (int)BinaryOperatorKind.LongLessThan,
                        (int)BinaryOperatorKind.ULongLessThan,
                        (int)BinaryOperatorKind.NIntLessThan,
                        (int)BinaryOperatorKind.NUIntLessThan,
                        (int)BinaryOperatorKind.FloatLessThan,
                        (int)BinaryOperatorKind.DoubleLessThan,
                        (int)BinaryOperatorKind.DecimalLessThan,
                        (int)BinaryOperatorKind.LiftedIntLessThan,
                        (int)BinaryOperatorKind.LiftedUIntLessThan,
                        (int)BinaryOperatorKind.LiftedLongLessThan,
                        (int)BinaryOperatorKind.LiftedULongLessThan,
                        (int)BinaryOperatorKind.LiftedNIntLessThan,
                        (int)BinaryOperatorKind.LiftedNUIntLessThan,
                        (int)BinaryOperatorKind.LiftedFloatLessThan,
                        (int)BinaryOperatorKind.LiftedDoubleLessThan,
                        (int)BinaryOperatorKind.LiftedDecimalLessThan,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntGreaterThanOrEqual,
                        (int)BinaryOperatorKind.UIntGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LongGreaterThanOrEqual,
                        (int)BinaryOperatorKind.ULongGreaterThanOrEqual,
                        (int)BinaryOperatorKind.NIntGreaterThanOrEqual,
                        (int)BinaryOperatorKind.NUIntGreaterThanOrEqual,
                        (int)BinaryOperatorKind.FloatGreaterThanOrEqual,
                        (int)BinaryOperatorKind.DoubleGreaterThanOrEqual,
                        (int)BinaryOperatorKind.DecimalGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedIntGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedUIntGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedLongGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedULongGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedNIntGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedNUIntGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedFloatGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedDoubleGreaterThanOrEqual,
                        (int)BinaryOperatorKind.LiftedDecimalGreaterThanOrEqual,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntLessThanOrEqual,
                        (int)BinaryOperatorKind.UIntLessThanOrEqual,
                        (int)BinaryOperatorKind.LongLessThanOrEqual,
                        (int)BinaryOperatorKind.ULongLessThanOrEqual,
                        (int)BinaryOperatorKind.NIntLessThanOrEqual,
                        (int)BinaryOperatorKind.NUIntLessThanOrEqual,
                        (int)BinaryOperatorKind.FloatLessThanOrEqual,
                        (int)BinaryOperatorKind.DoubleLessThanOrEqual,
                        (int)BinaryOperatorKind.DecimalLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedIntLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedUIntLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedLongLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedULongLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedNIntLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedNUIntLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedFloatLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedDoubleLessThanOrEqual,
                        (int)BinaryOperatorKind.LiftedDecimalLessThanOrEqual,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntAnd,
                        (int)BinaryOperatorKind.UIntAnd,
                        (int)BinaryOperatorKind.LongAnd,
                        (int)BinaryOperatorKind.ULongAnd,
                        (int)BinaryOperatorKind.NIntAnd,
                        (int)BinaryOperatorKind.NUIntAnd,
                        (int)BinaryOperatorKind.BoolAnd,
                        (int)BinaryOperatorKind.LiftedIntAnd,
                        (int)BinaryOperatorKind.LiftedUIntAnd,
                        (int)BinaryOperatorKind.LiftedLongAnd,
                        (int)BinaryOperatorKind.LiftedULongAnd,
                        (int)BinaryOperatorKind.LiftedNIntAnd,
                        (int)BinaryOperatorKind.LiftedNUIntAnd,
                        (int)BinaryOperatorKind.LiftedBoolAnd,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntXor,
                        (int)BinaryOperatorKind.UIntXor,
                        (int)BinaryOperatorKind.LongXor,
                        (int)BinaryOperatorKind.ULongXor,
                        (int)BinaryOperatorKind.NIntXor,
                        (int)BinaryOperatorKind.NUIntXor,
                        (int)BinaryOperatorKind.BoolXor,
                        (int)BinaryOperatorKind.LiftedIntXor,
                        (int)BinaryOperatorKind.LiftedUIntXor,
                        (int)BinaryOperatorKind.LiftedLongXor,
                        (int)BinaryOperatorKind.LiftedULongXor,
                        (int)BinaryOperatorKind.LiftedNIntXor,
                        (int)BinaryOperatorKind.LiftedNUIntXor,
                        (int)BinaryOperatorKind.LiftedBoolXor,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntOr,
                        (int)BinaryOperatorKind.UIntOr,
                        (int)BinaryOperatorKind.LongOr,
                        (int)BinaryOperatorKind.ULongOr,
                        (int)BinaryOperatorKind.NIntOr,
                        (int)BinaryOperatorKind.NUIntOr,
                        (int)BinaryOperatorKind.BoolOr,
                        (int)BinaryOperatorKind.LiftedIntOr,
                        (int)BinaryOperatorKind.LiftedUIntOr,
                        (int)BinaryOperatorKind.LiftedLongOr,
                        (int)BinaryOperatorKind.LiftedULongOr,
                        (int)BinaryOperatorKind.LiftedNIntOr,
                        (int)BinaryOperatorKind.LiftedNUIntOr,
                        (int)BinaryOperatorKind.LiftedBoolOr,
                    }),
                    GetSignaturesFromBinaryOperatorKinds(new []
                    {
                        (int)BinaryOperatorKind.IntUnsignedRightShift,
                        (int)BinaryOperatorKind.UIntUnsignedRightShift,
                        (int)BinaryOperatorKind.LongUnsignedRightShift,
                        (int)BinaryOperatorKind.ULongUnsignedRightShift,
                        (int)BinaryOperatorKind.NIntUnsignedRightShift,
                        (int)BinaryOperatorKind.NUIntUnsignedRightShift,
                        (int)BinaryOperatorKind.LiftedIntUnsignedRightShift,
                        (int)BinaryOperatorKind.LiftedUIntUnsignedRightShift,
                        (int)BinaryOperatorKind.LiftedLongUnsignedRightShift,
                        (int)BinaryOperatorKind.LiftedULongUnsignedRightShift,
                        (int)BinaryOperatorKind.LiftedNIntUnsignedRightShift,
                        (int)BinaryOperatorKind.LiftedNUIntUnsignedRightShift,
                    }),
                };

                var allOperators = new[] { nonLogicalOperators, logicalOperators };

                Interlocked.CompareExchange(ref _builtInOperators, allOperators, null);
            }

            foreach (var op in _builtInOperators[kind.IsLogical() ? 1 : 0][kind.OperatorIndex()])
            {
                if (skipNativeIntegerOperators)
                {
                    switch (op.Kind.OperandTypes())
                    {
                        case BinaryOperatorKind.NInt:
                        case BinaryOperatorKind.NUInt:
                            continue;
                    }
                }
                operators.Add(op);
            }
        }

        internal void GetUtf8ConcatenationBuiltInOperator(TypeSymbol readonlySpanOfByte, ArrayBuilder<BinaryOperatorSignature> operators)
        {
            Debug.Assert(_compilation.IsReadOnlySpanType(readonlySpanOfByte));
            Debug.Assert(((NamedTypeSymbol)readonlySpanOfByte).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type.SpecialType is SpecialType.System_Byte);

            var utfConcatenation = _builtInUtf8Concatenation.Initialize(
                static readonlySpanOfByte => new BinaryOperatorSignature(BinaryOperatorKind.Utf8Addition, readonlySpanOfByte, readonlySpanOfByte, readonlySpanOfByte),
                readonlySpanOfByte);

            operators.Add(utfConcatenation);
        }

        internal BinaryOperatorSignature GetSignature(BinaryOperatorKind kind)
        {
            var left = LeftType(kind);
            switch (kind.Operator())
            {
                case BinaryOperatorKind.Multiplication:
                case BinaryOperatorKind.Division:
                case BinaryOperatorKind.Subtraction:
                case BinaryOperatorKind.Remainder:
                case BinaryOperatorKind.And:
                case BinaryOperatorKind.Or:
                case BinaryOperatorKind.Xor:
                    return new BinaryOperatorSignature(kind, left, left, left);
                case BinaryOperatorKind.Addition:
                    return new BinaryOperatorSignature(kind, left, RightType(kind), ReturnType(kind));
                case BinaryOperatorKind.LeftShift:
                case BinaryOperatorKind.RightShift:
                case BinaryOperatorKind.UnsignedRightShift:
                    TypeSymbol rightType = _compilation.GetSpecialType(SpecialType.System_Int32);
                    if (kind.IsLifted())
                    {
                        rightType = _compilation.GetOrCreateNullableType(rightType);
                    }
                    return new BinaryOperatorSignature(kind, left, rightType, left);

                case BinaryOperatorKind.Equal:
                case BinaryOperatorKind.NotEqual:
                case BinaryOperatorKind.GreaterThan:
                case BinaryOperatorKind.LessThan:
                case BinaryOperatorKind.GreaterThanOrEqual:
                case BinaryOperatorKind.LessThanOrEqual:
                    return new BinaryOperatorSignature(kind, left, left, _compilation.GetSpecialType(SpecialType.System_Boolean));
            }
            return new BinaryOperatorSignature(kind, left, RightType(kind), ReturnType(kind));
        }

        private TypeSymbol LeftType(BinaryOperatorKind kind)
        {
            if (kind.IsLifted())
            {
                return LiftedType(kind);
            }
            else
            {
                switch (kind.OperandTypes())
                {
                    case BinaryOperatorKind.Int: return _compilation.GetSpecialType(SpecialType.System_Int32);
                    case BinaryOperatorKind.UInt: return _compilation.GetSpecialType(SpecialType.System_UInt32);
                    case BinaryOperatorKind.Long: return _compilation.GetSpecialType(SpecialType.System_Int64);
                    case BinaryOperatorKind.ULong: return _compilation.GetSpecialType(SpecialType.System_UInt64);
                    case BinaryOperatorKind.NInt: return _compilation.CreateNativeIntegerTypeSymbol(signed: true);
                    case BinaryOperatorKind.NUInt: return _compilation.CreateNativeIntegerTypeSymbol(signed: false);
                    case BinaryOperatorKind.Float: return _compilation.GetSpecialType(SpecialType.System_Single);
                    case BinaryOperatorKind.Double: return _compilation.GetSpecialType(SpecialType.System_Double);
                    case BinaryOperatorKind.Decimal: return _compilation.GetSpecialType(SpecialType.System_Decimal);
                    case BinaryOperatorKind.Bool: return _compilation.GetSpecialType(SpecialType.System_Boolean);
                    case BinaryOperatorKind.ObjectAndString:
                    case BinaryOperatorKind.Object:
                        return _compilation.GetSpecialType(SpecialType.System_Object);
                    case BinaryOperatorKind.String:
                    case BinaryOperatorKind.StringAndObject:
                        return _compilation.GetSpecialType(SpecialType.System_String);
                }
            }
            Debug.Assert(false, "Bad operator kind in left type");
            return null;
        }

        private TypeSymbol RightType(BinaryOperatorKind kind)
        {
            if (kind.IsLifted())
            {
                return LiftedType(kind);
            }
            else
            {
                switch (kind.OperandTypes())
                {
                    case BinaryOperatorKind.Int: return _compilation.GetSpecialType(SpecialType.System_Int32);
                    case BinaryOperatorKind.UInt: return _compilation.GetSpecialType(SpecialType.System_UInt32);
                    case BinaryOperatorKind.Long: return _compilation.GetSpecialType(SpecialType.System_Int64);
                    case BinaryOperatorKind.ULong: return _compilation.GetSpecialType(SpecialType.System_UInt64);
                    case BinaryOperatorKind.NInt: return _compilation.CreateNativeIntegerTypeSymbol(signed: true);
                    case BinaryOperatorKind.NUInt: return _compilation.CreateNativeIntegerTypeSymbol(signed: false);
                    case BinaryOperatorKind.Float: return _compilation.GetSpecialType(SpecialType.System_Single);
                    case BinaryOperatorKind.Double: return _compilation.GetSpecialType(SpecialType.System_Double);
                    case BinaryOperatorKind.Decimal: return _compilation.GetSpecialType(SpecialType.System_Decimal);
                    case BinaryOperatorKind.Bool: return _compilation.GetSpecialType(SpecialType.System_Boolean);
                    case BinaryOperatorKind.ObjectAndString:
                    case BinaryOperatorKind.String:
                        return _compilation.GetSpecialType(SpecialType.System_String);
                    case BinaryOperatorKind.StringAndObject:
                    case BinaryOperatorKind.Object:
                        return _compilation.GetSpecialType(SpecialType.System_Object);
                }
            }
            Debug.Assert(false, "Bad operator kind in right type");
            return null;
        }

        private TypeSymbol ReturnType(BinaryOperatorKind kind)
        {
            if (kind.IsLifted())
            {
                return LiftedType(kind);
            }
            else
            {
                switch (kind.OperandTypes())
                {
                    case BinaryOperatorKind.Int: return _compilation.GetSpecialType(SpecialType.System_Int32);
                    case BinaryOperatorKind.UInt: return _compilation.GetSpecialType(SpecialType.System_UInt32);
                    case BinaryOperatorKind.Long: return _compilation.GetSpecialType(SpecialType.System_Int64);
                    case BinaryOperatorKind.ULong: return _compilation.GetSpecialType(SpecialType.System_UInt64);
                    case BinaryOperatorKind.NInt: return _compilation.CreateNativeIntegerTypeSymbol(signed: true);
                    case BinaryOperatorKind.NUInt: return _compilation.CreateNativeIntegerTypeSymbol(signed: false);
                    case BinaryOperatorKind.Float: return _compilation.GetSpecialType(SpecialType.System_Single);
                    case BinaryOperatorKind.Double: return _compilation.GetSpecialType(SpecialType.System_Double);
                    case BinaryOperatorKind.Decimal: return _compilation.GetSpecialType(SpecialType.System_Decimal);
                    case BinaryOperatorKind.Bool: return _compilation.GetSpecialType(SpecialType.System_Boolean);
                    case BinaryOperatorKind.Object: return _compilation.GetSpecialType(SpecialType.System_Object);
                    case BinaryOperatorKind.ObjectAndString:
                    case BinaryOperatorKind.StringAndObject:
                    case BinaryOperatorKind.String:
                        return _compilation.GetSpecialType(SpecialType.System_String);
                }
            }
            Debug.Assert(false, "Bad operator kind in return type");
            return null;
        }

        private TypeSymbol LiftedType(BinaryOperatorKind kind)
        {
            Debug.Assert(kind.IsLifted());

            var typeArgument = kind.OperandTypes() switch
            {
                BinaryOperatorKind.Int => _compilation.GetSpecialType(SpecialType.System_Int32),
                BinaryOperatorKind.UInt => _compilation.GetSpecialType(SpecialType.System_UInt32),
                BinaryOperatorKind.Long => _compilation.GetSpecialType(SpecialType.System_Int64),
                BinaryOperatorKind.ULong => _compilation.GetSpecialType(SpecialType.System_UInt64),
                BinaryOperatorKind.NInt => _compilation.CreateNativeIntegerTypeSymbol(signed: true),
                BinaryOperatorKind.NUInt => _compilation.CreateNativeIntegerTypeSymbol(signed: false),
                BinaryOperatorKind.Float => _compilation.GetSpecialType(SpecialType.System_Single),
                BinaryOperatorKind.Double => _compilation.GetSpecialType(SpecialType.System_Double),
                BinaryOperatorKind.Decimal => _compilation.GetSpecialType(SpecialType.System_Decimal),
                BinaryOperatorKind.Bool => _compilation.GetSpecialType(SpecialType.System_Boolean),
                var v => throw ExceptionUtilities.UnexpectedValue(v),
            };

            return _compilation.GetOrCreateNullableType(typeArgument);
        }

        internal static bool IsValidObjectEquality(Conversions Conversions, TypeSymbol leftType, bool leftIsNull, bool leftIsDefault, TypeSymbol rightType, bool rightIsNull, bool rightIsDefault, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            // SPEC: The predefined reference type equality operators require one of the following:

            // SPEC: (1) Both operands are a value of a type known to be a reference-type or the literal null.
            // SPEC:     Furthermore, an explicit reference conversion exists from the type of either
            // SPEC:     operand to the type of the other operand. Or:
            // SPEC: (2) One operand is a value of type T where T is a type-parameter and the other operand is 
            // SPEC:     the literal null. Furthermore T does not have the value type constraint.
            // SPEC: (3) One operand is the literal default and the other operand is a reference-type.

            // SPEC ERROR: Notice that the spec calls out that an explicit reference conversion must exist;
            // SPEC ERROR: in fact it should say that an explicit reference conversion, implicit reference
            // SPEC ERROR: conversion or identity conversion must exist. The conversion from object to object
            // SPEC ERROR: is not classified as a reference conversion at all; it is an identity conversion.

            // Dev10 does not follow the spec exactly for type parameters. Specifically, in Dev10,
            // if a type parameter argument is known to be a value type, or if a type parameter
            // argument is not known to be either a value type or reference type and the other
            // argument is not null, reference type equality cannot be applied. Otherwise, the
            // effective base class of the type parameter is used to determine the conversion
            // to the other argument type. (See ExpressionBinder::GetRefEqualSigs.)

            if (((object)leftType != null) && leftType.IsTypeParameter())
            {
                if (leftType.IsValueType || (!leftType.IsReferenceType && !rightIsNull))
                {
                    return false;
                }

                leftType = ((TypeParameterSymbol)leftType).EffectiveBaseClass(ref useSiteInfo);
                Debug.Assert((object)leftType != null);
            }

            if (((object)rightType != null) && rightType.IsTypeParameter())
            {
                if (rightType.IsValueType || (!rightType.IsReferenceType && !leftIsNull))
                {
                    return false;
                }

                rightType = ((TypeParameterSymbol)rightType).EffectiveBaseClass(ref useSiteInfo);
                Debug.Assert((object)rightType != null);
            }

            var leftIsReferenceType = ((object)leftType != null) && leftType.IsReferenceType;
            if (!leftIsReferenceType && !leftIsNull && !leftIsDefault)
            {
                return false;
            }

            var rightIsReferenceType = ((object)rightType != null) && rightType.IsReferenceType;
            if (!rightIsReferenceType && !rightIsNull && !rightIsDefault)
            {
                return false;
            }

            if (leftIsDefault && rightIsDefault)
            {
                return false;
            }

            if (leftIsDefault && rightIsNull)
            {
                return false;
            }

            if (leftIsNull && rightIsDefault)
            {
                return false;
            }

            // If at least one side is null or default then clearly a conversion exists.
            if (leftIsNull || rightIsNull || leftIsDefault || rightIsDefault)
            {
                return true;
            }

            var leftConversion = Conversions.ClassifyConversionFromType(leftType, rightType, isChecked: false, ref useSiteInfo);
            if (leftConversion.IsIdentity || leftConversion.IsReference)
            {
                return true;
            }

            var rightConversion = Conversions.ClassifyConversionFromType(rightType, leftType, isChecked: false, ref useSiteInfo);
            if (rightConversion.IsIdentity || rightConversion.IsReference)
            {
                return true;
            }

            return false;
        }
    }
}
