|
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Represents a constant value used as an argument to a custom attribute.
/// </summary>
public readonly struct TypedConstant : IEquatable<TypedConstant>
{
private readonly TypedConstantKind _kind;
private readonly ITypeSymbolInternal? _type;
private readonly object? _value;
internal TypedConstant(ITypeSymbolInternal? type, TypedConstantKind kind, object? value)
{
Debug.Assert(kind == TypedConstantKind.Array || !(value is ImmutableArray<TypedConstant>));
Debug.Assert(!(value is ISymbol) || value is ISymbolInternal);
Debug.Assert(type is object || kind == TypedConstantKind.Error);
_kind = kind;
_type = type;
_value = value;
}
internal TypedConstant(ITypeSymbolInternal type, ImmutableArray<TypedConstant> array)
: this(type, TypedConstantKind.Array, value: array.IsDefault ? null : (object)array)
{
}
/// <summary>
/// The kind of the constant.
/// </summary>
public TypedConstantKind Kind
{
get { return _kind; }
}
/// <summary>
/// Returns the <see cref="ITypeSymbol"/> of the constant,
/// or null if the type can't be determined (error).
/// </summary>
public ITypeSymbol? Type
{
get { return _type?.GetITypeSymbol(); }
}
internal ITypeSymbolInternal? TypeInternal
{
get { return _type; }
}
/// <summary>
/// True if the constant represents a null reference.
/// </summary>
public bool IsNull
{
get
{
return _value == null;
}
}
/// <summary>
/// The value for a non-array constant.
/// </summary>
public object? Value
{
get
{
object? result = ValueInternal;
if (result is ISymbolInternal symbol)
{
return symbol.GetISymbol();
}
return result;
}
}
/// <summary>
/// Unlike <see cref="Value"/> returns <see cref="ISymbolInternal"/> when the value is a symbol.
/// </summary>
internal object? ValueInternal
{
get
{
if (Kind == TypedConstantKind.Array)
{
throw new InvalidOperationException("TypedConstant is an array. Use Values property.");
}
return _value;
}
}
/// <summary>
/// The value for a <see cref="TypedConstant"/> array.
/// Returns a <see langword="default" /> <c>ImmutableArray</c> if <see langword="null"/> was passed as the array value;
/// <see cref="IsNull"/> can be used to check for this.
/// </summary>
public ImmutableArray<TypedConstant> Values
{
get
{
if (Kind != TypedConstantKind.Array)
{
throw new InvalidOperationException("TypedConstant is not an array. Use Value property.");
}
if (this.IsNull)
{
return default;
}
return (ImmutableArray<TypedConstant>)_value!;
}
}
internal T? DecodeValue<T>(SpecialType specialType)
{
TryDecodeValue(specialType, out T? value);
return value;
}
internal bool TryDecodeValue<T>(SpecialType specialType, [MaybeNullWhen(false)] out T value)
{
if (_kind == TypedConstantKind.Error)
{
value = default;
return false;
}
if (_type!.SpecialType == specialType || (_type.TypeKind == TypeKind.Enum && specialType == SpecialType.System_Enum))
{
value = (T)_value!;
return true;
}
// the actual argument type doesn't match the type of the parameter - an error has already been reported by the binder
value = default;
return false;
}
/// <remarks>
/// TypedConstant isn't computing its own kind from the type symbol because it doesn't
/// have a way to recognize the well-known type System.Type.
/// </remarks>
internal static TypedConstantKind GetTypedConstantKind(ITypeSymbolInternal type, Compilation compilation)
{
RoslynDebug.Assert(type != null);
switch (type.SpecialType)
{
case SpecialType.System_Boolean:
case SpecialType.System_SByte:
case SpecialType.System_Int16:
case SpecialType.System_Int32:
case SpecialType.System_Int64:
case SpecialType.System_Byte:
case SpecialType.System_UInt16:
case SpecialType.System_UInt32:
case SpecialType.System_UInt64:
case SpecialType.System_Single:
case SpecialType.System_Double:
case SpecialType.System_Char:
case SpecialType.System_String:
case SpecialType.System_Object:
return TypedConstantKind.Primitive;
default:
switch (type.TypeKind)
{
case TypeKind.Array:
return TypedConstantKind.Array;
case TypeKind.Enum:
return TypedConstantKind.Enum;
case TypeKind.Error:
return TypedConstantKind.Error;
}
if (compilation != null &&
compilation.IsSystemTypeReference(type))
{
return TypedConstantKind.Type;
}
return TypedConstantKind.Error;
}
}
public override bool Equals(object? obj)
{
return obj is TypedConstant && Equals((TypedConstant)obj);
}
public bool Equals(TypedConstant other)
{
return _kind == other._kind
&& object.Equals(_value, other._value)
&& object.Equals(_type, other._type);
}
public override int GetHashCode()
{
return Hash.Combine(_value,
Hash.Combine(_type, (int)this.Kind));
}
#region Testing & Debugging
#if false
/// <summary>
/// Returns the System.String that represents the current TypedConstant.
/// </summary>
/// <returns>A System.String that represents the current TypedConstant.</returns>
public override string ToString()
{
if (value.IsNull)
{
return "null";
}
if (kind == TypedConstantKind.Array)
{
return "{" + string.Join(", ", Values.Select(v => new TypedConstant(v).ToString())) + "}";
}
if (kind == TypedConstantKind.Type || type.SpecialType == SpecialType.System_Object)
{
return "typeof(" + value.Object.ToString() + ")";
}
if (kind == TypedConstantKind.Enum)
{
// TODO (tomat): replace with SymbolDisplay
return DisplayEnumConstant();
}
return SymbolDisplay.FormatPrimitive(value.Object, quoteStrings: true, useHexadecimalNumbers: false);
}
// Decode the value of enum constant
private string DisplayEnumConstant()
{
Debug.Assert(Kind == TypedConstantKind.Enum);
// Create a ConstantValue of enum underlying type
SpecialType splType = this.Type.GetEnumUnderlyingType().SpecialType;
ConstantValue valueConstant = ConstantValue.Create(this.Value, splType);
string typeName = this.Type.ToDisplayString(SymbolDisplayFormat.QualifiedNameOnlyFormat);
if (valueConstant.IsUnsigned)
{
return DisplayUnsignedEnumConstant(splType, valueConstant.UInt64Value, typeName);
}
else
{
return DisplaySignedEnumConstant(splType, valueConstant.Int64Value, typeName);
}
}
private string DisplayUnsignedEnumConstant(SpecialType specialType, ulong constantToDecode, string typeName)
{
// Specified valueConstant might have an exact matching enum field
// or it might be a bitwise Or of multiple enum fields.
// For the later case, we keep track of the current value of
// bitwise Or of possible enum fields.
ulong curValue = 0;
// Initialize the value string to empty
PooledStringBuilder pooledBuilder = null;
StringBuilder valueStringBuilder = null;
// Iterate through all the constant members in the enum type
ImmutableArray<Symbol> members = this.Type.GetMembers();
foreach (Symbol member in members)
{
var field = member as FieldSymbol;
if ((object)field != null && field.HasConstantValue)
{
ConstantValue memberConstant = ConstantValue.Create(field.ConstantValue, specialType);
ulong memberValue = memberConstant.UInt64Value;
// Do we have an exact matching enum field
if (memberValue == constantToDecode)
{
if (pooledBuilder != null)
{
pooledBuilder.Free();
}
return typeName + "." + field.Name;
}
// specifiedValue might be a bitwise Or of multiple enum fields
// Is the current member included in the specified value?
if ((memberValue & constantToDecode) == memberValue)
{
// update the current value
curValue = curValue | memberValue;
if (valueStringBuilder == null)
{
pooledBuilder = PooledStringBuilder.GetInstance();
valueStringBuilder = pooledBuilder.Builder;
}
else
{
valueStringBuilder.Append(" | ");
}
valueStringBuilder.Append(typeName);
valueStringBuilder.Append(".");
valueStringBuilder.Append(field.Name);
}
}
}
if (pooledBuilder != null)
{
if (curValue == constantToDecode)
{
// return decoded enum constant
return pooledBuilder.ToStringAndFree();
}
// Unable to decode the enum constant
pooledBuilder.Free();
}
// Unable to decode the enum constant, just display the integral value
return this.Value.ToString();
}
private string DisplaySignedEnumConstant(SpecialType specialType, long constantToDecode, string typeName)
{
// Specified valueConstant might have an exact matching enum field
// or it might be a bitwise Or of multiple enum fields.
// For the later case, we keep track of the current value of
// bitwise Or of possible enum fields.
long curValue = 0;
// Initialize the value string to empty
PooledStringBuilder pooledBuilder = null;
StringBuilder valueStringBuilder = null;
// Iterate through all the constant members in the enum type
ImmutableArray<Symbol> members = this.Type.GetMembers();
foreach (Symbol member in members)
{
var field = member as FieldSymbol;
if ((object)field != null && field.HasConstantValue)
{
ConstantValue memberConstant = ConstantValue.Create(field.ConstantValue, specialType);
long memberValue = memberConstant.Int64Value;
// Do we have an exact matching enum field
if (memberValue == constantToDecode)
{
if (pooledBuilder != null)
{
pooledBuilder.Free();
}
return typeName + "." + field.Name;
}
// specifiedValue might be a bitwise Or of multiple enum fields
// Is the current member included in the specified value?
if ((memberValue & constantToDecode) == memberValue)
{
// update the current value
curValue = curValue | memberValue;
if (valueStringBuilder == null)
{
pooledBuilder = PooledStringBuilder.GetInstance();
valueStringBuilder = pooledBuilder.Builder;
}
else
{
valueStringBuilder.Append(" | ");
}
valueStringBuilder.Append(typeName);
valueStringBuilder.Append(".");
valueStringBuilder.Append(field.Name);
}
}
}
if (pooledBuilder != null)
{
if (curValue == constantToDecode)
{
// return decoded enum constant
return pooledBuilder.ToStringAndFree();
}
// Unable to decode the enum constant
pooledBuilder.Free();
}
// Unable to decode the enum constant, just display the integral value
return this.Value.ToString();
}
#endif
#endregion
}
}
|