File: src\Analyzers\Core\Analyzers\PopulateSwitch\PopulateSwitchExpressionHelpers.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
 
namespace Microsoft.CodeAnalysis.PopulateSwitch;
 
internal static class PopulateSwitchExpressionHelpers
{
    public static ICollection<ISymbol> GetMissingEnumMembers(ISwitchExpressionOperation operation)
    {
        var switchExpression = operation.Value;
        var switchExpressionType = switchExpression?.Type;
 
        // Check if the type of the expression is a nullable INamedTypeSymbol
        // if the type is both nullable and an INamedTypeSymbol extract the type argument from the nullable
        // and check if it is of enum type
        if (switchExpressionType != null)
            switchExpressionType = switchExpressionType.IsNullable(out var underlyingType) ? underlyingType : switchExpressionType;
 
        if (switchExpressionType?.TypeKind == TypeKind.Enum)
        {
            var enumMembers = new Dictionary<long, ISymbol>();
            if (PopulateSwitchStatementHelpers.TryGetAllEnumMembers(switchExpressionType, enumMembers))
            {
                RemoveExistingEnumMembers(operation, enumMembers);
                return enumMembers.Values;
            }
        }
 
        return [];
    }
 
    public static bool HasNullSwitchArm(ISwitchExpressionOperation operation)
    {
        foreach (var arm in operation.Arms)
        {
            if (arm.Pattern is IConstantPatternOperation { Value.ConstantValue: { HasValue: true, Value: null } })
                return true;
        }
 
        return false;
    }
 
    private static void RemoveExistingEnumMembers(
        ISwitchExpressionOperation operation, Dictionary<long, ISymbol> enumMembers)
    {
        foreach (var arm in operation.Arms)
        {
            RemoveIfConstantPatternHasValue(arm.Pattern, enumMembers);
            if (arm.Pattern is IBinaryPatternOperation binaryPattern)
            {
                HandleBinaryPattern(binaryPattern, enumMembers);
            }
        }
    }
 
    internal static void HandleBinaryPattern(IBinaryPatternOperation? binaryPattern, Dictionary<long, ISymbol> enumMembers)
    {
        if (binaryPattern?.OperatorKind == BinaryOperatorKind.Or)
        {
            RemoveIfConstantPatternHasValue(binaryPattern.LeftPattern, enumMembers);
            RemoveIfConstantPatternHasValue(binaryPattern.RightPattern, enumMembers);
 
            HandleBinaryPattern(binaryPattern.LeftPattern as IBinaryPatternOperation, enumMembers);
            HandleBinaryPattern(binaryPattern.RightPattern as IBinaryPatternOperation, enumMembers);
        }
    }
 
    private static void RemoveIfConstantPatternHasValue(IOperation operation, Dictionary<long, ISymbol> enumMembers)
    {
        if (operation is IConstantPatternOperation { Value.ConstantValue: { HasValue: true, Value: not null and var value } })
            enumMembers.Remove(IntegerUtilities.ToInt64(value));
    }
 
    public static bool HasDefaultCase(ISwitchExpressionOperation operation)
        => operation.Arms.Any(IsDefault);
 
    public static bool IsDefault(ISwitchExpressionArmOperation arm)
        => IsDefault(arm.Pattern);
 
    public static bool IsDefault(IPatternOperation pattern)
        => pattern switch
        {
            // _ => ...
            IDiscardPatternOperation => true,
            // var v => ...
            IDeclarationPatternOperation declarationPattern => declarationPattern.MatchesNull,
            IBinaryPatternOperation binaryPattern => binaryPattern.OperatorKind switch
            {
                // x or _ => ...
                BinaryOperatorKind.Or => IsDefault(binaryPattern.LeftPattern) || IsDefault(binaryPattern.RightPattern),
                // _ and var x => ...
                BinaryOperatorKind.And => IsDefault(binaryPattern.LeftPattern) && IsDefault(binaryPattern.RightPattern),
                _ => false,
            },
            _ => false
        };
 
    public static bool HasExhaustiveNullAndTypeCheckCases(ISwitchExpressionOperation operation)
    {
        var type = operation.Value.Type;
        var underlyingType = type.RemoveNullableIfPresent();
 
        var hasNullArm = false;
        var hasUnderlyingTypeArm = false;
 
        foreach (var arm in operation.Arms)
        {
            var pattern = arm.Pattern;
            if (arm.Guard != null)
                continue;
 
            if (pattern is IConstantPatternOperation { Value: IConversionOperation { ConstantValue: { HasValue: true, Value: null } } })
            {
                hasNullArm = true;
            }
            else
            {
                hasUnderlyingTypeArm |= SymbolEqualityComparer.Default.Equals(
                    underlyingType,
                    pattern switch
                    {
                        ITypePatternOperation typePattern => typePattern.MatchedType,
                        IDeclarationPatternOperation declarationPattern => declarationPattern.MatchedType,
                        _ => null
                    });
            }
 
            if (hasNullArm && hasUnderlyingTypeArm)
                return true;
        }
 
        return false;
    }
}