| File: SymbolDisplay\SymbolDisplayVisitor_Minimal.cs | Web Access |
| Project: src\roslyn\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp) |
// 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.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal partial class SymbolDisplayVisitor { private bool TryAddAlias( INamespaceOrTypeSymbol symbol, ArrayBuilder<SymbolDisplayPart> builder) { var alias = GetAliasSymbol(symbol); if (alias != null) { Debug.Assert(IsMinimizing); // We must verify that the alias actually binds back to the thing it's aliasing. // It's possible there's another symbol with the same name as the alias that binds // first var aliasName = alias.Name; var boundSymbols = SemanticModelOpt.LookupNamespacesAndTypes(PositionOpt, name: aliasName); if (boundSymbols.Length == 1) { var boundAlias = boundSymbols[0] as IAliasSymbol; if ((object?)boundAlias != null && alias.Target.Equals(symbol)) { builder.Add(CreatePart(SymbolDisplayPartKind.AliasName, alias, aliasName)); return true; } } } return false; } protected override bool ShouldRestrictMinimallyQualifyLookupToNamespacesAndTypes() { Debug.Assert(IsMinimizing); var token = SemanticModelOpt.SyntaxTree.GetRoot().FindToken(PositionOpt); var startNode = token.Parent; return SyntaxFacts.IsInNamespaceOrTypeContext(startNode as ExpressionSyntax) || token.IsKind(SyntaxKind.NewKeyword) || this.InNamespaceOrType; } private void MinimallyQualify(INamespaceSymbol symbol) { Debug.Assert(IsMinimizing); // only the global namespace does not have a containing namespace Debug.Assert(symbol.ContainingNamespace != null || symbol.IsGlobalNamespace); // NOTE(cyrusn): We only call this once we've already checked if there is an alias that // corresponds to this namespace. if (symbol.IsGlobalNamespace) { // nothing to add for global namespace itself return; } // Check if the name of this namespace binds to the same namespace symbol. If so, // then that's all we need to add. Otherwise, we will add the minimally qualified // version of our parent, and then add ourselves to that. var symbols = ShouldRestrictMinimallyQualifyLookupToNamespacesAndTypes() ? SemanticModelOpt.LookupNamespacesAndTypes(PositionOpt, name: symbol.Name) : SemanticModelOpt.LookupSymbols(PositionOpt, name: symbol.Name); var firstSymbol = symbols.OfType<ISymbol>().FirstOrDefault(); if (symbols.Length != 1 || firstSymbol == null || !firstSymbol.Equals(symbol)) { // Just the name alone didn't bind properly. Add our minimally qualified parent (if // we have one), a dot, and then our name. var containingNamespace = symbol.ContainingNamespace == null ? null : SemanticModelOpt.Compilation.GetCompilationNamespace(symbol.ContainingNamespace); if (containingNamespace != null) { if (containingNamespace.IsGlobalNamespace) { Debug.Assert(Format.GlobalNamespaceStyle == SymbolDisplayGlobalNamespaceStyle.Included || Format.GlobalNamespaceStyle == SymbolDisplayGlobalNamespaceStyle.Omitted || Format.GlobalNamespaceStyle == SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining); if (Format.GlobalNamespaceStyle == SymbolDisplayGlobalNamespaceStyle.Included) { AddGlobalNamespace(containingNamespace); AddPunctuation(SyntaxKind.ColonColonToken); } } else { containingNamespace.Accept(this.NotFirstVisitor); AddPunctuation(SyntaxKind.DotToken); } } } // If we bound properly, then we'll just add our name. Builder.Add(CreatePart(SymbolDisplayPartKind.NamespaceName, symbol, symbol.Name)); } private void MinimallyQualify(INamedTypeSymbol symbol) { // NOTE(cyrusn): We only call this once we've already checked if there is an alias or // special type that corresponds to this type. // // We first start by trying to bind just our name and type arguments. If they bind to // the symbol that we were constructed from, then we have our minimal name. Otherwise, // we get the minimal name of our parent, add a dot, and then add ourselves. // TODO(cyrusn): This code needs to see if type is an attribute and if it can be shown // in simplified form here. if (!(symbol.IsAnonymousType || symbol.IsTupleType)) { Debug.Assert(IsMinimizing); if (!NameBoundSuccessfullyToSameSymbol(symbol)) { // Just the name alone didn't bind properly. Add our minimally qualified parent (if // we have one), a dot, and then our name. if (IncludeNamedType(symbol.ContainingType)) { symbol.ContainingType.Accept(this.NotFirstVisitor); AddPunctuation(SyntaxKind.DotToken); } else { var containingNamespace = symbol.ContainingNamespace == null ? null : SemanticModelOpt.Compilation.GetCompilationNamespace(symbol.ContainingNamespace); if (containingNamespace != null) { if (containingNamespace.IsGlobalNamespace) { // Error symbols are put into the global namespace if the compiler has // no better guess for it, so we shouldn't go spitting it everywhere. if (symbol.TypeKind != TypeKind.Error) { AddKeyword(SyntaxKind.GlobalKeyword); AddPunctuation(SyntaxKind.ColonColonToken); } } else { containingNamespace.Accept(this.NotFirstVisitor); AddPunctuation(SyntaxKind.DotToken); } } } } } AddNameAndTypeArgumentsOrParameters(symbol); } private IDictionary<INamespaceOrTypeSymbol, IAliasSymbol> CreateAliasMap() { if (!this.IsMinimizing) { return SpecializedCollections.EmptyDictionary<INamespaceOrTypeSymbol, IAliasSymbol>(); } // Walk up the ancestors from the current position. If this is a speculative // model, walk up the corresponding ancestors in the parent model. SemanticModel semanticModel; int position; if (SemanticModelOpt.IsSpeculativeSemanticModel) { semanticModel = SemanticModelOpt.ParentModel; position = SemanticModelOpt.OriginalPositionForSpeculation; } else { semanticModel = SemanticModelOpt; position = PositionOpt; } var token = semanticModel.SyntaxTree.GetRoot().FindToken(position); var startNode = token.Parent!; // NOTE(cyrusn): If we're currently in a block of usings, then we want to collect the // aliases that are higher up than this block. Using aliases declared in a block of // usings are not usable from within that same block. This loop moves us outside of the // immediately enclosing using directive, if any. while (startNode != null) { if (startNode is UsingDirectiveSyntax) { startNode = startNode.Parent!.Parent!; break; } startNode = startNode.Parent; } startNode ??= token.Parent; var builder = ImmutableDictionary.CreateBuilder<INamespaceOrTypeSymbol, IAliasSymbol>(); while (startNode != null) { var usings = (startNode as BaseNamespaceDeclarationSyntax)?.Usings; usings ??= (startNode as CompilationUnitSyntax)?.Usings; if (usings != null) { foreach (var u in usings) { if (u.Alias != null && semanticModel.GetDeclaredSymbol(u) is IAliasSymbol aliasSymbol && !builder.ContainsKey(aliasSymbol.Target)) { builder.Add(aliasSymbol.Target, aliasSymbol); } } } startNode = startNode.Parent; } return builder.ToImmutable(); } private ITypeSymbol? GetRangeVariableType(IRangeVariableSymbol symbol) { ITypeSymbol? type = null; if (this.IsMinimizing && !symbol.Locations.IsEmpty) { var location = symbol.Locations.First(); if (location.IsInSource && location.SourceTree == SemanticModelOpt.SyntaxTree) { var token = location.SourceTree.GetRoot().FindToken(PositionOpt); var queryBody = GetQueryBody(token); if (queryBody != null) { // To heuristically determine the type of the range variable in a query // clause, we speculatively bind the name of the variable in the select // or group clause of the query body. var identifierName = SyntaxFactory.IdentifierName(symbol.Name); type = SemanticModelOpt.GetSpeculativeTypeInfo( queryBody.SelectOrGroup.Span.End - 1, identifierName, SpeculativeBindingOption.BindAsExpression).Type; } var identifier = token.Parent as IdentifierNameSyntax; if (identifier != null) { type = SemanticModelOpt.GetTypeInfo(identifier).Type; } } } return type; } private static QueryBodySyntax? GetQueryBody(SyntaxToken token) => token.Parent switch { FromClauseSyntax fromClause when fromClause.Identifier == token => fromClause.Parent as QueryBodySyntax ?? ((QueryExpressionSyntax)fromClause.Parent!).Body, LetClauseSyntax letClause when letClause.Identifier == token => letClause.Parent as QueryBodySyntax, JoinClauseSyntax joinClause when joinClause.Identifier == token => joinClause.Parent as QueryBodySyntax, QueryContinuationSyntax continuation when continuation.Identifier == token => continuation.Body, _ => null }; private string RemoveAttributeSuffixIfNecessary(INamedTypeSymbol symbol, string symbolName) { if (this.IsMinimizing && Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.RemoveAttributeSuffix) && SemanticModelOpt.Compilation.IsAttributeType(symbol)) { string? nameWithoutAttributeSuffix; if (symbolName.TryGetWithoutAttributeSuffix(out nameWithoutAttributeSuffix)) { var token = SyntaxFactory.ParseToken(nameWithoutAttributeSuffix); if (token.IsKind(SyntaxKind.IdentifierToken)) { symbolName = nameWithoutAttributeSuffix; } } } return symbolName; } private IDictionary<INamespaceOrTypeSymbol, IAliasSymbol> AliasMap { get { var map = _lazyAliasMap; if (map != null) { return map; } map = CreateAliasMap(); return Interlocked.CompareExchange(ref _lazyAliasMap, map, null) ?? map; } } private IAliasSymbol? GetAliasSymbol(INamespaceOrTypeSymbol symbol) { IAliasSymbol? result; return AliasMap.TryGetValue(symbol, out result) ? result : null; } } }