File: Binder\WithCrefTypeParametersBinder.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.
 
#nullable disable
 
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// The scope within a documentation cref.  Contains the implicitly declared type parameters
    /// of the cref (see <see cref="CrefTypeParameterSymbol"/> for details).
    /// </summary>
    internal sealed class WithCrefTypeParametersBinder : WithTypeParametersBinder
    {
        private readonly CrefSyntax _crefSyntax;
        private MultiDictionary<string, TypeParameterSymbol> _lazyTypeParameterMap;
 
        internal WithCrefTypeParametersBinder(CrefSyntax crefSyntax, Binder next)
            : base(next)
        {
            _crefSyntax = crefSyntax;
        }
 
        protected override MultiDictionary<string, TypeParameterSymbol> TypeParameterMap
        {
            get
            {
                if (_lazyTypeParameterMap == null)
                {
                    MultiDictionary<string, TypeParameterSymbol> map = CreateTypeParameterMap();
                    Interlocked.CompareExchange(ref _lazyTypeParameterMap, map, null);
                }
 
                return _lazyTypeParameterMap;
            }
        }
 
        private MultiDictionary<string, TypeParameterSymbol> CreateTypeParameterMap()
        {
            var map = new MultiDictionary<string, TypeParameterSymbol>();
 
            switch (_crefSyntax.Kind())
            {
                case SyntaxKind.TypeCref:
                    {
                        AddTypeParameters(((TypeCrefSyntax)_crefSyntax).Type, map);
                        break;
                    }
                case SyntaxKind.QualifiedCref:
                    {
                        QualifiedCrefSyntax qualifiedCrefSyntax = ((QualifiedCrefSyntax)_crefSyntax);
                        AddTypeParameters(qualifiedCrefSyntax.Member, map);
                        AddTypeParameters(qualifiedCrefSyntax.Container, map);
                        break;
                    }
                case SyntaxKind.NameMemberCref:
                case SyntaxKind.IndexerMemberCref:
                case SyntaxKind.OperatorMemberCref:
                case SyntaxKind.ConversionOperatorMemberCref:
                    {
                        AddTypeParameters((MemberCrefSyntax)_crefSyntax, map);
                        break;
                    }
                default:
                    {
                        throw ExceptionUtilities.UnexpectedValue(_crefSyntax.Kind());
                    }
            }
            return map;
        }
 
        private void AddTypeParameters(TypeSyntax typeSyntax, MultiDictionary<string, TypeParameterSymbol> map)
        {
            switch (typeSyntax.Kind())
            {
                case SyntaxKind.AliasQualifiedName:
                    AddTypeParameters(((AliasQualifiedNameSyntax)typeSyntax).Name, map);
                    break;
                case SyntaxKind.QualifiedName:
                    // NOTE: Dev11 does not warn about duplication, it just matches parameter types to the
                    // *last* type parameter with the same name.  That's why we're iterating backwards.
                    QualifiedNameSyntax qualifiedNameSyntax = (QualifiedNameSyntax)typeSyntax;
                    AddTypeParameters(qualifiedNameSyntax.Right, map);
                    AddTypeParameters(qualifiedNameSyntax.Left, map);
                    break;
                case SyntaxKind.GenericName:
                    AddTypeParameters((GenericNameSyntax)typeSyntax, map);
                    break;
                case SyntaxKind.IdentifierName:
                case SyntaxKind.PredefinedType:
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(typeSyntax.Kind());
            }
        }
 
        private void AddTypeParameters(MemberCrefSyntax memberSyntax, MultiDictionary<string, TypeParameterSymbol> map)
        {
            // Other members have arity 0.
            if (memberSyntax.Kind() == SyntaxKind.NameMemberCref)
            {
                AddTypeParameters(((NameMemberCrefSyntax)memberSyntax).Name, map);
            }
        }
 
        private static void AddTypeParameters(GenericNameSyntax genericNameSyntax, MultiDictionary<string, TypeParameterSymbol> map)
        {
            // NOTE: Dev11 does not warn about duplication, it just matches parameter types to the
            // *last* type parameter with the same name.  That's why we're iterating backwards and
            // skipping subsequent symbols with the same name.  This can result in some surprising
            // behavior.  For example, both 'T's in "A<T>.B<T>" bind to the second implicitly
            // declared type parameter.
            SeparatedSyntaxList<TypeSyntax> typeArguments = genericNameSyntax.TypeArgumentList.Arguments;
            for (int i = typeArguments.Count - 1; i >= 0; i--)
            {
                // Other types (non-identifiers) are allowed in error scenarios, but they do not introduce new 
                // cref type parameters.
                if (typeArguments[i].Kind() == SyntaxKind.IdentifierName)
                {
                    IdentifierNameSyntax typeParameterSyntax = (IdentifierNameSyntax)typeArguments[i];
                    Debug.Assert(typeParameterSyntax != null, "Syntactic requirement of crefs");
 
                    string name = typeParameterSyntax.Identifier.ValueText;
                    if (SyntaxFacts.IsValidIdentifier(name) && !map.ContainsKey(name))
                    {
                        TypeParameterSymbol typeParameterSymbol = new CrefTypeParameterSymbol(name, i, typeParameterSyntax);
                        map.Add(name, typeParameterSymbol);
                    }
                }
            }
        }
 
        internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder)
        {
            if (CanConsiderTypeParameters(options))
            {
                foreach (var kvp in TypeParameterMap)
                {
                    foreach (TypeParameterSymbol typeParameter in kvp.Value)
                    {
                        // In any context where this binder applies, the type parameters are always viable/speakable.
                        Debug.Assert(!result.CanBeAdded(typeParameter.Name) || originalBinder.CanAddLookupSymbolInfo(typeParameter, options, result, null));
 
                        result.AddSymbol(typeParameter, kvp.Key, 0);
                    }
                }
            }
        }
    }
}