File: Symbols\ObsoleteAttributeHelpers.vb
Web Access
Project: src\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' 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.
 
Imports System.Diagnostics
Imports System.Reflection.Metadata
Imports System.Threading
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
 
    Friend Enum ObsoleteDiagnosticKind
        NotObsolete
        Suppressed
        Diagnostic
        Lazy
        LazyPotentiallySuppressed
    End Enum
 
    Friend NotInheritable 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>
        Friend Shared Sub InitializeObsoleteDataFromMetadata(ByRef data As ObsoleteAttributeData, token As EntityHandle, containingModule As PEModuleSymbol)
            If data Is ObsoleteAttributeData.Uninitialized Then
                Dim obsoleteAttributeData As ObsoleteAttributeData = GetObsoleteDataFromMetadata(token, containingModule)
                Interlocked.CompareExchange(data, obsoleteAttributeData, ObsoleteAttributeData.Uninitialized)
            End If
        End Sub
 
        Friend Shared Function GetObsoleteDataFromMetadata(token As EntityHandle, containingModule As PEModuleSymbol) As ObsoleteAttributeData
            Dim obsoleteAttributeData As ObsoleteAttributeData = Nothing
            ' ignoreByRefLikeMarker := False, since VB does not support ref-like types
            obsoleteAttributeData = containingModule.Module.TryGetDeprecatedOrExperimentalOrObsoleteAttribute(token, New MetadataDecoder(containingModule), ignoreByRefLikeMarker:=False, ignoreRequiredMemberMarker:=True)
            Debug.Assert(obsoleteAttributeData Is Nothing OrElse Not obsoleteAttributeData.IsUninitialized)
            Return obsoleteAttributeData
        End Function
 
        ''' <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 Shared Function GetObsoleteContextState(symbol As Symbol, forceComplete As Boolean, getStateFromSymbol As Func(Of Symbol, ThreeState)) As ThreeState
            While symbol IsNot Nothing
                If forceComplete Then
                    symbol.ForceCompleteObsoleteAttribute()
                End If
 
                Dim state = getStateFromSymbol(symbol)
                If state <> ThreeState.False Then
                    Return state
                End If
 
                ' For property or event accessors, check the associated property or event instead.
                If symbol.IsAccessor() Then
                    symbol = DirectCast(symbol, MethodSymbol).AssociatedSymbol
                Else
                    symbol = symbol.ContainingSymbol
                End If
            End While
 
            Return ThreeState.False
        End Function
 
        Friend Shared Function GetObsoleteDiagnosticKind(context As Symbol, symbol As Symbol, Optional forceComplete As Boolean = False) As ObsoleteDiagnosticKind
            Debug.Assert(context IsNot Nothing)
            Debug.Assert(symbol IsNot Nothing)
 
            Select Case symbol.ObsoleteKind
                Case ObsoleteAttributeKind.None
                    Dim moduleObsoleteKind As ObsoleteAttributeKind? = symbol.ContainingModule?.ObsoleteKind
                    Dim assemblyObsoleteKind As ObsoleteAttributeKind? = symbol.ContainingAssembly?.ObsoleteKind
 
                    If moduleObsoleteKind = Global.Microsoft.CodeAnalysis.ObsoleteAttributeKind.Experimental OrElse
                       assemblyObsoleteKind = Global.Microsoft.CodeAnalysis.ObsoleteAttributeKind.Experimental Then
 
                        Return GetDiagnosticKind(context, forceComplete, getStateFromSymbol:=Function(s) s.ExperimentalState)
                    End If
 
                    If moduleObsoleteKind = Global.Microsoft.CodeAnalysis.ObsoleteAttributeKind.Uninitialized OrElse
                       assemblyObsoleteKind = Global.Microsoft.CodeAnalysis.ObsoleteAttributeKind.Uninitialized Then
 
                        Return ObsoleteDiagnosticKind.Lazy
                    End If
 
                    Return ObsoleteDiagnosticKind.NotObsolete
                Case ObsoleteAttributeKind.WindowsExperimental
                    Return ObsoleteDiagnosticKind.Diagnostic
                Case ObsoleteAttributeKind.Experimental
                    Return GetDiagnosticKind(context, forceComplete, getStateFromSymbol:=Function(s) s.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
            End Select
 
            Return GetDiagnosticKind(context, forceComplete, getStateFromSymbol:=Function(s) s.ObsoleteState)
        End Function
 
        Private Shared Function GetDiagnosticKind(containingMember As Symbol, forceComplete As Boolean, getStateFromSymbol As Func(Of Symbol, ThreeState)) As ObsoleteDiagnosticKind
 
            Select Case 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
                Case Else
                    ' If the context is unknown, then store the symbol so that we can do this check at a
                    ' later stage
                    Return ObsoleteDiagnosticKind.LazyPotentiallySuppressed
            End Select
        End Function
 
        ''' <summary>
        ''' Create a diagnostic for the given symbol. This could be an error or a warning based on
        ''' the ObsoleteAttribute's arguments.
        ''' </summary>
        Friend Shared Function CreateObsoleteDiagnostic(symbol As Symbol) As DiagnosticInfo
            Dim data = If(If(symbol.ObsoleteAttributeData, symbol.ContainingModule.ObsoleteAttributeData), symbol.ContainingAssembly.ObsoleteAttributeData)
 
            Debug.Assert(data IsNot Nothing)
 
            If data Is Nothing Then
                Return Nothing
            End If
 
            ' At this point, we are going to issue diagnostics and therefore the data shouldn't be
            ' uninitialized.
            Debug.Assert(Not data.IsUninitialized)
 
            If data.Kind = ObsoleteAttributeKind.WindowsExperimental Then
                Debug.Assert(data.Message Is Nothing)
                Debug.Assert(Not data.IsError)
                ' Provide an explicit format for fully-qualified type names.
                Return ErrorFactory.ErrorInfo(ERRID.WRN_Experimental, New FormattedSymbol(symbol, SymbolDisplayFormat.VisualBasicErrorMessageFormat))
            End If
 
            If data.Kind = ObsoleteAttributeKind.Experimental Then
                Debug.Assert(data.Message Is Nothing)
                Debug.Assert(Not data.IsError)
                ' Provide an explicit format for fully-qualified type names.
                Return New CustomObsoleteDiagnosticInfo(MessageProvider.Instance, ERRID.WRN_Experimental,
                    data, New FormattedSymbol(symbol, SymbolDisplayFormat.VisualBasicErrorMessageFormat))
            End If
 
            ' For property accessors we report a special diagnostic which indicates whether the getter or setter is obsolete.
            ' For all other symbols, report the regular diagnostic.
            If symbol.IsAccessor() AndAlso (DirectCast(symbol, MethodSymbol).AssociatedSymbol).Kind = SymbolKind.Property Then
                Dim accessorSymbol = DirectCast(symbol, MethodSymbol)
                Dim accessorString = If(accessorSymbol.MethodKind = MethodKind.PropertyGet, "Get", "Set")
 
                If String.IsNullOrEmpty(data.Message) Then
                    Return ErrorFactory.ObsoleteErrorInfo(If(data.IsError, ERRID.ERR_UseOfObsoletePropertyAccessor2, ERRID.WRN_UseOfObsoletePropertyAccessor2), data,
                                        accessorString, accessorSymbol.AssociatedSymbol)
                Else
                    Return ErrorFactory.ObsoleteErrorInfo(If(data.IsError, ERRID.ERR_UseOfObsoletePropertyAccessor3, ERRID.WRN_UseOfObsoletePropertyAccessor3), data,
                                        accessorString, accessorSymbol.AssociatedSymbol, data.Message)
                End If
            Else
                If String.IsNullOrEmpty(data.Message) Then
                    Return ErrorFactory.ObsoleteErrorInfo(If(data.IsError, ERRID.ERR_UseOfObsoleteSymbolNoMessage1, ERRID.WRN_UseOfObsoleteSymbolNoMessage1), data, symbol)
                Else
                    Return ErrorFactory.ObsoleteErrorInfo(If(data.IsError, ERRID.ERR_UseOfObsoleteSymbol2, ERRID.WRN_UseOfObsoleteSymbol2), data, symbol, data.Message)
                End If
            End If
 
        End Function
 
    End Class
End Namespace