@@ -49,7 +49,7 @@ public static SyntaxNode Negate(
4949 bool negateBinary ,
5050 CancellationToken cancellationToken )
5151 {
52- return Negate ( generator , generatorInternal , expressionOrPattern , semanticModel , negateBinary , allowSwappingBooleans : true , cancellationToken ) ;
52+ return Negate ( generator , generatorInternal , expressionOrPattern , semanticModel , negateBinary , patternValueType : null , cancellationToken ) ;
5353 }
5454
5555 public static SyntaxNode Negate (
@@ -58,7 +58,7 @@ public static SyntaxNode Negate(
5858 SyntaxNode expressionOrPattern ,
5959 SemanticModel semanticModel ,
6060 bool negateBinary ,
61- bool allowSwappingBooleans ,
61+ SpecialType ? patternValueType ,
6262 CancellationToken cancellationToken )
6363 {
6464 var options = semanticModel . SyntaxTree . Options ;
@@ -105,7 +105,7 @@ public static SyntaxNode Negate(
105105 return GetNegationOfBinaryPattern ( expressionOrPattern , generator , generatorInternal , semanticModel , cancellationToken ) ;
106106
107107 if ( syntaxFacts . IsConstantPattern ( expressionOrPattern ) )
108- return GetNegationOfConstantPattern ( expressionOrPattern , generator , generatorInternal , allowSwappingBooleans ) ;
108+ return GetNegationOfConstantPattern ( expressionOrPattern , generator , generatorInternal , patternValueType ) ;
109109
110110 if ( syntaxFacts . IsUnaryPattern ( expressionOrPattern ) )
111111 return GetNegationOfUnaryPattern ( expressionOrPattern , generator , generatorInternal , syntaxFacts ) ;
@@ -126,9 +126,10 @@ public static SyntaxNode Negate(
126126 return generator . IsTypeExpression ( expression , type ) ;
127127 }
128128
129- // TODO(cyrusn): We could support negating relational patterns in the future. i.e.
130- //
131- // not >= 0 -> < 0
129+ if ( syntaxFacts . IsRelationalPattern ( expressionOrPattern ) )
130+ {
131+ return GetNegationOfRelationalPattern ( expressionOrPattern , generatorInternal , patternValueType ) ;
132+ }
132133
133134 return syntaxFacts . IsAnyPattern ( expressionOrPattern )
134135 ? generatorInternal . NotPattern ( expressionOrPattern )
@@ -207,7 +208,7 @@ private static SyntaxNode GetNegationOfBinaryPattern(
207208 SemanticModel semanticModel ,
208209 CancellationToken cancellationToken )
209210 {
210- // Apply demorgans 's law here.
211+ // Apply De Morgan 's laws here.
211212 //
212213 // not (a and b) -> not a or not b
213214 // not (a or b) -> not a and not b
@@ -241,11 +242,11 @@ private static SyntaxNode GetNegationOfIsPatternExpression(SyntaxNode isExpressi
241242 if ( syntaxFacts . SupportsNotPattern ( semanticModel . SyntaxTree . Options ) )
242243 {
243244 // We do support 'not' patterns. So attempt to push a 'not' pattern into the current is-pattern RHS.
244- // If the value isn't a Boolean and the pattern `is true/false`, swapping to `is false/true` is incorrect since non-Booleans match neither.
245- // As an example, `!(new object() is true)` is equivalent to `new object() is not true` but not `new object() is false` .
245+ // We include the type of the value when negating the pattern, since it allows for nicer negations of
246+ // ` is true/false` for Boolean values and relational patterns for numeric values .
246247 var operation = semanticModel . GetOperation ( isExpression , cancellationToken ) ;
247- var isValueBoolean = operation is IIsPatternOperation isPatternOperation && isPatternOperation . Value . Type ? . SpecialType == SpecialType . System_Boolean ;
248- negatedPattern = generator . Negate ( generatorInternal , pattern , semanticModel , negateBinary : true , allowSwappingBooleans : isValueBoolean , cancellationToken ) ;
248+ var valueType = ( operation as IIsPatternOperation ) ? . Value . Type ? . SpecialType ;
249+ negatedPattern = generator . Negate ( generatorInternal , pattern , semanticModel , negateBinary : true , valueType , cancellationToken ) ;
249250 }
250251 else if ( syntaxFacts . IsNotPattern ( pattern ) )
251252 {
@@ -272,6 +273,33 @@ private static SyntaxNode GetNegationOfIsPatternExpression(SyntaxNode isExpressi
272273 return generator . LogicalNotExpression ( isExpression ) ;
273274 }
274275
276+ private static SyntaxNode GetNegationOfRelationalPattern (
277+ SyntaxNode expressionNode ,
278+ SyntaxGeneratorInternal generatorInternal ,
279+ SpecialType ? patternValueType )
280+ {
281+ if ( patternValueType is SpecialType specialType && specialType . IsNumericType ( ) )
282+ {
283+ // If we know the value is numeric, we can negate the relational operator.
284+ // This is not valid for non-numeric value since they never match a relational pattern.
285+ // Similarly, it's not valid for nullable values, since null never matches a relational pattern.
286+ // As an example, `!(new object() is < 1)` is equivalent to `new object() is not < 1` but not `new object() is >= 1`.
287+ var syntaxFacts = generatorInternal . SyntaxFacts ;
288+ syntaxFacts . GetPartsOfRelationalPattern ( expressionNode , out var operatorToken , out var expression ) ;
289+ syntaxFacts . TryGetPredefinedOperator ( operatorToken , out var predefinedOperator ) ;
290+ return predefinedOperator switch
291+ {
292+ PredefinedOperator . LessThan => generatorInternal . GreaterThanEqualsRelationalPattern ( expression ) ,
293+ PredefinedOperator . LessThanOrEqual => generatorInternal . GreaterThanRelationalPattern ( expression ) ,
294+ PredefinedOperator . GreaterThan => generatorInternal . LessThanEqualsRelationalPattern ( expression ) ,
295+ PredefinedOperator . GreaterThanOrEqual => generatorInternal . LessThanRelationalPattern ( expression ) ,
296+ _ => generatorInternal . NotPattern ( expressionNode )
297+ } ;
298+ }
299+
300+ return generatorInternal . NotPattern ( expressionNode ) ;
301+ }
302+
275303 private static bool IsLegalPattern ( ISyntaxFacts syntaxFacts , SyntaxNode pattern , bool designatorsLegal )
276304 {
277305 // It is illegal to create a pattern that has a designator under a not-pattern or or-pattern
@@ -440,12 +468,14 @@ private static SyntaxNode GetNegationOfConstantPattern(
440468 SyntaxNode pattern ,
441469 SyntaxGenerator generator ,
442470 SyntaxGeneratorInternal generatorInternal ,
443- bool allowSwappingBooleans )
471+ SpecialType ? patternValueType )
444472 {
445473 var syntaxFacts = generatorInternal . SyntaxFacts ;
446474
447- // If we have `is true/false` just swap that to be `is false/true` if allowed.
448- if ( allowSwappingBooleans )
475+ // If we have `is true/false` and a Boolean value, just swap that to be `is false/true`.
476+ // If the value isn't a Boolean, swapping to `is false/true` is incorrect since non-Booleans match neither.
477+ // As an example, `!(new object() is true)` is equivalent to `new object() is not true` but not `new object() is false`.
478+ if ( patternValueType == SpecialType . System_Boolean )
449479 {
450480 var expression = syntaxFacts . GetExpressionOfConstantPattern ( pattern ) ;
451481 if ( syntaxFacts . IsTrueLiteralExpression ( expression ) )
0 commit comments