Skip to content

Commit

Permalink
Merge pull request #72466 from sdelarosbil/notpattern-qualify-type
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Mar 21, 2024
2 parents 35aea35 + 35d371c commit 9a299e6
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,37 @@ void M(object x)
}.RunAsync();
}

[Fact]
public async Task BinaryIsExpression2()
{
await new VerifyCS.Test
{
TestCode = """
class C
{
void M(object x)
{
if (!(x [|is|] string /*trailing*/))
{
}
}
}
""",
FixedCode = """
class C
{
void M(object x)
{
if (x is not string /*trailing*/)
{
}
}
}
""",
LanguageVersion = LanguageVersion.CSharp9,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/50690")]
public async Task ConstantPattern()
{
Expand Down Expand Up @@ -174,6 +205,86 @@ void M(object x)
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72370")]
public async Task UseNotPattern2()
{
await new VerifyCS.Test
{
TestCode = """
public class Program
{
public class C
{
}
public static void Main()
{
C C = new();
object O = C;
if (!(O [|is|] C))
{
}
}
}
""",
FixedCode = """
public class Program
{
public class C
{
}
public static void Main()
{
C C = new();
object O = C;
if (O is not Program.C)
{
}
}
}
""",
LanguageVersion = LanguageVersion.CSharp9,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72370")]
public async Task UseNotPattern3()
{
await new VerifyCS.Test
{
TestCode = """
public class Program
{
public class C
{
}
public static void Main(object O)
{
if (!(O [|is|] C))
{
}
}
}
""",
FixedCode = """
public class Program
{
public class C
{
}
public static void Main(object O)
{
if (O is not C)
{
}
}
}
""",
LanguageVersion = LanguageVersion.CSharp9,
}.RunAsync();
}

[Fact]
public async Task UnavailableInCSharp8()
{
Expand Down Expand Up @@ -225,6 +336,72 @@ void M(object x)
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/50690")]
public async Task BinaryIsObject2()
{
await new VerifyCS.Test
{
TestCode = """
class C
{
void M(object x)
{
if (!(x [|is|] object /*trailing*/))
{
}
}
}
""",
FixedCode = """
class C
{
void M(object x)
{
if (x is null /*trailing*/)
{
}
}
}
""",
LanguageVersion = LanguageVersion.CSharp9,
}.RunAsync();
}

[Fact]
public async Task BinaryIsObject3()
{
await new VerifyCS.Test
{
TestCode = """
using System;
class C
{
void M(object x)
{
if (!(x [|is|] Object))
{
}
}
}
""",
FixedCode = """
using System;
class C
{
void M(object x)
{
if (x is null)
{
}
}
}
""",
LanguageVersion = LanguageVersion.CSharp9,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68784")]
public async Task NotInExpressionTree()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,12 @@ public bool IsMethodDeclaration([NotNullWhen(true)] SyntaxNode? node)
public bool IsSimpleName([NotNullWhen(true)] SyntaxNode? node)
=> node is SimpleNameSyntax;

public bool IsAnyName([NotNullWhen(true)] SyntaxNode? node)
=> node is NameSyntax;

public bool IsAnyType([NotNullWhen(true)] SyntaxNode? node)
=> node is TypeSyntax;

public bool IsNamedMemberInitializer([NotNullWhen(true)] SyntaxNode? node)
=> node is AssignmentExpressionSyntax(SyntaxKind.SimpleAssignmentExpression) { Left: IdentifierNameSyntax };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ void GetPartsOfTupleExpression<TArgumentSyntax>(SyntaxNode node,
bool IsMemberAccessExpression([NotNullWhen(true)] SyntaxNode? node);
bool IsMethodDeclaration([NotNullWhen(true)] SyntaxNode? node);
bool IsSimpleName([NotNullWhen(true)] SyntaxNode? node);
bool IsAnyName([NotNullWhen(true)] SyntaxNode? node);
bool IsAnyType([NotNullWhen(true)] SyntaxNode? node);

bool IsNamedMemberInitializer([NotNullWhen(true)] SyntaxNode? node);
bool IsElementAccessInitializer([NotNullWhen(true)] SyntaxNode? node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService
Return TypeOf node Is SimpleNameSyntax
End Function

Public Function IsAnyName(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsAnyName
Return TypeOf node Is NameSyntax
End Function

Public Function IsAnyType(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsAnyType
Return TypeOf node Is TypeSyntax
End Function

Public Function IsNamedMemberInitializer(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsNamedMemberInitializer
Return TypeOf node Is NamedFieldInitializerSyntax
End Function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageService;
Expand Down Expand Up @@ -147,58 +148,85 @@ private static SyntaxNode GetNegationOfBinaryExpression(
syntaxFacts.GetPartsOfBinaryExpression(expressionNode, out var leftOperand, out var operatorToken, out var rightOperand);

var operation = semanticModel.GetOperation(expressionNode, cancellationToken);
if (operation is not IBinaryOperation binaryOperation)
if (operation is IBinaryOperation binaryOperation)
{
if (syntaxFacts.IsIsTypeExpression(expressionNode))
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)
{
// `is object` -> `is null`
if (syntaxFacts.IsPredefinedType(rightOperand, PredefinedType.Object) &&
generatorInternal.SupportsPatterns(semanticModel.SyntaxTree.Options))
{
return generatorInternal.IsPatternExpression(leftOperand, operatorToken, generatorInternal.ConstantPattern(generator.NullLiteralExpression()));
}
return generator.LogicalNotExpression(expressionNode);
}

// `is y` -> `is not y`
if (syntaxFacts.SupportsNotPattern(semanticModel.SyntaxTree.Options))
return generatorInternal.IsPatternExpression(leftOperand, operatorToken, generatorInternal.NotPattern(generatorInternal.TypePattern(rightOperand)));
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);
}

// Apply the logical not operator if it is not a binary operation.
return generator.LogicalNotExpression(expressionNode);
}
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);

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)
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))
{
return generator.LogicalNotExpression(expressionNode);
// `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);
}

if (binaryOperation.OperatorKind is BinaryOperatorKind.Or or
BinaryOperatorKind.And or
BinaryOperatorKind.ConditionalAnd or
BinaryOperatorKind.ConditionalOr)
return generatorInternal.IsPatternExpression(
leftOperand,
operatorToken,
generatorInternal.NotPattern(innerPattern.WithTriviaFrom(rightOperand)));
}
else
{
leftOperand = generator.Negate(generatorInternal, leftOperand, semanticModel, cancellationToken);
rightOperand = generator.Negate(generatorInternal, rightOperand, semanticModel, cancellationToken);
// Apply the logical not operator if it is not a binary operation and also doesn't support patterns.
return generator.LogicalNotExpression(expressionNode);
}

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

private static SyntaxNode GetNegationOfBinaryPattern(
Expand Down

0 comments on commit 9a299e6

Please sign in to comment.