Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qualify the type when negating a pattern to a not pattern #72466

Merged
merged 10 commits into from
Mar 21, 2024
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
Loading