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

Make C# source generators incremental #64899

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
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 @@ -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