Skip to content

Commit

Permalink
Make C# source generators incremental
Browse files Browse the repository at this point in the history
  • Loading branch information
raulsntos committed Jan 6, 2023
1 parent b14f7aa commit d94f562
Show file tree
Hide file tree
Showing 15 changed files with 423 additions and 335 deletions.
23 changes: 12 additions & 11 deletions modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Godot.SourceGenerators
public static class Common
{
public static void ReportNonPartialGodotScriptClass(
GeneratorExecutionContext context,
SourceProductionContext context,
ClassDeclarationSyntax cds, INamedTypeSymbol symbol
)
{
Expand All @@ -32,11 +32,12 @@ public static void ReportNonPartialGodotScriptClass(
}

public static void ReportNonPartialGodotScriptOuterClass(
GeneratorExecutionContext context,
SourceProductionContext context,
Compilation compilation,
TypeDeclarationSyntax outerTypeDeclSyntax
)
{
var outerSymbol = context.Compilation
var outerSymbol = compilation
.GetSemanticModel(outerTypeDeclSyntax.SyntaxTree)
.GetDeclaredSymbol(outerTypeDeclSyntax);

Expand Down Expand Up @@ -64,7 +65,7 @@ TypeDeclarationSyntax outerTypeDeclSyntax
}

public static void ReportExportedMemberIsStatic(
GeneratorExecutionContext context,
SourceProductionContext context,
ISymbol exportedMemberSymbol
)
{
Expand All @@ -91,7 +92,7 @@ ISymbol exportedMemberSymbol
}

public static void ReportExportedMemberTypeNotSupported(
GeneratorExecutionContext context,
SourceProductionContext context,
ISymbol exportedMemberSymbol
)
{
Expand All @@ -117,7 +118,7 @@ ISymbol exportedMemberSymbol
}

public static void ReportExportedMemberIsReadOnly(
GeneratorExecutionContext context,
SourceProductionContext context,
ISymbol exportedMemberSymbol
)
{
Expand Down Expand Up @@ -145,7 +146,7 @@ ISymbol exportedMemberSymbol
}

public static void ReportExportedMemberIsWriteOnly(
GeneratorExecutionContext context,
SourceProductionContext context,
ISymbol exportedMemberSymbol
)
{
Expand All @@ -169,7 +170,7 @@ ISymbol exportedMemberSymbol
}

public static void ReportExportedMemberIsIndexer(
GeneratorExecutionContext context,
SourceProductionContext context,
ISymbol exportedMemberSymbol
)
{
Expand All @@ -195,7 +196,7 @@ ISymbol exportedMemberSymbol
}

public static void ReportSignalDelegateMissingSuffix(
GeneratorExecutionContext context,
SourceProductionContext context,
INamedTypeSymbol delegateSymbol)
{
var locations = delegateSymbol.Locations;
Expand All @@ -220,7 +221,7 @@ public static void ReportSignalDelegateMissingSuffix(
}

public static void ReportSignalParameterTypeNotSupported(
GeneratorExecutionContext context,
SourceProductionContext context,
IParameterSymbol parameterSymbol)
{
var locations = parameterSymbol.Locations;
Expand All @@ -244,7 +245,7 @@ public static void ReportSignalParameterTypeNotSupported(
}

public static void ReportSignalDelegateSignatureMustReturnVoid(
GeneratorExecutionContext context,
SourceProductionContext context,
INamedTypeSymbol delegateSymbol)
{
var locations = delegateSymbol.Locations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Godot.SourceGenerators
{
static class ExtensionMethods
{
public static bool TryGetGlobalAnalyzerProperty(
this GeneratorExecutionContext context, string property, out string? value
) => context.AnalyzerConfigOptions.GlobalOptions
.TryGetValue("build_property." + property, out value);
this AnalyzerConfigOptionsProvider provider, string property, out string? value
) => provider.GlobalOptions.TryGetValue("build_property." + property, out value);

public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context)
public static bool AreGodotSourceGeneratorsDisabled(this AnalyzerConfigOptionsProvider context)
=> context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) &&
toggle != null &&
toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase);

public static bool IsGodotToolsProject(this GeneratorExecutionContext context)
public static bool IsGodotToolsProject(this AnalyzerConfigOptionsProvider context)
=> context.TryGetGlobalAnalyzerProperty("IsGodotToolsProject", out string? toggle) &&
toggle != null &&
toggle.Equals("true", StringComparison.OrdinalIgnoreCase);
Expand Down Expand Up @@ -69,19 +70,17 @@ public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyNa

string? godotClassName = null;

if (godotClassNameAttr is { ConstructorArguments: { Length: > 0 } })
if (godotClassNameAttr is { ConstructorArguments.Length: > 0 })
godotClassName = godotClassNameAttr.ConstructorArguments[0].Value?.ToString();

return godotClassName ?? nativeType.Name;
}

private static bool IsGodotScriptClass(
this ClassDeclarationSyntax cds, Compilation compilation,
public static bool IsGodotScriptClass(
this ClassDeclarationSyntax cds, SemanticModel sm,
out INamedTypeSymbol? symbol
)
{
var sm = compilation.GetSemanticModel(cds.SyntaxTree);

var classTypeSymbol = sm.GetDeclaredSymbol(cds);

if (classTypeSymbol?.BaseType == null
Expand All @@ -95,16 +94,22 @@ out INamedTypeSymbol? symbol
return true;
}

public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectGodotScriptClasses(
this IEnumerable<ClassDeclarationSyntax> source,
Compilation compilation
)
public static IncrementalValuesProvider<GodotClassData> CreateValuesProviderForGodotClasses(this SyntaxValueProvider provider, Func<SyntaxNode, CancellationToken, bool>? customPredicate = null, Func<GeneratorSyntaxContext, CancellationToken, GodotClassData>? customTransform = null)
{
foreach (var cds in source)
{
if (cds.IsGodotScriptClass(compilation, out var symbol))
yield return (cds, symbol!);
}
return provider.CreateSyntaxProvider(
// By default select class declarations that inherit from something
// since Godot classes must at least inherit from Godot.Object
predicate: customPredicate ?? (static (s, _) => s is ClassDeclarationSyntax { BaseList.Types.Count: > 0 }),
// Filter out non-Godot classes and retrieve the symbol
transform: customTransform ?? (static (ctx, _) =>
{
var cds = (ClassDeclarationSyntax)ctx.Node;
if (!cds.IsGodotScriptClass(ctx.SemanticModel, out var symbol))
return default;
return new GodotClassData(cds, symbol);
})
).Where(static x => x.Symbol is not null);
}

public static bool IsNested(this TypeDeclarationSyntax cds)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9.0</LangVersion>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
Expand All @@ -20,7 +20,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Godot.SourceGenerators
{
public readonly struct GodotClassData
{
public GodotClassData(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)
{
DeclarationSyntax = cds;
Symbol = symbol;
}

public ClassDeclarationSyntax DeclarationSyntax { get; }
public INamedTypeSymbol Symbol { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@
namespace Godot.SourceGenerators
{
[Generator]
public class GodotPluginsInitializerGenerator : ISourceGenerator
public class GodotPluginsInitializerGenerator : IIncrementalGenerator
{
public void Initialize(GeneratorInitializationContext context)
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var isGodotToolsProject = context.AnalyzerConfigOptionsProvider.Select((provider, _) => provider.IsGodotToolsProject());

context.RegisterSourceOutput(isGodotToolsProject, static (spc, source) =>
{
if (source)
return;
Execute(spc);
});
}

public void Execute(GeneratorExecutionContext context)
private static void Execute(SourceProductionContext context)
{
if (context.IsGodotToolsProject())
return;

string source =
@"using System;
using System.Runtime.InteropServices;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,62 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Godot.SourceGenerators
{
[Generator]
public class ScriptMethodsGenerator : ISourceGenerator
public class ScriptMethodsGenerator : IIncrementalGenerator
{
public void Initialize(GeneratorInitializationContext context)
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var areGodotSourceGeneratorsDisabled = context.AnalyzerConfigOptionsProvider.Select(static (provider, _) => provider.AreGodotSourceGeneratorsDisabled());

var godotClasses = context.SyntaxProvider.CreateValuesProviderForGodotClasses();

var values = areGodotSourceGeneratorsDisabled
.Combine(context.CompilationProvider)
.Combine(godotClasses.Collect());

context.RegisterSourceOutput(values, static (spc, source) =>
{
(bool areGodotSourceGeneratorsDisabled, Compilation compilation) = source.Left;
var godotClasses = source.Right;
if (areGodotSourceGeneratorsDisabled)
return;
Execute(spc, compilation, godotClasses);
});
}

public void Execute(GeneratorExecutionContext context)
private static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray<GodotClassData> godotClassDatas)
{
if (context.AreGodotSourceGeneratorsDisabled())
return;

INamedTypeSymbol[] godotClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.SelectGodotScriptClasses(context.Compilation)
// Report and skip non-partial classes
.Where(x =>
{
if (x.cds.IsPartial())
{
if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
{
Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
return false;
}
return true;
}
Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
return false;
})
.Select(x => x.symbol)
)
INamedTypeSymbol[] godotClasses = godotClassDatas.Where(x =>
{
// Report and skip non-partial classes
if (x.DeclarationSyntax.IsPartial())
{
if (x.DeclarationSyntax.IsNested() && !x.DeclarationSyntax.AreAllOuterTypesPartial(out var typeMissingPartial))
{
Common.ReportNonPartialGodotScriptOuterClass(context, compilation, typeMissingPartial);
return false;
}
return true;
}
Common.ReportNonPartialGodotScriptClass(context, x.DeclarationSyntax, x.Symbol);
return false;
}).Select(x => x.Symbol)
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
.ToArray();

if (godotClasses.Length > 0)
{
var typeCache = new MarshalUtils.TypeCache(context.Compilation);
var typeCache = new MarshalUtils.TypeCache(compilation);

foreach (var godotClass in godotClasses)
{
Expand All @@ -73,7 +80,7 @@ public int GetHashCode(GodotMethodData obj)
}

private static void VisitGodotScriptClass(
GeneratorExecutionContext context,
SourceProductionContext context,
MarshalUtils.TypeCache typeCache,
INamedTypeSymbol symbol
)
Expand Down
Loading

0 comments on commit d94f562

Please sign in to comment.