File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Extensions\SymbolUsageInfo.cs
Web Access
Project: src\src\RoslynAnalyzers\PerformanceSensitiveAnalyzers\Core\Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.csproj (Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers)
// 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.Diagnostics;
using System.Runtime.Serialization;
using System.Threading;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis;
 
/// <summary>
/// Provides information about the way a particular symbol is being used at a symbol reference node.
/// For namespaces and types, this corresponds to values from <see cref="TypeOrNamespaceUsageInfo"/>.
/// For methods, fields, properties, events, locals and parameters, this corresponds to values from <see cref="ValueUsageInfo"/>.
/// </summary>
[DataContract]
internal readonly record struct SymbolUsageInfo
{
    public static readonly SymbolUsageInfo None = Create(ValueUsageInfo.None);
 
    [DataMember(Order = 0)]
    public ValueUsageInfo? ValueUsageInfoOpt { get; }
 
    [DataMember(Order = 1)]
    public TypeOrNamespaceUsageInfo? TypeOrNamespaceUsageInfoOpt { get; }
 
    // Must be public since it's used for deserialization.
    public SymbolUsageInfo(ValueUsageInfo? valueUsageInfoOpt, TypeOrNamespaceUsageInfo? typeOrNamespaceUsageInfoOpt)
    {
        Debug.Assert(valueUsageInfoOpt.HasValue ^ typeOrNamespaceUsageInfoOpt.HasValue);
 
        ValueUsageInfoOpt = valueUsageInfoOpt;
        TypeOrNamespaceUsageInfoOpt = typeOrNamespaceUsageInfoOpt;
    }
 
    public static SymbolUsageInfo Create(ValueUsageInfo valueUsageInfo)
        => new(valueUsageInfo, typeOrNamespaceUsageInfoOpt: null);
 
    public static SymbolUsageInfo Create(TypeOrNamespaceUsageInfo typeOrNamespaceUsageInfo)
        => new(valueUsageInfoOpt: null, typeOrNamespaceUsageInfo);
 
    public bool IsReadFrom()
        => ValueUsageInfoOpt.HasValue && ValueUsageInfoOpt.Value.IsReadFrom();
 
    public bool IsWrittenTo()
        => ValueUsageInfoOpt.HasValue && ValueUsageInfoOpt.Value.IsWrittenTo();
 
    public static SymbolUsageInfo GetSymbolUsageInfo(
        ISemanticFacts semanticFacts,
        SemanticModel semanticModel,
        SyntaxNode node,
        CancellationToken cancellationToken)
    {
        var syntaxFacts = semanticFacts.SyntaxFacts;
 
        var topNameNode = node;
        while (syntaxFacts.IsQualifiedName(topNameNode.Parent))
            topNameNode = topNameNode.Parent;
 
        var parent = topNameNode?.Parent;
 
        // typeof/sizeof are a special case where we don't want to return a TypeOrNamespaceUsageInfo, but rather a ValueUsageInfo.Name.
        // This brings it in line with nameof(...), making all those operators appear in a similar fashion.
        if (parent?.RawKind == syntaxFacts.SyntaxKinds.TypeOfExpression ||
            parent?.RawKind == syntaxFacts.SyntaxKinds.SizeOfExpression)
        {
            return new(ValueUsageInfo.Name, typeOrNamespaceUsageInfoOpt: null);
        }
 
        var isInNamespaceNameContext = syntaxFacts.IsBaseNamespaceDeclaration(parent);
        return syntaxFacts.IsInNamespaceOrTypeContext(topNameNode)
            ? Create(GetTypeOrNamespaceUsageInfo())
            : GetSymbolUsageInfoCommon();
 
        // Local functions.
        TypeOrNamespaceUsageInfo GetTypeOrNamespaceUsageInfo()
        {
            var usageInfo = IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts) || syntaxFacts.IsLeftSideOfExplicitInterfaceSpecifier(node)
                ? TypeOrNamespaceUsageInfo.Qualified
                : TypeOrNamespaceUsageInfo.None;
 
            if (isInNamespaceNameContext)
            {
                usageInfo |= TypeOrNamespaceUsageInfo.NamespaceDeclaration;
            }
            else if (node.FirstAncestorOrSelf<SyntaxNode, ISyntaxFacts>((node, syntaxFacts) => syntaxFacts.IsUsingOrExternOrImport(node), syntaxFacts) != null)
            {
                usageInfo |= TypeOrNamespaceUsageInfo.Import;
            }
 
            while (syntaxFacts.IsQualifiedName(node.Parent))
                node = node.Parent;
 
            if (syntaxFacts.IsTypeArgumentList(node.Parent))
            {
                usageInfo |= TypeOrNamespaceUsageInfo.TypeArgument;
            }
            else if (syntaxFacts.IsTypeConstraint(node.Parent))
            {
                usageInfo |= TypeOrNamespaceUsageInfo.TypeConstraint;
            }
            else if (syntaxFacts.IsBaseTypeList(node.Parent) ||
                syntaxFacts.IsBaseTypeList(node.Parent?.Parent))
            {
                usageInfo |= TypeOrNamespaceUsageInfo.Base;
            }
            else if (syntaxFacts.IsTypeOfObjectCreationExpression(node))
            {
                usageInfo |= TypeOrNamespaceUsageInfo.ObjectCreation;
            }
 
            return usageInfo;
        }
 
        SymbolUsageInfo GetSymbolUsageInfoCommon()
        {
            if (semanticFacts.IsInOutContext(semanticModel, node, cancellationToken))
            {
                return Create(ValueUsageInfo.WritableReference);
            }
            else if (semanticFacts.IsInRefContext(semanticModel, node, cancellationToken))
            {
                return Create(ValueUsageInfo.ReadableWritableReference);
            }
            else if (semanticFacts.IsInInContext(semanticModel, node, cancellationToken))
            {
                return Create(ValueUsageInfo.ReadableReference);
            }
            else if (semanticFacts.IsOnlyWrittenTo(semanticModel, node, cancellationToken))
            {
                return Create(ValueUsageInfo.Write);
            }
            else
            {
                var operation = semanticModel.GetOperation(node, cancellationToken);
                if (operation is IObjectCreationOperation)
                    return Create(TypeOrNamespaceUsageInfo.ObjectCreation);
 
                // Note: sizeof/typeof also return 'name', but are handled above in GetSymbolUsageInfo.
                if (operation?.Parent is INameOfOperation)
                    return Create(ValueUsageInfo.Name);
 
                if (node.IsPartOfStructuredTrivia())
                    return Create(ValueUsageInfo.Name);
 
                var symbolInfo = semanticModel.GetSymbolInfo(node, cancellationToken);
                if (symbolInfo.Symbol != null)
                {
                    switch (symbolInfo.Symbol.Kind)
                    {
                        case SymbolKind.Namespace:
                            var namespaceUsageInfo = TypeOrNamespaceUsageInfo.None;
                            if (isInNamespaceNameContext)
                                namespaceUsageInfo |= TypeOrNamespaceUsageInfo.NamespaceDeclaration;
 
                            if (IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts))
                                namespaceUsageInfo |= TypeOrNamespaceUsageInfo.Qualified;
 
                            return Create(namespaceUsageInfo);
 
                        case SymbolKind.NamedType:
                            var typeUsageInfo = TypeOrNamespaceUsageInfo.None;
                            if (IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts))
                                typeUsageInfo |= TypeOrNamespaceUsageInfo.Qualified;
 
                            return Create(typeUsageInfo);
 
                        case SymbolKind.Method:
                        case SymbolKind.Property:
                        case SymbolKind.Field:
                        case SymbolKind.Event:
                        case SymbolKind.Parameter:
                        case SymbolKind.Local:
                            var valueUsageInfo = ValueUsageInfo.Read;
                            if (semanticFacts.IsWrittenTo(semanticModel, node, cancellationToken))
                                valueUsageInfo |= ValueUsageInfo.Write;
 
                            return Create(valueUsageInfo);
                    }
                }
 
                return SymbolUsageInfo.None;
            }
        }
    }
 
    private static bool IsNodeOrAnyAncestorLeftSideOfDot(SyntaxNode node, ISyntaxFacts syntaxFacts)
    {
        if (syntaxFacts.IsLeftSideOfDot(node))
        {
            return true;
        }
 
        if (syntaxFacts.IsRightOfQualifiedName(node) ||
            syntaxFacts.IsNameOfSimpleMemberAccessExpression(node) ||
            syntaxFacts.IsNameOfMemberBindingExpression(node))
        {
            return syntaxFacts.IsLeftSideOfDot(node.Parent);
        }
 
        return false;
    }
}