// 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); } |