Skip to content

Commit 3501f92

Browse files
Copilot333fred
andcommitted
Add support for await using and await foreach preview feature detection
Co-authored-by: 333fred <[email protected]>
1 parent f5ef3c7 commit 3501f92

File tree

9 files changed

+393
-3
lines changed

9 files changed

+393
-3
lines changed

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.CSharp.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/CSharpDetectPreviewFeatureAnalyzer.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,65 @@
22

33
using System.Collections.Immutable;
44
using System.Diagnostics.CodeAnalysis;
5+
using Analyzer.Utilities.Lightup;
56
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp;
68
using Microsoft.CodeAnalysis.CSharp.Syntax;
79
using Microsoft.CodeAnalysis.Diagnostics;
10+
using Microsoft.CodeAnalysis.Operations;
811
using Microsoft.NetCore.Analyzers.Runtime;
912

1013
namespace Microsoft.NetCore.CSharp.Analyzers.Runtime
1114
{
1215
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1316
public class CSharpDetectPreviewFeatureAnalyzer : DetectPreviewFeatureAnalyzer
1417
{
18+
protected override ISymbol? SymbolFromAwaitOperation(IAwaitOperation operation)
19+
{
20+
if (operation.Syntax is not AwaitExpressionSyntax awaitSyntax)
21+
{
22+
return null;
23+
}
24+
25+
var awaitableInfo = operation.SemanticModel.GetAwaitExpressionInfo(awaitSyntax);
26+
return AwaitExpressionInfoWrapper.GetRuntimeAwaitMethod(awaitableInfo);
27+
}
28+
29+
protected override ISymbol? SymbolFromUsingOperation(IUsingOperation operation)
30+
{
31+
// Only handle await using, not regular using
32+
var syntax = operation.Syntax;
33+
if (syntax is LocalDeclarationStatementSyntax localDeclaration &&
34+
localDeclaration.UsingKeyword.Kind() != SyntaxKind.None &&
35+
localDeclaration.AwaitKeyword.Kind() != SyntaxKind.None)
36+
{
37+
var awaitableInfo = operation.SemanticModel!.GetAwaitExpressionInfo(localDeclaration);
38+
return AwaitExpressionInfoWrapper.GetRuntimeAwaitMethod(awaitableInfo);
39+
}
40+
else if (syntax is UsingStatementSyntax usingStatement &&
41+
usingStatement.AwaitKeyword.Kind() != SyntaxKind.None)
42+
{
43+
var awaitableInfo = operation.SemanticModel!.GetAwaitExpressionInfo(usingStatement);
44+
return AwaitExpressionInfoWrapper.GetRuntimeAwaitMethod(awaitableInfo);
45+
}
46+
47+
return null;
48+
}
49+
50+
protected override ISymbol? SymbolFromForEachOperation(IForEachLoopOperation operation)
51+
{
52+
// Only handle await foreach, not regular foreach
53+
if (operation.Syntax is not CommonForEachStatementSyntax forEachSyntax ||
54+
forEachSyntax is not ForEachStatementSyntax { AwaitKeyword.RawKind: not 0 })
55+
{
56+
return null;
57+
}
58+
59+
var forEachInfo = operation.SemanticModel.GetForEachStatementInfo(forEachSyntax);
60+
var moveNextAwaitableInfo = ForEachStatementInfoWrapper.GetMoveNextAwaitableInfo(forEachInfo);
61+
return AwaitExpressionInfoWrapper.GetRuntimeAwaitMethod(moveNextAwaitableInfo);
62+
}
63+
1564
protected override SyntaxNode? GetPreviewSyntaxNodeForFieldsOrEvents(ISymbol fieldOrEventSymbol, ISymbol previewSymbol)
1665
{
1766
ImmutableArray<SyntaxReference> fieldOrEventReferences = fieldOrEventSymbol.DeclaringSyntaxReferences;

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/DetectPreviewFeatureAnalyzer.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,10 @@ public override void Initialize(AnalysisContext context)
269269
OperationKind.ArrayCreation,
270270
OperationKind.CatchClause,
271271
OperationKind.TypeOf,
272-
OperationKind.EventAssignment
272+
OperationKind.EventAssignment,
273+
OperationKind.Await,
274+
OperationKind.Using,
275+
OperationKind.Loop
273276
);
274277

275278
// Handle preview symbol definitions
@@ -809,7 +812,7 @@ private bool OperationUsesPreviewFeatures(OperationAnalysisContext context,
809812
return false;
810813
}
811814

812-
private static ISymbol? GetOperationSymbol(IOperation operation)
815+
private ISymbol? GetOperationSymbol(IOperation operation)
813816
=> operation switch
814817
{
815818
IInvocationOperation iOperation => iOperation.TargetMethod,
@@ -824,6 +827,9 @@ private bool OperationUsesPreviewFeatures(OperationAnalysisContext context,
824827
ICatchClauseOperation catchClauseOperation => catchClauseOperation.ExceptionType,
825828
ITypeOfOperation typeOfOperation => typeOfOperation.TypeOperand,
826829
IEventAssignmentOperation eventAssignment => GetOperationSymbol(eventAssignment.EventReference),
830+
IAwaitOperation awaitOperation => SymbolFromAwaitOperation(awaitOperation),
831+
IUsingOperation usingOperation => SymbolFromUsingOperation(usingOperation),
832+
IForEachLoopOperation forEachOperation => SymbolFromForEachOperation(forEachOperation),
827833
_ => null,
828834
};
829835

@@ -838,6 +844,12 @@ private bool OperationUsesPreviewFeatures(OperationAnalysisContext context,
838844
return ret;
839845
}
840846

847+
protected abstract ISymbol? SymbolFromAwaitOperation(IAwaitOperation operation);
848+
849+
protected abstract ISymbol? SymbolFromUsingOperation(IUsingOperation operation);
850+
851+
protected abstract ISymbol? SymbolFromForEachOperation(IForEachLoopOperation operation);
852+
841853
private bool TypeParametersHavePreviewAttribute(ISymbol namedTypeSymbolOrMethodSymbol,
842854
ImmutableArray<ITypeParameterSymbol> typeParameters,
843855
ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/BasicDetectPreviewFeatureAnalyzer.vb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
Imports Microsoft.CodeAnalysis
44
Imports Microsoft.CodeAnalysis.Diagnostics
5+
Imports Microsoft.CodeAnalysis.Operations
56
Imports Microsoft.NetCore.Analyzers.Runtime
67
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
78

@@ -12,6 +13,21 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime
1213

1314
Inherits DetectPreviewFeatureAnalyzer
1415

16+
Protected Overrides Function SymbolFromAwaitOperation(operation As IAwaitOperation) As ISymbol
17+
' VB doesn't support runtime async yet
18+
Return Nothing
19+
End Function
20+
21+
Protected Overrides Function SymbolFromUsingOperation(operation As IUsingOperation) As ISymbol
22+
' VB doesn't support runtime async yet
23+
Return Nothing
24+
End Function
25+
26+
Protected Overrides Function SymbolFromForEachOperation(operation As IForEachLoopOperation) As ISymbol
27+
' VB doesn't support runtime async yet
28+
Return Nothing
29+
End Function
30+
1531
Private Shared Function IsSyntaxToken(identifier As SyntaxToken, previewInterfaceSymbol As ISymbol) As Boolean
1632
Return identifier.ValueText.Equals(previewInterfaceSymbol.Name, StringComparison.OrdinalIgnoreCase)
1733
End Function

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler.CSharp/Analyzer.CSharp.Utilities.projitems

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
<Import_RootNamespace>Analyzer.CSharp.Utilities</Import_RootNamespace>
99
</PropertyGroup>
1010
<ItemGroup>
11+
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SemanticModelExtensions.cs" />
1112
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SyntaxGeneratorExtensions.cs" />
1213
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SyntaxNodeExtensions.cs" />
14+
<Compile Include="$(MSBuildThisFileDirectory)Lightup\AwaitExpressionInfoWrapper.cs" />
15+
<Compile Include="$(MSBuildThisFileDirectory)Lightup\ForEachStatementInfoWrapper.cs" />
1316
<Compile Include="$(MSBuildThisFileDirectory)Lightup\SyntaxKindEx.cs" />
1417
</ItemGroup>
1518
</Project>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Threading;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
9+
namespace Analyzer.Utilities.Lightup
10+
{
11+
internal static class SemanticModelExtensions
12+
{
13+
private static Func<SemanticModel, LocalDeclarationStatementSyntax, AwaitExpressionInfo>? s_GetAwaitExpressionInfoForLocalDeclaration;
14+
private static Func<SemanticModel, UsingStatementSyntax, AwaitExpressionInfo>? s_GetAwaitExpressionInfoForUsingStatement;
15+
16+
public static AwaitExpressionInfo GetAwaitExpressionInfo(this SemanticModel semanticModel, LocalDeclarationStatementSyntax awaitUsingDeclaration)
17+
{
18+
LazyInitializer.EnsureInitialized(ref s_GetAwaitExpressionInfoForLocalDeclaration, () =>
19+
{
20+
// Try to get the method from CSharpExtensions
21+
var csharpExtensionsType = typeof(Microsoft.CodeAnalysis.CSharpExtensions);
22+
var method = csharpExtensionsType.GetMethod(
23+
"GetAwaitExpressionInfo",
24+
new[] { typeof(SemanticModel), typeof(LocalDeclarationStatementSyntax) });
25+
26+
if (method != null)
27+
{
28+
return (model, syntax) => (AwaitExpressionInfo)method.Invoke(null, new object?[] { model, syntax })!;
29+
}
30+
31+
// Fallback if method doesn't exist
32+
return (model, syntax) => default;
33+
});
34+
35+
return s_GetAwaitExpressionInfoForLocalDeclaration!(semanticModel, awaitUsingDeclaration);
36+
}
37+
38+
public static AwaitExpressionInfo GetAwaitExpressionInfo(this SemanticModel semanticModel, UsingStatementSyntax awaitUsingStatement)
39+
{
40+
LazyInitializer.EnsureInitialized(ref s_GetAwaitExpressionInfoForUsingStatement, () =>
41+
{
42+
// Try to get the method from CSharpExtensions
43+
var csharpExtensionsType = typeof(Microsoft.CodeAnalysis.CSharpExtensions);
44+
var method = csharpExtensionsType.GetMethod(
45+
"GetAwaitExpressionInfo",
46+
new[] { typeof(SemanticModel), typeof(UsingStatementSyntax) });
47+
48+
if (method != null)
49+
{
50+
return (model, syntax) => (AwaitExpressionInfo)method.Invoke(null, new object?[] { model, syntax })!;
51+
}
52+
53+
// Fallback if method doesn't exist
54+
return (model, syntax) => default;
55+
});
56+
57+
return s_GetAwaitExpressionInfoForUsingStatement!(semanticModel, awaitUsingStatement);
58+
}
59+
}
60+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Threading;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
8+
namespace Analyzer.Utilities.Lightup
9+
{
10+
internal static class AwaitExpressionInfoWrapper
11+
{
12+
private static Func<AwaitExpressionInfo, IMethodSymbol?>? s_RuntimeAwaitMethodAccessor;
13+
14+
public static IMethodSymbol? GetRuntimeAwaitMethod(AwaitExpressionInfo info)
15+
{
16+
LazyInitializer.EnsureInitialized(ref s_RuntimeAwaitMethodAccessor, () =>
17+
{
18+
return LightupHelpers.CreatePropertyAccessor<AwaitExpressionInfo, IMethodSymbol?>(
19+
typeof(AwaitExpressionInfo),
20+
"info",
21+
"RuntimeAwaitMethod",
22+
fallbackResult: null);
23+
});
24+
25+
return s_RuntimeAwaitMethodAccessor!(info);
26+
}
27+
}
28+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Threading;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
8+
namespace Analyzer.Utilities.Lightup
9+
{
10+
internal static class ForEachStatementInfoWrapper
11+
{
12+
private static Func<ForEachStatementInfo, AwaitExpressionInfo>? s_MoveNextAwaitableInfoAccessor;
13+
private static Func<ForEachStatementInfo, AwaitExpressionInfo>? s_DisposeAwaitableInfoAccessor;
14+
15+
public static AwaitExpressionInfo GetMoveNextAwaitableInfo(ForEachStatementInfo info)
16+
{
17+
LazyInitializer.EnsureInitialized(ref s_MoveNextAwaitableInfoAccessor, () =>
18+
{
19+
return LightupHelpers.CreatePropertyAccessor<ForEachStatementInfo, AwaitExpressionInfo>(
20+
typeof(ForEachStatementInfo),
21+
"info",
22+
"MoveNextAwaitableInfo",
23+
fallbackResult: default);
24+
});
25+
26+
return s_MoveNextAwaitableInfoAccessor!(info);
27+
}
28+
29+
public static AwaitExpressionInfo GetDisposeAwaitableInfo(ForEachStatementInfo info)
30+
{
31+
LazyInitializer.EnsureInitialized(ref s_DisposeAwaitableInfoAccessor, () =>
32+
{
33+
return LightupHelpers.CreatePropertyAccessor<ForEachStatementInfo, AwaitExpressionInfo>(
34+
typeof(ForEachStatementInfo),
35+
"info",
36+
"DisposeAwaitableInfo",
37+
fallbackResult: default);
38+
});
39+
40+
return s_DisposeAwaitableInfoAccessor!(info);
41+
}
42+
}
43+
}

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/Lightup/LightupHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ internal static Func<TSymbol, TProperty> CreateSymbolPropertyAccessor<TSymbol, T
5353
where TSymbol : ISymbol
5454
=> CreatePropertyAccessor<TSymbol, TProperty>(type, "symbol", propertyName, fallbackResult);
5555

56-
private static Func<T, TProperty> CreatePropertyAccessor<T, TProperty>(Type? type, string parameterName, string propertyName, TProperty fallbackResult)
56+
internal static Func<T, TProperty> CreatePropertyAccessor<T, TProperty>(Type? type, string parameterName, string propertyName, TProperty fallbackResult)
5757
{
5858
if (!TryGetProperty<T, TProperty>(type, propertyName, out var property))
5959
{

0 commit comments

Comments
 (0)