diff --git a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs index ffa004f227e5c..6c5fe787f8cac 100644 --- a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs +++ b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs @@ -2913,4 +2913,115 @@ public void Dispose() } } """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81322")] + public Task TestIfStatement_CompoundAssignment_Event() + => TestInRegularAndScriptAsync( + """ + using System; + + class C + { + event Action SomeEvent; + + static void M(C c) + { + [|if|] (c is not null) + c.SomeEvent += () => { }; + } + } + """, + """ + using System; + + class C + { + event Action SomeEvent; + + static void M(C c) + { + c?.SomeEvent += () => { }; + } + } + """, languageVersion: LanguageVersion.CSharp14); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81322")] + public Task TestIfStatement_CompoundAssignment_AddAssignment() + => TestInRegularAndScriptAsync( + """ + using System; + + class C + { + int Value; + + static void M(C c) + { + [|if|] (c != null) + c.Value += 5; + } + } + """, + """ + using System; + + class C + { + int Value; + + static void M(C c) + { + c?.Value += 5; + } + } + """, languageVersion: LanguageVersion.CSharp14); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81322")] + public Task TestIfStatement_CompoundAssignment_SubtractAssignment() + => TestInRegularAndScriptAsync( + """ + using System; + + class C + { + int Value; + + static void M(C c) + { + [|if|] (c != null) + c.Value -= 5; + } + } + """, + """ + using System; + + class C + { + int Value; + + static void M(C c) + { + c?.Value -= 5; + } + } + """, languageVersion: LanguageVersion.CSharp14); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81322")] + public Task TestIfStatement_CompoundAssignment_NotAvailableInCSharp13() + => TestMissingInRegularAndScriptAsync( + """ + using System; + + class C + { + event Action SomeEvent; + + static void M(C c) + { + if (c is not null) + c.SomeEvent += () => { }; + } + } + """, LanguageVersion.CSharp13); } diff --git a/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForIfNullCheckDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForIfNullCheckDiagnosticAnalyzer.cs index 567fe73d8300c..c91b586688b52 100644 --- a/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForIfNullCheckDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCoalesceExpression/AbstractUseCoalesceExpressionForIfNullCheckDiagnosticAnalyzer.cs @@ -96,7 +96,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) if (!AnalyzeLocalDeclarationForm(previousStatement, out expressionToCoalesce)) return; } - else if (syntaxFacts.IsAnyAssignmentStatement(previousStatement)) + else if (syntaxFacts.IsSimpleAssignmentStatement(previousStatement)) { // v = Expr(); // if (v == null) diff --git a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs index fd6b8ce86afc6..dd9da106136ab 100644 --- a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer.cs @@ -440,7 +440,8 @@ private static TExpressionSyntax RemoveObjectCastIfAny( if (node is TElementAccessExpressionSyntax elementAccess) return (TExpressionSyntax?)syntaxFacts.GetExpressionOfElementAccessExpression(elementAccess); - if (syntaxFacts.SyntaxKinds.SimpleAssignmentExpression == node.RawKind && syntaxFacts.SupportsNullConditionalAssignment(node.SyntaxTree.Options)) + if (syntaxFacts.IsAnyAssignmentStatement(node.Parent) && + syntaxFacts.SupportsNullConditionalAssignment(node.SyntaxTree.Options)) { syntaxFacts.GetPartsOfAssignmentExpressionOrStatement(node, out var left, out _, out _); return (TExpressionSyntax)left; diff --git a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer_IfStatement.cs b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer_IfStatement.cs index a071bd90072b5..567c8eeca8ffb 100644 --- a/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer_IfStatement.cs +++ b/src/Analyzers/Core/Analyzers/UseNullPropagation/AbstractUseNullPropagationDiagnosticAnalyzer_IfStatement.cs @@ -94,7 +94,7 @@ private void AnalyzeIfStatementAndReportDiagnostic( // `if ( != null) { .Method(); = null; }` // // If 'expr' is not null, then we execute the body and then end up with expr being null. So `expr?.Method(); expr = null;` - // preserves those semantics. Simialrly, if is expr is null, then `expr?.Method();` does nothing, and `expr = null` keeps it + // preserves those semantics. Simialarly, if is expr is null, then `expr?.Method();` does nothing, and `expr = null` keeps it // the same as well. So this is a valid conversion in all cases. if (!syntaxFacts.IsSimpleAssignmentStatement(nullAssignmentOpt)) return null; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index ab972009e8a36..e794d4a3b0552 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1203,9 +1203,11 @@ public bool IsDeclaration(SyntaxNode? node) public bool IsTypeDeclaration(SyntaxNode node) => SyntaxFacts.IsTypeDeclaration(node.Kind()); - public bool IsSimpleAssignmentStatement([NotNullWhen(true)] SyntaxNode? statement) - => statement is ExpressionStatementSyntax exprStatement && - exprStatement.Expression.IsKind(SyntaxKind.SimpleAssignmentExpression); + public bool IsSimpleAssignmentStatement([NotNullWhen(true)] SyntaxNode? node) + => node is ExpressionStatementSyntax { Expression: (kind: SyntaxKind.SimpleAssignmentExpression) }; + + public bool IsAnyAssignmentStatement([NotNullWhen(true)] SyntaxNode? node) + => node is ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax }; public void GetPartsOfAssignmentStatement( SyntaxNode statement, out SyntaxNode left, out SyntaxToken operatorToken, out SyntaxNode right) @@ -1229,10 +1231,6 @@ public void GetPartsOfAssignmentExpressionOrStatement( right = assignment.Right; } - // C# does not have assignment statements. - public bool IsAnyAssignmentStatement([NotNullWhen(true)] SyntaxNode? node) - => false; - public SyntaxToken GetIdentifierOfSimpleName(SyntaxNode node) => ((SimpleNameSyntax)node).Identifier;