Skip to content

Async overloads source generator #1575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
eac51ef
Setup source generator for async overloads
bash Jun 22, 2021
6a7e6f6
Use source generator to generate AggregateAsync overloads
bash Jun 22, 2021
5261258
Refactor and split source generator into multiple files
bash Jun 22, 2021
ba8ba26
Add solution filter for System.Linq.Async projects
bash Jun 22, 2021
dc96f68
Use source generator to generate AllAsync overloads
bash Jun 22, 2021
494816f
Use source generator to generate AnyAsync overloads
bash Jun 22, 2021
fe3518a
Use source generator to generate AverageAsync overloads
bash Jun 22, 2021
28ded8a
Use source generator to generate CountAsync overloads
bash Jun 22, 2021
48fda12
Use source generator to generate FirstAsync overloads
bash Jun 22, 2021
e29b53a
Remove trailing whitespace
bash Jun 22, 2021
204bab4
Use source generator to generate FirstOrDefaultAsync overloads
bash Jun 22, 2021
4e091da
Use source generator to generate ForEachAsync overloads
bash Jun 22, 2021
7f92687
Use source generator to generate GroupByAsync overloads
bash Jun 22, 2021
6682a14
Use source generator to generate GroupJoinAsync overloads
bash Jun 22, 2021
6bc4010
Use source generator to generate JoinAsync overloads
bash Jun 22, 2021
71f4c0d
Use source generator to generate LastAsync overloads
bash Jun 22, 2021
5089f33
Use source generator to generate LastOrDefaultAsync overloads
bash Jun 22, 2021
74d2d1d
Use source generator to generate LongCount overloads
bash Jun 22, 2021
3ad4493
Use source generator to generate MaxAsync overloads
bash Jun 22, 2021
4c6f66a
Use source generator to generate MinAsync overloads
bash Jun 22, 2021
59e761d
Use source generator to generate OrderByAsync/ThenByAsync overloads
bash Jun 22, 2021
b9795ae
Use source generator to generate SelectAsync overloads
bash Jun 22, 2021
5ca2bb2
Use source generator to generate SelectManyAsync overloads
bash Jun 22, 2021
687efd2
Use source generator to generate SingleAsync overloads
bash Jun 22, 2021
e0a2a92
Use source generator to generate SingleOrDefault overloads
bash Jun 22, 2021
35d1400
Use source generator to generate SkipWhileAsync overloads
bash Jun 22, 2021
9d0aaca
Use source generator to generate SumAsync overloads
bash Jun 22, 2021
9831e39
Use source generator to generate TakeWhileAsync overloads
bash Jun 22, 2021
b800176
Use source generator to generate ToDictionary overloads
bash Jun 22, 2021
a9883be
Use source generator to generate ToLookupAsync overloads
bash Jun 22, 2021
8c85e82
Use source generator to generate WhereAsync overloads
bash Jun 22, 2021
0cbfe65
Use source generator to generate Zip overloads
bash Jun 22, 2021
381e0f8
Remove T4 template for AsyncOverloads
bash Jun 22, 2021
0e5cf37
Make attribute internal
bash Jul 7, 2021
43570c9
Merge branch 'main' into async-overloads-source-generator
bartdesmet Aug 23, 2021
fa9b7bc
Merge branch 'main' into async-overloads-source-generator
bartdesmet Aug 24, 2021
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
7 changes: 7 additions & 0 deletions Ix.NET/Source/Ix.NET.sln
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks.System.Interacti
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Linq.Async.Ref", "refs\System.Linq.Async.Ref\System.Linq.Async.Ref.csproj", "{1754B36C-D0DB-4E5D-8C30-1F116046DC0F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Linq.Async.SourceGenerator", "System.Linq.Async.SourceGenerator\System.Linq.Async.SourceGenerator.csproj", "{5C26D649-5ED4-49EE-AFBD-8FA8F12C4AE4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -136,6 +138,10 @@ Global
{1754B36C-D0DB-4E5D-8C30-1F116046DC0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1754B36C-D0DB-4E5D-8C30-1F116046DC0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1754B36C-D0DB-4E5D-8C30-1F116046DC0F}.Release|Any CPU.Build.0 = Release|Any CPU
{5C26D649-5ED4-49EE-AFBD-8FA8F12C4AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C26D649-5ED4-49EE-AFBD-8FA8F12C4AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C26D649-5ED4-49EE-AFBD-8FA8F12C4AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C26D649-5ED4-49EE-AFBD-8FA8F12C4AE4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -158,6 +164,7 @@ Global
{2EC0C302-B029-4DDB-AC91-000BF11006AD} = {A3D72E6E-4ADA-42E0-8B2A-055B1F244281}
{5DF341BE-B369-4250-AFD4-604DE8C95E45} = {A3D72E6E-4ADA-42E0-8B2A-055B1F244281}
{1754B36C-D0DB-4E5D-8C30-1F116046DC0F} = {A3D72E6E-4ADA-42E0-8B2A-055B1F244281}
{5C26D649-5ED4-49EE-AFBD-8FA8F12C4AE4} = {80EFE3A1-1414-42EA-949B-1B5370A1B2EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AF70B0C6-C9D9-43B1-9BE4-08720EC1B7B7}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace System.Linq.Async.SourceGenerator
{
internal sealed record AsyncMethod(IMethodSymbol Symbol, MethodDeclarationSyntax Syntax);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Collections.Generic;

using Microsoft.CodeAnalysis;

namespace System.Linq.Async.SourceGenerator
{
internal sealed record AsyncMethodGrouping(SyntaxTree SyntaxTree, IEnumerable<AsyncMethod> Methods);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace System.Linq.Async.SourceGenerator
{
[Generator]
public sealed class AsyncOverloadsGenerator : ISourceGenerator
{
private const string AttributeSource =
"using System;\n" +
"using System.Diagnostics;\n" +
"namespace System.Linq\n" +
"{\n" +
" [AttributeUsage(AttributeTargets.Method)]\n" +
" [Conditional(\"COMPILE_TIME_ONLY\")]\n" +
" internal sealed class GenerateAsyncOverloadAttribute : Attribute { }\n" +
"}\n";

public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
context.RegisterForPostInitialization(c => c.AddSource("GenerateAsyncOverloadAttribute", AttributeSource));
}

public void Execute(GeneratorExecutionContext context)
{
if (context.SyntaxReceiver is not SyntaxReceiver syntaxReceiver) return;

var options = GetGenerationOptions(context);
var methodsBySyntaxTree = GetMethodsGroupedBySyntaxTree(context, syntaxReceiver);

foreach (var grouping in methodsBySyntaxTree)
context.AddSource(
$"{Path.GetFileNameWithoutExtension(grouping.SyntaxTree.FilePath)}.AsyncOverloads",
GenerateOverloads(grouping, options));
}

private static GenerationOptions GetGenerationOptions(GeneratorExecutionContext context)
=> new(SupportFlatAsyncApi: context.ParseOptions.PreprocessorSymbolNames.Contains("SUPPORT_FLAT_ASYNC_API"));

private static IEnumerable<AsyncMethodGrouping> GetMethodsGroupedBySyntaxTree(GeneratorExecutionContext context, SyntaxReceiver syntaxReceiver)
=> GetMethodsGroupedBySyntaxTree(
context,
syntaxReceiver,
GetAsyncOverloadAttributeSymbol(context));

private static string GenerateOverloads(AsyncMethodGrouping grouping, GenerationOptions options)
{
var usings = grouping.SyntaxTree.GetRoot() is CompilationUnitSyntax compilationUnit
? compilationUnit.Usings.ToString()
: string.Empty;

var overloads = new StringBuilder();
overloads.AppendLine("#nullable enable");
overloads.AppendLine(usings);
overloads.AppendLine("namespace System.Linq");
overloads.AppendLine("{");
overloads.AppendLine(" partial class AsyncEnumerable");
overloads.AppendLine(" {");

foreach (var method in grouping.Methods)
overloads.AppendLine(GenerateOverload(method, options));

overloads.AppendLine(" }");
overloads.AppendLine("}");

return overloads.ToString();
}

private static string GenerateOverload(AsyncMethod method, GenerationOptions options)
=> MethodDeclaration(method.Syntax.ReturnType, GetMethodName(method.Symbol, options))
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
.WithTypeParameterList(method.Syntax.TypeParameterList)
.WithParameterList(method.Syntax.ParameterList)
.WithConstraintClauses(method.Syntax.ConstraintClauses)
.WithExpressionBody(ArrowExpressionClause(
InvocationExpression(
IdentifierName(method.Symbol.Name),
ArgumentList(
SeparatedList(
method.Syntax.ParameterList.Parameters
.Select(p => Argument(IdentifierName(p.Identifier))))))))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.WithLeadingTrivia(method.Syntax.GetLeadingTrivia().Where(t => t.GetStructure() is not DirectiveTriviaSyntax))
.NormalizeWhitespace()
.ToFullString();

private static INamedTypeSymbol GetAsyncOverloadAttributeSymbol(GeneratorExecutionContext context)
=> context.Compilation.GetTypeByMetadataName("System.Linq.GenerateAsyncOverloadAttribute") ?? throw new InvalidOperationException();

private static IEnumerable<AsyncMethodGrouping> GetMethodsGroupedBySyntaxTree(GeneratorExecutionContext context, SyntaxReceiver syntaxReceiver, INamedTypeSymbol attributeSymbol)
=> from candidate in syntaxReceiver.Candidates
group candidate by candidate.SyntaxTree into grouping
let model = context.Compilation.GetSemanticModel(grouping.Key)
select new AsyncMethodGrouping(
grouping.Key,
from methodSyntax in grouping
let methodSymbol = model.GetDeclaredSymbol(methodSyntax) ?? throw new InvalidOperationException()
where methodSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass!, attributeSymbol))
select new AsyncMethod(methodSymbol, methodSyntax));

private static string GetMethodName(IMethodSymbol methodSymbol, GenerationOptions options)
{
var methodName = methodSymbol.Name.Replace("Core", "");
return options.SupportFlatAsyncApi
? methodName.Replace("Await", "").Replace("WithCancellation", "")
: methodName;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace System.Linq.Async.SourceGenerator
{
internal sealed record GenerationOptions(bool SupportFlatAsyncApi);
}
20 changes: 20 additions & 0 deletions Ix.NET/Source/System.Linq.Async.SourceGenerator/SyntaxReceiver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;

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

namespace System.Linq.Async.SourceGenerator
{
internal sealed class SyntaxReceiver : ISyntaxReceiver
{
public IList<MethodDeclarationSyntax> Candidates { get; } = new List<MethodDeclarationSyntax>();

public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is MethodDeclarationSyntax { AttributeLists: { Count: >0 } } methodDeclarationSyntax)
{
Candidates.Add(methodDeclarationSyntax);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
<PackageReference Include="IsExternalInit" Version="1.0.0" PrivateAssets="all" />
</ItemGroup>
</Project>
11 changes: 11 additions & 0 deletions Ix.NET/Source/System.Linq.Async.slnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"solution": {
"path": "Ix.NET.sln",
"projects": [
"System.Linq.Async\\System.Linq.Async.csproj",
"System.Linq.Async.Tests\\System.Linq.Async.Tests.csproj",
"System.Linq.Async.SourceGenerator\\System.Linq.Async.SourceGenerator.csproj",
"refs\\System.Linq.Async.Ref\\System.Linq.Async.Ref.csproj"
]
}
}
1 change: 1 addition & 0 deletions Ix.NET/Source/System.Linq.Async/System.Linq.Async.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<ItemGroup>
<PackageReference Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'netstandard2.1' " Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<ReferenceAssemblyProjectReference Include="..\refs\System.Linq.Async.Ref\System.Linq.Async.Ref.csproj" />
<ProjectReference Include="..\System.Linq.Async.SourceGenerator\System.Linq.Async.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" Private="false" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading