BizHawk/ExternalProjects/BizHawk.SrcGen.SettingsUtil/DefaultSetterGenerator.cs

108 lines
3.2 KiB
C#

namespace BizHawk.SrcGen.SettingsUtil;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using BizHawk.Analyzers;
using TestType = (ClassDeclarationSyntax CDS, SemanticModel SemanticModel);
[Generator]
public class DefaultSetterGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classDecls = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax,
transform: static (ctx, _) => ((ClassDeclarationSyntax) ctx.Node, ctx.SemanticModel));
context.RegisterSourceOutput(context.CompilationProvider.Combine(classDecls.Collect()), Execute);
}
private static void CreateDefaultSetter(StringBuilder source, INamespaceOrTypeSymbol symbol)
{
var props = symbol
.GetMembers()
.Where(m => m.Kind == SymbolKind.Property)
.ToImmutableArray();
source.Append($@"
public static void SetDefaultValues({symbol} settings)
{{");
foreach (var prop in props)
{
var defaultValueAttribute = prop
.GetAttributes()
.FirstOrDefault(
a => a.AttributeClass?.Name == "DefaultValueAttribute");
var ctorArgs = defaultValueAttribute?.ConstructorArguments;
if (!ctorArgs.HasValue)
{
continue;
}
switch (ctorArgs.Value.Length)
{
case 1:
// this single arg is just the value assigned to the default value
var arg = ctorArgs.Value[0];
// a bit lame, but it'll work
// TODO: do we even want to handle arrays? we don't even have arrays in default values...
var converionStr = arg.Kind == TypedConstantKind.Array
? $"new {arg.Type}[] " // new T[]
: ""; // do we need a cast (i.e. (T)) here? probably not?
source.Append($@"
settings.{prop.Name} = {converionStr}{arg.ToCSharpString()};");
break;
case 2:
// first arg is the type, the second arg is a string which converts it
source.Append($@"
settings.{prop.Name} = ({ctorArgs.Value[0].Value})System.ComponentModel.TypeDescriptor
.GetConverter({ctorArgs.Value[0].ToCSharpString()})
.ConvertFromInvariantString({ctorArgs.Value[1].ToCSharpString()});");
break;
}
}
source.Append(@"
}
");
}
public void Execute(
SourceProductionContext context,
(Compilation Compilation, ImmutableArray<TestType> ClassDeclarations) value)
{
var (compilation, classDeclarations) = value;
var consumerAttrSym = compilation.GetTypeByMetadataName("BizHawk.Emulation.Common.CoreSettingsAttribute");
if (consumerAttrSym is null) return;
// Generated source code
var source = new StringBuilder(@"
namespace BizHawk.Emulation.Cores
{
public static partial class SettingsUtil
{");
foreach (var (cds, semanticModel) in classDeclarations
.Where(tuple => tuple.CDS.AttributeLists.Matching(
consumerAttrSym,
tuple.SemanticModel,
context.CancellationToken).Any()))
{
var symbol = semanticModel.GetDeclaredSymbol(cds, context.CancellationToken);
if (symbol is null) continue; // probably never happens?
CreateDefaultSetter(source, symbol);
}
source.Append(@"
}
}");
// Add the source code to the compilation
context.AddSource("DefaultSetters.g.cs", source.ToString());
}
}