mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 09:06:41 +02:00
Generate StatementInformation classes with code generator
This commit is contained in:
@@ -27,8 +27,14 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
||||
|
||||
CollectMethods(compilation.Assembly.GlobalNamespace, statementMethods, staticStatementMethods);
|
||||
|
||||
string source = GenerateRegistrySource(statementMethods, staticStatementMethods);
|
||||
context.AddSource("GeneratedStatementRegistry.g.cs", source);
|
||||
INamedTypeSymbol? statementAttrSymbol = compilation.GetTypeByMetadataName(StatementAttributeName);
|
||||
INamedTypeSymbol? staticStatementAttrSymbol = compilation.GetTypeByMetadataName(StaticStatementAttributeName);
|
||||
|
||||
string infoSource = GenerateInformationClassesSource(statementAttrSymbol, staticStatementAttrSymbol);
|
||||
context.AddSource("GeneratedStatementInformation.g.cs", infoSource);
|
||||
|
||||
string registrySource = GenerateRegistrySource(statementMethods, staticStatementMethods);
|
||||
context.AddSource("GeneratedStatementRegistry.g.cs", registrySource);
|
||||
}
|
||||
|
||||
private static void CollectMethods(
|
||||
@@ -77,6 +83,187 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Information class generation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private static string GenerateInformationClassesSource(
|
||||
INamedTypeSymbol? statementAttrSymbol,
|
||||
INamedTypeSymbol? staticStatementAttrSymbol)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
_ = sb.AppendLine("// <auto-generated />");
|
||||
_ = sb.AppendLine("#nullable enable");
|
||||
_ = sb.AppendLine();
|
||||
_ = sb.AppendLine("namespace YesNt.Interpreter.Runtime;");
|
||||
_ = sb.AppendLine();
|
||||
|
||||
if (statementAttrSymbol is not null)
|
||||
{
|
||||
EmitInformationClass(sb, statementAttrSymbol, "StatementInformation");
|
||||
}
|
||||
|
||||
if (staticStatementAttrSymbol is not null)
|
||||
{
|
||||
EmitInformationClass(sb, staticStatementAttrSymbol, "StaticStatementInformation");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void EmitInformationClass(
|
||||
StringBuilder sb,
|
||||
INamedTypeSymbol attributeSymbol,
|
||||
string className)
|
||||
{
|
||||
List<IMethodSymbol> ctors = attributeSymbol.Constructors
|
||||
.Where(c => !c.IsImplicitlyDeclared)
|
||||
.OrderByDescending(c => c.Parameters.Length)
|
||||
.ToList();
|
||||
|
||||
// All parameters across all ctors that are covered by a ctor (get-only properties).
|
||||
HashSet<string> ctorParamNames = new HashSet<string>(
|
||||
ctors.SelectMany(c => c.Parameters).Select(p => p.Name),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Settable properties NOT covered by any ctor parameter.
|
||||
List<IPropertySymbol> settableProps = attributeSymbol
|
||||
.GetMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.IsStatic
|
||||
&& p.DeclaredAccessibility == Accessibility.Public
|
||||
&& p.SetMethod is not null
|
||||
&& !ctorParamNames.Contains(p.Name))
|
||||
.ToList();
|
||||
|
||||
// All properties ever assigned by any ctor, deduplicated by name.
|
||||
// We need a get-only property for each of them.
|
||||
List<(string PropName, ITypeSymbol PropType)> ctorProps = ctors
|
||||
.SelectMany(c => c.Parameters)
|
||||
.Select(p => (
|
||||
PropName: ResolvePropertyName(attributeSymbol, p),
|
||||
PropType: p.Type))
|
||||
.GroupBy(x => x.PropName, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
// ---- class declaration ----
|
||||
_ = sb.AppendLine($"public sealed class {className}");
|
||||
_ = sb.AppendLine("{");
|
||||
|
||||
// ---- one constructor per attribute constructor ----
|
||||
foreach (IMethodSymbol ctor in ctors)
|
||||
{
|
||||
if (ctor.Parameters.Length == 0)
|
||||
{
|
||||
_ = sb.AppendLine($" public {className}() {{ }}");
|
||||
_ = sb.AppendLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
string ctorParams = string.Join(", ",
|
||||
ctor.Parameters.Select(p => $"{GlobalType(p.Type)} {p.Name}"));
|
||||
|
||||
_ = sb.AppendLine($" public {className}({ctorParams})");
|
||||
_ = sb.AppendLine(" {");
|
||||
|
||||
foreach (IParameterSymbol p in ctor.Parameters)
|
||||
{
|
||||
string propName = ResolvePropertyName(attributeSymbol, p);
|
||||
_ = sb.AppendLine($" {propName} = {p.Name};");
|
||||
}
|
||||
|
||||
_ = sb.AppendLine(" }");
|
||||
_ = sb.AppendLine();
|
||||
}
|
||||
|
||||
// ---- get-only properties sourced from ctor parameters ----
|
||||
foreach ((string propName, ITypeSymbol propType) in ctorProps)
|
||||
{
|
||||
_ = sb.AppendLine($" public {GlobalType(propType)} {propName} {{ get; }}");
|
||||
}
|
||||
|
||||
// ---- settable properties (named-argument style) ----
|
||||
foreach (IPropertySymbol prop in settableProps)
|
||||
{
|
||||
string defaultClause = GetDefaultClause(prop);
|
||||
_ = sb.AppendLine($" public {GlobalType(prop.Type)} {prop.Name} {{ get; init; }}{defaultClause}");
|
||||
}
|
||||
|
||||
_ = sb.AppendLine("}");
|
||||
_ = sb.AppendLine();
|
||||
}
|
||||
|
||||
// Finds the attribute property that corresponds to a constructor parameter.
|
||||
private static string ResolvePropertyName(INamedTypeSymbol attributeSymbol, IParameterSymbol parameter)
|
||||
{
|
||||
IPropertySymbol? match = attributeSymbol
|
||||
.GetMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.FirstOrDefault(p => string.Equals(p.Name, parameter.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return match?.Name ?? ToPascalCase(parameter.Name);
|
||||
}
|
||||
|
||||
private static string GlobalType(ITypeSymbol type)
|
||||
{
|
||||
// Primitive aliases don't have a global:: form — emit the C# keyword instead.
|
||||
string keyword = type.SpecialType switch
|
||||
{
|
||||
SpecialType.System_String => "string",
|
||||
SpecialType.System_Boolean => "bool",
|
||||
SpecialType.System_Byte => "byte",
|
||||
SpecialType.System_SByte => "sbyte",
|
||||
SpecialType.System_Int16 => "short",
|
||||
SpecialType.System_UInt16 => "ushort",
|
||||
SpecialType.System_Int32 => "int",
|
||||
SpecialType.System_UInt32 => "uint",
|
||||
SpecialType.System_Int64 => "long",
|
||||
SpecialType.System_UInt64 => "ulong",
|
||||
SpecialType.System_Single => "float",
|
||||
SpecialType.System_Double => "double",
|
||||
SpecialType.System_Decimal => "decimal",
|
||||
SpecialType.System_Char => "char",
|
||||
SpecialType.System_Object => "object",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
if (keyword != "")
|
||||
{
|
||||
// Preserve nullability annotation (e.g. string?)
|
||||
return type.NullableAnnotation == NullableAnnotation.Annotated
|
||||
? keyword + "?"
|
||||
: keyword;
|
||||
}
|
||||
|
||||
return $"global::{type.ToDisplayString()}";
|
||||
}
|
||||
|
||||
private static string ToPascalCase(string name)
|
||||
{
|
||||
return string.IsNullOrEmpty(name) ? name : char.ToUpperInvariant(name[0]) + name.Substring(1);
|
||||
}
|
||||
|
||||
private static string GetDefaultClause(IPropertySymbol prop)
|
||||
{
|
||||
// Only emit a default for a handful of well-known "safe" defaults so the
|
||||
// generated code compiles even when the caller omits the named argument.
|
||||
return prop.Type.IsReferenceType || prop.Type.NullableAnnotation == NullableAnnotation.Annotated
|
||||
? " = null!;"
|
||||
: prop.Type.SpecialType switch
|
||||
{
|
||||
SpecialType.System_Boolean => " = false;",
|
||||
SpecialType.System_Int32 => " = 0;",
|
||||
SpecialType.System_String => " = \"\";",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Registry source generation (unchanged from original)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private static string GenerateRegistrySource(
|
||||
List<MethodRegistration> statementMethods,
|
||||
List<MethodRegistration> staticStatementMethods)
|
||||
@@ -94,8 +281,8 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
||||
_ = sb.AppendLine("{");
|
||||
_ = sb.AppendLine(" internal static void Register(");
|
||||
_ = sb.AppendLine(" RuntimeInformation runtimeInfo,");
|
||||
_ = sb.AppendLine(" out Dictionary<global::YesNt.Interpreter.Attributes.StatementAttributeContainer, Action<string>> statements,");
|
||||
_ = sb.AppendLine(" out List<KeyValuePair<global::YesNt.Interpreter.Attributes.StaticStatementAttributeContainer, Action>> staticStatements)");
|
||||
_ = sb.AppendLine(" out Dictionary<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>> statements,");
|
||||
_ = sb.AppendLine(" out List<KeyValuePair<global::YesNt.Interpreter.Runtime.StaticStatementInformation, Action>> staticStatements)");
|
||||
_ = sb.AppendLine(" {");
|
||||
|
||||
List<INamedTypeSymbol> allTypes = statementMethods
|
||||
@@ -116,25 +303,25 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
||||
_ = sb.AppendLine($" {instanceName}.RuntimeInfo = runtimeInfo;");
|
||||
}
|
||||
|
||||
_ = sb.AppendLine(" var statementEntries = new List<KeyValuePair<global::YesNt.Interpreter.Attributes.StatementAttributeContainer, Action<string>>>();");
|
||||
_ = sb.AppendLine(" var statementEntries = new List<KeyValuePair<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>>>();");
|
||||
|
||||
foreach (MethodRegistration method in statementMethods
|
||||
.OrderBy(x => x.ContainingType.ToDisplayString())
|
||||
.ThenBy(x => x.Method.Name))
|
||||
{
|
||||
string instanceName = instanceNames[method.ContainingType];
|
||||
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Attributes.StatementAttributeContainer", method.Attribute);
|
||||
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Runtime.StatementInformation", method.Attribute);
|
||||
_ = sb.AppendLine($" statementEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));");
|
||||
}
|
||||
|
||||
_ = sb.AppendLine(" var staticEntries = new List<KeyValuePair<global::YesNt.Interpreter.Attributes.StaticStatementAttributeContainer, Action>>();");
|
||||
_ = sb.AppendLine(" var staticEntries = new List<KeyValuePair<global::YesNt.Interpreter.Runtime.StaticStatementInformation, Action>>();");
|
||||
|
||||
foreach (MethodRegistration method in staticStatementMethods
|
||||
.OrderBy(x => x.ContainingType.ToDisplayString())
|
||||
.ThenBy(x => x.Method.Name))
|
||||
{
|
||||
string instanceName = instanceNames[method.ContainingType];
|
||||
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Attributes.StaticStatementAttributeContainer", method.Attribute);
|
||||
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Runtime.StaticStatementInformation", method.Attribute);
|
||||
_ = sb.AppendLine($" staticEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));");
|
||||
}
|
||||
|
||||
@@ -143,7 +330,7 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
||||
_ = sb.AppendLine(" int cmp = a.Key.Priority.CompareTo(b.Key.Priority);");
|
||||
_ = sb.AppendLine(" return cmp != 0 ? cmp : b.Key.Name.Length.CompareTo(a.Key.Name.Length);");
|
||||
_ = sb.AppendLine(" });");
|
||||
_ = sb.AppendLine(" statements = new Dictionary<global::YesNt.Interpreter.Attributes.StatementAttributeContainer, Action<string>>();");
|
||||
_ = sb.AppendLine(" statements = new Dictionary<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>>();");
|
||||
_ = sb.AppendLine(" foreach (var entry in statementEntries)");
|
||||
_ = sb.AppendLine(" statements.Add(entry.Key, entry.Value);");
|
||||
_ = sb.AppendLine();
|
||||
@@ -236,4 +423,4 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
||||
|
||||
public AttributeData Attribute { get; } = attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user