File: Binder\OpenTypeVisitor.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 Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.CodeAnalysis.CSharp;
 
internal partial class Binder
{
    /// <summary>
    /// This visitor walks over a type expression looking for open types.
    /// 
    /// Open types are allowed if an only if:
    /// <list type="number">
    /// <item>There is no constructed generic type elsewhere in the visited syntax; and</item>
    /// <item>The open type is not used as a type argument or array/pointer/nullable element type.</item>
    /// </list>
    /// 
    /// Open types can be used both in <c>typeof(...)</c> and <c>nameof(...)</c> expressions.
    /// </summary>
    protected sealed class OpenTypeVisitor : CSharpSyntaxVisitor
    {
        private Dictionary<GenericNameSyntax, bool>? _allowedMap;
        private bool _seenConstructed;
 
        /// <param name="typeSyntax">The argument to typeof.</param>
        /// <param name="allowedMap">
        /// Keys are GenericNameSyntax nodes representing unbound generic types.
        /// Values are false if the node should result in an error and true otherwise.
        /// </param>
        public static void Visit(ExpressionSyntax typeSyntax, out Dictionary<GenericNameSyntax, bool>? allowedMap)
        {
            OpenTypeVisitor visitor = new OpenTypeVisitor();
            visitor.Visit(typeSyntax);
            allowedMap = visitor._allowedMap;
        }
 
        public override void VisitGenericName(GenericNameSyntax node)
        {
            SeparatedSyntaxList<TypeSyntax> typeArguments = node.TypeArgumentList.Arguments;
            if (node.IsUnboundGenericName)
            {
                _allowedMap ??= new Dictionary<GenericNameSyntax, bool>();
                _allowedMap[node] = !_seenConstructed;
            }
            else
            {
                _seenConstructed = true;
                foreach (TypeSyntax arg in typeArguments)
                {
                    Visit(arg);
                }
            }
        }
 
        public override void VisitQualifiedName(QualifiedNameSyntax node)
        {
            bool seenConstructedBeforeRight = _seenConstructed;
 
            // Visit Right first because it's smaller (to make backtracking cheaper).
            Visit(node.Right);
 
            bool seenConstructedBeforeLeft = _seenConstructed;
 
            Visit(node.Left);
 
            // If the first time we saw a constructed type was in Left, then we need to re-visit Right
            if (!seenConstructedBeforeRight && !seenConstructedBeforeLeft && _seenConstructed)
            {
                Visit(node.Right);
            }
        }
 
        public override void VisitAliasQualifiedName(AliasQualifiedNameSyntax node)
        {
            Visit(node.Name);
        }
 
        public override void VisitArrayType(ArrayTypeSyntax node)
        {
            _seenConstructed = true;
            Visit(node.ElementType);
        }
 
        public override void VisitPointerType(PointerTypeSyntax node)
        {
            _seenConstructed = true;
            Visit(node.ElementType);
        }
 
        public override void VisitNullableType(NullableTypeSyntax node)
        {
            _seenConstructed = true;
            Visit(node.ElementType);
        }
    }
}