File: ConstantValue.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    internal enum ConstantValueTypeDiscriminator : byte
    {
        Nothing,
        Null = Nothing,
        Bad,
        SByte,
        Byte,
        Int16,
        UInt16,
        Int32,
        UInt32,
        Int64,
        UInt64,
        NInt,
        NUInt,
        Char,
        Boolean,
        Single,
        Double,
        String,
        Decimal,
        DateTime,
        // Note: new values may need handling in CryptographicHashProvider.ComputeSourceHash
    }
 
    internal abstract partial class ConstantValue : IEquatable<ConstantValue?>, IFormattable
    {
        public abstract ConstantValueTypeDiscriminator Discriminator { get; }
 
        internal abstract SpecialType SpecialType { get; }
 
        public virtual string? StringValue { get { throw new InvalidOperationException(); } }
        internal virtual Rope? RopeValue { get { throw new InvalidOperationException(); } }
 
        public virtual bool BooleanValue { get { throw new InvalidOperationException(); } }
 
        public virtual sbyte SByteValue { get { throw new InvalidOperationException(); } }
        public virtual byte ByteValue { get { throw new InvalidOperationException(); } }
 
        // If we can get SByteValue, we can automatically get Int16Value, Int32Value, Int64Value. 
        // This is needed when constant values are reinterpreted during constant folding - 
        // for example a Byte value may be read via UIntValue accessor when folding Byte + Uint
        //
        // I have decided that default implementation of Int16Value in terms of SByteValue is appropriate here.
        // Same pattern is used for providing default implementation of Int32Value in terms of Int16Value and so on.
        //
        // An alternative solution would be to override Int16Value, Int32Value, Int64Value whenever I override SByteValue
        // and so on for Int16Value, Int32Value. That could work mildly faster but would result in a lot more code.
 
        public virtual short Int16Value { get { return SByteValue; } }
        public virtual ushort UInt16Value { get { return ByteValue; } }
 
        public virtual int Int32Value { get { return Int16Value; } }
        public virtual uint UInt32Value { get { return UInt16Value; } }
 
        public virtual long Int64Value { get { return Int32Value; } }
        public virtual ulong UInt64Value { get { return UInt32Value; } }
 
        public virtual char CharValue { get { throw new InvalidOperationException(); } }
        public virtual decimal DecimalValue { get { throw new InvalidOperationException(); } }
        public virtual DateTime DateTimeValue { get { throw new InvalidOperationException(); } }
 
        public virtual double DoubleValue { get { throw new InvalidOperationException(); } }
        public virtual float SingleValue { get { throw new InvalidOperationException(); } }
 
        // returns true if value is in its default (zero-inited) form.
        public virtual bool IsDefaultValue { get { return false; } }
        public virtual bool IsOne { get { return false; } }
 
        // NOTE: We do not have IsNumericZero. 
        //       The reason is that integral zeroes are same as default values
        //       and singles, floats and decimals have multiple zero values. 
        //       It appears that in all cases so far we considered isDefaultValue, and not about value being 
        //       arithmetic zero (especially when definition is ambiguous).
 
        public const ConstantValue NotAvailable = null;
 
        public static ConstantValue Bad { get { return ConstantValueBad.Instance; } }
        public static ConstantValue Null { get { return ConstantValueNull.Instance; } }
        public static ConstantValue Nothing { get { return Null; } }
        // Null, Nothing and Unset are all ConstantValueNull. Null and Nothing are equivalent and represent the null and
        // nothing constants in C# and VB.  Unset indicates an uninitialized ConstantValue.
        public static ConstantValue Unset { get { return ConstantValueNull.Uninitialized; } }
 
        public static ConstantValue True { get { return ConstantValueOne.Boolean; } }
        public static ConstantValue False { get { return ConstantValueDefault.Boolean; } }
 
        public static ConstantValue Create(string? value)
        {
            if (value == null)
            {
                return Null;
            }
 
            return new ConstantValueString(value);
        }
 
        internal static ConstantValue CreateFromRope(Rope value)
        {
            RoslynDebug.Assert(value != null);
            return new ConstantValueString(value);
        }
 
        public static ConstantValue Create(char value)
        {
            if (value == default(char))
            {
                return ConstantValueDefault.Char;
            }
            else if (value == (char)1)
            {
                return ConstantValueOne.Char;
            }
 
            return new ConstantValueI16(value);
        }
 
        public static ConstantValue Create(sbyte value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.SByte;
            }
            else if (value == 1)
            {
                return ConstantValueOne.SByte;
            }
 
            return new ConstantValueI8(value);
        }
 
        public static ConstantValue Create(byte value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.Byte;
            }
            else if (value == 1)
            {
                return ConstantValueOne.Byte;
            }
 
            return new ConstantValueI8(value);
        }
 
        public static ConstantValue Create(Int16 value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.Int16;
            }
            else if (value == 1)
            {
                return ConstantValueOne.Int16;
            }
 
            return new ConstantValueI16(value);
        }
 
        public static ConstantValue Create(UInt16 value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.UInt16;
            }
            else if (value == 1)
            {
                return ConstantValueOne.UInt16;
            }
 
            return new ConstantValueI16(value);
        }
 
        public static ConstantValue Create(Int32 value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.Int32;
            }
            else if (value == 1)
            {
                return ConstantValueOne.Int32;
            }
 
            return new ConstantValueI32(value);
        }
 
        public static ConstantValue Create(UInt32 value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.UInt32;
            }
            else if (value == 1)
            {
                return ConstantValueOne.UInt32;
            }
 
            return new ConstantValueI32(value);
        }
 
        public static ConstantValue Create(Int64 value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.Int64;
            }
            else if (value == 1)
            {
                return ConstantValueOne.Int64;
            }
 
            return new ConstantValueI64(value);
        }
 
        public static ConstantValue Create(UInt64 value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.UInt64;
            }
            else if (value == 1)
            {
                return ConstantValueOne.UInt64;
            }
 
            return new ConstantValueI64(value);
        }
 
        public static ConstantValue CreateNativeInt(Int32 value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.NInt;
            }
            else if (value == 1)
            {
                return ConstantValueOne.NInt;
            }
 
            return new ConstantValueNativeInt(value);
        }
 
        public static ConstantValue CreateNativeUInt(UInt32 value)
        {
            if (value == 0)
            {
                return ConstantValueDefault.NUInt;
            }
            else if (value == 1)
            {
                return ConstantValueOne.NUInt;
            }
 
            return new ConstantValueNativeInt(value);
        }
 
        public static ConstantValue Create(bool value)
        {
            if (value)
            {
                return ConstantValueOne.Boolean;
            }
            else
            {
                return ConstantValueDefault.Boolean;
            }
        }
 
        public static ConstantValue Create(float value)
        {
            if (BitConverter.DoubleToInt64Bits(value) == 0)
            {
                return ConstantValueDefault.Single;
            }
            else if (value == 1)
            {
                return ConstantValueOne.Single;
            }
 
            return new ConstantValueSingle(value);
        }
 
        public static ConstantValue CreateSingle(double value)
        {
            if (BitConverter.DoubleToInt64Bits(value) == 0)
            {
                return ConstantValueDefault.Single;
            }
            else if (value == 1)
            {
                return ConstantValueOne.Single;
            }
 
            return new ConstantValueSingle(value);
        }
 
        public static ConstantValue Create(double value)
        {
            if (BitConverter.DoubleToInt64Bits(value) == 0)
            {
                return ConstantValueDefault.Double;
            }
            else if (value == 1)
            {
                return ConstantValueOne.Double;
            }
 
            return new ConstantValueDouble(value);
        }
 
        public static ConstantValue Create(decimal value)
        {
            // The significant bits should be preserved even for Zero or One.
            // The fourth element of the returned array contains the scale factor and sign.
            int[] decimalBits = System.Decimal.GetBits(value);
            if (decimalBits[3] == 0)
            {
                if (value == 0)
                {
                    return ConstantValueDefault.Decimal;
                }
                else if (value == 1)
                {
                    return ConstantValueOne.Decimal;
                }
            }
 
            return new ConstantValueDecimal(value);
        }
 
        public static ConstantValue Create(DateTime value)
        {
            if (value == default(DateTime))
            {
                return ConstantValueDefault.DateTime;
            }
 
            return new ConstantValueDateTime(value);
        }
 
        public static ConstantValue Create(object value, SpecialType st)
        {
            var discriminator = GetDiscriminator(st);
            Debug.Assert(discriminator != ConstantValueTypeDiscriminator.Bad);
            return Create(value, discriminator);
        }
 
        public static ConstantValue CreateSizeOf(SpecialType st)
        {
            int size = st.SizeInBytes();
            return (size == 0) ? ConstantValue.NotAvailable : ConstantValue.Create(size);
        }
 
        public static ConstantValue Create(object value, ConstantValueTypeDiscriminator discriminator)
        {
            switch (discriminator)
            {
                case ConstantValueTypeDiscriminator.Null: return Null;
                case ConstantValueTypeDiscriminator.SByte: return Create((sbyte)value);
                case ConstantValueTypeDiscriminator.Byte: return Create((byte)value);
                case ConstantValueTypeDiscriminator.Int16: return Create((short)value);
                case ConstantValueTypeDiscriminator.UInt16: return Create((ushort)value);
                case ConstantValueTypeDiscriminator.Int32: return Create((int)value);
                case ConstantValueTypeDiscriminator.UInt32: return Create((uint)value);
                case ConstantValueTypeDiscriminator.Int64: return Create((long)value);
                case ConstantValueTypeDiscriminator.UInt64: return Create((ulong)value);
                case ConstantValueTypeDiscriminator.NInt: return CreateNativeInt((int)value);
                case ConstantValueTypeDiscriminator.NUInt: return CreateNativeUInt((uint)value);
                case ConstantValueTypeDiscriminator.Char: return Create((char)value);
                case ConstantValueTypeDiscriminator.Boolean: return Create((bool)value);
                case ConstantValueTypeDiscriminator.Single:
                    // values for singles may actually have double precision
                    return value is double ?
                        CreateSingle((double)value) :
                        Create((float)value);
                case ConstantValueTypeDiscriminator.Double: return Create((double)value);
                case ConstantValueTypeDiscriminator.Decimal: return Create((decimal)value);
                case ConstantValueTypeDiscriminator.DateTime: return Create((DateTime)value);
                case ConstantValueTypeDiscriminator.String: return Create((string)value);
                default:
                    throw new InvalidOperationException();  //Not using ExceptionUtilities.UnexpectedValue() because this failure path is tested.
            }
        }
 
        public static ConstantValue Default(SpecialType st)
        {
            var discriminator = GetDiscriminator(st);
            Debug.Assert(discriminator != ConstantValueTypeDiscriminator.Bad);
            return Default(discriminator);
        }
 
        public static ConstantValue Default(ConstantValueTypeDiscriminator discriminator)
        {
            switch (discriminator)
            {
                case ConstantValueTypeDiscriminator.Bad: return Bad;
 
                case ConstantValueTypeDiscriminator.SByte: return ConstantValueDefault.SByte;
                case ConstantValueTypeDiscriminator.Byte: return ConstantValueDefault.Byte;
                case ConstantValueTypeDiscriminator.Int16: return ConstantValueDefault.Int16;
                case ConstantValueTypeDiscriminator.UInt16: return ConstantValueDefault.UInt16;
                case ConstantValueTypeDiscriminator.Int32: return ConstantValueDefault.Int32;
                case ConstantValueTypeDiscriminator.UInt32: return ConstantValueDefault.UInt32;
                case ConstantValueTypeDiscriminator.Int64: return ConstantValueDefault.Int64;
                case ConstantValueTypeDiscriminator.UInt64: return ConstantValueDefault.UInt64;
                case ConstantValueTypeDiscriminator.NInt: return ConstantValueDefault.NInt;
                case ConstantValueTypeDiscriminator.NUInt: return ConstantValueDefault.NUInt;
                case ConstantValueTypeDiscriminator.Char: return ConstantValueDefault.Char;
                case ConstantValueTypeDiscriminator.Boolean: return ConstantValueDefault.Boolean;
                case ConstantValueTypeDiscriminator.Single: return ConstantValueDefault.Single;
                case ConstantValueTypeDiscriminator.Double: return ConstantValueDefault.Double;
                case ConstantValueTypeDiscriminator.Decimal: return ConstantValueDefault.Decimal;
                case ConstantValueTypeDiscriminator.DateTime: return ConstantValueDefault.DateTime;
 
                case ConstantValueTypeDiscriminator.Null:
                case ConstantValueTypeDiscriminator.String: return Null;
            }
 
            throw ExceptionUtilities.UnexpectedValue(discriminator);
        }
 
        internal static ConstantValueTypeDiscriminator GetDiscriminator(SpecialType st)
        {
            switch (st)
            {
                case SpecialType.System_SByte: return ConstantValueTypeDiscriminator.SByte;
                case SpecialType.System_Byte: return ConstantValueTypeDiscriminator.Byte;
                case SpecialType.System_Int16: return ConstantValueTypeDiscriminator.Int16;
                case SpecialType.System_UInt16: return ConstantValueTypeDiscriminator.UInt16;
                case SpecialType.System_Int32: return ConstantValueTypeDiscriminator.Int32;
                case SpecialType.System_UInt32: return ConstantValueTypeDiscriminator.UInt32;
                case SpecialType.System_Int64: return ConstantValueTypeDiscriminator.Int64;
                case SpecialType.System_UInt64: return ConstantValueTypeDiscriminator.UInt64;
                case SpecialType.System_IntPtr: return ConstantValueTypeDiscriminator.NInt;
                case SpecialType.System_UIntPtr: return ConstantValueTypeDiscriminator.NUInt;
                case SpecialType.System_Char: return ConstantValueTypeDiscriminator.Char;
                case SpecialType.System_Boolean: return ConstantValueTypeDiscriminator.Boolean;
                case SpecialType.System_Single: return ConstantValueTypeDiscriminator.Single;
                case SpecialType.System_Double: return ConstantValueTypeDiscriminator.Double;
                case SpecialType.System_Decimal: return ConstantValueTypeDiscriminator.Decimal;
                case SpecialType.System_DateTime: return ConstantValueTypeDiscriminator.DateTime;
                case SpecialType.System_String: return ConstantValueTypeDiscriminator.String;
            }
 
            return ConstantValueTypeDiscriminator.Bad;
        }
 
        public string GetPrimitiveTypeName()
        {
            return Discriminator switch
            {
                ConstantValueTypeDiscriminator.SByte => "sbyte",
                ConstantValueTypeDiscriminator.Byte => "byte",
                ConstantValueTypeDiscriminator.Int16 => "short",
                ConstantValueTypeDiscriminator.UInt16 => "ushort",
                ConstantValueTypeDiscriminator.Int32 => "int",
                ConstantValueTypeDiscriminator.NInt => "nint",
                ConstantValueTypeDiscriminator.UInt32 => "uint",
                ConstantValueTypeDiscriminator.NUInt => "nuint",
                ConstantValueTypeDiscriminator.Int64 => "long",
                ConstantValueTypeDiscriminator.UInt64 => "ulong",
                ConstantValueTypeDiscriminator.Char => "char",
                ConstantValueTypeDiscriminator.Boolean => "bool",
                ConstantValueTypeDiscriminator.Single => "float",
                ConstantValueTypeDiscriminator.Double => "double",
                ConstantValueTypeDiscriminator.String => "string",
                ConstantValueTypeDiscriminator.Decimal => "decimal",
                ConstantValueTypeDiscriminator.DateTime => "DateTime",
                ConstantValueTypeDiscriminator.Null or ConstantValueTypeDiscriminator.Bad => throw ExceptionUtilities.UnexpectedValue(Discriminator),
                _ => throw ExceptionUtilities.UnexpectedValue(Discriminator)
            };
        }
 
        private static SpecialType GetSpecialType(ConstantValueTypeDiscriminator discriminator)
        {
            switch (discriminator)
            {
                case ConstantValueTypeDiscriminator.SByte: return SpecialType.System_SByte;
                case ConstantValueTypeDiscriminator.Byte: return SpecialType.System_Byte;
                case ConstantValueTypeDiscriminator.Int16: return SpecialType.System_Int16;
                case ConstantValueTypeDiscriminator.UInt16: return SpecialType.System_UInt16;
                case ConstantValueTypeDiscriminator.Int32: return SpecialType.System_Int32;
                case ConstantValueTypeDiscriminator.UInt32: return SpecialType.System_UInt32;
                case ConstantValueTypeDiscriminator.Int64: return SpecialType.System_Int64;
                case ConstantValueTypeDiscriminator.UInt64: return SpecialType.System_UInt64;
                case ConstantValueTypeDiscriminator.NInt: return SpecialType.System_IntPtr;
                case ConstantValueTypeDiscriminator.NUInt: return SpecialType.System_UIntPtr;
                case ConstantValueTypeDiscriminator.Char: return SpecialType.System_Char;
                case ConstantValueTypeDiscriminator.Boolean: return SpecialType.System_Boolean;
                case ConstantValueTypeDiscriminator.Single: return SpecialType.System_Single;
                case ConstantValueTypeDiscriminator.Double: return SpecialType.System_Double;
                case ConstantValueTypeDiscriminator.Decimal: return SpecialType.System_Decimal;
                case ConstantValueTypeDiscriminator.DateTime: return SpecialType.System_DateTime;
                case ConstantValueTypeDiscriminator.String: return SpecialType.System_String;
                default: return SpecialType.None;
            }
        }
 
        public object? Value
        {
            get
            {
                switch (this.Discriminator)
                {
                    case ConstantValueTypeDiscriminator.Bad: return null;
                    case ConstantValueTypeDiscriminator.Null: return null;
                    case ConstantValueTypeDiscriminator.SByte: return Boxes.Box(SByteValue);
                    case ConstantValueTypeDiscriminator.Byte: return Boxes.Box(ByteValue);
                    case ConstantValueTypeDiscriminator.Int16: return Boxes.Box(Int16Value);
                    case ConstantValueTypeDiscriminator.UInt16: return Boxes.Box(UInt16Value);
                    case ConstantValueTypeDiscriminator.Int32: return Boxes.Box(Int32Value);
                    case ConstantValueTypeDiscriminator.UInt32: return Boxes.Box(UInt32Value);
                    case ConstantValueTypeDiscriminator.Int64: return Boxes.Box(Int64Value);
                    case ConstantValueTypeDiscriminator.UInt64: return Boxes.Box(UInt64Value);
                    case ConstantValueTypeDiscriminator.NInt: return Boxes.Box(Int32Value);
                    case ConstantValueTypeDiscriminator.NUInt: return Boxes.Box(UInt32Value);
                    case ConstantValueTypeDiscriminator.Char: return Boxes.Box(CharValue);
                    case ConstantValueTypeDiscriminator.Boolean: return Boxes.Box(BooleanValue);
                    case ConstantValueTypeDiscriminator.Single: return Boxes.Box(SingleValue);
                    case ConstantValueTypeDiscriminator.Double: return Boxes.Box(DoubleValue);
                    case ConstantValueTypeDiscriminator.Decimal: return Boxes.Box(DecimalValue);
                    case ConstantValueTypeDiscriminator.DateTime: return DateTimeValue;
                    case ConstantValueTypeDiscriminator.String: return StringValue;
                    default: throw ExceptionUtilities.UnexpectedValue(this.Discriminator);
                }
            }
        }
 
        public static bool IsIntegralType(ConstantValueTypeDiscriminator discriminator)
        {
            switch (discriminator)
            {
                case ConstantValueTypeDiscriminator.SByte:
                case ConstantValueTypeDiscriminator.Byte:
                case ConstantValueTypeDiscriminator.Int16:
                case ConstantValueTypeDiscriminator.UInt16:
                case ConstantValueTypeDiscriminator.Int32:
                case ConstantValueTypeDiscriminator.UInt32:
                case ConstantValueTypeDiscriminator.Int64:
                case ConstantValueTypeDiscriminator.UInt64:
                case ConstantValueTypeDiscriminator.NInt:
                case ConstantValueTypeDiscriminator.NUInt:
                    return true;
 
                default:
                    return false;
            }
        }
 
        public bool IsIntegral
        {
            get
            {
                return IsIntegralType(this.Discriminator);
            }
        }
 
        public bool IsNegativeNumeric
        {
            get
            {
                switch (this.Discriminator)
                {
                    case ConstantValueTypeDiscriminator.SByte:
                        return SByteValue < 0;
                    case ConstantValueTypeDiscriminator.Int16:
                        return Int16Value < 0;
                    case ConstantValueTypeDiscriminator.Int32:
                    case ConstantValueTypeDiscriminator.NInt:
                        return Int32Value < 0;
                    case ConstantValueTypeDiscriminator.Int64:
                        return Int64Value < 0;
                    case ConstantValueTypeDiscriminator.Single:
                        return SingleValue < 0;
                    case ConstantValueTypeDiscriminator.Double:
                        return DoubleValue < 0;
                    case ConstantValueTypeDiscriminator.Decimal:
                        return DecimalValue < 0;
 
                    default:
                        return false;
                }
            }
        }
 
        public bool IsNumeric
        {
            get
            {
                switch (this.Discriminator)
                {
                    case ConstantValueTypeDiscriminator.SByte:
                    case ConstantValueTypeDiscriminator.Int16:
                    case ConstantValueTypeDiscriminator.Int32:
                    case ConstantValueTypeDiscriminator.Int64:
                    case ConstantValueTypeDiscriminator.Single:
                    case ConstantValueTypeDiscriminator.Double:
                    case ConstantValueTypeDiscriminator.Decimal:
                    case ConstantValueTypeDiscriminator.Byte:
                    case ConstantValueTypeDiscriminator.UInt16:
                    case ConstantValueTypeDiscriminator.UInt32:
                    case ConstantValueTypeDiscriminator.UInt64:
                    case ConstantValueTypeDiscriminator.NInt:
                    case ConstantValueTypeDiscriminator.NUInt:
                        return true;
 
                    default:
                        return false;
                }
            }
        }
 
        public static bool IsUnsignedIntegralType(ConstantValueTypeDiscriminator discriminator)
        {
            switch (discriminator)
            {
                case ConstantValueTypeDiscriminator.Byte:
                case ConstantValueTypeDiscriminator.UInt16:
                case ConstantValueTypeDiscriminator.UInt32:
                case ConstantValueTypeDiscriminator.UInt64:
                case ConstantValueTypeDiscriminator.NUInt:
                    return true;
 
                default:
                    return false;
            }
        }
 
        public bool IsUnsigned
        {
            get
            {
                return IsUnsignedIntegralType(this.Discriminator);
            }
        }
 
        public static bool IsBooleanType(ConstantValueTypeDiscriminator discriminator)
        {
            return discriminator == ConstantValueTypeDiscriminator.Boolean;
        }
 
        public bool IsBoolean
        {
            get
            {
                return this.Discriminator == ConstantValueTypeDiscriminator.Boolean;
            }
        }
 
        public static bool IsCharType(ConstantValueTypeDiscriminator discriminator)
        {
            return discriminator == ConstantValueTypeDiscriminator.Char;
        }
 
        public bool IsChar
        {
            get
            {
                return this.Discriminator == ConstantValueTypeDiscriminator.Char;
            }
        }
 
        public static bool IsStringType(ConstantValueTypeDiscriminator discriminator)
        {
            return discriminator == ConstantValueTypeDiscriminator.String;
        }
 
        [MemberNotNullWhen(true, nameof(StringValue))]
        public bool IsString
        {
            get
            {
                return this.Discriminator == ConstantValueTypeDiscriminator.String;
            }
        }
 
        public static bool IsDecimalType(ConstantValueTypeDiscriminator discriminator)
        {
            return discriminator == ConstantValueTypeDiscriminator.Decimal;
        }
 
        public bool IsDecimal
        {
            get
            {
                return this.Discriminator == ConstantValueTypeDiscriminator.Decimal;
            }
        }
 
        public static bool IsDateTimeType(ConstantValueTypeDiscriminator discriminator)
        {
            return discriminator == ConstantValueTypeDiscriminator.DateTime;
        }
 
        public bool IsDateTime
        {
            get
            {
                return this.Discriminator == ConstantValueTypeDiscriminator.DateTime;
            }
        }
 
        public static bool IsFloatingType(ConstantValueTypeDiscriminator discriminator)
        {
            return discriminator == ConstantValueTypeDiscriminator.Double ||
                discriminator == ConstantValueTypeDiscriminator.Single;
        }
 
        public bool IsFloating
        {
            get
            {
                return this.Discriminator == ConstantValueTypeDiscriminator.Double ||
                    this.Discriminator == ConstantValueTypeDiscriminator.Single;
            }
        }
 
        public bool IsBad
        {
            get
            {
                return this.Discriminator == ConstantValueTypeDiscriminator.Bad;
            }
        }
 
        public bool IsNull
        {
            get
            {
                return ReferenceEquals(this, Null);
            }
        }
 
        public bool IsNothing
        {
            get
            {
                return ReferenceEquals(this, Nothing);
            }
        }
 
        public void Serialize(BlobBuilder writer)
        {
            switch (this.Discriminator)
            {
                case ConstantValueTypeDiscriminator.Boolean:
                    writer.WriteBoolean(this.BooleanValue);
                    break;
 
                case ConstantValueTypeDiscriminator.SByte:
                    writer.WriteSByte(this.SByteValue);
                    break;
 
                case ConstantValueTypeDiscriminator.Byte:
                    writer.WriteByte(this.ByteValue);
                    break;
 
                case ConstantValueTypeDiscriminator.Char:
                case ConstantValueTypeDiscriminator.Int16:
                    writer.WriteInt16(this.Int16Value);
                    break;
 
                case ConstantValueTypeDiscriminator.UInt16:
                    writer.WriteUInt16(this.UInt16Value);
                    break;
 
                case ConstantValueTypeDiscriminator.Single:
                    writer.WriteSingle(this.SingleValue);
                    break;
 
                case ConstantValueTypeDiscriminator.Int32:
                    writer.WriteInt32(this.Int32Value);
                    break;
 
                case ConstantValueTypeDiscriminator.UInt32:
                    writer.WriteUInt32(this.UInt32Value);
                    break;
 
                case ConstantValueTypeDiscriminator.Double:
                    writer.WriteDouble(this.DoubleValue);
                    break;
 
                case ConstantValueTypeDiscriminator.Int64:
                    writer.WriteInt64(this.Int64Value);
                    break;
 
                case ConstantValueTypeDiscriminator.UInt64:
                    writer.WriteUInt64(this.UInt64Value);
                    break;
 
                default: throw ExceptionUtilities.UnexpectedValue(this.Discriminator);
            }
        }
 
        public override string ToString()
        {
            string? valueToDisplay = this.GetValueToDisplay();
            return String.Format("{0}({1}: {2})", this.GetType().Name, valueToDisplay, this.Discriminator);
        }
 
        public virtual string ToString(string? format, IFormatProvider? provider)
        {
            return Discriminator switch
            {
                ConstantValueTypeDiscriminator.SByte => SByteValue.ToString(provider),
                ConstantValueTypeDiscriminator.Byte => ByteValue.ToString(provider),
                ConstantValueTypeDiscriminator.Int16 => Int16Value.ToString(provider),
                ConstantValueTypeDiscriminator.UInt16 => UInt16Value.ToString(provider),
                ConstantValueTypeDiscriminator.NInt or ConstantValueTypeDiscriminator.Int32 => Int32Value.ToString(provider),
                ConstantValueTypeDiscriminator.NUInt or ConstantValueTypeDiscriminator.UInt32 => UInt32Value.ToString(provider),
                ConstantValueTypeDiscriminator.UInt64 => UInt64Value.ToString(provider),
                ConstantValueTypeDiscriminator.Int64 => Int64Value.ToString(provider),
                ConstantValueTypeDiscriminator.Char => CharValue.ToString(provider),
                ConstantValueTypeDiscriminator.Boolean => BooleanValue.ToString(provider),
                ConstantValueTypeDiscriminator.Single => SingleValue.ToString(provider),
                ConstantValueTypeDiscriminator.Double => DoubleValue.ToString(provider),
                ConstantValueTypeDiscriminator.Decimal => DecimalValue.ToString(provider),
                ConstantValueTypeDiscriminator.DateTime => DateTimeValue.ToString(provider),
                _ => throw ExceptionUtilities.UnexpectedValue(Discriminator)
            };
        }
 
        internal virtual string? GetValueToDisplay()
        {
            return this.Value?.ToString();
        }
 
        internal bool IsIntegralValueZeroOrOne(out bool isOne)
        {
            if (IsDefaultValue)
            {
                isOne = false;
            }
            else if (IsOne)
            {
                isOne = true;
            }
            else
            {
                isOne = default;
                return false;
            }
 
            return IsIntegral || IsBoolean || IsChar;
        }
 
        // equal constants must have matching discriminators
        // derived types override this if equivalence is more than just discriminators match. 
        // singletons also override this since they only need a reference compare.
        public virtual bool Equals(ConstantValue? other)
        {
            if (ReferenceEquals(other, this))
            {
                return true;
            }
 
            if (ReferenceEquals(other, null))
            {
                return false;
            }
 
            return this.Discriminator == other.Discriminator;
        }
 
        public static bool operator ==(ConstantValue? left, ConstantValue? right)
        {
            if (ReferenceEquals(right, left))
            {
                return true;
            }
 
            if (ReferenceEquals(left, null))
            {
                return false;
            }
 
            return left.Equals(right);
        }
 
        public static bool operator !=(ConstantValue? left, ConstantValue? right)
        {
            return !(left == right);
        }
 
        public override int GetHashCode()
        {
            return ((int)this.Discriminator).GetHashCode();
        }
 
        public override bool Equals(object? obj)
        {
            return this.Equals(obj as ConstantValue);
        }
    }
}