Skip to content

Commit b9f60ec

Browse files
Copilotdrewnoakes
andcommitted
Add CompletedTaskAttribute support for VSTHRD003
Co-authored-by: drewnoakes <[email protected]>
1 parent 91a1e36 commit b9f60ec

File tree

5 files changed

+247
-1
lines changed

5 files changed

+247
-1
lines changed

src/Microsoft.VisualStudio.Threading.Analyzers.CSharp/VSTHRD003UseJtfRunAsyncAnalyzer.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ public override void Initialize(AnalysisContext context)
7373

7474
private static bool IsSymbolAlwaysOkToAwait(ISymbol? symbol)
7575
{
76+
// Check if the symbol has the CompletedTaskAttribute
77+
if (symbol?.GetAttributes().Any(attr =>
78+
attr.AttributeClass?.Name == Types.CompletedTaskAttribute.TypeName &&
79+
attr.AttributeClass.BelongsToNamespace(Types.CompletedTaskAttribute.Namespace)) == true)
80+
{
81+
return true;
82+
}
83+
7684
if (symbol is IFieldSymbol field)
7785
{
7886
// Allow the TplExtensions.CompletedTask and related fields.
@@ -288,6 +296,12 @@ private void AnalyzeAwaitExpression(SyntaxNodeAnalysisContext context)
288296

289297
break;
290298
case IMethodSymbol methodSymbol:
299+
// Check if the method itself has the CompletedTaskAttribute
300+
if (IsSymbolAlwaysOkToAwait(methodSymbol))
301+
{
302+
return null;
303+
}
304+
291305
if (Utils.IsTask(methodSymbol.ReturnType) && focusedExpression is InvocationExpressionSyntax invocationExpressionSyntax)
292306
{
293307
// Consider all arguments
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
#if !COMPLETEDTASKATTRIBUTE_INCLUDED
5+
6+
namespace Microsoft.VisualStudio.Threading;
7+
8+
/// <summary>
9+
/// Indicates that a property, method, or field returns a task that is already completed.
10+
/// This suppresses VSTHRD003 warnings when awaiting the returned task.
11+
/// </summary>
12+
/// <remarks>
13+
/// Apply this attribute to properties, methods, or fields that return cached, pre-completed tasks
14+
/// such as singleton instances with well-known immutable values.
15+
/// The VSTHRD003 analyzer will not report warnings when these members are awaited,
16+
/// as awaiting an already-completed task does not pose a risk of deadlock.
17+
/// </remarks>
18+
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
19+
internal sealed class CompletedTaskAttribute : System.Attribute
20+
{
21+
}
22+
23+
#pragma warning disable SA1403 // File may only contain a single namespace
24+
#pragma warning disable SA1649 // File name should match first type name
25+
internal static class CompletedTaskAttributeDefinition
26+
{
27+
internal const bool Included = true;
28+
}
29+
#pragma warning restore SA1649 // File name should match first type name
30+
#pragma warning restore SA1403 // File may only contain a single namespace
31+
32+
#define COMPLETEDTASKATTRIBUTE_INCLUDED
33+
#endif
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<ItemGroup>
4-
<AdditionalFiles Include="$(MSBuildThisFileDirectory)AdditionalFiles\**">
4+
<AdditionalFiles Include="$(MSBuildThisFileDirectory)AdditionalFiles\*.txt">
55
<Visible>false</Visible>
66
</AdditionalFiles>
7+
<Compile Include="$(MSBuildThisFileDirectory)AdditionalFiles\*.cs" Link="%(Filename)%(Extension)">
8+
<Visible>false</Visible>
9+
</Compile>
710
</ItemGroup>
811
</Project>

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,11 @@ public static class TypeLibTypeAttribute
246246

247247
public static readonly ImmutableArray<string> Namespace = Namespaces.SystemRuntimeInteropServices;
248248
}
249+
250+
public static class CompletedTaskAttribute
251+
{
252+
public const string TypeName = "CompletedTaskAttribute";
253+
254+
public static readonly ImmutableArray<string> Namespace = Namespaces.MicrosoftVisualStudioThreading;
255+
}
249256
}

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

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,195 @@ static async Task ListenAndWait()
14531453
await CSVerify.VerifyAnalyzerAsync(test);
14541454
}
14551455

1456+
[Fact]
1457+
public async Task DoNotReportWarningWhenAwaitingPropertyWithCompletedTaskAttribute()
1458+
{
1459+
var test = @"
1460+
using System.Threading.Tasks;
1461+
1462+
namespace Microsoft.VisualStudio.Threading
1463+
{
1464+
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
1465+
internal sealed class CompletedTaskAttribute : System.Attribute
1466+
{
1467+
}
1468+
}
1469+
1470+
class Tests
1471+
{
1472+
[Microsoft.VisualStudio.Threading.CompletedTask]
1473+
private static Task MyCompletedTask { get; } = Task.CompletedTask;
1474+
1475+
async Task GetTask()
1476+
{
1477+
await MyCompletedTask;
1478+
}
1479+
}
1480+
";
1481+
await CSVerify.VerifyAnalyzerAsync(test);
1482+
}
1483+
1484+
[Fact]
1485+
public async Task DoNotReportWarningWhenAwaitingFieldWithCompletedTaskAttribute()
1486+
{
1487+
var test = @"
1488+
using System.Threading.Tasks;
1489+
1490+
namespace Microsoft.VisualStudio.Threading
1491+
{
1492+
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
1493+
internal sealed class CompletedTaskAttribute : System.Attribute
1494+
{
1495+
}
1496+
}
1497+
1498+
class Tests
1499+
{
1500+
[Microsoft.VisualStudio.Threading.CompletedTask]
1501+
private static readonly Task MyCompletedTask = Task.CompletedTask;
1502+
1503+
async Task GetTask()
1504+
{
1505+
await MyCompletedTask;
1506+
}
1507+
}
1508+
";
1509+
await CSVerify.VerifyAnalyzerAsync(test);
1510+
}
1511+
1512+
[Fact]
1513+
public async Task DoNotReportWarningWhenAwaitingMethodWithCompletedTaskAttribute()
1514+
{
1515+
var test = @"
1516+
using System.Threading.Tasks;
1517+
1518+
namespace Microsoft.VisualStudio.Threading
1519+
{
1520+
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
1521+
internal sealed class CompletedTaskAttribute : System.Attribute
1522+
{
1523+
}
1524+
}
1525+
1526+
class Tests
1527+
{
1528+
[Microsoft.VisualStudio.Threading.CompletedTask]
1529+
private static Task GetCompletedTask() => Task.CompletedTask;
1530+
1531+
async Task TestMethod()
1532+
{
1533+
await GetCompletedTask();
1534+
}
1535+
}
1536+
";
1537+
await CSVerify.VerifyAnalyzerAsync(test);
1538+
}
1539+
1540+
[Fact]
1541+
public async Task DoNotReportWarningWhenReturningPropertyWithCompletedTaskAttribute()
1542+
{
1543+
var test = @"
1544+
using System.Threading.Tasks;
1545+
1546+
namespace Microsoft.VisualStudio.Threading
1547+
{
1548+
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
1549+
internal sealed class CompletedTaskAttribute : System.Attribute
1550+
{
1551+
}
1552+
}
1553+
1554+
class Tests
1555+
{
1556+
[Microsoft.VisualStudio.Threading.CompletedTask]
1557+
private static Task MyCompletedTask { get; } = Task.CompletedTask;
1558+
1559+
Task GetTask() => MyCompletedTask;
1560+
}
1561+
";
1562+
await CSVerify.VerifyAnalyzerAsync(test);
1563+
}
1564+
1565+
[Fact]
1566+
public async Task ReportWarningWhenAwaitingPropertyWithoutCompletedTaskAttribute()
1567+
{
1568+
var test = @"
1569+
using System.Threading.Tasks;
1570+
1571+
class Tests
1572+
{
1573+
private static Task MyTask { get; } = Task.Run(() => {});
1574+
1575+
async Task GetTask()
1576+
{
1577+
await [|MyTask|];
1578+
}
1579+
}
1580+
";
1581+
await CSVerify.VerifyAnalyzerAsync(test);
1582+
}
1583+
1584+
[Fact]
1585+
public async Task DoNotReportWarningWhenAwaitingTaskGenericPropertyWithCompletedTaskAttribute()
1586+
{
1587+
var test = @"
1588+
using System.Threading.Tasks;
1589+
1590+
namespace Microsoft.VisualStudio.Threading
1591+
{
1592+
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
1593+
internal sealed class CompletedTaskAttribute : System.Attribute
1594+
{
1595+
}
1596+
}
1597+
1598+
class Tests
1599+
{
1600+
[Microsoft.VisualStudio.Threading.CompletedTask]
1601+
private static Task<int> MyCompletedTask { get; } = Task.FromResult(42);
1602+
1603+
async Task<int> GetResult()
1604+
{
1605+
return await MyCompletedTask;
1606+
}
1607+
}
1608+
";
1609+
await CSVerify.VerifyAnalyzerAsync(test);
1610+
}
1611+
1612+
[Fact]
1613+
public async Task DoNotReportWarningWhenAwaitingPropertyWithCompletedTaskAttributeInJtfRun()
1614+
{
1615+
var test = @"
1616+
using System.Threading.Tasks;
1617+
using Microsoft.VisualStudio.Threading;
1618+
1619+
namespace Microsoft.VisualStudio.Threading
1620+
{
1621+
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
1622+
internal sealed class CompletedTaskAttribute : System.Attribute
1623+
{
1624+
}
1625+
}
1626+
1627+
class Tests
1628+
{
1629+
[Microsoft.VisualStudio.Threading.CompletedTask]
1630+
private static Task MyCompletedTask { get; } = Task.CompletedTask;
1631+
1632+
void TestMethod()
1633+
{
1634+
JoinableTaskFactory jtf = null;
1635+
jtf.Run(async delegate
1636+
{
1637+
await MyCompletedTask;
1638+
});
1639+
}
1640+
}
1641+
";
1642+
await CSVerify.VerifyAnalyzerAsync(test);
1643+
}
1644+
14561645
private DiagnosticResult CreateDiagnostic(int line, int column, int length) =>
14571646
CSVerify.Diagnostic().WithSpan(line, column, line, column + length);
14581647
}

0 commit comments

Comments
 (0)