// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.Reflection; using Roslyn.Utilities; using System.Threading; namespace Microsoft.CodeAnalysis { /// /// A DiagnosticInfo object has information about a diagnostic, but without any attached location information. /// /// /// 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. /// [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] internal class DiagnosticInfo : IFormattable, IObjectWritable { private readonly CommonMessageProvider _messageProvider; private readonly int _errorCode; private readonly DiagnosticSeverity _defaultSeverity; private readonly DiagnosticSeverity _effectiveSeverity; private readonly object[] _arguments; private static ImmutableDictionary s_errorCodeToDescriptorMap = ImmutableDictionary.Empty; // Mark compiler errors as non-configurable to ensure they can never be suppressed or filtered. private static readonly ImmutableArray s_compilerErrorCustomTags = ImmutableArray.Create(WellKnownDiagnosticTags.Compiler, WellKnownDiagnosticTags.Telemetry, WellKnownDiagnosticTags.NotConfigurable); private static readonly ImmutableArray s_compilerNonErrorCustomTags = ImmutableArray.Create(WellKnownDiagnosticTags.Compiler, WellKnownDiagnosticTags.Telemetry); static DiagnosticInfo() { ObjectBinder.RegisterTypeReader(typeof(DiagnosticInfo), r => new DiagnosticInfo(r)); } // Only the compiler creates instances. internal DiagnosticInfo(CommonMessageProvider messageProvider, int errorCode) { _messageProvider = messageProvider; _errorCode = errorCode; _defaultSeverity = messageProvider.GetSeverity(errorCode); _effectiveSeverity = _defaultSeverity; } // Only the compiler creates instances. internal DiagnosticInfo(CommonMessageProvider messageProvider, int errorCode, params object[] arguments) : this(messageProvider, errorCode) { AssertMessageSerializable(arguments); _arguments = arguments; } private 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, code => CreateDescriptor(code, 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: true, description: description, helpLinkUri: helpLink, customTags: customTags); } [Conditional("DEBUG")] internal static void AssertMessageSerializable(object[] args) { foreach (var arg in args) { Debug.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); } } // 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) { return new DiagnosticInfo(this, severity); } #region Serialization bool IObjectWritable.ShouldReuseInSerialization => false; void IObjectWritable.WriteTo(ObjectWriter writer) { this.WriteTo(writer); } protected virtual void WriteTo(ObjectWriter writer) { writer.WriteValue(_messageProvider); writer.WriteUInt32((uint)_errorCode); writer.WriteInt32((int)_effectiveSeverity); writer.WriteInt32((int)_defaultSeverity); int count = _arguments?.Length ?? 0; writer.WriteUInt32((uint)count); if (count > 0) { foreach (var arg in _arguments) { writer.WriteString(arg.ToString()); } } } protected DiagnosticInfo(ObjectReader reader) { _messageProvider = (CommonMessageProvider)reader.ReadValue(); _errorCode = (int)reader.ReadUInt32(); _effectiveSeverity = (DiagnosticSeverity)reader.ReadInt32(); _defaultSeverity = (DiagnosticSeverity)reader.ReadInt32(); var count = (int)reader.ReadUInt32(); if (count == 0) { _arguments = Array.Empty(); } else if (count > 0) { _arguments = new string[count]; for (int i = 0; i < count; i++) { _arguments[i] = reader.ReadString(); } } } #endregion /// /// The error code, as an integer. /// public int Code { get { return _errorCode; } } public DiagnosticDescriptor Descriptor { get { return GetOrCreateDescriptor(_errorCode, _defaultSeverity, _messageProvider); } } /// /// Returns the effective severity of the diagnostic: whether this diagnostic is informational, warning, or error. /// If IsWarningsAsError is true, then this returns , while returns . /// public DiagnosticSeverity Severity { get { return _effectiveSeverity; } } /// /// Returns whether this diagnostic is informational, warning, or error by default, based on the error code. /// To get diagnostic's effective severity, use . /// public DiagnosticSeverity DefaultSeverity { get { return _defaultSeverity; } } /// /// Gets the warning level. This is 0 for diagnostics with severity , /// otherwise an integer between 1 and 4. /// public int WarningLevel { get { if (_effectiveSeverity != _defaultSeverity) { return Diagnostic.GetDefaultWarningLevel(_effectiveSeverity); } return _messageProvider.GetWarningLevel(_errorCode); } } /// /// Returns true if this is a warning treated as an error. /// /// /// True implies = and /// = . /// public bool IsWarningAsError { get { return this.DefaultSeverity == DiagnosticSeverity.Warning && this.Severity == DiagnosticSeverity.Error; } } /// /// Get the diagnostic category for the given diagnostic code. /// Default category is . /// public string Category { get { return _messageProvider.GetCategory(_errorCode); } } internal ImmutableArray CustomTags { get { return GetCustomTags(_defaultSeverity); } } private static ImmutableArray GetCustomTags(DiagnosticSeverity defaultSeverity) { return defaultSeverity == DiagnosticSeverity.Error ? s_compilerErrorCustomTags : s_compilerNonErrorCustomTags; } internal bool IsNotConfigurable() { // Only compiler errors are non-configurable. return _defaultSeverity == DiagnosticSeverity.Error; } /// /// 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. /// public virtual IReadOnlyList AdditionalLocations { get { return SpecializedCollections.EmptyReadOnlyList(); } } /// /// Get the message id (for example "CS1001") for the message. This includes both the error number /// and a prefix identifying the source. /// public string MessageIdentifier { get { return _messageProvider.GetIdForErrorCode(_errorCode); } } /// /// Get the text of the message in the given language. /// 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 == null || _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; 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; if (_arguments != null) { 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 && this.GetType() == obj.GetType()) { if (_arguments == null && other._arguments == null) { result = true; } else if (_arguments != null && other._arguments != null && _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(); } } /// /// For a DiagnosticInfo that is lazily evaluated, this method evaluates it /// and returns a non-lazy DiagnosticInfo. /// internal virtual DiagnosticInfo GetResolvedInfo() { // We should never call GetResolvedInfo on a non-lazy DiagnosticInfo throw ExceptionUtilities.Unreachable; } } }