File: Symbols\ObsoleteAttributeHelpers.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal enum ObsoleteDiagnosticKind
    {
        NotObsolete,
        Suppressed,
        Diagnostic,
        Lazy,
        LazyPotentiallySuppressed,
    }
 
    internal static class ObsoleteAttributeHelpers
    {
        /// <summary>
        /// Initialize the ObsoleteAttributeData by fetching attributes and decoding ObsoleteAttributeData. This can be 
        /// done for Metadata symbol easily whereas trying to do this for source symbols could result in cycles.
        /// </summary>
        internal static void InitializeObsoleteDataFromMetadata(ref ObsoleteAttributeData data, EntityHandle token, PEModuleSymbol containingModule, bool ignoreByRefLikeMarker, bool ignoreRequiredMemberMarker)
        {
            if (ReferenceEquals(data, ObsoleteAttributeData.Uninitialized))
            {
                ObsoleteAttributeData obsoleteAttributeData = GetObsoleteDataFromMetadata(token, containingModule, ignoreByRefLikeMarker, ignoreRequiredMemberMarker);
                Interlocked.CompareExchange(ref data, obsoleteAttributeData, ObsoleteAttributeData.Uninitialized);
            }
        }
 
        /// <summary>
        /// Get the ObsoleteAttributeData by fetching attributes and decoding ObsoleteAttributeData. This can be 
        /// done for Metadata symbol easily whereas trying to do this for source symbols could result in cycles.
        /// </summary>
        internal static ObsoleteAttributeData GetObsoleteDataFromMetadata(EntityHandle token, PEModuleSymbol containingModule, bool ignoreByRefLikeMarker, bool ignoreRequiredMemberMarker)
        {
            var obsoleteAttributeData = containingModule.Module.TryGetDeprecatedOrExperimentalOrObsoleteAttribute(token, new MetadataDecoder(containingModule), ignoreByRefLikeMarker, ignoreRequiredMemberMarker);
            Debug.Assert(obsoleteAttributeData == null || !obsoleteAttributeData.IsUninitialized);
            return obsoleteAttributeData;
        }
 
        /// <summary>
        /// This method checks to see if the given symbol is Obsolete or if any symbol in the parent hierarchy is Obsolete.
        /// </summary>
        /// <returns>
        /// True if some symbol in the parent hierarchy is known to be Obsolete. Unknown if any
        /// symbol's Obsoleteness is Unknown. False, if we are certain that no symbol in the parent
        /// hierarchy is Obsolete.
        /// </returns>
        private static ThreeState GetObsoleteContextState(Symbol symbol, bool forceComplete, Func<Symbol, ThreeState> getStateFromSymbol)
        {
            while ((object)symbol != null)
            {
                if (symbol.Kind == SymbolKind.Field)
                {
                    // If this is the backing field of an event, look at the event instead.
                    var associatedSymbol = ((FieldSymbol)symbol).AssociatedSymbol;
                    if ((object)associatedSymbol != null)
                    {
                        symbol = associatedSymbol;
                    }
                }
 
                if (forceComplete)
                {
                    symbol.ForceCompleteObsoleteAttribute();
                }
 
                var state = getStateFromSymbol(symbol);
                if (state != ThreeState.False)
                {
                    return state;
                }
 
                // For property or event accessors, check the associated property or event next.
                if (symbol.IsAccessor())
                {
                    symbol = ((MethodSymbol)symbol).AssociatedSymbol;
                }
                else
                {
                    symbol = symbol.ContainingSymbol;
                }
            }
 
            return ThreeState.False;
        }
 
        internal static ObsoleteDiagnosticKind GetObsoleteDiagnosticKind(Symbol symbol, Symbol containingMember, bool forceComplete = false)
        {
            switch (symbol.ObsoleteKind)
            {
                case ObsoleteAttributeKind.None:
                    if (symbol.ContainingModule.ObsoleteKind is ObsoleteAttributeKind.Experimental
                        || symbol.ContainingAssembly.ObsoleteKind is ObsoleteAttributeKind.Experimental)
                    {
                        return getDiagnosticKind(containingMember, forceComplete, getStateFromSymbol: static (symbol) => symbol.ExperimentalState);
                    }
 
                    if (symbol.ContainingModule.ObsoleteKind is ObsoleteAttributeKind.Uninitialized
                        || symbol.ContainingAssembly.ObsoleteKind is ObsoleteAttributeKind.Uninitialized)
                    {
                        return ObsoleteDiagnosticKind.Lazy;
                    }
 
                    return ObsoleteDiagnosticKind.NotObsolete;
                case ObsoleteAttributeKind.WindowsExperimental:
                    return ObsoleteDiagnosticKind.Diagnostic;
                case ObsoleteAttributeKind.Experimental:
                    return getDiagnosticKind(containingMember, forceComplete, getStateFromSymbol: static (symbol) => symbol.ExperimentalState);
                case ObsoleteAttributeKind.Uninitialized:
                    // If we haven't cracked attributes on the symbol at all or we haven't
                    // cracked attribute arguments enough to be able to report diagnostics for
                    // ObsoleteAttribute, store the symbol so that we can report diagnostics at a 
                    // later stage.
                    return ObsoleteDiagnosticKind.Lazy;
            }
 
            return getDiagnosticKind(containingMember, forceComplete, getStateFromSymbol: static (symbol) => symbol.ObsoleteState);
 
            static ObsoleteDiagnosticKind getDiagnosticKind(Symbol containingMember, bool forceComplete, Func<Symbol, ThreeState> getStateFromSymbol)
            {
                switch (GetObsoleteContextState(containingMember, forceComplete, getStateFromSymbol))
                {
                    case ThreeState.False:
                        return ObsoleteDiagnosticKind.Diagnostic;
                    case ThreeState.True:
                        // If we are in a context that is already experimental/obsolete, there is no point reporting
                        // more experimental/obsolete diagnostics.
                        return ObsoleteDiagnosticKind.Suppressed;
                    default:
                        // If the context is unknown, then store the symbol so that we can do this check at a
                        // later stage
                        return ObsoleteDiagnosticKind.LazyPotentiallySuppressed;
                }
            }
        }
 
        /// <summary>
        /// Create a diagnostic for the given symbol. This could be an error or a warning based on
        /// the ObsoleteAttribute's arguments.
        /// </summary>
        internal static DiagnosticInfo CreateObsoleteDiagnostic(Symbol symbol, BinderFlags location)
        {
            DiagnosticInfo result = createObsoleteDiagnostic(symbol, location);
            Debug.Assert(result?.IsObsoleteDiagnostic() != false);
            return result;
 
            static DiagnosticInfo createObsoleteDiagnostic(Symbol symbol, BinderFlags location)
            {
                var data = symbol.ObsoleteAttributeData ?? symbol.ContainingModule.ObsoleteAttributeData ?? symbol.ContainingAssembly.ObsoleteAttributeData;
                Debug.Assert(data != null);
 
                if (data == null)
                {
                    return null;
                }
 
                // At this point, we are going to issue diagnostics and therefore the data shouldn't be
                // uninitialized.
                Debug.Assert(!data.IsUninitialized);
 
                // The native compiler suppresses Obsolete diagnostics in these locations.
                if (location.Includes(BinderFlags.SuppressObsoleteChecks))
                {
                    return null;
                }
 
                if (data.Kind == ObsoleteAttributeKind.WindowsExperimental)
                {
                    Debug.Assert(data.Message == null);
                    Debug.Assert(!data.IsError);
                    // Provide an explicit format for fully-qualified type names.
                    return new CSDiagnosticInfo(ErrorCode.WRN_WindowsExperimental,
                        new FormattedSymbol(symbol, SymbolDisplayFormat.CSharpErrorMessageFormat));
                }
 
                if (data.Kind == ObsoleteAttributeKind.Experimental)
                {
                    Debug.Assert(!data.IsError);
 
                    // Provide an explicit format for fully-qualified type names.
                    if (string.IsNullOrEmpty(data.Message))
                    {
                        return new CustomObsoleteDiagnosticInfo(MessageProvider.Instance, (int)ErrorCode.WRN_Experimental, data,
                            new FormattedSymbol(symbol, SymbolDisplayFormat.CSharpErrorMessageFormat));
                    }
                    else
                    {
                        return new CustomObsoleteDiagnosticInfo(MessageProvider.Instance, (int)ErrorCode.WRN_ExperimentalWithMessage, data,
                            new FormattedSymbol(symbol, SymbolDisplayFormat.CSharpErrorMessageFormat), data.Message);
                    }
                }
 
                // Issue a specialized diagnostic for add methods of collection initializers
                var isColInit = location.Includes(BinderFlags.CollectionInitializerAddMethod);
                Debug.Assert(!isColInit || symbol.Name == WellKnownMemberNames.CollectionInitializerAddMethodName);
                var errorCode = (message: data.Message, isError: data.IsError, isColInit) switch
                {
                    // dev11 had a bug in this area (i.e. always produce a warning when there's no message) and we have to match it.
                    (message: null, isError: _, isColInit: true) => ErrorCode.WRN_DeprecatedCollectionInitAdd,
                    (message: null, isError: _, isColInit: false) => ErrorCode.WRN_DeprecatedSymbol,
                    (message: { }, isError: true, isColInit: true) => ErrorCode.ERR_DeprecatedCollectionInitAddStr,
                    (message: { }, isError: true, isColInit: false) => ErrorCode.ERR_DeprecatedSymbolStr,
                    (message: { }, isError: false, isColInit: true) => ErrorCode.WRN_DeprecatedCollectionInitAddStr,
                    (message: { }, isError: false, isColInit: false) => ErrorCode.WRN_DeprecatedSymbolStr
                };
 
                var arguments = data.Message is string message
                    ? new object[] { symbol, message }
                    : new object[] { symbol };
 
                return new CustomObsoleteDiagnosticInfo(MessageProvider.Instance, (int)errorCode, data, arguments);
            }
        }
 
        internal static bool IsObsoleteDiagnostic(this DiagnosticInfo diagnosticInfo)
        {
            return (ErrorCode)diagnosticInfo.Code is
                       (ErrorCode.WRN_Experimental or ErrorCode.WRN_ExperimentalWithMessage or ErrorCode.WRN_WindowsExperimental or ErrorCode.WRN_DeprecatedCollectionInitAdd or
                        ErrorCode.WRN_DeprecatedSymbol or ErrorCode.ERR_DeprecatedCollectionInitAddStr or
                        ErrorCode.ERR_DeprecatedSymbolStr or ErrorCode.WRN_DeprecatedCollectionInitAddStr or
                        ErrorCode.WRN_DeprecatedSymbolStr);
        }
    }
}