Skip to content

Commit 12d74df

Browse files
committed
IDISP013 handle returning new ValueTask
fix #475
1 parent 05240d9 commit 12d74df

3 files changed

Lines changed: 75 additions & 23 deletions

File tree

IDisposableAnalyzers.Test/IDISP013AwaitInUsingTests/Valid.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,4 +420,42 @@ public ValueTask<IDisposable> M()
420420
""";
421421
RoslynAssert.Valid(Analyzer, disposable, code);
422422
}
423+
424+
[TestCase("default")]
425+
[TestCase("new ValueTask<int>(1)")]
426+
[TestCase("new ValueTask<int>(disposable.Equals(disposable) ? 1 : 0)")]
427+
public static void ReturnNewValueTask(string expression)
428+
{
429+
var disposable = """
430+
namespace N;
431+
432+
using System;
433+
434+
public class Disposable : IDisposable
435+
{
436+
public void Dispose()
437+
{
438+
}
439+
}
440+
""";
441+
442+
var code = """
443+
444+
namespace N;
445+
446+
using System.Threading.Tasks;
447+
448+
public class C
449+
{
450+
public ValueTask<int> M1Async()
451+
{
452+
using (var disposable = new Disposable())
453+
{
454+
return new ValueTask<int>(1);
455+
}
456+
}
457+
}
458+
""".AssertReplace("new ValueTask<int>(1)", expression);
459+
RoslynAssert.Valid(Analyzer, disposable, code);
460+
}
423461
}

IDisposableAnalyzers/Analyzers/ReturnValueAnalyzer.cs

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,39 @@ private static void HandleReturnValue(SyntaxNodeAnalysisContext context, Express
125125
if (ReturnType(context)?.IsAwaitable() == true &&
126126
IsInUsing(returnValue) &&
127127
!returnValue.TryFirstAncestorOrSelf<AwaitExpressionSyntax>(out _) &&
128-
context.SemanticModel.TryGetType(returnValue, context.CancellationToken, out var returnValueType2) &&
129-
returnValueType2.IsAwaitable() &&
130128
ShouldAwait(context, returnValue))
131129
{
132130
context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP013AwaitInUsing, returnValue.GetLocation()));
133131
}
132+
133+
static bool ShouldAwait(SyntaxNodeAnalysisContext context, ExpressionSyntax returnValue)
134+
{
135+
if (context.SemanticModel.GetType(returnValue, context.CancellationToken)?.IsAwaitable() == true)
136+
{
137+
if (returnValue.TryFirstAncestor(out InvocationExpressionSyntax? ancestor) &&
138+
ancestor.TryGetMethodName(out var ancestorName) &&
139+
ancestorName == "ThrowsAsync")
140+
{
141+
return false;
142+
}
143+
144+
return returnValue switch
145+
{
146+
InvocationExpressionSyntax invocation
147+
=> !(invocation.IsSymbol(KnownSymbols.Task.FromResult, context.SemanticModel, context.CancellationToken)
148+
|| invocation.IsSymbol(KnownSymbols.ValueTask.FromResult, context.SemanticModel, context.CancellationToken)),
149+
MemberAccessExpressionSyntax { Name.Identifier.ValueText: "CompletedTask" } memberAccess
150+
=> !(memberAccess.IsSymbol(KnownSymbols.Task.CompletedTask, context.SemanticModel, context.CancellationToken)
151+
|| memberAccess.IsSymbol(KnownSymbols.ValueTask.CompletedTask, context.SemanticModel, context.CancellationToken)),
152+
DefaultExpressionSyntax => false,
153+
LiteralExpressionSyntax => false,
154+
ObjectCreationExpressionSyntax => false,
155+
_ => true,
156+
};
157+
}
158+
159+
return false;
160+
}
134161
}
135162

136163
private static bool IsInUsing(SyntaxNode node)
@@ -176,27 +203,6 @@ private static bool IsUsing(ISymbol symbol, CancellationToken cancellationToken)
176203
return false;
177204
}
178205

179-
private static bool ShouldAwait(SyntaxNodeAnalysisContext context, ExpressionSyntax returnValue)
180-
{
181-
if (returnValue.TryFirstAncestor(out InvocationExpressionSyntax? ancestor) &&
182-
ancestor.TryGetMethodName(out var ancestorName) &&
183-
ancestorName == "ThrowsAsync")
184-
{
185-
return false;
186-
}
187-
188-
return returnValue switch
189-
{
190-
InvocationExpressionSyntax invocation
191-
=> !(invocation.IsSymbol(KnownSymbols.Task.FromResult, context.SemanticModel, context.CancellationToken)
192-
|| invocation.IsSymbol(KnownSymbols.ValueTask.FromResult, context.SemanticModel, context.CancellationToken)),
193-
MemberAccessExpressionSyntax { Name.Identifier.ValueText: "CompletedTask" } memberAccess
194-
=> !(memberAccess.IsSymbol(KnownSymbols.Task.CompletedTask, context.SemanticModel, context.CancellationToken)
195-
|| memberAccess.IsSymbol(KnownSymbols.ValueTask.CompletedTask, context.SemanticModel, context.CancellationToken)),
196-
_ => true,
197-
};
198-
}
199-
200206
private static bool IsLazyEnumerable(InvocationExpressionSyntax invocation, INamedTypeSymbol containingType, SemanticModel semanticModel, CancellationToken cancellationToken)
201207
{
202208
using var recursion = Recursion.Borrow(containingType, semanticModel, cancellationToken);

ValidCode/Async.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,12 @@ private static void M()
152152
using var ct = new CancellationTokenSource();
153153
_ = ct.Token.Register(() => { }, useSynchronizationContext: false);
154154
}
155+
156+
public ValueTask<int> Issue457Async()
157+
{
158+
using (var disposable = new Disposable())
159+
{
160+
return new ValueTask<int>(disposable.Equals(disposable) ? 1 : 0);
161+
}
162+
}
155163
}

0 commit comments

Comments
 (0)