// 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; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.CodeAnalysis.Shared.Extensions; internal static partial class SyntaxGeneratorExtensions { private static readonly ImmutableDictionary<BinaryOperatorKind, BinaryOperatorKind> s_negatedBinaryMap = new Dictionary<BinaryOperatorKind, BinaryOperatorKind> { { BinaryOperatorKind.Equals, BinaryOperatorKind.NotEquals }, { BinaryOperatorKind.NotEquals, BinaryOperatorKind.Equals }, { BinaryOperatorKind.LessThan, BinaryOperatorKind.GreaterThanOrEqual }, { BinaryOperatorKind.GreaterThan, BinaryOperatorKind.LessThanOrEqual }, { BinaryOperatorKind.LessThanOrEqual, BinaryOperatorKind.GreaterThan }, { BinaryOperatorKind.GreaterThanOrEqual, BinaryOperatorKind.LessThan }, { BinaryOperatorKind.Or, BinaryOperatorKind.And }, { BinaryOperatorKind.And, BinaryOperatorKind.Or }, { BinaryOperatorKind.ConditionalOr, BinaryOperatorKind.ConditionalAnd }, { BinaryOperatorKind.ConditionalAnd, BinaryOperatorKind.ConditionalOr }, }.ToImmutableDictionary(); public static SyntaxNode Negate( this SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken) { return Negate(generator, generatorInternal, expression, semanticModel, negateBinary: true, cancellationToken); } public static SyntaxNode Negate( this SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, SyntaxNode expressionOrPattern, SemanticModel semanticModel, bool negateBinary, CancellationToken cancellationToken) { return Negate(generator, generatorInternal, expressionOrPattern, semanticModel, negateBinary, patternValueType: null, cancellationToken); } public static SyntaxNode Negate( this SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, SyntaxNode expressionOrPattern, SemanticModel semanticModel, bool negateBinary, SpecialType? patternValueType, CancellationToken cancellationToken) { var options = semanticModel.SyntaxTree.Options; var syntaxFacts = generatorInternal.SyntaxFacts; if (syntaxFacts.IsParenthesizedExpression(expressionOrPattern)) { return generatorInternal.AddParentheses( generator.Negate( generatorInternal, syntaxFacts.GetExpressionOfParenthesizedExpression(expressionOrPattern), semanticModel, negateBinary, cancellationToken)) .WithTriviaFrom(expressionOrPattern); } if (negateBinary && syntaxFacts.IsBinaryExpression(expressionOrPattern)) return GetNegationOfBinaryExpression(expressionOrPattern, generator, generatorInternal, semanticModel, cancellationToken); if (syntaxFacts.IsLiteralExpression(expressionOrPattern)) return GetNegationOfLiteralExpression(expressionOrPattern, generator, generatorInternal, semanticModel); if (syntaxFacts.IsLogicalNotExpression(expressionOrPattern)) return GetNegationOfLogicalNotExpression(expressionOrPattern, syntaxFacts); if (negateBinary && syntaxFacts.IsIsPatternExpression(expressionOrPattern)) return GetNegationOfIsPatternExpression(expressionOrPattern, generator, generatorInternal, semanticModel, cancellationToken); if (syntaxFacts.IsParenthesizedPattern(expressionOrPattern)) { // Push the negation inside the parenthesized pattern. return generatorInternal.AddParentheses( generator.Negate( generatorInternal, syntaxFacts.GetPatternOfParenthesizedPattern(expressionOrPattern), semanticModel, negateBinary, cancellationToken)) .WithTriviaFrom(expressionOrPattern); } if (negateBinary && syntaxFacts.IsBinaryPattern(expressionOrPattern)) return GetNegationOfBinaryPattern(expressionOrPattern, generator, generatorInternal, semanticModel, cancellationToken); if (syntaxFacts.IsConstantPattern(expressionOrPattern)) return GetNegationOfConstantPattern(expressionOrPattern, generator, generatorInternal, patternValueType); if (syntaxFacts.IsUnaryPattern(expressionOrPattern)) return GetNegationOfUnaryPattern(expressionOrPattern, generator, generatorInternal, syntaxFacts); if (syntaxFacts.IsIsTypeExpression(expressionOrPattern)) { syntaxFacts.GetPartsOfAnyIsTypeExpression(expressionOrPattern, out var expression, out var type); if (syntaxFacts.SupportsNotPattern(options)) return generatorInternal.IsPatternExpression(expression, generatorInternal.NotPattern(type)); if (syntaxFacts.SupportsIsNotTypeExpression(options)) return generatorInternal.IsNotTypeExpression(expression, type); } if (syntaxFacts.IsIsNotTypeExpression(expressionOrPattern)) { syntaxFacts.GetPartsOfAnyIsTypeExpression(expressionOrPattern, out var expression, out var type); return generator.IsTypeExpression(expression, type); } if (syntaxFacts.IsRelationalPattern(expressionOrPattern)) return GetNegationOfRelationalPattern(expressionOrPattern, generatorInternal, patternValueType); return syntaxFacts.IsAnyPattern(expressionOrPattern) ? generatorInternal.NotPattern(expressionOrPattern) : generator.LogicalNotExpression(expressionOrPattern); } private static SyntaxNode GetNegationOfBinaryExpression( SyntaxNode expressionNode, SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, SemanticModel semanticModel, CancellationToken cancellationToken) { var syntaxFacts = generatorInternal.SyntaxFacts; syntaxFacts.GetPartsOfBinaryExpression(expressionNode, out var leftOperand, out var operatorToken, out var rightOperand); var operation = semanticModel.GetOperation(expressionNode, cancellationToken); if (operation is IBinaryOperation binaryOperation) { if (!s_negatedBinaryMap.TryGetValue(binaryOperation.OperatorKind, out var negatedKind)) return generator.LogicalNotExpression(expressionNode); // Lifted relational operators return false if either operand is null. // Inverting the operator fails to invert the behavior when an operand is null. if (binaryOperation.IsLifted && binaryOperation.OperatorKind is BinaryOperatorKind.LessThan or BinaryOperatorKind.LessThanOrEqual or BinaryOperatorKind.GreaterThan or BinaryOperatorKind.GreaterThanOrEqual) { return generator.LogicalNotExpression(expressionNode); } if (binaryOperation.OperatorKind is BinaryOperatorKind.Or or BinaryOperatorKind.And or BinaryOperatorKind.ConditionalAnd or BinaryOperatorKind.ConditionalOr) { leftOperand = generator.Negate(generatorInternal, leftOperand, semanticModel, cancellationToken); rightOperand = generator.Negate(generatorInternal, rightOperand, semanticModel, cancellationToken); } var newBinaryExpressionSyntax = negatedKind is BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals ? generatorInternal.NegateEquality(generator, expressionNode, leftOperand, negatedKind, rightOperand) : NegateRelational(generator, binaryOperation, leftOperand, negatedKind, rightOperand); newBinaryExpressionSyntax = newBinaryExpressionSyntax.WithTriviaFrom(expressionNode); var newToken = syntaxFacts.GetOperatorTokenOfBinaryExpression(newBinaryExpressionSyntax); return newBinaryExpressionSyntax.ReplaceToken( newToken, newToken.WithTriviaFrom(operatorToken)); } else if (operation is IIsTypeOperation { TypeOperand.SpecialType: SpecialType.System_Object } && generatorInternal.SupportsPatterns(semanticModel.SyntaxTree.Options)) { // `is object` -> `is null` return generatorInternal.IsPatternExpression(leftOperand, operatorToken, generatorInternal.ConstantPattern(generator.NullLiteralExpression().WithTriviaFrom(rightOperand))); } else if (syntaxFacts.IsIsTypeExpression(expressionNode) && syntaxFacts.SupportsNotPattern(semanticModel.SyntaxTree.Options)) { // `is y` -> `is not y` SyntaxNode innerPattern; if (operation is IIsTypeOperation isTypeOperation) { if (syntaxFacts.IsAnyName(rightOperand)) { // For named types, we need to convert to a constant expression (where the named type becomes a member // access expression). For other types (predefined, arrays, etc) we can keep this as a type pattern. // For example: `x is int` can just become `x is not int` where that's a type pattern. But `x is Y` // will need to become `x is not Y` where that's a constant pattern instead. var typeExpression = generatorInternal.Type(isTypeOperation.TypeOperand, typeContext: false); innerPattern = syntaxFacts.IsAnyType(typeExpression) && !syntaxFacts.IsAnyName(typeExpression) ? generatorInternal.TypePattern(typeExpression) : generatorInternal.ConstantPattern(typeExpression); } else { // original form was already not a name (like a predefined type, or array type, etc.). Can just // use as is as a type pattern. innerPattern = generatorInternal.TypePattern(rightOperand); } } else { innerPattern = generatorInternal.ConstantPattern(rightOperand); } return generatorInternal.IsPatternExpression( leftOperand, operatorToken, generatorInternal.NotPattern(innerPattern.WithTriviaFrom(rightOperand))); } else { // Apply the logical not operator if it is not a binary operation and also doesn't support patterns. return generator.LogicalNotExpression(expressionNode); } } private static SyntaxNode GetNegationOfBinaryPattern( SyntaxNode pattern, SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, SemanticModel semanticModel, CancellationToken cancellationToken) { // Apply De Morgan's laws here. // // not (a and b) -> not a or not b // not (a or b) -> not a and not b var syntaxFacts = generatorInternal.SyntaxFacts; syntaxFacts.GetPartsOfBinaryPattern(pattern, out var left, out var operatorToken, out var right); var newLeft = generator.Negate(generatorInternal, left, semanticModel, cancellationToken); var newRight = generator.Negate(generatorInternal, right, semanticModel, cancellationToken); var newPattern = syntaxFacts.IsAndPattern(pattern) ? generatorInternal.OrPattern(newLeft, newRight) : syntaxFacts.IsOrPattern(pattern) ? generatorInternal.AndPattern(newLeft, newRight) : throw ExceptionUtilities.UnexpectedValue(pattern.RawKind); newPattern = newPattern.WithTriviaFrom(pattern); syntaxFacts.GetPartsOfBinaryPattern(newPattern, out _, out var newToken, out _); var newTokenWithTrivia = newToken.WithTriviaFrom(operatorToken); return newPattern.ReplaceToken(newToken, newTokenWithTrivia); } private static SyntaxNode GetNegationOfIsPatternExpression(SyntaxNode isExpression, SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, SemanticModel semanticModel, CancellationToken cancellationToken) { // Don't recurse into patterns if the language doesn't support negated patterns. // Just wrap with a normal '!' expression. var syntaxFacts = generatorInternal.SyntaxFacts; syntaxFacts.GetPartsOfIsPatternExpression(isExpression, out var left, out var isToken, out var pattern); SyntaxNode? negatedPattern = null; if (syntaxFacts.SupportsNotPattern(semanticModel.SyntaxTree.Options)) { // We do support 'not' patterns. So attempt to push a 'not' pattern into the current is-pattern RHS. // We include the type of the value when negating the pattern, since it allows for nicer negations of // `is true/false` for Boolean values and relational patterns for numeric values. var operation = semanticModel.GetOperation(isExpression, cancellationToken); var valueType = (operation as IIsPatternOperation)?.Value.Type?.SpecialType; negatedPattern = generator.Negate(generatorInternal, pattern, semanticModel, negateBinary: true, valueType, cancellationToken); } else if (syntaxFacts.IsNotPattern(pattern)) { // we don't support 'not' patterns, but we have a 'not' pattern in code. Do a simple unwrapping of it. negatedPattern = GetNegationOfNotPattern(pattern, generator, generatorInternal, syntaxFacts); } // Negating the pattern may have formed something illegal. If so, just do a normal `!` negation. if (negatedPattern != null && IsLegalPattern(syntaxFacts, negatedPattern, designatorsLegal: true)) { if (syntaxFacts.IsTypePattern(negatedPattern)) { // We started with `x is not t`. Unwrap the type pattern for 't' and create a simple `is` binary expr `x is t`. var type = syntaxFacts.GetTypeOfTypePattern(negatedPattern); return generator.IsTypeExpression(left, type); } else { // Keep this as a normal `is-pattern`, just with the pattern portion negated. return generatorInternal.IsPatternExpression(left, isToken, negatedPattern); } } return generator.LogicalNotExpression(isExpression); } private static SyntaxNode GetNegationOfRelationalPattern( SyntaxNode expressionNode, SyntaxGeneratorInternal generatorInternal, SpecialType? patternValueType) { if (patternValueType is SpecialType specialType && specialType.IsNumericType()) { // If we know the value is numeric, we can negate the relational operator. // This is not valid for non-numeric value since they never match a relational pattern. // Similarly, it's not valid for nullable values, since null never matches a relational pattern. // As an example, `!(new object() is < 1)` is equivalent to `new object() is not < 1` but not `new object() is >= 1`. var syntaxFacts = generatorInternal.SyntaxFacts; syntaxFacts.GetPartsOfRelationalPattern(expressionNode, out var operatorToken, out var expression); syntaxFacts.TryGetPredefinedOperator(operatorToken, out var predefinedOperator); return predefinedOperator switch { PredefinedOperator.LessThan => generatorInternal.GreaterThanEqualsRelationalPattern(expression), PredefinedOperator.LessThanOrEqual => generatorInternal.GreaterThanRelationalPattern(expression), PredefinedOperator.GreaterThan => generatorInternal.LessThanEqualsRelationalPattern(expression), PredefinedOperator.GreaterThanOrEqual => generatorInternal.LessThanRelationalPattern(expression), _ => generatorInternal.NotPattern(expressionNode) }; } return generatorInternal.NotPattern(expressionNode); } private static bool IsLegalPattern(ISyntaxFacts syntaxFacts, SyntaxNode pattern, bool designatorsLegal) { // It is illegal to create a pattern that has a designator under a not-pattern or or-pattern if (syntaxFacts.IsBinaryPattern(pattern)) { syntaxFacts.GetPartsOfBinaryPattern(pattern, out var left, out _, out var right); designatorsLegal = designatorsLegal && !syntaxFacts.IsOrPattern(pattern); return IsLegalPattern(syntaxFacts, left, designatorsLegal) && IsLegalPattern(syntaxFacts, right, designatorsLegal); } if (syntaxFacts.IsNotPattern(pattern)) { // it's fine to have `not string s` (or `not (string s)`) as long as we're currently in a location where // designators are legal themselves. syntaxFacts.GetPartsOfUnaryPattern(pattern, out _, out var subPattern); if (syntaxFacts.IsParenthesizedPattern(subPattern)) subPattern = syntaxFacts.GetPatternOfParenthesizedPattern(subPattern); if (syntaxFacts.IsDeclarationPattern(subPattern)) return designatorsLegal; return IsLegalPattern(syntaxFacts, subPattern, designatorsLegal: false); } if (syntaxFacts.IsParenthesizedPattern(pattern)) { syntaxFacts.GetPartsOfParenthesizedPattern(pattern, out _, out var subPattern, out _); return IsLegalPattern(syntaxFacts, subPattern, designatorsLegal); } if (syntaxFacts.IsDeclarationPattern(pattern)) { syntaxFacts.GetPartsOfDeclarationPattern(pattern, out _, out var designator); return designator == null || designatorsLegal; } if (syntaxFacts.IsRecursivePattern(pattern)) { syntaxFacts.GetPartsOfRecursivePattern(pattern, out _, out _, out _, out var designator); return designator == null || designatorsLegal; } if (syntaxFacts.IsVarPattern(pattern)) return designatorsLegal; return true; } private static SyntaxNode NegateRelational( SyntaxGenerator generator, IBinaryOperation binaryOperation, SyntaxNode leftOperand, BinaryOperatorKind operationKind, SyntaxNode rightOperand) { return operationKind switch { BinaryOperatorKind.LessThanOrEqual => IsSpecialCaseBinaryExpression(binaryOperation, operationKind) ? generator.ValueEqualsExpression(leftOperand, rightOperand) : generator.LessThanOrEqualExpression(leftOperand, rightOperand), BinaryOperatorKind.GreaterThanOrEqual => IsSpecialCaseBinaryExpression(binaryOperation, operationKind) ? generator.ValueEqualsExpression(leftOperand, rightOperand) : generator.GreaterThanOrEqualExpression(leftOperand, rightOperand), BinaryOperatorKind.LessThan => generator.LessThanExpression(leftOperand, rightOperand), BinaryOperatorKind.GreaterThan => generator.GreaterThanExpression(leftOperand, rightOperand), BinaryOperatorKind.Or => generator.BitwiseOrExpression(leftOperand, rightOperand), BinaryOperatorKind.And => generator.BitwiseAndExpression(leftOperand, rightOperand), BinaryOperatorKind.ConditionalOr => generator.LogicalOrExpression(leftOperand, rightOperand), BinaryOperatorKind.ConditionalAnd => generator.LogicalAndExpression(leftOperand, rightOperand), _ => throw ExceptionUtilities.UnexpectedValue(operationKind), }; } /// <summary> /// Returns true if the binaryExpression consists of an expression that can never be negative, /// such as length or unsigned numeric types, being compared to zero with greater than, /// less than, or equals relational operator. /// </summary> public static bool IsSpecialCaseBinaryExpression( IBinaryOperation binaryOperation, BinaryOperatorKind operationKind) { if (binaryOperation == null) return false; var rightOperand = RemoveImplicitConversion(binaryOperation.RightOperand); var leftOperand = RemoveImplicitConversion(binaryOperation.LeftOperand); return operationKind switch { BinaryOperatorKind.LessThanOrEqual when rightOperand.IsNumericLiteral() => CanSimplifyToLengthEqualsZeroExpression(leftOperand, (ILiteralOperation)rightOperand), BinaryOperatorKind.GreaterThanOrEqual when leftOperand.IsNumericLiteral() => CanSimplifyToLengthEqualsZeroExpression(rightOperand, (ILiteralOperation)leftOperand), _ => false, }; } private static IOperation RemoveImplicitConversion(IOperation operation) { return operation is IConversionOperation conversion && conversion.IsImplicit ? RemoveImplicitConversion(conversion.Operand) : operation; } private static bool CanSimplifyToLengthEqualsZeroExpression( IOperation variableExpression, ILiteralOperation numericLiteralExpression) { var numericValue = numericLiteralExpression.ConstantValue; if (numericValue.HasValue && numericValue.Value is 0) { if (variableExpression is IPropertyReferenceOperation propertyOperation) { var property = propertyOperation.Property; if (property.Name is nameof(Array.Length) or nameof(Array.LongLength)) { var containingType = property.ContainingType; if (containingType?.SpecialType == SpecialType.System_Array || containingType?.SpecialType == SpecialType.System_String) { return true; } } } var type = variableExpression.Type; switch (type?.SpecialType) { case SpecialType.System_Byte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: case SpecialType.System_UInt64: return true; } } return false; } private static SyntaxNode GetNegationOfLiteralExpression( SyntaxNode expression, SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, SemanticModel semanticModel) { var syntaxFacts = generatorInternal.SyntaxFacts; var operation = semanticModel.GetOperation(expression); SyntaxNode newLiteralExpression; if (expression.RawKind == syntaxFacts.SyntaxKinds.TrueLiteralExpression || operation is { Kind: OperationKind.Literal, ConstantValue: { HasValue: true, Value: true } }) { newLiteralExpression = generator.FalseLiteralExpression(); } else if ( expression.RawKind == syntaxFacts.SyntaxKinds.FalseLiteralExpression || operation is { Kind: OperationKind.Literal, ConstantValue: { HasValue: true, Value: false } }) { newLiteralExpression = generator.TrueLiteralExpression(); } else { newLiteralExpression = generator.LogicalNotExpression(expression.WithoutTrivia()); } return newLiteralExpression.WithTriviaFrom(expression); } private static SyntaxNode GetNegationOfConstantPattern( SyntaxNode pattern, SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, SpecialType? patternValueType) { var syntaxFacts = generatorInternal.SyntaxFacts; // If we have `is true/false` and a Boolean value, just swap that to be `is false/true`. // If the value isn't a Boolean, swapping to `is false/true` is incorrect since non-Booleans match neither. // As an example, `!(new object() is true)` is equivalent to `new object() is not true` but not `new object() is false`. if (patternValueType == SpecialType.System_Boolean) { var expression = syntaxFacts.GetExpressionOfConstantPattern(pattern); if (syntaxFacts.IsTrueLiteralExpression(expression)) return generatorInternal.ConstantPattern(generator.FalseLiteralExpression()); if (syntaxFacts.IsFalseLiteralExpression(expression)) return generatorInternal.ConstantPattern(generator.TrueLiteralExpression()); } // Otherwise, just negate the entire pattern, we don't have anything else special we can do here. return generatorInternal.NotPattern(pattern); } private static SyntaxNode GetNegationOfLogicalNotExpression( SyntaxNode expression, ISyntaxFacts syntaxFacts) { var operatorToken = syntaxFacts.GetOperatorTokenOfPrefixUnaryExpression(expression); var operand = syntaxFacts.GetOperandOfPrefixUnaryExpression(expression); return operand.WithPrependedLeadingTrivia(operatorToken.LeadingTrivia) .WithAdditionalAnnotations(Simplifier.Annotation); } private static SyntaxNode GetNegationOfUnaryPattern( SyntaxNode pattern, SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, ISyntaxFacts syntaxFacts) { // not not p -> p if (syntaxFacts.IsNotPattern(pattern)) { return GetNegationOfNotPattern(pattern, generator, generatorInternal, syntaxFacts); } // If there are other interesting unary patterns in the future, we can support specialized logic for // negating them here. return generatorInternal.NotPattern(pattern); } private static SyntaxNode GetNegationOfNotPattern( SyntaxNode pattern, SyntaxGenerator generator, SyntaxGeneratorInternal generatorInternal, ISyntaxFacts syntaxFacts) { Contract.ThrowIfFalse(syntaxFacts.IsNotPattern(pattern)); syntaxFacts.GetPartsOfUnaryPattern(pattern, out var opToken, out var subPattern); // If we started with `not object`, instead of converting to `object`, directly convert to `not null` if (syntaxFacts.SupportsNotPattern(pattern.SyntaxTree.Options) && syntaxFacts.IsTypePattern(subPattern)) { var type = syntaxFacts.GetTypeOfTypePattern(subPattern); if (syntaxFacts.IsPredefinedType(type, PredefinedType.Object)) { return generatorInternal.UnaryPattern(opToken, generatorInternal.ConstantPattern( generator.NullLiteralExpression().WithTriviaFrom(type))); } } return subPattern.WithPrependedLeadingTrivia(opToken.LeadingTrivia) .WithAdditionalAnnotations(Simplifier.Annotation); } } |