|
// 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Symbols;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// A DiagnosticInfo object has information about a diagnostic, but without any attached location information.
/// </summary>
/// <remarks>
/// More specialized diagnostics with additional information (e.g., ambiguity errors) can derive from this class to
/// provide access to additional information about the error, such as what symbols were involved in the ambiguity.
/// </remarks>
[DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
internal class DiagnosticInfo : IFormattable
{
private readonly CommonMessageProvider _messageProvider;
private readonly int _errorCode;
private readonly DiagnosticSeverity _defaultSeverity;
private readonly DiagnosticSeverity _effectiveSeverity;
private readonly object[] _arguments;
private static ImmutableDictionary<int, DiagnosticDescriptor> s_errorCodeToDescriptorMap = ImmutableDictionary<int, DiagnosticDescriptor>.Empty;
// Mark compiler errors as non-configurable to ensure they can never be suppressed or filtered.
private static readonly ImmutableArray<string> s_compilerErrorCustomTags = ImmutableArray.Create(WellKnownDiagnosticTags.Compiler, WellKnownDiagnosticTags.Telemetry, WellKnownDiagnosticTags.NotConfigurable);
private static readonly ImmutableArray<string> s_compilerNonErrorCustomTags = ImmutableArray.Create(WellKnownDiagnosticTags.Compiler, WellKnownDiagnosticTags.Telemetry);
// Only the compiler creates instances.
internal DiagnosticInfo(CommonMessageProvider messageProvider, int errorCode)
: this(messageProvider, errorCode, Array.Empty<object>())
{
}
// Only the compiler creates instances.
internal DiagnosticInfo(CommonMessageProvider messageProvider, int errorCode, params object[] arguments)
{
AssertMessageSerializable(arguments);
AssertExpectedMessageArgumentsLength(messageProvider, errorCode, arguments.Length);
_messageProvider = messageProvider;
_errorCode = errorCode;
_defaultSeverity = messageProvider.GetSeverity(errorCode);
_effectiveSeverity = _defaultSeverity;
_arguments = arguments;
}
protected DiagnosticInfo(DiagnosticInfo original, DiagnosticSeverity overriddenSeverity)
{
_messageProvider = original.MessageProvider;
_errorCode = original._errorCode;
_defaultSeverity = original.DefaultSeverity;
_arguments = original._arguments;
_effectiveSeverity = overriddenSeverity;
}
internal static DiagnosticDescriptor GetDescriptor(int errorCode, CommonMessageProvider messageProvider)
{
var defaultSeverity = messageProvider.GetSeverity(errorCode);
return GetOrCreateDescriptor(errorCode, defaultSeverity, messageProvider);
}
private static DiagnosticDescriptor GetOrCreateDescriptor(int errorCode, DiagnosticSeverity defaultSeverity, CommonMessageProvider messageProvider)
{
return ImmutableInterlocked.GetOrAdd(
ref s_errorCodeToDescriptorMap,
errorCode,
static (code, arg) => CreateDescriptor(code, arg.defaultSeverity, arg.messageProvider),
(defaultSeverity, messageProvider));
}
private static DiagnosticDescriptor CreateDescriptor(int errorCode, DiagnosticSeverity defaultSeverity, CommonMessageProvider messageProvider)
{
var id = messageProvider.GetIdForErrorCode(errorCode);
var title = messageProvider.GetTitle(errorCode);
var description = messageProvider.GetDescription(errorCode);
var messageFormat = messageProvider.GetMessageFormat(errorCode);
var helpLink = messageProvider.GetHelpLink(errorCode);
var category = messageProvider.GetCategory(errorCode);
var customTags = GetCustomTags(defaultSeverity);
return new DiagnosticDescriptor(id, title, messageFormat, category, defaultSeverity,
isEnabledByDefault: messageProvider.GetIsEnabledByDefault(errorCode), description: description, helpLinkUri: helpLink, customTags: customTags);
}
[Conditional("DEBUG")]
internal static void AssertMessageSerializable(object[] args)
{
foreach (var arg in args)
{
RoslynDebug.Assert(arg != null);
if (arg is IFormattable)
{
continue;
}
var type = arg.GetType();
if (type == typeof(string) || type == typeof(AssemblyIdentity))
{
continue;
}
var info = type.GetTypeInfo();
if (info.IsPrimitive)
{
continue;
}
throw ExceptionUtilities.UnexpectedValue(type);
}
}
[Conditional("DEBUG")]
private static void AssertExpectedMessageArgumentsLength(CommonMessageProvider messageProvider, int errorCode, int actualLength)
{
#if DEBUG
if (!messageProvider.ShouldAssertExpectedMessageArgumentsLength(errorCode))
{
return;
}
string message = messageProvider.LoadMessage(errorCode, language: null);
var matches = Regex.Matches(message, @"\{\d+[}:]");
int expectedLength = 0;
var bits = BitVector.Create(actualLength);
foreach (object? m in matches)
{
if (m is Match match)
{
int value = int.Parse(match.Value[1..^1]);
expectedLength = Math.Max(value + 1, expectedLength);
bits[value] = true;
}
}
Debug.Assert(expectedLength == actualLength);
Debug.Assert(bits == BitVector.AllSet(actualLength));
#endif
}
// Only the compiler creates instances.
internal DiagnosticInfo(CommonMessageProvider messageProvider, bool isWarningAsError, int errorCode, params object[] arguments)
: this(messageProvider, errorCode, arguments)
{
Debug.Assert(!isWarningAsError || _defaultSeverity == DiagnosticSeverity.Warning);
if (isWarningAsError)
{
_effectiveSeverity = DiagnosticSeverity.Error;
}
}
// Create a copy of this instance with a explicit overridden severity
internal DiagnosticInfo GetInstanceWithSeverity(DiagnosticSeverity severity)
{
if (Severity != severity)
{
var result = GetInstanceWithSeverityCore(severity);
// If this assert fails it means a subtype of DiagnosticInfo failed to override GetInstanceWithSeverityCore
Debug.Assert(this.GetType() == result.GetType());
Debug.Assert(severity == result.Severity);
return result;
}
return this;
}
protected virtual DiagnosticInfo GetInstanceWithSeverityCore(DiagnosticSeverity severity)
{
return new DiagnosticInfo(this, severity);
}
/// <summary>
/// The error code, as an integer.
/// </summary>
public int Code { get { return _errorCode; } }
public virtual DiagnosticDescriptor Descriptor
{
get
{
return GetOrCreateDescriptor(_errorCode, _defaultSeverity, _messageProvider);
}
}
/// <summary>
/// Returns the effective severity of the diagnostic: whether this diagnostic is informational, warning, or error.
/// If IsWarningsAsError is true, then this returns <see cref="DiagnosticSeverity.Error"/>, while <see cref="DefaultSeverity"/> returns <see cref="DiagnosticSeverity.Warning"/>.
/// </summary>
public DiagnosticSeverity Severity
{
get
{
return _effectiveSeverity;
}
}
/// <summary>
/// Returns whether this diagnostic is informational, warning, or error by default, based on the error code.
/// To get diagnostic's effective severity, use <see cref="Severity"/>.
/// </summary>
public DiagnosticSeverity DefaultSeverity
{
get
{
return _defaultSeverity;
}
}
/// <summary>
/// Gets the warning level. This is 0 for diagnostics with severity <see cref="DiagnosticSeverity.Error"/>,
/// otherwise an integer greater than zero.
/// </summary>
public int WarningLevel
{
get
{
if (_effectiveSeverity != _defaultSeverity)
{
return Diagnostic.GetDefaultWarningLevel(_effectiveSeverity);
}
return _messageProvider.GetWarningLevel(_errorCode);
}
}
/// <summary>
/// Returns true if this is a warning treated as an error.
/// </summary>
/// <remarks>
/// True implies <see cref="Severity"/> = <see cref="DiagnosticSeverity.Error"/> and
/// <see cref="DefaultSeverity"/> = <see cref="DiagnosticSeverity.Warning"/>.
/// </remarks>
public bool IsWarningAsError
{
get
{
return this.DefaultSeverity == DiagnosticSeverity.Warning &&
this.Severity == DiagnosticSeverity.Error;
}
}
/// <summary>
/// Get the diagnostic category for the given diagnostic code.
/// Default category is <see cref="Diagnostic.CompilerDiagnosticCategory"/>.
/// </summary>
public string Category
{
get
{
return _messageProvider.GetCategory(_errorCode);
}
}
internal ImmutableArray<string> CustomTags
{
get
{
return GetCustomTags(_defaultSeverity);
}
}
private static ImmutableArray<string> GetCustomTags(DiagnosticSeverity defaultSeverity)
{
return defaultSeverity == DiagnosticSeverity.Error ?
s_compilerErrorCustomTags :
s_compilerNonErrorCustomTags;
}
internal bool IsNotConfigurable()
{
// Only compiler errors are non-configurable.
return _defaultSeverity == DiagnosticSeverity.Error;
}
/// <summary>
/// If a derived class has additional information about other referenced symbols, it can
/// expose the locations of those symbols in a general way, so they can be reported along
/// with the error.
/// </summary>
public virtual IReadOnlyList<Location> AdditionalLocations
{
get
{
return SpecializedCollections.EmptyReadOnlyList<Location>();
}
}
/// <summary>
/// Get the message id (for example "CS1001") for the message. This includes both the error number
/// and a prefix identifying the source.
/// </summary>
public virtual string MessageIdentifier
{
get
{
return _messageProvider.GetIdForErrorCode(_errorCode);
}
}
/// <summary>
/// Get the text of the message in the given language.
/// </summary>
public virtual string GetMessage(IFormatProvider? formatProvider = null)
{
// Get the message and fill in arguments.
string message = _messageProvider.LoadMessage(_errorCode, formatProvider as CultureInfo);
if (string.IsNullOrEmpty(message))
{
return string.Empty;
}
if (_arguments.Length == 0)
{
return message;
}
return String.Format(formatProvider, message, GetArgumentsToUse(formatProvider));
}
protected object[] GetArgumentsToUse(IFormatProvider? formatProvider)
{
object[]? argumentsToUse = null;
for (int i = 0; i < _arguments.Length; i++)
{
var embedded = _arguments[i] as DiagnosticInfo;
if (embedded != null)
{
argumentsToUse = InitializeArgumentListIfNeeded(argumentsToUse);
argumentsToUse[i] = embedded.GetMessage(formatProvider);
continue;
}
var symbol = _arguments[i] as ISymbol ?? (_arguments[i] as ISymbolInternal)?.GetISymbol();
if (symbol != null)
{
argumentsToUse = InitializeArgumentListIfNeeded(argumentsToUse);
argumentsToUse[i] = _messageProvider.GetErrorDisplayString(symbol);
}
}
return argumentsToUse ?? _arguments;
}
private object[] InitializeArgumentListIfNeeded(object[]? argumentsToUse)
{
if (argumentsToUse != null)
{
return argumentsToUse;
}
var newArguments = new object[_arguments.Length];
Array.Copy(_arguments, newArguments, newArguments.Length);
return newArguments;
}
internal object[] Arguments
{
get { return _arguments; }
}
internal CommonMessageProvider MessageProvider
{
get { return _messageProvider; }
}
// TODO (tomat): remove
public override string? ToString()
{
return ToString(null);
}
public string ToString(IFormatProvider? formatProvider)
{
return ((IFormattable)this).ToString(null, formatProvider);
}
string IFormattable.ToString(string? format, IFormatProvider? formatProvider)
{
return String.Format(formatProvider, "{0}: {1}",
_messageProvider.GetMessagePrefix(this.MessageIdentifier, this.Severity, this.IsWarningAsError, formatProvider as CultureInfo),
this.GetMessage(formatProvider));
}
public sealed override int GetHashCode()
{
int hashCode = _errorCode;
for (int i = 0; i < _arguments.Length; i++)
{
hashCode = Hash.Combine(_arguments[i], hashCode);
}
return hashCode;
}
public sealed override bool Equals(object? obj)
{
DiagnosticInfo? other = obj as DiagnosticInfo;
bool result = false;
if (other != null &&
other._errorCode == _errorCode &&
other.GetType() == this.GetType())
{
if (_arguments.Length == other._arguments.Length)
{
result = true;
for (int i = 0; i < _arguments.Length; i++)
{
if (!object.Equals(_arguments[i], other._arguments[i]))
{
result = false;
break;
}
}
}
}
return result;
}
private string? GetDebuggerDisplay()
{
// There aren't message resources for our internal error codes, so make
// sure we don't call ToString for those.
switch (Code)
{
case InternalErrorCode.Unknown:
return "Unresolved DiagnosticInfo";
case InternalErrorCode.Void:
return "Void DiagnosticInfo";
default:
return ToString();
}
}
/// <summary>
/// For a DiagnosticInfo that is lazily evaluated, this method evaluates it
/// and returns a non-lazy DiagnosticInfo.
/// </summary>
internal virtual DiagnosticInfo GetResolvedInfo()
{
// We should never call GetResolvedInfo on a non-lazy DiagnosticInfo
throw ExceptionUtilities.Unreachable();
}
}
}
|