Skip to content

Commit c9b00c7

Browse files
Fix: Support compound assignments in "Use null propagation" analyzer (#81328)
2 parents f6824bc + abe9c47 commit c9b00c7

File tree

5 files changed

+120
-10
lines changed

5 files changed

+120
-10
lines changed

src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2913,4 +2913,115 @@ public void Dispose()
29132913
}
29142914
}
29152915
""");
2916+
2917+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81322")]
2918+
public Task TestIfStatement_CompoundAssignment_Event()
2919+
=> TestInRegularAndScriptAsync(
2920+
"""
2921+
using System;
2922+
2923+
class C
2924+
{
2925+
event Action SomeEvent;
2926+
2927+
static void M(C c)
2928+
{
2929+
[|if|] (c is not null)
2930+
c.SomeEvent += () => { };
2931+
}
2932+
}
2933+
""",
2934+
"""
2935+
using System;
2936+
2937+
class C
2938+
{
2939+
event Action SomeEvent;
2940+
2941+
static void M(C c)
2942+
{
2943+
c?.SomeEvent += () => { };
2944+
}
2945+
}
2946+
""", languageVersion: LanguageVersion.CSharp14);
2947+
2948+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81322")]
2949+
public Task TestIfStatement_CompoundAssignment_AddAssignment()
2950+
=> TestInRegularAndScriptAsync(
2951+
"""
2952+
using System;
2953+
2954+
class C
2955+
{
2956+
int Value;
2957+
2958+
static void M(C c)
2959+
{
2960+
[|if|] (c != null)
2961+
c.Value += 5;
2962+
}
2963+
}
2964+
""",
2965+
"""
2966+
using System;
2967+
2968+
class C
2969+
{
2970+
int Value;
2971+
2972+
static void M(C c)
2973+
{
2974+
c?.Value += 5;
2975+
}
2976+
}
2977+
""", languageVersion: LanguageVersion.CSharp14);
2978+
2979+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81322")]
2980+
public Task TestIfStatement_CompoundAssignment_SubtractAssignment()
2981+
=> TestInRegularAndScriptAsync(
2982+
"""
2983+
using System;
2984+
2985+
class C
2986+
{
2987+
int Value;
2988+
2989+
static void M(C c)
2990+
{
2991+
[|if|] (c != null)
2992+
c.Value -= 5;
2993+
}
2994+
}
2995+
""",
2996+
"""
2997+
using System;
2998+
2999+
class C
3000+
{
3001+
int Value;
3002+
3003+
static void M(C c)
3004+
{
3005+
c?.Value -= 5;
3006+
}
3007+
}
3008+
""", languageVersion: LanguageVersion.CSharp14);
3009+
3010+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81322")]
3011+
public Task TestIfStatement_CompoundAssignment_NotAvailableInCSharp13()
3012+
=> TestMissingInRegularAndScriptAsync(
3013+
"""
3014+
using System;
3015+
3016+
class C
3017+
{
3018+
event Action SomeEvent;
3019+
3020+
static void M(C c)
3021+
{
3022+
if (c is not null)
3023+
c.SomeEvent += () => { };
3024+
}
3025+
}
3026+
""", LanguageVersion.CSharp13);
29163027
}

src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForIfNullCheckDiagnosticAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
9696
if (!AnalyzeLocalDeclarationForm(previousStatement, out expressionToCoalesce))
9797
return;
9898
}
99-
else if (syntaxFacts.IsAnyAssignmentStatement(previousStatement))
99+
else if (syntaxFacts.IsSimpleAssignmentStatement(previousStatement))
100100
{
101101
// v = Expr();
102102
// if (v == null)

src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,8 @@ private static TExpressionSyntax RemoveObjectCastIfAny(
440440
if (node is TElementAccessExpressionSyntax elementAccess)
441441
return (TExpressionSyntax?)syntaxFacts.GetExpressionOfElementAccessExpression(elementAccess);
442442

443-
if (syntaxFacts.SyntaxKinds.SimpleAssignmentExpression == node.RawKind && syntaxFacts.SupportsNullConditionalAssignment(node.SyntaxTree.Options))
443+
if (syntaxFacts.IsAnyAssignmentStatement(node.Parent) &&
444+
syntaxFacts.SupportsNullConditionalAssignment(node.SyntaxTree.Options))
444445
{
445446
syntaxFacts.GetPartsOfAssignmentExpressionOrStatement(node, out var left, out _, out _);
446447
return (TExpressionSyntax)left;

src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer_IfStatement.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ private void AnalyzeIfStatementAndReportDiagnostic(
9494
// `if (<expr> != null) { <expr>.Method(); <expr> = null; }`
9595
//
9696
// If 'expr' is not null, then we execute the body and then end up with expr being null. So `expr?.Method(); expr = null;`
97-
// preserves those semantics. Simialrly, if is expr is null, then `expr?.Method();` does nothing, and `expr = null` keeps it
97+
// preserves those semantics. Simialarly, if is expr is null, then `expr?.Method();` does nothing, and `expr = null` keeps it
9898
// the same as well. So this is a valid conversion in all cases.
9999
if (!syntaxFacts.IsSimpleAssignmentStatement(nullAssignmentOpt))
100100
return null;

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,9 +1203,11 @@ public bool IsDeclaration(SyntaxNode? node)
12031203
public bool IsTypeDeclaration(SyntaxNode node)
12041204
=> SyntaxFacts.IsTypeDeclaration(node.Kind());
12051205

1206-
public bool IsSimpleAssignmentStatement([NotNullWhen(true)] SyntaxNode? statement)
1207-
=> statement is ExpressionStatementSyntax exprStatement &&
1208-
exprStatement.Expression.IsKind(SyntaxKind.SimpleAssignmentExpression);
1206+
public bool IsSimpleAssignmentStatement([NotNullWhen(true)] SyntaxNode? node)
1207+
=> node is ExpressionStatementSyntax { Expression: (kind: SyntaxKind.SimpleAssignmentExpression) };
1208+
1209+
public bool IsAnyAssignmentStatement([NotNullWhen(true)] SyntaxNode? node)
1210+
=> node is ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax };
12091211

12101212
public void GetPartsOfAssignmentStatement(
12111213
SyntaxNode statement, out SyntaxNode left, out SyntaxToken operatorToken, out SyntaxNode right)
@@ -1229,10 +1231,6 @@ public void GetPartsOfAssignmentExpressionOrStatement(
12291231
right = assignment.Right;
12301232
}
12311233

1232-
// C# does not have assignment statements.
1233-
public bool IsAnyAssignmentStatement([NotNullWhen(true)] SyntaxNode? node)
1234-
=> false;
1235-
12361234
public SyntaxToken GetIdentifierOfSimpleName(SyntaxNode node)
12371235
=> ((SimpleNameSyntax)node).Identifier;
12381236

0 commit comments

Comments
 (0)