File: Shared\Extensions\SemanticModelExtensions.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Shared.Extensions;
 
internal static partial class SemanticModelExtensions
{
    public static SemanticMap GetSemanticMap(this SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
        => SemanticMap.From(semanticModel, node, cancellationToken);
 
    private static ISymbol? MapSymbol(ISymbol? symbol, ITypeSymbol? type)
    {
        if (symbol.IsConstructor() && symbol.ContainingType.IsAnonymousType)
        {
            return symbol.ContainingType;
        }
 
        if (symbol.IsThisParameter())
        {
            // Map references to this/base to the actual type that those correspond to.
            return type;
        }
 
        if (symbol.IsFunctionValue() &&
            symbol.ContainingSymbol is IMethodSymbol method)
        {
            if (method.AssociatedSymbol != null)
            {
                return method.AssociatedSymbol;
            }
            else
            {
                return method;
            }
        }
 
        // see if we can map the built-in language operator to a real method on the containing
        // type of the symbol.  built-in operators can happen when querying the semantic model
        // for operators.  However, we would prefer to just use the real operator on the type
        // if it has one.
        if (symbol is IMethodSymbol methodSymbol &&
            methodSymbol.MethodKind == MethodKind.BuiltinOperator &&
            methodSymbol.ContainingType is ITypeSymbol containingType)
        {
            var comparer = SymbolEquivalenceComparer.Instance.ParameterEquivalenceComparer;
 
            // Note: this will find the real method vs the built-in.  That's because the
            // built-in is synthesized operator that isn't actually in the list of members of
            // its 'ContainingType'.
            var mapped = containingType.GetMembers(methodSymbol.Name)
                                       .OfType<IMethodSymbol>()
                                       .FirstOrDefault(s => s.Parameters.SequenceEqual(methodSymbol.Parameters, comparer));
            symbol = mapped ?? symbol;
        }
 
        return symbol;
    }
 
    public static TokenSemanticInfo GetSemanticInfo(
        this SemanticModel semanticModel,
        SyntaxToken token,
        SolutionServices services,
        CancellationToken cancellationToken)
    {
        var languageServices = services.GetLanguageServices(token.Language);
        var syntaxFacts = languageServices.GetRequiredService<ISyntaxFactsService>();
        var syntaxKinds = languageServices.GetRequiredService<ISyntaxKindsService>();
 
        if (!syntaxFacts.IsBindableToken(token))
            return TokenSemanticInfo.Empty;
 
        var semanticFacts = languageServices.GetRequiredService<ISemanticFactsService>();
        var overriddingIdentifier = syntaxFacts.GetDeclarationIdentifierIfOverride(token);
 
        IAliasSymbol? aliasSymbol = null;
        ITypeSymbol? type = null;
        ITypeSymbol? convertedType = null;
        ISymbol? declaredSymbol = null;
        IPreprocessingSymbol? preprocessingSymbol = null;
        var allSymbols = ImmutableArray<ISymbol?>.Empty;
 
        var tokenParent = token.Parent;
        if (token.RawKind == syntaxKinds.UsingKeyword &&
            (tokenParent?.RawKind == syntaxKinds.UsingStatement || tokenParent?.RawKind == syntaxKinds.LocalDeclarationStatement))
        {
            var usingStatement = token.Parent;
            declaredSymbol = semanticFacts.TryGetDisposeMethod(semanticModel, tokenParent, cancellationToken);
        }
        else if (overriddingIdentifier.HasValue)
        {
            // on an "override" token, we'll find the overridden symbol
            var overriddingSymbol = semanticFacts.GetDeclaredSymbol(semanticModel, overriddingIdentifier.Value, cancellationToken);
            var overriddenSymbol = overriddingSymbol.GetOverriddenMember(allowLooseMatch: true);
 
            allSymbols = overriddenSymbol is null ? [] : [overriddenSymbol];
        }
        else
        {
            Debug.Assert(tokenParent is not null);
            aliasSymbol = semanticModel.GetAliasInfo(tokenParent, cancellationToken);
            var bindableParent = syntaxFacts.TryGetBindableParent(token);
            var typeInfo = bindableParent != null ? semanticModel.GetTypeInfo(bindableParent, cancellationToken) : default;
            type = typeInfo.Type;
            convertedType = typeInfo.ConvertedType;
            declaredSymbol = MapSymbol(semanticFacts.GetDeclaredSymbol(semanticModel, token, cancellationToken), type);
            preprocessingSymbol = semanticFacts.GetPreprocessingSymbol(semanticModel, tokenParent);
 
            if (preprocessingSymbol != null)
            {
                allSymbols = [preprocessingSymbol];
            }
            else if (!declaredSymbol.IsKind(SymbolKind.RangeVariable))
            {
                allSymbols = semanticFacts
                    .GetBestOrAllSymbols(semanticModel, bindableParent, token, cancellationToken)
                    .WhereAsArray(s => s != null && !s.Equals(declaredSymbol))
                    .SelectAsArray(s => MapSymbol(s, type));
            }
        }
 
        // NOTE(cyrusn): This is a workaround to how the semantic model binds and returns
        // information for VB event handlers.  Namely, if you have:
        //
        // Event X]()
        // Sub Goo()
        //      Dim y = New $$XEventHandler(AddressOf bar)
        // End Sub
        //
        // Only GetTypeInfo will return any information for XEventHandler.  So, in this
        // case, we upgrade the type to be the symbol we return.
        if (type != null && allSymbols.Length == 0)
        {
            if (type.Kind == SymbolKind.NamedType)
            {
                var namedType = (INamedTypeSymbol)type;
                if (namedType.TypeKind == TypeKind.Delegate ||
                    namedType.AssociatedSymbol != null)
                {
                    allSymbols = [type];
                    type = null;
                }
            }
        }
 
        if (allSymbols.Length == 0 && syntaxFacts.IsQueryKeyword(token))
        {
            type = null;
            convertedType = null;
        }
 
        return new TokenSemanticInfo(declaredSymbol, preprocessingSymbol, aliasSymbol, allSymbols, type, convertedType, token.Span);
    }
}