Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,40 @@ public class CSharpDetectPreviewFeatureAnalyzer : DetectPreviewFeatureAnalyzer
return awaitableInfo.RuntimeAwaitMethod;
}

protected override ISymbol? SymbolFromUsingOperation(IUsingOperation operation)
{
// Only handle await using, not regular using
var syntax = operation.Syntax;
if (syntax is LocalDeclarationStatementSyntax localDeclaration &&
localDeclaration.UsingKeyword.Kind() != SyntaxKind.None &&
localDeclaration.AwaitKeyword.Kind() != SyntaxKind.None)
{
var awaitableInfo = operation.SemanticModel!.GetAwaitExpressionInfo(localDeclaration);
return awaitableInfo.RuntimeAwaitMethod;
}
else if (syntax is UsingStatementSyntax usingStatement &&
usingStatement.AwaitKeyword.Kind() != SyntaxKind.None)
{
var awaitableInfo = operation.SemanticModel!.GetAwaitExpressionInfo(usingStatement);
return awaitableInfo.RuntimeAwaitMethod;
}

return null;
}

protected override ISymbol? SymbolFromForEachOperation(IForEachLoopOperation operation)
{
// Only handle await foreach, not regular foreach
if (operation.Syntax is not CommonForEachStatementSyntax forEachSyntax ||
forEachSyntax is not ForEachStatementSyntax { AwaitKeyword.RawKind: not 0 })
{
return null;
}

var forEachInfo = operation.SemanticModel.GetForEachStatementInfo(forEachSyntax);
return forEachInfo.MoveNextAwaitableInfo.RuntimeAwaitMethod;
}

protected override SyntaxNode? GetPreviewSyntaxNodeForFieldsOrEvents(ISymbol fieldOrEventSymbol, ISymbol previewSymbol)
{
ImmutableArray<SyntaxReference> fieldOrEventReferences = fieldOrEventSymbol.DeclaringSyntaxReferences;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,9 @@ public override void Initialize(AnalysisContext context)
OperationKind.CatchClause,
OperationKind.TypeOf,
OperationKind.EventAssignment,
OperationKind.Await
);
OperationKind.Await,
OperationKind.Using,
OperationKind.Loop);

// Handle preview symbol definitions
context.RegisterSymbolAction(context => AnalyzeSymbol(context, requiresPreviewFeaturesSymbols, virtualStaticsInInterfaces, previewFeaturesAttribute), s_symbols);
Expand Down Expand Up @@ -826,6 +827,8 @@ private bool OperationUsesPreviewFeatures(OperationAnalysisContext context,
ITypeOfOperation typeOfOperation => typeOfOperation.TypeOperand,
IEventAssignmentOperation eventAssignment => GetOperationSymbol(eventAssignment.EventReference),
IAwaitOperation awaitOperation => SymbolFromAwaitOperation(awaitOperation),
IUsingOperation usingOperation => SymbolFromUsingOperation(usingOperation),
IForEachLoopOperation forEachOperation => SymbolFromForEachOperation(forEachOperation),
_ => null,
};

Expand All @@ -842,6 +845,9 @@ private bool OperationUsesPreviewFeatures(OperationAnalysisContext context,

protected abstract ISymbol? SymbolFromAwaitOperation(IAwaitOperation operation);

protected abstract ISymbol? SymbolFromUsingOperation(IUsingOperation operation);

protected abstract ISymbol? SymbolFromForEachOperation(IForEachLoopOperation operation);
private bool TypeParametersHavePreviewAttribute(ISymbol namedTypeSymbolOrMethodSymbol,
ImmutableArray<ITypeParameterSymbol> typeParameters,
ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime
Return Nothing
End Function

Protected Overrides Function SymbolFromUsingOperation(operation As IUsingOperation) As ISymbol
Return Nothing
End Function

Protected Overrides Function SymbolFromForEachOperation(operation As IForEachLoopOperation) As ISymbol
Return Nothing
End Function

Private Shared Function GetElementTypeForNullableAndArrayTypeNodes(parameterType As TypeSyntax) As TypeSyntax
Dim ret As TypeSyntax = parameterType
Dim loopVariable = TryCast(parameterType, NullableTypeSyntax)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
<Import_RootNamespace>Analyzer.CSharp.Utilities</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SemanticModelExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SyntaxGeneratorExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SyntaxNodeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Lightup\AwaitExpressionInfoWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Lightup\ForEachStatementInfoWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Lightup\SyntaxKindEx.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Analyzer.Utilities.Lightup
{
internal static class SemanticModelExtensions
{
private static Func<SemanticModel, LocalDeclarationStatementSyntax, AwaitExpressionInfo>? s_GetAwaitExpressionInfoForLocalDeclaration;
private static Func<SemanticModel, UsingStatementSyntax, AwaitExpressionInfo>? s_GetAwaitExpressionInfoForUsingStatement;

public static AwaitExpressionInfo GetAwaitExpressionInfo(this SemanticModel semanticModel, LocalDeclarationStatementSyntax awaitUsingDeclaration)
{
LazyInitializer.EnsureInitialized(ref s_GetAwaitExpressionInfoForLocalDeclaration, () =>
{
// Try to get the method from CSharpExtensions
var csharpExtensionsType = typeof(Microsoft.CodeAnalysis.CSharp.CSharpExtensions);
var method = csharpExtensionsType.GetMethod(
"GetAwaitExpressionInfo",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
null,
new[] { typeof(SemanticModel), typeof(LocalDeclarationStatementSyntax) },
null);

if (method != null)
{
return (model, syntax) => (AwaitExpressionInfo)method.Invoke(null, new object?[] { model, syntax })!;
}

// Fallback if method doesn't exist
return (model, syntax) => default;
});

return s_GetAwaitExpressionInfoForLocalDeclaration!(semanticModel, awaitUsingDeclaration);
}

public static AwaitExpressionInfo GetAwaitExpressionInfo(this SemanticModel semanticModel, UsingStatementSyntax awaitUsingStatement)
{
LazyInitializer.EnsureInitialized(ref s_GetAwaitExpressionInfoForUsingStatement, () =>
{
// Try to get the method from CSharpExtensions
var csharpExtensionsType = typeof(Microsoft.CodeAnalysis.CSharp.CSharpExtensions);
var method = csharpExtensionsType.GetMethod(
"GetAwaitExpressionInfo",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
null,
new[] { typeof(SemanticModel), typeof(UsingStatementSyntax) },
null);

if (method != null)
{
return (model, syntax) => (AwaitExpressionInfo)method.Invoke(null, new object?[] { model, syntax })!;
}

// Fallback if method doesn't exist
return (model, syntax) => default;
});

return s_GetAwaitExpressionInfoForUsingStatement!(semanticModel, awaitUsingStatement);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Analyzer.Utilities.Lightup
{
internal static class ForEachStatementInfoWrapper
{
private static Func<ForEachStatementInfo, AwaitExpressionInfo>? s_MoveNextAwaitableInfoAccessor;
private static Func<ForEachStatementInfo, AwaitExpressionInfo>? s_DisposeAwaitableInfoAccessor;

extension(ForEachStatementInfo info)
{
public AwaitExpressionInfo MoveNextAwaitableInfo
{
get
{
LazyInitializer.EnsureInitialized(ref s_MoveNextAwaitableInfoAccessor, () =>
{
return LightupHelpers.CreatePropertyAccessor<ForEachStatementInfo, AwaitExpressionInfo>(
typeof(ForEachStatementInfo),
"info",
"MoveNextAwaitableInfo",
fallbackResult: default);
});

RoslynDebug.Assert(s_MoveNextAwaitableInfoAccessor is not null);
return s_MoveNextAwaitableInfoAccessor(info);
}
}

public AwaitExpressionInfo DisposeAwaitableInfo
{
get
{
LazyInitializer.EnsureInitialized(ref s_DisposeAwaitableInfoAccessor, () =>
{
return LightupHelpers.CreatePropertyAccessor<ForEachStatementInfo, AwaitExpressionInfo>(
typeof(ForEachStatementInfo),
"info",
"DisposeAwaitableInfo",
fallbackResult: default);
});

RoslynDebug.Assert(s_DisposeAwaitableInfoAccessor is not null);
return s_DisposeAwaitableInfoAccessor(info);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ End Module
}

[Fact]
public async Task VerifyRuntimeAsyncReportsDiagnostic()
public async Task VerifyRuntimeAsyncAwaitReportsDiagnostic()
{
var csInput = """
using System.Threading.Tasks;
Expand All @@ -818,7 +818,7 @@ async Task M()
}
""";

var test = new RuntimeAsyncFixVerifier
var test = new RuntimeAsyncTestVerifier
{
TestState =
{
Expand All @@ -838,7 +838,7 @@ async Task M()
}

[Fact]
public async Task VerifyRuntimeAsyncReportsDiagnostic_CustomAwaiter()
public async Task VerifyRuntimeAsyncAwaitCustomAwaiterReportsDiagnostic()
{
var csInput = """
using System.Threading.Tasks;
Expand All @@ -851,7 +851,7 @@ async Task M()
}
""";

var test = new RuntimeAsyncFixVerifier
var test = new RuntimeAsyncTestVerifier
{
TestState =
{
Expand All @@ -869,11 +869,133 @@ async Task M()
await test.RunAsync();
}

private class RuntimeAsyncFixVerifier : VerifyCS.Test
[Fact]
public async Task VerifyRuntimeAsyncAwaitUsingDeclarationReportsDiagnostic()
{
var csInput = """
using System.Threading.Tasks;
using System;
class C
{
async Task M()
{
await using var stream = new MemoryStream();
}
}

class MemoryStream : IAsyncDisposable
{
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
""";

var test = new RuntimeAsyncTestVerifier
{
TestState =
{
Sources =
{
csInput
}
},
ExpectedDiagnostics =
{
// /0/Test0.cs(7,9): error CA2252: Using 'UnsafeAwaitAwaiter' requires opting into preview features. See https://aka.ms/dotnet-warnings/preview-features for more information.
VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithSpan(7, 9, 7, 52).WithArguments("UnsafeAwaitAwaiter", DetectPreviewFeatureAnalyzer.DefaultURL)
}
};

await test.RunAsync();
}

[Fact]
public async Task VerifyRuntimeAsyncAwaitUsingStatementReportsDiagnostic()
{
var csInput = """
using System.Threading.Tasks;
using System;
class C
{
async Task M()
{
await using (var stream = new MemoryStream())
{
}
}
}

class MemoryStream : IAsyncDisposable
{
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
""";

var test = new RuntimeAsyncTestVerifier
{
TestState =
{
Sources =
{
csInput
}
},
ExpectedDiagnostics =
{
// /0/Test0.cs(7,9): error CA2252: Using 'UnsafeAwaitAwaiter' requires opting into preview features. See https://aka.ms/dotnet-warnings/preview-features for more information.
VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithSpan(7, 9, 7, 54).WithArguments("UnsafeAwaitAwaiter", DetectPreviewFeatureAnalyzer.DefaultURL)
}
};

await test.RunAsync();
}

[Fact]
public async Task VerifyRuntimeAsyncAwaitForeachReportsDiagnostic()
{
var csInput = """
using System.Collections.Generic;
using System.Threading.Tasks;
class C
{
async Task M()
{
await foreach (var item in GetItemsAsync())
{
}
}

async IAsyncEnumerable<int> GetItemsAsync()
{
yield return 1;
await Task.CompletedTask;
}
}
""";

var test = new RuntimeAsyncTestVerifier
{
TestState =
{
Sources =
{
csInput
}
},
ExpectedDiagnostics =
{
// /0/Test0.cs(7,9): error CA2252: Using 'UnsafeAwaitAwaiter' requires opting into preview features. See https://aka.ms/dotnet-warnings/preview-features for more information.
VerifyCS.Diagnostic(DetectPreviewFeatureAnalyzer.GeneralPreviewFeatureAttributeRule).WithSpan(7, 9, 7, 56).WithArguments("UnsafeAwaitAwaiter", DetectPreviewFeatureAnalyzer.DefaultURL)
}
};

await test.RunAsync();
}

private class RuntimeAsyncTestVerifier : VerifyCS.Test
{
public static readonly ReferenceAssemblies Net100 = new("net10.0", new PackageIdentity("Microsoft.NETCore.App.Ref", "10.0.0-rc.1.25451.107"), Path.Combine("ref", "net10.0"));
public static readonly ReferenceAssemblies Net100 = new("net10.0", new PackageIdentity("Microsoft.NETCore.App.Ref", "10.0.0-rc.1.25451.107"), System.IO.Path.Combine("ref", "net10.0"));

public RuntimeAsyncFixVerifier()
public RuntimeAsyncTestVerifier()
{
ReferenceAssemblies = Net100;
LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp10;
Expand Down
Loading