File: SymbolDisplay\SymbolDisplayVisitor_Minimal.cs
Web Access
Project: src\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.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;
        }
    }
}