Skip to content

Commit 05374d1

Browse files
CopilotAArnott
andauthored
Fix VSTHRD114 not firing for null in ternary conditional expressions (#1548)
* Initial plan * Fix VSTHRD114: detect null in ternary/conditional expressions Co-authored-by: AArnott <3548+AArnott@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: AArnott <3548+AArnott@users.noreply.github.com>
1 parent 2f19f04 commit 05374d1

2 files changed

Lines changed: 74 additions & 10 deletions

File tree

src/Microsoft.VisualStudio.Threading.Analyzers/AbstractVSTHRD114AvoidReturningNullTaskAnalyzer.cs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,42 @@ public override void Initialize(AnalysisContext context)
5555
};
5656
}
5757

58+
private static void CheckForNullValue(OperationAnalysisContext context, IOperation operation)
59+
{
60+
if (operation is IConditionalOperation conditionalOp)
61+
{
62+
if (conditionalOp.WhenTrue is { } whenTrue)
63+
{
64+
CheckForNullValue(context, whenTrue);
65+
}
66+
67+
if (conditionalOp.WhenFalse is { } whenFalse)
68+
{
69+
CheckForNullValue(context, whenFalse);
70+
}
71+
}
72+
else if (operation.ConstantValue is { HasValue: true, Value: null } &&
73+
operation.Syntax is { } nullSyntax)
74+
{
75+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, nullSyntax.GetLocation()));
76+
}
77+
}
78+
5879
private void AnalyzerReturnOperation(OperationAnalysisContext context)
5980
{
6081
var returnOperation = (IReturnOperation)context.Operation;
6182

62-
if (returnOperation.ReturnedValue is { ConstantValue: { HasValue: true, Value: null } } && // could be null for implicit returns
63-
returnOperation.ReturnedValue.Syntax is { } returnedValueSyntax &&
64-
Utils.GetContainingFunctionBlock(returnOperation) is { } block &&
65-
FindOwningSymbol(block, context.ContainingSymbol) is { } method &&
66-
!method.IsAsync &&
67-
Utils.IsTask(method.ReturnType) &&
68-
!this.LanguageUtils.MethodReturnsNullableReferenceType(method))
83+
// ReturnedValue is null for implicit/void returns
84+
if (returnOperation.ReturnedValue is not { } returnedValue ||
85+
Utils.GetContainingFunctionBlock(returnOperation) is not { } block ||
86+
FindOwningSymbol(block, context.ContainingSymbol) is not { } owningMethod ||
87+
owningMethod.IsAsync ||
88+
!Utils.IsTask(owningMethod.ReturnType) ||
89+
this.LanguageUtils.MethodReturnsNullableReferenceType(owningMethod))
6990
{
70-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, returnedValueSyntax.GetLocation()));
91+
return;
7192
}
93+
94+
CheckForNullValue(context, returnedValue);
7295
}
7396
}

test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD114AvoidReturningNullTaskAnalyzerTests.cs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public Task<object> GetTaskObj()
147147
}
148148

149149
[Fact]
150-
public async Task NullInTernary_NoDiagnostic_FalseNegative()
150+
public async Task NullInTernary_Diagnostic()
151151
{
152152
var test = @"
153153
using System.Threading.Tasks;
@@ -156,7 +156,7 @@ class Test
156156
{
157157
public Task<object> GetTaskObj(bool b)
158158
{
159-
return b ? default(Task<object>) : null;
159+
return b ? [|default(Task<object>)|] : [|null|];
160160
}
161161
}
162162
";
@@ -166,6 +166,47 @@ public Task<object> GetTaskObj(bool b)
166166
}.RunAsync();
167167
}
168168

169+
[Fact]
170+
public async Task NullInTernaryReturnStatement_Diagnostic()
171+
{
172+
var csharpTest = @"
173+
using System.Threading.Tasks;
174+
175+
class Test
176+
{
177+
public Task First(bool flag)
178+
{
179+
return flag
180+
? [|null|]
181+
: Task.CompletedTask;
182+
}
183+
184+
public Task Second(bool flag) =>
185+
flag
186+
? [|null|]
187+
: Task.CompletedTask;
188+
}
189+
";
190+
await new CSVerify.Test
191+
{
192+
TestCode = csharpTest,
193+
}.RunAsync();
194+
195+
var vbTest = @"
196+
Imports System.Threading.Tasks
197+
198+
Friend Class Test
199+
Public Function First(flag As Boolean) As Task
200+
Return If(flag, [|Nothing|], Task.CompletedTask)
201+
End Function
202+
End Class
203+
";
204+
await new VerifyVB.Test
205+
{
206+
TestCode = vbTest,
207+
}.RunAsync();
208+
}
209+
169210
[Fact]
170211
public async Task MultipleFaultyReturns_MultipleDiagnostics()
171212
{

0 commit comments

Comments
 (0)