File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Services\SemanticFacts\ISemanticFactsExtensions.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.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.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis.LanguageService;
 
internal static class ISemanticFactsExtensions
{
    public static bool IsSafeToChangeAssociativity<TBinaryExpressionSyntax>(
        this ISemanticFacts semanticFacts,
        TBinaryExpressionSyntax innerBinary,
        TBinaryExpressionSyntax parentBinary,
        SemanticModel semanticModel)
        where TBinaryExpressionSyntax : SyntaxNode
    {
        // Now we'll perform a few semantic checks to determine whether removal 
        // of the parentheses might break semantics. Note that we'll try and be 
        // fairly conservative with these. For example, we'll assume that failing 
        // any of these checks results in the parentheses being declared as necessary 
        // -- even if they could be removed depending on whether the parenthesized
        // expression appears on the left or right side of the parent binary expression.
 
        // First, does the binary expression result in an operator overload being 
        // called?
        var symbolInfo = semanticModel.GetSymbolInfo(innerBinary);
        if (AnySymbolIsUserDefinedOperator(symbolInfo))
            return false;
 
        // Second, check the type and converted type of the binary expression.
        // Are they the same?
        var innerTypeInfo = semanticModel.GetTypeInfo(innerBinary);
        if (innerTypeInfo.Type != null && innerTypeInfo.ConvertedType != null)
        {
            if (!innerTypeInfo.Type.Equals(innerTypeInfo.ConvertedType))
                return false;
        }
 
        // It's not safe to change associativity for dynamic variables as the actual type isn't known. See https://github.com/dotnet/roslyn/issues/47365
        if (innerTypeInfo.Type is IDynamicTypeSymbol)
            return false;
 
        semanticFacts.SyntaxFacts.GetPartsOfBinaryExpression(parentBinary, out var parentBinaryLeft, out var parentBinaryRight);
 
        // Only allow us to change associativity if all the types are the same.
        // for example, if we have: int + (int + long)  then we don't want to
        // change things such that we effectively have (int + int) + long
        if (!Equals(semanticModel.GetTypeInfo(parentBinaryLeft).Type,
                    semanticModel.GetTypeInfo(parentBinaryRight).Type))
        {
            return false;
        }
 
        if (!Equals(semanticModel.GetTypeInfo(parentBinaryLeft).ConvertedType,
                    semanticModel.GetTypeInfo(parentBinaryRight).ConvertedType))
        {
            return false;
        }
 
        // Floating point is not safe to change associativity of.  For example, if the user has "large * (large *
        // small)" then this will become "(large * large) * small.  And that could easily overflow to Inf (and other
        // badness).
        var outerTypeInfo = semanticModel.GetTypeInfo(parentBinary);
        if (IsFloatingPoint(innerTypeInfo) || IsFloatingPoint(outerTypeInfo))
            return false;
 
        if (semanticModel.GetOperation(parentBinary) is IBinaryOperation parentBinaryOp &&
            semanticModel.GetOperation(innerBinary) is IBinaryOperation innerBinaryOp)
        {
            if ((parentBinaryOp.IsChecked || innerBinaryOp.IsChecked) &&
                (IsArithmetic(parentBinaryOp) || IsArithmetic(innerBinaryOp)))
            {
                // For checked operations, we can't change which type of operator we're performing in a row as that
                // could lead to overflow if we end up doing something like an addition prior to a subtraction.
                return false;
            }
        }
 
        return true;
 
        static bool IsArithmetic(IBinaryOperation op)
        {
            return op.OperatorKind is BinaryOperatorKind.Add or
                BinaryOperatorKind.Subtract or
                BinaryOperatorKind.Multiply or
                BinaryOperatorKind.Divide;
        }
    }
 
    private static bool AnySymbolIsUserDefinedOperator(SymbolInfo symbolInfo)
    {
        if (IsUserDefinedOperator(symbolInfo.Symbol))
        {
            return true;
        }
 
        foreach (var symbol in symbolInfo.CandidateSymbols)
        {
            if (IsUserDefinedOperator(symbol))
            {
                return true;
            }
        }
 
        return false;
    }
 
    private static bool IsUserDefinedOperator([NotNullWhen(returnValue: true)] ISymbol? symbol)
        => symbol is IMethodSymbol methodSymbol &&
           methodSymbol.MethodKind == MethodKind.UserDefinedOperator;
 
    private static bool IsFloatingPoint(TypeInfo typeInfo)
        => IsFloatingPoint(typeInfo.Type) || IsFloatingPoint(typeInfo.ConvertedType);
 
    private static bool IsFloatingPoint([NotNullWhen(returnValue: true)] ITypeSymbol? type)
        => type?.SpecialType is SpecialType.System_Single or SpecialType.System_Double;
 
    public static IParameterSymbol? FindParameterForArgument(this ISemanticFacts semanticFacts, SemanticModel semanticModel, SyntaxNode argument, CancellationToken cancellationToken)
        => semanticFacts.FindParameterForArgument(semanticModel, argument, allowUncertainCandidates: false, allowParams: false, cancellationToken);
 
    public static IParameterSymbol? FindParameterForAttributeArgument(this ISemanticFacts semanticFacts, SemanticModel semanticModel, SyntaxNode argument, CancellationToken cancellationToken)
        => semanticFacts.FindParameterForAttributeArgument(semanticModel, argument, allowUncertainCandidates: false, allowParams: false, cancellationToken);
}