Skip to content

Commit ee6668f

Browse files
Improve SyntaxLogicalInverter to Handling Additional Cases and reduce Unnecessary Parentheses (#1086)
Co-authored-by: Josef Pihrt <josef@pihrt.net>
1 parent 6833900 commit ee6668f

File tree

3 files changed

+91
-9
lines changed

3 files changed

+91
-9
lines changed

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Improve inversion of logical expressions to handling additional cases ([#1086](https://github.com/josefpihrt/roslynator/pull/1086)).
13+
1014
## [4.3.0] - 2023-04-24
1115

1216
### Changed

src/CSharp.Workspaces/CSharp/SyntaxLogicalInverter.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,16 @@ public ExpressionSyntax LogicallyInvert(
6767
return newExpression.WithTriviaFrom(expression);
6868
}
6969

70-
private ParenthesizedExpressionSyntax LogicallyInvertAndParenthesize(
70+
private ExpressionSyntax LogicallyInvertAndParenthesize(
7171
ExpressionSyntax expression,
7272
SemanticModel semanticModel,
7373
CancellationToken cancellationToken)
7474
{
7575
if (expression is null)
7676
return null;
7777

78-
return LogicallyInvertImpl(expression, semanticModel, cancellationToken).Parenthesize();
78+
var inverted = LogicallyInvertImpl(expression, semanticModel, cancellationToken);
79+
return inverted.IsKind(SyntaxKind.LogicalNotExpression, SyntaxKind.ParenthesizedExpression) ? inverted : inverted.Parenthesize();
7980
}
8081

8182
private ExpressionSyntax LogicallyInvertImpl(
@@ -88,18 +89,22 @@ private ExpressionSyntax LogicallyInvertImpl(
8889

8990
switch (expression.Kind())
9091
{
92+
case SyntaxKind.IdentifierName:
9193
case SyntaxKind.SimpleMemberAccessExpression:
9294
case SyntaxKind.InvocationExpression:
9395
case SyntaxKind.ElementAccessExpression:
96+
case SyntaxKind.CheckedExpression:
97+
case SyntaxKind.UncheckedExpression:
98+
case SyntaxKind.DefaultExpression:
99+
case SyntaxKind.ConditionalAccessExpression:
100+
{
101+
return DefaultInvert(expression, false);
102+
}
94103
case SyntaxKind.PostIncrementExpression:
95104
case SyntaxKind.PostDecrementExpression:
96105
case SyntaxKind.ObjectCreationExpression:
97106
case SyntaxKind.AnonymousObjectCreationExpression:
98107
case SyntaxKind.TypeOfExpression:
99-
case SyntaxKind.DefaultExpression:
100-
case SyntaxKind.CheckedExpression:
101-
case SyntaxKind.UncheckedExpression:
102-
case SyntaxKind.IdentifierName:
103108
{
104109
return DefaultInvert(expression);
105110
}
@@ -500,7 +505,7 @@ private ExpressionSyntax InvertIsPattern(IsPatternExpressionSyntax isPattern)
500505

501506
return isPattern.WithPattern(newConstantPattern);
502507
}
503-
else if (constantExpression.IsKind(SyntaxKind.NullLiteralExpression))
508+
else if (constantExpression.IsKind(SyntaxKind.NullLiteralExpression, SyntaxKind.NumericLiteralExpression, SyntaxKind.StringLiteralExpression))
504509
{
505510
UnaryPatternSyntax notPattern = NotPattern(constantPattern.WithoutTrivia()).WithTriviaFrom(constantPattern);
506511

@@ -515,14 +520,17 @@ private ExpressionSyntax InvertIsPattern(IsPatternExpressionSyntax isPattern)
515520
return DefaultInvert(isPattern);
516521
}
517522

518-
private static PrefixUnaryExpressionSyntax DefaultInvert(ExpressionSyntax expression)
523+
private static PrefixUnaryExpressionSyntax DefaultInvert(ExpressionSyntax expression, bool needsParenthesize = true)
519524
{
520525
SyntaxDebug.Assert(expression.Kind() != SyntaxKind.ParenthesizedExpression, expression);
521526

522527
SyntaxTriviaList leadingTrivia = expression.GetLeadingTrivia();
528+
expression = expression.WithoutLeadingTrivia();
529+
if (needsParenthesize)
530+
expression = expression.Parenthesize();
523531

524532
return LogicalNotExpression(
525-
expression.WithoutLeadingTrivia().Parenthesize(),
533+
expression,
526534
Token(leadingTrivia, SyntaxKind.ExclamationToken, SyntaxTriviaList.Empty));
527535
}
528536
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Linq;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
using Microsoft.CodeAnalysis.Text;
7+
using Xunit;
8+
9+
namespace Roslynator.CSharp.Workspaces.Tests;
10+
11+
public class SyntaxLogicallyInvertTests
12+
{
13+
private SyntaxLogicalInverter _inverter;
14+
15+
public SyntaxLogicallyInvertTests()
16+
{
17+
_inverter = SyntaxLogicalInverter.Default;
18+
}
19+
20+
[Theory]
21+
[InlineData(@"x", @"!x")]
22+
[InlineData(@"!x", @"x")]
23+
[InlineData(@"x is ""abc""", @"x is not ""abc""")]
24+
[InlineData(@"x is 1", @"x is not 1")]
25+
[InlineData(@"x is null", @"x is not null")]
26+
[InlineData(@"x is true", @"x is false")]
27+
[InlineData(@"true", @"false")]
28+
[InlineData(@"false", @"true")]
29+
[InlineData(@"x >= 3", @"x < 3")]
30+
[InlineData(@"x > 3", @"x <= 3")]
31+
[InlineData(@"x <= 3", @"x > 3")]
32+
[InlineData(@"x < 3", @"x >= 3")]
33+
[InlineData(@"x == y", @"x != y")]
34+
[InlineData(@"x != y", @"x == y")]
35+
[InlineData(@"(bool)x || (bool)y", @"!((bool)x) && !((bool)y)")]
36+
[InlineData(@"(bool)x && (bool)y", @"!((bool)x) || !((bool)y)")]
37+
[InlineData(@"x ?? true", @"x == false")]
38+
[InlineData(@"x ?? false", @"x != true")]
39+
[InlineData(@"(bool)x ? y : z", @"(bool)x ? !y : !z")]
40+
[InlineData(@"x[0]", @"!x[0]")]
41+
[InlineData(@"default(bool)", @"!default(bool)")]
42+
[InlineData(@"checked(x + y)", @"!checked(x + y)")]
43+
[InlineData(@"unchecked(x + y)", @"!unchecked(x + y)")]
44+
[InlineData(@"(bool)x", @"!((bool)x)")]
45+
[InlineData(@"x & y", @"!x | !y")]
46+
[InlineData(@"x ^ y", @"!(x ^ y)")]
47+
[InlineData(@"x | y", @"!x & !y")]
48+
[InlineData(@"x = y", @"!(x = y)")]
49+
[InlineData(@"await x", @"!(await x)")]
50+
[InlineData(@"x ?? y", @"!(x ?? y)")]
51+
[InlineData(@"x.a", @"!x.a")]
52+
[InlineData(@"x.a()", @"!x.a()")]
53+
[InlineData(@"x?.a", @"!x?.a")]
54+
public async Task LogicallyInvert(string source, string expected)
55+
{
56+
var sourceCode = $"class C {{ void M(dynamic x, dynamic y, dynamic z){{ if({source})return;}} }}";
57+
var workspace = new AdhocWorkspace();
58+
var newProject = workspace.AddProject("TestProject", LanguageNames.CSharp);
59+
var newDocument = workspace.AddDocument(newProject.Id, "TestDocument.cs", SourceText.From(sourceCode));
60+
var syntaxTree = await newDocument.GetSyntaxTreeAsync();
61+
var compilation = await newDocument.Project.GetCompilationAsync();
62+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
63+
64+
var expression = syntaxTree.GetRoot().DescendantNodes().OfType<IfStatementSyntax>().Single().Condition;
65+
66+
var result = _inverter.LogicallyInvert(expression, semanticModel, CancellationToken.None);
67+
Assert.Equal(expected, result.NormalizeWhitespace().ToFullString());
68+
}
69+
70+
}

0 commit comments

Comments
 (0)