File: UsesIsNullableVisitor.cs
Web Access
Project: src\src\Compilers\Test\Utilities\CSharp\Microsoft.CodeAnalysis.CSharp.Test.Utilities.csproj (Microsoft.CodeAnalysis.CSharp.Test.Utilities)
// 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.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Test.Utilities
{
    /// <summary>
    /// Returns the set of members that contain reference types with IsNullable set.
    /// </summary>
    internal sealed class UsesIsNullableVisitor : CSharpSymbolVisitor<bool>
    {
        private readonly ArrayBuilder<Symbol> _builder;
 
        private UsesIsNullableVisitor(ArrayBuilder<Symbol> builder)
        {
            _builder = builder;
        }
 
        internal static void GetUses(ArrayBuilder<Symbol> builder, Symbol symbol)
        {
            var visitor = new UsesIsNullableVisitor(builder);
            visitor.Visit(symbol);
        }
 
        private void Add(Symbol symbol)
            => _builder.Add(symbol);
 
        public override bool VisitNamespace(NamespaceSymbol symbol)
        {
            return VisitList(symbol.GetMembers());
        }
 
        public override bool VisitNamedType(NamedTypeSymbol symbol)
        {
            if (AddIfUsesIsNullable(symbol, symbol.BaseTypeNoUseSiteDiagnostics, inProgress: null) ||
                AddIfUsesIsNullable(symbol, symbol.InterfacesNoUseSiteDiagnostics(), inProgress: null) ||
                AddIfUsesIsNullable(symbol, symbol.TypeParameters, inProgress: null))
            {
                return true;
            }
            return VisitList(symbol.GetMembers());
        }
 
        public override bool VisitMethod(MethodSymbol symbol)
        {
            return AddIfUsesIsNullable(symbol, symbol.TypeParameters, inProgress: null) ||
                AddIfUsesIsNullable(symbol, symbol.ReturnTypeWithAnnotations, inProgress: null) ||
                AddIfUsesIsNullable(symbol, symbol.Parameters, inProgress: null);
        }
 
        public override bool VisitProperty(PropertySymbol symbol)
        {
            return AddIfUsesIsNullable(symbol, symbol.TypeWithAnnotations, inProgress: null) ||
                AddIfUsesIsNullable(symbol, symbol.Parameters, inProgress: null);
        }
 
        public override bool VisitEvent(EventSymbol symbol)
        {
            return AddIfUsesIsNullable(symbol, symbol.TypeWithAnnotations, inProgress: null);
        }
 
        public override bool VisitField(FieldSymbol symbol)
        {
            return AddIfUsesIsNullable(symbol, symbol.TypeWithAnnotations, inProgress: null);
        }
 
        private bool VisitList<TSymbol>(ImmutableArray<TSymbol> symbols) where TSymbol : Symbol
        {
            bool result = false;
            foreach (var symbol in symbols)
            {
                if (this.Visit(symbol))
                {
                    result = true;
                }
            }
            return result;
        }
 
        /// <summary>
        /// Check the parameters of a method or property, but report that method/property rather than
        /// the parameter itself.
        /// </summary>
        private bool AddIfUsesIsNullable(Symbol symbol, ImmutableArray<ParameterSymbol> parameters, ConsList<TypeParameterSymbol> inProgress)
        {
            foreach (var parameter in parameters)
            {
                if (UsesIsNullable(parameter.TypeWithAnnotations, inProgress))
                {
                    Add(symbol);
                    return true;
                }
            }
            return false;
        }
 
        private bool AddIfUsesIsNullable(Symbol symbol, ImmutableArray<TypeParameterSymbol> typeParameters, ConsList<TypeParameterSymbol> inProgress)
        {
            foreach (var type in typeParameters)
            {
                if (UsesIsNullable(type, inProgress))
                {
                    Add(symbol);
                    return true;
                }
            }
            return false;
        }
 
        private bool AddIfUsesIsNullable(Symbol symbol, ImmutableArray<NamedTypeSymbol> types, ConsList<TypeParameterSymbol> inProgress)
        {
            foreach (var type in types)
            {
                if (UsesIsNullable(type, inProgress))
                {
                    Add(symbol);
                    return true;
                }
            }
            return false;
        }
 
        private bool AddIfUsesIsNullable(Symbol symbol, TypeWithAnnotations type, ConsList<TypeParameterSymbol> inProgress)
        {
            if (UsesIsNullable(type, inProgress))
            {
                Add(symbol);
                return true;
            }
            return false;
        }
 
        private bool AddIfUsesIsNullable(Symbol symbol, TypeSymbol type, ConsList<TypeParameterSymbol> inProgress)
        {
            if (UsesIsNullable(type, inProgress))
            {
                Add(symbol);
                return true;
            }
            return false;
        }
 
        private bool UsesIsNullable(TypeWithAnnotations type, ConsList<TypeParameterSymbol> inProgress)
        {
            if (!type.HasType)
            {
                return false;
            }
            var typeSymbol = type.Type;
            return (type.NullableAnnotation != NullableAnnotation.Oblivious && typeSymbol.IsReferenceType && !typeSymbol.IsErrorType()) ||
                UsesIsNullable(typeSymbol, inProgress);
        }
 
        private bool UsesIsNullable(TypeSymbol type, ConsList<TypeParameterSymbol> inProgress)
        {
            if (type is null)
            {
                return false;
            }
            switch (type.TypeKind)
            {
                case TypeKind.Class:
                case TypeKind.Delegate:
                case TypeKind.Interface:
                case TypeKind.Struct:
                case TypeKind.Enum:
                    if (UsesIsNullable(type.ContainingType, inProgress))
                    {
                        return true;
                    }
                    break;
            }
            switch (type.TypeKind)
            {
                case TypeKind.Array:
                    return UsesIsNullable(((ArrayTypeSymbol)type).ElementTypeWithAnnotations, inProgress);
                case TypeKind.Class:
                case TypeKind.Delegate:
                case TypeKind.Error:
                case TypeKind.Interface:
                case TypeKind.Struct:
                    return UsesIsNullable(((NamedTypeSymbol)type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics, inProgress);
                case TypeKind.Dynamic:
                case TypeKind.Enum:
                    return false;
                case TypeKind.Pointer:
                    return UsesIsNullable(((PointerTypeSymbol)type).PointedAtTypeWithAnnotations, inProgress);
                case TypeKind.TypeParameter:
                    var typeParameter = (TypeParameterSymbol)type;
                    if (inProgress?.ContainsReference(typeParameter) == true)
                    {
                        return false;
                    }
                    inProgress = inProgress ?? ConsList<TypeParameterSymbol>.Empty;
                    inProgress = inProgress.Prepend(typeParameter);
                    return UsesIsNullable(typeParameter.ConstraintTypesNoUseSiteDiagnostics, inProgress) ||
                        typeParameter.ReferenceTypeConstraintIsNullable == true;
                default:
                    throw ExceptionUtilities.UnexpectedValue(type.TypeKind);
            }
        }
 
        private bool UsesIsNullable(ImmutableArray<TypeWithAnnotations> types, ConsList<TypeParameterSymbol> inProgress)
        {
            return types.Any(t => UsesIsNullable(t, inProgress));
        }
    }
}