File: Recommendations\CSharpRecommendationServiceRunner_Operators.cs
Web Access
Project: src\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.Workspaces)
// 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.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Recommendations;
 
internal partial class CSharpRecommendationService
{
    /// <summary>
    /// Adds user defined operators to the unnamed recommendation set.
    /// </summary>
    private sealed partial class CSharpRecommendationServiceRunner
    {
        private static void AddOperators(ITypeSymbol container, ArrayBuilder<ISymbol> symbols)
        {
            var containerWithoutNullable = container.RemoveNullableIfPresent();
 
            // Don't bother showing operators for basic built-in types.  They're well known already and will only
            // clutter the display.
            if (ExcludeOperatorType(containerWithoutNullable))
                return;
 
            var containerIsNullable = container.IsNullable();
            foreach (var type in containerWithoutNullable.GetBaseTypesAndThis())
            {
                foreach (var member in type.GetMembers())
                {
                    if (member is not IMethodSymbol { MethodKind: MethodKind.UserDefinedOperator } method)
                        continue;
 
                    // Don't add operator true/false.  They only are used for conversions in special boolean contexts
                    // (like `if` statement conditions), and are not invoked explicitly by the user.
                    if (method.Name is WellKnownMemberNames.TrueOperatorName or WellKnownMemberNames.FalseOperatorName)
                        continue;
 
                    // If we're on a nullable version of the type, but this operator wouldn't naturally 'lift' to be
                    // available for it, then don't include it.
                    if (containerIsNullable && !IsLiftableOperator(method))
                        continue;
 
                    // We don't need to bother lifting operators. We'll just show the basic operator in the list as the
                    // information for it is sufficient for completion (i.e. we only insert the operator itself, not any
                    // of the parameter or return types).
                    symbols.Add(method);
                }
            }
        }
 
        private static bool ExcludeOperatorType(ITypeSymbol container)
            => container.IsSpecialType() || container.SpecialType is SpecialType.System_IntPtr or SpecialType.System_UIntPtr;
 
        private static bool IsLiftableOperator(IMethodSymbol symbol)
        {
            // https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#lifted-operators
 
            // Common for all:
            if (symbol.IsUserDefinedOperator() && symbol.Parameters.All(p => p.Type.IsValueType))
            {
                switch (symbol.Name)
                {
                    // Unary
                    case WellKnownMemberNames.UnaryPlusOperatorName:
                    case WellKnownMemberNames.IncrementOperatorName:
                    case WellKnownMemberNames.UnaryNegationOperatorName:
                    case WellKnownMemberNames.DecrementOperatorName:
                    case WellKnownMemberNames.LogicalNotOperatorName:
                    case WellKnownMemberNames.OnesComplementOperatorName:
                        return symbol.Parameters.Length == 1 && symbol.ReturnType.IsValueType;
 
                    // Binary 
                    case WellKnownMemberNames.AdditionOperatorName:
                    case WellKnownMemberNames.SubtractionOperatorName:
                    case WellKnownMemberNames.MultiplyOperatorName:
                    case WellKnownMemberNames.DivisionOperatorName:
                    case WellKnownMemberNames.ModulusOperatorName:
                    case WellKnownMemberNames.BitwiseAndOperatorName:
                    case WellKnownMemberNames.BitwiseOrOperatorName:
                    case WellKnownMemberNames.ExclusiveOrOperatorName:
                    case WellKnownMemberNames.LeftShiftOperatorName:
                    case WellKnownMemberNames.RightShiftOperatorName:
                    case WellKnownMemberNames.UnsignedRightShiftOperatorName:
                        return symbol.Parameters.Length == 2 && symbol.ReturnType.IsValueType;
 
                    // Equality + Relational 
                    case WellKnownMemberNames.EqualityOperatorName:
                    case WellKnownMemberNames.InequalityOperatorName:
                    case WellKnownMemberNames.LessThanOperatorName:
                    case WellKnownMemberNames.GreaterThanOperatorName:
                    case WellKnownMemberNames.LessThanOrEqualOperatorName:
                    case WellKnownMemberNames.GreaterThanOrEqualOperatorName:
                        return symbol.Parameters.Length == 2 && symbol.ReturnType.SpecialType == SpecialType.System_Boolean;
                }
            }
 
            return false;
        }
    }
}