Skip to content
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

Polyfill the incremental generator ForAttributeWithMetadataName from roslyn (for EventSourceGeneration). #71662

Merged
merged 8 commits into from
Jul 12, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -4,116 +4,90 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.DotnetRuntime.Extensions;

namespace Generators
{
public partial class EventSourceGenerator
{
private static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken cancellationToken) =>
node is ClassDeclarationSyntax { AttributeLists.Count: > 0 };

private static EventSourceClass? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken)
private static EventSourceClass? GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
{
const string EventSourceAutoGenerateAttribute = "System.Diagnostics.Tracing.EventSourceAutoGenerateAttribute";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need to check for EventSourceAutoGenerateAttribute. we wouldn't have gotten here unless we had that attribute on the class we're examining.

const string EventSourceAttribute = "System.Diagnostics.Tracing.EventSourceAttribute";

var classDef = (ClassDeclarationSyntax)context.Node;
SemanticModel sm = context.SemanticModel;
var classDef = (ClassDeclarationSyntax)context.TargetNode;

EventSourceClass? eventSourceClass = null;

bool autoGenerate = false;
foreach (AttributeListSyntax cal in classDef.AttributeLists)
foreach (AttributeData attribute in context.TargetSymbol.GetAttributes())
{
foreach (AttributeSyntax ca in cal.Attributes)
{
if (sm.GetSymbolInfo(ca, cancellationToken).Symbol is not IMethodSymbol caSymbol)
{
// badly formed attribute definition, or not the right attribute
continue;
}

string attributeFullName = caSymbol.ContainingType.ToDisplayString();
if (attribute.AttributeClass is null)
continue;

if (attributeFullName.Equals(EventSourceAutoGenerateAttribute, StringComparison.Ordinal))
NamespaceDeclarationSyntax? ns = classDef.Parent as NamespaceDeclarationSyntax;
if (ns is null)
{
if (classDef.Parent is not CompilationUnitSyntax)
{
autoGenerate = true;
// since this generator doesn't know how to generate a nested type...
continue;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we required having EventSourceAutoGenerateAttribute before. otherwise, autogenerate would not be true, and we'd bail out below.

}
}

if (!attributeFullName.Equals(EventSourceAttribute, StringComparison.Ordinal))
{
continue;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the code below would only execute if we had EventSourceAttribute, so that's what we maintain below.

if (attribute.AttributeClass.Name != "EventSourceAttribute" &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is && correct here? It shouldn't be ||?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed!

CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
attribute.AttributeClass.ToDisplayString() != EventSourceAttribute)
{
continue;
}

string nspace = string.Empty;
NamespaceDeclarationSyntax? ns = classDef.Parent as NamespaceDeclarationSyntax;
if (ns is null)
string nspace = string.Empty;
if (ns is not null)
{
nspace = ns.Name.ToString();
while (true)
{
if (classDef.Parent is not CompilationUnitSyntax)
ns = ns.Parent as NamespaceDeclarationSyntax;
if (ns == null)
{
// since this generator doesn't know how to generate a nested type...
continue;
break;
}
}
else
{
nspace = ns.Name.ToString();
while (true)
{
ns = ns.Parent as NamespaceDeclarationSyntax;
if (ns == null)
{
break;
}

nspace = $"{ns.Name}.{nspace}";
}
nspace = $"{ns.Name}.{nspace}";
}
}

string className = classDef.Identifier.ToString();
string name = className;
string guid = "";
string className = classDef.Identifier.ValueText;
string name = className;
string guid = "";

SeparatedSyntaxList<AttributeArgumentSyntax>? args = ca.ArgumentList?.Arguments;
if (args is not null)
{
foreach (AttributeArgumentSyntax arg in args)
{
string argName = arg.NameEquals!.Name.Identifier.ToString();
string value = sm.GetConstantValue(arg.Expression, cancellationToken).ToString();

switch (argName)
{
case "Guid":
guid = value;
break;
case "Name":
name = value;
break;
}
}
}
ImmutableArray<KeyValuePair<string, TypedConstant>> args = attribute.NamedArguments;
foreach (KeyValuePair<string, TypedConstant> arg in args)
{
string argName = arg.Key;
string value = arg.Value.Value?.ToString();

if (!Guid.TryParse(guid, out Guid result))
switch (argName)
{
result = GenerateGuidFromName(name.ToUpperInvariant());
case "Guid":
guid = value;
break;
case "Name":
name = value;
break;
}
}

eventSourceClass = new EventSourceClass(nspace, className, name, result);
continue;
if (!Guid.TryParse(guid, out Guid result))
{
result = GenerateGuidFromName(name.ToUpperInvariant());
}
}

if (!autoGenerate)
{
return null;
eventSourceClass = new EventSourceClass(nspace, className, name, result);
continue;
}

return eventSourceClass;
Expand Down
10 changes: 8 additions & 2 deletions src/libraries/System.Private.CoreLib/gen/EventSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.DotnetRuntime.Extensions;

namespace Generators
{
Expand Down Expand Up @@ -35,9 +36,14 @@ public partial class EventSourceGenerator : IIncrementalGenerator

public void Initialize(IncrementalGeneratorInitializationContext context)
{
const string EventSourceAutoGenerateAttribute = "System.Diagnostics.Tracing.EventSourceAutoGenerateAttribute";

IncrementalValuesProvider<EventSourceClass> eventSourceClasses =
context.SyntaxProvider
.CreateSyntaxProvider(IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration)
context.SyntaxProvider.ForAttributeWithMetadataName(
context,
EventSourceAutoGenerateAttribute,
(node, _) => node is ClassDeclarationSyntax,
GetSemanticTargetForGeneration)
.Where(x => x is not null);

context.RegisterSourceOutput(eventSourceClasses, EmitSourceFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
<Compile Include="$(CoreLibSharedDir)\System\Runtime\CompilerServices\IsExternalInit.cs" Link="Common\System\Runtime\CompilerServices\IsExternalInit.cs" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(CommonPath)Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
<Compile Include="$(CommonPath)Roslyn\Hash.cs" Link="Common\Roslyn\Hash.cs" />
<Compile Include="$(CommonPath)Roslyn\ISyntaxHelper.cs" Link="Common\Roslyn\ISyntaxHelper.cs" />
<Compile Include="$(CommonPath)Roslyn\CSharpSyntaxHelper.cs" Link="Common\Roslyn\CSharpSyntaxHelper.cs" />
<Compile Include="$(CommonPath)Roslyn\GlobalAliases.cs" Link="Common\Roslyn\GlobalAliases.cs" />
<Compile Include="$(CommonPath)Roslyn\SyntaxNodeGrouping.cs" Link="Common\Roslyn\SyntaxNodeGrouping.cs" />
<Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider.ImmutableArrayValueComparer.cs" Link="Common\Roslyn\SyntaxValueProvider.ImmutableArrayValueComparer.cs" />
<Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider_ForAttributeWithMetadataName.cs" Link="Common\Roslyn\SyntaxValueProvider_ForAttributeWithMetadataName.cs" />
<Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider_ForAttributeWithSimpleName.cs" Link="Common\Roslyn\SyntaxValueProvider_ForAttributeWithSimpleName.cs" />

<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.cs" Link="Production\ValueListBuilder.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.Pop.cs" Link="Production\ValueListBuilder.Pop.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" Version="$(MicrosoftCodeAnalysisCSharpVersion)" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.DotnetRuntime.Extensions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

[assembly: System.Resources.NeutralResourcesLanguage("en-US")]
Expand Down Expand Up @@ -61,19 +62,13 @@ public static class StepNames
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var attributedMethods = context.SyntaxProvider
.CreateSyntaxProvider(
static (node, ct) => ShouldVisitNode(node),
static (context, ct) =>
{
MethodDeclarationSyntax syntax = (MethodDeclarationSyntax)context.Node;
if (context.SemanticModel.GetDeclaredSymbol(syntax, ct) is IMethodSymbol methodSymbol
&& methodSymbol.GetAttributes().Any(static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.LibraryImportAttribute))
{
return new { Syntax = syntax, Symbol = methodSymbol };
}

return null;
})
.ForAttributeWithMetadataName(
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
context,
TypeNames.LibraryImportAttribute,
static (node, ct) => node is MethodDeclarationSyntax,
static (context, ct) => context.TargetSymbol is IMethodSymbol methodSymbol
? new { Syntax = (MethodDeclarationSyntax)context.TargetNode, Symbol = methodSymbol }
: null)
.Where(
static modelData => modelData is not null);

Expand Down Expand Up @@ -584,19 +579,6 @@ static ExpressionSyntax CreateEnumExpressionSyntax<T>(T value) where T : Enum
}
}

private static bool ShouldVisitNode(SyntaxNode syntaxNode)
{
// We only support C# method declarations.
if (syntaxNode.Language != LanguageNames.CSharp
|| !syntaxNode.IsKind(SyntaxKind.MethodDeclaration))
{
return false;
}

// Filter out methods with no attributes early.
return ((MethodDeclarationSyntax)syntaxNode).AttributeLists.Count > 0;
}

private static Diagnostic? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax methodSyntax, IMethodSymbol method)
{
// Verify the method has no generic types or defined implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,19 @@
<ProjectReference Include="..\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" Pack="true" PackagePath="analyzers/dotnet/cs" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(CommonPath)Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
<Compile Include="$(CommonPath)Roslyn\Hash.cs" Link="Common\Roslyn\Hash.cs" />
<Compile Include="$(CommonPath)Roslyn\ISyntaxHelper.cs" Link="Common\Roslyn\ISyntaxHelper.cs" />
<Compile Include="$(CommonPath)Roslyn\CSharpSyntaxHelper.cs" Link="Common\Roslyn\CSharpSyntaxHelper.cs" />
<Compile Include="$(CommonPath)Roslyn\GlobalAliases.cs" Link="Common\Roslyn\GlobalAliases.cs" />
<Compile Include="$(CommonPath)Roslyn\SyntaxNodeGrouping.cs" Link="Common\Roslyn\SyntaxNodeGrouping.cs" />
<Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider.ImmutableArrayValueComparer.cs" Link="Common\Roslyn\SyntaxValueProvider.ImmutableArrayValueComparer.cs" />
<Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider_ForAttributeWithMetadataName.cs" Link="Common\Roslyn\SyntaxValueProvider_ForAttributeWithMetadataName.cs" />
<Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider_ForAttributeWithSimpleName.cs" Link="Common\Roslyn\SyntaxValueProvider_ForAttributeWithSimpleName.cs" />

<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.cs" Link="Production\ValueListBuilder.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.Pop.cs" Link="Production\ValueListBuilder.Pop.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<Compile Include="$(CommonPath)System\HexConverter.cs" Link="Production\HexConverter.cs" />
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs" Link="Production\ValueStringBuilder.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.cs" Link="Production\ValueListBuilder.cs" />
<Compile Include="..\src\System\Collections\Generic\ValueListBuilder.Pop.cs" Link="Production\ValueListBuilder.Pop.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.Pop.cs" Link="Production\ValueListBuilder.Pop.cs" />
<Compile Include="..\src\System\Threading\StackHelper.cs" Link="Production\StackHelper.cs" />
<Compile Include="..\src\System\Text\RegularExpressions\RegexCaseEquivalences.Data.cs" Link="Production\RegexCaseEquivalences.Data.cs" />
<Compile Include="..\src\System\Text\RegularExpressions\RegexCaseEquivalences.cs" Link="Production\RegexCaseEquivalences.cs" />
Expand Down