// 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; } } |