File: Symbols\Source\OverloadingHelper.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 Microsoft.CodeAnalysis.PooledObjects
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
    ''' <summary>
    ''' Function that help implement the overloading rules for VB, in particular the rules
    ''' for recasing method and property names.
    ''' </summary>
    Friend Module OverloadingHelper
 
        ''' <summary>
        ''' Set the correct metadata name for all overloads of a particular name and symbol kind
        ''' (must be method or property) inside a container.
        ''' 
        ''' The rules are as follows:
        '''    1) If a method or property overrides one from its base class, its metadata name
        '''       must match that.
        '''    2) If method overloads those in the base (because the Overloads keyword is used), and
        '''       all metadata names in the base are consistent in case, use that name.
        '''    3) All overloads with a class should match, except possibly for overrides. If there is
        '''       an override or overload from base, use that. Otherwise, use casing of first member in
        '''       class.
        ''' </summary>
        Public Sub SetMetadataNameForAllOverloads(name As String, kind As SymbolKind, container As NamedTypeSymbol)
            Debug.Assert(kind = SymbolKind.Method OrElse kind = SymbolKind.Property)
 
            Dim compilation = container.DeclaringCompilation
 
            Dim overloadedMembers = ArrayBuilder(Of Symbol).GetInstance()    ' the set of overloaded symbols.
            Dim hasOverloadSpecifier As Boolean = False                      ' do any overloads have "Overloads" (not counting Overrides)
            Dim hasOverrideSpecifier As Boolean = False                      ' do any overloads have "Overrides" 
 
            Dim metadataName As String = Nothing                             ' the metadata name we have chosen
 
            Try
                ' Check for overloads or overrides.
                ' Find all the overloads that we are processing, and if any have "Overloads" or "Overrides"
                FindOverloads(name, kind, container, overloadedMembers, hasOverloadSpecifier, hasOverrideSpecifier)
 
                If overloadedMembers.Count = 1 AndAlso Not hasOverloadSpecifier AndAlso Not hasOverrideSpecifier Then
                    ' Quick, common case: one symbol of name in type, no "Overrides" or "Overloads".
                    ' Just use the current name.
                    overloadedMembers(0).SetMetadataName(overloadedMembers(0).Name)
                    Return
                ElseIf hasOverrideSpecifier Then
                    ' Note: in error conditions (an override didn't exist), this could return Nothing.
                    ' That is dealt with below. 
                    metadataName = SetMetadataNamesOfOverrides(overloadedMembers, compilation)
                ElseIf hasOverloadSpecifier Then
                    metadataName = GetBaseMemberMetadataName(name, kind, container)
                End If
 
                If metadataName Is Nothing Then
                    ' We did not get a name from the overrides or base class. Pick the name of the first member
                    metadataName = NameOfFirstMember(overloadedMembers, compilation)
                End If
 
                ' We now have the metadata name we want to apply to each non-override member
                ' (override member names have already been applied)
                For Each member In overloadedMembers
                    If Not (member.IsOverrides AndAlso member.OverriddenMember() IsNot Nothing) Then
                        member.SetMetadataName(metadataName)
                    End If
                Next
            Finally
                overloadedMembers.Free()
            End Try
        End Sub
 
        ''' <summary>
        ''' Collect all overloads in "container" of the given name and kind.
        ''' Also determine if any have "Overloads" or "Overrides" specifiers.
        ''' </summary>
        Private Sub FindOverloads(name As String,
                                  kind As SymbolKind,
                                  container As NamedTypeSymbol,
                                  overloadsMembers As ArrayBuilder(Of Symbol),
                                  ByRef hasOverloadSpecifier As Boolean,
                                  ByRef hasOverrideSpecifier As Boolean)
            For Each member In container.GetMembers(name)
                If IsCandidateMember(member, kind) Then
                    overloadsMembers.Add(member)
 
                    If member.IsOverrides Then
                        hasOverrideSpecifier = True
                    ElseIf member.IsOverloads Then
                        hasOverloadSpecifier = True
                    End If
                End If
            Next
        End Sub
 
        ''' <summary>
        ''' For each member in "overloadedMembers" that is marked Overrides, set its
        ''' metadata name to be the metadata name of its overridden member. Return the
        ''' first such name, lexically.
        ''' 
        ''' Note: can return null if no override member with an actual overridden member was found.
        ''' </summary>
        Private Function SetMetadataNamesOfOverrides(overloadedMembers As ArrayBuilder(Of Symbol), compilation As VisualBasicCompilation) As String
            Dim locationOfFirstOverride As Location = Nothing
            Dim firstOverrideName As String = Nothing
 
            For Each member In overloadedMembers
                If member.IsOverrides Then
                    Dim overriddenMember As Symbol = member.OverriddenMember()
                    If overriddenMember IsNot Nothing Then
                        Dim metadataName As String = overriddenMember.MetadataName
                        member.SetMetadataName(metadataName)
 
                        ' Remember the metadata name of the lexically first override
                        If firstOverrideName Is Nothing OrElse compilation.CompareSourceLocations(member.Locations(0), locationOfFirstOverride) < 0 Then
                            firstOverrideName = metadataName
                            locationOfFirstOverride = member.Locations(0)
                        End If
                    End If
                End If
            Next
 
            Return firstOverrideName
        End Function
 
        ''' <summary>
        ''' Return the name of the lexically first symbol in "overloadedMembers".
        ''' </summary>
        Private Function NameOfFirstMember(overloadedMembers As ArrayBuilder(Of Symbol), compilation As VisualBasicCompilation) As String
            Dim firstName As String = Nothing
            Dim locationOfFirstName As Location = Nothing
            For Each member In overloadedMembers
                Dim memberLocation = member.Locations(0)
                If firstName Is Nothing OrElse compilation.CompareSourceLocations(memberLocation, locationOfFirstName) < 0 Then
                    firstName = member.Name
                    locationOfFirstName = memberLocation
                End If
            Next
 
            Return firstName
        End Function
 
        ''' <summary>
        ''' Check all accessible, visible members of the base types of container for the given name and kind. If they
        ''' all have the same case-sensitive metadata name, return that name. Otherwise, return Nothing.
        ''' </summary>
        Private Function GetBaseMemberMetadataName(name As String, kind As SymbolKind, container As NamedTypeSymbol) As String
            Dim metadataName As String = Nothing
            Dim metadataLocation As Location = Nothing
 
            ' We are creating a binder for the first partial declaration, so we can use member lookup to find accessible & visible
            ' members. For the lookup we are doing, it doesn't matter which partial we use because Imports and Options can't
            ' affect a lookup that ignores extension methods.
            Dim binder = BinderBuilder.CreateBinderForType(DirectCast(container.ContainingModule, SourceModuleSymbol),
                                                           container.Locations(0).PossiblyEmbeddedOrMySourceTree(),
                                                           container)
 
            Dim result = LookupResult.GetInstance()
            binder.LookupMember(result, container, name, 0, LookupOptions.AllMethodsOfAnyArity Or LookupOptions.IgnoreExtensionMethods, useSiteInfo:=CompoundUseSiteInfo(Of AssemblySymbol).Discarded)
            If result.IsGoodOrAmbiguous Then
                Dim lookupSymbols As ArrayBuilder(Of Symbol) = result.Symbols
                If result.Kind = LookupResultKind.Ambiguous AndAlso result.HasDiagnostic AndAlso TypeOf result.Diagnostic Is AmbiguousSymbolDiagnostic Then
                    lookupSymbols.AddRange(DirectCast(result.Diagnostic, AmbiguousSymbolDiagnostic).AmbiguousSymbols)
                End If
 
                For Each foundMember In lookupSymbols
                    ' Go through each member found in a base class or interface
                    If IsCandidateMember(foundMember, kind) AndAlso foundMember.ContainingType IsNot container Then
                        If metadataName Is Nothing Then
                            metadataName = foundMember.MetadataName
                        Else
                            ' Intentionally using case-sensitive comparison here.
                            If Not String.Equals(metadataName, foundMember.MetadataName, StringComparison.Ordinal) Then
                                ' We have found two members with conflicting casing of metadata names.
                                metadataName = Nothing
                                Exit For
                            End If
                        End If
                    End If
                Next
            End If
 
            result.Free()
 
            Return metadataName
        End Function
 
        Private Function IsCandidateMember(member As Symbol, kind As SymbolKind) As Boolean
            Return member.Kind = kind AndAlso Not member.IsAccessor()
        End Function
 
    End Module
End Namespace