Skip to content

Switch generators to use data abstraction layer#87

Merged
dex3r merged 3 commits intomainfrom
copilot/switch-generators-data-abstraction-layer
Mar 25, 2026
Merged

Switch generators to use data abstraction layer#87
dex3r merged 3 commits intomainfrom
copilot/switch-generators-data-abstraction-layer

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 24, 2026

Generators now produce intermediate data objects (DataMethodBody) before generating C# source, decoupling data extraction from code emission. Explicit [SwitchCase] attribute-based generation is commented out pending replacement with the new data-driven approach.

Data model

  • DataMethodBody (abstract), DataSimpleReturnBody, DataSwitchBody, DataSwitchCase, DataSwitchDefaultCase — unified representation of what a method body should contain

Code generation from data

  • DataMethodBodyBuilders — takes a DataMethodBody and produces C# source. Single entry point regardless of how the data was built:
DataSwitchBody data = DataGeneratorsFactory.CreateSwitchBodyFromFluentData(switchBodyData, defaultExpression);
return DataMethodBodyBuilders.BuildMethodSource(data, partialMethod, containingType);

Data construction

  • DataGeneratorsFactory — bridges fluent API recordings (SwitchBodyRecord) and simple execution results into DataMethodBody instances

Pipeline refactoring

  • GeneratesMethodGenerationPipeline routes both fluent and simple patterns through DataGeneratorsFactoryDataMethodBodyBuilders

Commented out (to be replaced)

  • [SwitchCase]/[SwitchDefault] attribute-based generation path and all dependent tests/examples — the data layer will be extended to support a new attribute pattern in a follow-up

Tests

  • 8 tests for DataGeneratorsFactory (data construction correctness)
  • 14 tests for DataMethodBodyBuilders (source generation from data: simple return, switch bodies, bool keys, throw defaults, void, formatting)
Original prompt

This section details on the original issue you should resolve

<issue_title>Switch Generators to use "data abstraction layer"</issue_title>
<issue_description>Fluent API Generators should now create a "Data" layer that states how the method should be implemented (see the new DataGeneratorsFactory and DataMethodBodyBuilders).

The non-fluent part, aka. the generators using attributes, should also rely on this abstraction layer.

Generators should build the method body based on the provided "Data" alone, regardless of how it was built. Extend the "Data" layer as needed.

Create tests for the data building layer if they build matching data.
Refactor and tests the code generators themselves, make sure they use the "data" layer as input.

  • Focus on Body Generators, entire method generators (including name) are WIP and should be commented out if needed in this PR.
  • Explicit SwitchCase statements will be replaced in the future, so comment out those as well (see DataMethodBodyBuilders.cs for details)</issue_description>

Comments on the Issue (you are @copilot in this section)


⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.

Copilot AI and others added 2 commits March 24, 2026 21:12
…ataGeneratorsFactory

- Create DataMethodBody hierarchy (DataSimpleReturnBody, DataSwitchBody)
- Create DataMethodBodyBuilders for generating C# source from data model
- Create DataGeneratorsFactory for building data from various sources
- Refactor fluent and simple patterns in pipeline to use data layer
- Add 22 unit tests for data building layer and code generation

Co-authored-by: dex3r <3155725+dex3r@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dex3r/EasySourceGenerators/sessions/e9007179-53d4-426e-8329-db95a8b21854
…eration

Commented out the attribute-based SwitchCase/SwitchDefault pattern in the
pipeline and all related tests. This pattern will be replaced with a
data-driven approach in a future PR. The data abstraction layer
(DataMethodBody, DataMethodBodyBuilders, DataGeneratorsFactory) is ready
to be extended for the new pattern.

Co-authored-by: dex3r <3155725+dex3r@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dex3r/EasySourceGenerators/sessions/e9007179-53d4-426e-8329-db95a8b21854
Copilot AI changed the title [WIP] Switch generators to use data abstraction layer Switch generators to use data abstraction layer Mar 24, 2026
Copilot AI requested a review from dex3r March 24, 2026 21:22
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 25, 2026

Codecov Report

❌ Patch coverage is 81.52866% with 29 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...rceGenerators.Generators/DataMethodBodyBuilders.cs 82.45% 10 Missing and 10 partials ⚠️
...rs.Generators/GeneratesMethodGenerationPipeline.cs 60.86% 7 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@dex3r dex3r marked this pull request as ready for review March 25, 2026 11:14
Copilot AI review requested due to automatic review settings March 25, 2026 11:14
@dex3r dex3r merged commit 4f4d08d into main Mar 25, 2026
3 checks passed
@dex3r dex3r deleted the copilot/switch-generators-data-abstraction-layer branch March 25, 2026 11:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the method-body generation pipeline to build an intermediate data model (DataMethodBody) first and then emit C# from that model, while temporarily disabling the legacy [SwitchCase]/[SwitchDefault] attribute-based switch generation path.

Changes:

  • Introduces DataMethodBody + DataGeneratorsFactory + DataMethodBodyBuilders to decouple data extraction from source emission.
  • Updates GeneratesMethodGenerationPipeline to route fluent + simple patterns through the new data layer.
  • Comments out attribute-based switch generation and dependent tests/examples until a replacement pattern is implemented.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
EasySourceGenerators.Tests/PiExampleTests.cs Comments out [SwitchCase] attribute-based tests/examples pending new pattern.
EasySourceGenerators.Tests/DefaultCaseThrowExpressionTests.cs Comments out [SwitchDefault] attribute-based tests pending new pattern.
EasySourceGenerators.Tests/DefaultCaseConstValue.cs Comments out [SwitchDefault] attribute-based tests pending new pattern.
EasySourceGenerators.Tests/BoolSwitchKeyTests.cs Comments out attribute-based bool switch tests pending new pattern.
EasySourceGenerators.Generators/GeneratesMethodPatternSourceBuilder.cs Exposes fluent default-expression extraction helper for pipeline reuse.
EasySourceGenerators.Generators/GeneratesMethodGenerationPipeline.cs Routes fluent/simple patterns through data layer; comments out attribute-switch path.
EasySourceGenerators.Generators/DataMethodBodyBuilders.cs New: emits method bodies from DataMethodBody representations.
EasySourceGenerators.Generators/DataMethodBody.cs New: defines the intermediate data model for method bodies.
EasySourceGenerators.Generators/DataGeneratorsFactory.cs New: constructs DataMethodBody from fluent/simple inputs.
EasySourceGenerators.GeneratorTests/GeneratorDiagnosticsTests.cs Comments out switch-attribute diagnostics tests while the pattern is disabled.
EasySourceGenerators.GeneratorTests/DataMethodBodyBuildersTests.cs New: unit tests for source emission from DataMethodBody.
EasySourceGenerators.GeneratorTests/DataGeneratorsFactoryTests.cs New: unit tests for data construction from fluent/simple inputs.
EasySourceGenerators.Examples/PiExample.cs Comments out attribute-based example in favor of fluent equivalent.
Comments suppressed due to low confidence (1)

EasySourceGenerators.Generators/GeneratesMethodGenerationPipeline.cs:86

  • With the [SwitchCase]/[SwitchDefault] path commented out, generator methods using those attributes (and therefore having a runtime parameter) will now fall into the “simple pattern” validation and emit MSGH007 (“Method generators cannot have any parameters…”). That diagnostic is misleading for the (temporarily disabled) switch-attribute pattern; consider short-circuiting when SwitchCase/SwitchDefault attributes are present and emitting a dedicated “attribute-based switch generation is currently disabled” diagnostic (or suppressing MSGH007 for that case).
        List<GeneratesMethodGenerationTarget> methodsWithParameters = methods
            .Where(method => method.Symbol.Parameters.Length > 0)
            .ToList();
        if (methodsWithParameters.Count > 0)
        {
            foreach (GeneratesMethodGenerationTarget methodWithParameters in methodsWithParameters)
            {
                context.ReportDiagnostic(Diagnostic.Create(
                    GeneratesMethodGeneratorDiagnostics.CannotUseRuntimeParameterForCompileTimeGeneratorError,
                    GetMethodSignatureLocation(methodWithParameters.Syntax)));
            }


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +90 to +164
builder.AppendLine("// <auto-generated/>");
builder.AppendLine($"// Generated by {typeof(GeneratesMethodGenerator).FullName} for method '{partialMethod.Name}'.");
builder.AppendLine("#pragma warning disable");
builder.AppendLine();

string? namespaceName = containingType.ContainingNamespace?.IsGlobalNamespace == false
? containingType.ContainingNamespace.ToDisplayString()
: null;
if (namespaceName != null)
{
builder.AppendLine($"namespace {namespaceName};");
builder.AppendLine();
}

string typeKeyword = containingType.TypeKind switch
{
TypeKind.Struct => "struct",
TypeKind.Interface => "interface",
_ => "class"
};

string typeModifiers = containingType.IsStatic ? "static partial" : "partial";
builder.AppendLine($"{typeModifiers} {typeKeyword} {containingType.Name}");
builder.AppendLine("{");

string accessibility = partialMethod.DeclaredAccessibility switch
{
Accessibility.Public => "public",
Accessibility.Protected => "protected",
Accessibility.Internal => "internal",
Accessibility.ProtectedOrInternal => "protected internal",
Accessibility.ProtectedAndInternal => "private protected",
_ => "private"
};

string returnTypeName = partialMethod.ReturnType.ToDisplayString();
string methodName = partialMethod.Name;
string parameters = string.Join(", ", partialMethod.Parameters.Select(parameter => $"{parameter.Type.ToDisplayString()} {parameter.Name}"));
string methodModifiers = partialMethod.IsStatic ? "static partial" : "partial";

builder.AppendLine($" {accessibility} {methodModifiers} {returnTypeName} {methodName}({parameters})");
builder.AppendLine(" {");
}

internal static string FormatValueAsCSharpLiteral(string? value, ITypeSymbol returnType)
{
if (value == null)
{
return "default";
}

return returnType.SpecialType switch
{
SpecialType.System_String => SyntaxFactory.Literal(value).Text,
SpecialType.System_Char when value.Length == 1 => SyntaxFactory.Literal(value[0]).Text,
SpecialType.System_Boolean => value.ToLowerInvariant(),
_ when returnType.TypeKind == TypeKind.Enum => $"{returnType.ToDisplayString()}.{value}",
_ => value
};
}

internal static string FormatKeyAsCSharpLiteral(object key, ITypeSymbol? parameterType)
{
if (parameterType?.TypeKind == TypeKind.Enum)
{
return $"{parameterType.ToDisplayString()}.{key}";
}

return key switch
{
bool b => b ? "true" : "false",
// SyntaxFactory.Literal handles escaping and quoting (e.g. "hello" → "\"hello\"")
string s => SyntaxFactory.Literal(s).Text,
_ => key.ToString()!
};
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

DataMethodBodyBuilders duplicates the string/namespace/type header and literal formatting logic that already exists in GeneratesMethodPatternSourceBuilder. Keeping two implementations in sync is error-prone; consider extracting the shared helpers into a single utility (or delegating from one builder to the other) so formatting and escaping rules don’t drift.

Suggested change
builder.AppendLine("// <auto-generated/>");
builder.AppendLine($"// Generated by {typeof(GeneratesMethodGenerator).FullName} for method '{partialMethod.Name}'.");
builder.AppendLine("#pragma warning disable");
builder.AppendLine();
string? namespaceName = containingType.ContainingNamespace?.IsGlobalNamespace == false
? containingType.ContainingNamespace.ToDisplayString()
: null;
if (namespaceName != null)
{
builder.AppendLine($"namespace {namespaceName};");
builder.AppendLine();
}
string typeKeyword = containingType.TypeKind switch
{
TypeKind.Struct => "struct",
TypeKind.Interface => "interface",
_ => "class"
};
string typeModifiers = containingType.IsStatic ? "static partial" : "partial";
builder.AppendLine($"{typeModifiers} {typeKeyword} {containingType.Name}");
builder.AppendLine("{");
string accessibility = partialMethod.DeclaredAccessibility switch
{
Accessibility.Public => "public",
Accessibility.Protected => "protected",
Accessibility.Internal => "internal",
Accessibility.ProtectedOrInternal => "protected internal",
Accessibility.ProtectedAndInternal => "private protected",
_ => "private"
};
string returnTypeName = partialMethod.ReturnType.ToDisplayString();
string methodName = partialMethod.Name;
string parameters = string.Join(", ", partialMethod.Parameters.Select(parameter => $"{parameter.Type.ToDisplayString()} {parameter.Name}"));
string methodModifiers = partialMethod.IsStatic ? "static partial" : "partial";
builder.AppendLine($" {accessibility} {methodModifiers} {returnTypeName} {methodName}({parameters})");
builder.AppendLine(" {");
}
internal static string FormatValueAsCSharpLiteral(string? value, ITypeSymbol returnType)
{
if (value == null)
{
return "default";
}
return returnType.SpecialType switch
{
SpecialType.System_String => SyntaxFactory.Literal(value).Text,
SpecialType.System_Char when value.Length == 1 => SyntaxFactory.Literal(value[0]).Text,
SpecialType.System_Boolean => value.ToLowerInvariant(),
_ when returnType.TypeKind == TypeKind.Enum => $"{returnType.ToDisplayString()}.{value}",
_ => value
};
}
internal static string FormatKeyAsCSharpLiteral(object key, ITypeSymbol? parameterType)
{
if (parameterType?.TypeKind == TypeKind.Enum)
{
return $"{parameterType.ToDisplayString()}.{key}";
}
return key switch
{
bool b => b ? "true" : "false",
// SyntaxFactory.Literal handles escaping and quoting (e.g. "hello" → "\"hello\"")
string s => SyntaxFactory.Literal(s).Text,
_ => key.ToString()!
};
// Delegate to the shared implementation in GeneratesMethodPatternSourceBuilder
GeneratesMethodPatternSourceBuilder.AppendNamespaceAndTypeHeader(builder, containingType, partialMethod);
}
internal static string FormatValueAsCSharpLiteral(string? value, ITypeSymbol returnType)
{
// Delegate to the shared implementation in GeneratesMethodPatternSourceBuilder
return GeneratesMethodPatternSourceBuilder.FormatValueAsCSharpLiteral(value, returnType);
}
internal static string FormatKeyAsCSharpLiteral(object key, ITypeSymbol? parameterType)
{
// Delegate to the shared implementation in GeneratesMethodPatternSourceBuilder
return GeneratesMethodPatternSourceBuilder.FormatKeyAsCSharpLiteral(key, parameterType);

Copilot uses AI. Check for mistakes.
Comment on lines +324 to +336
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (!string.IsNullOrEmpty(assembly.Location) && references.All(reference => reference.Display != assembly.Location))
{
try
{
references.Add(MetadataReference.CreateFromFile(assembly.Location));
}
catch (FileNotFoundException) { }
catch (BadImageFormatException) { }
}
}

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The test-local GetMetadataReferences() enumerates and adds every loaded AppDomain assembly as a Roslyn reference. This can make tests environment-dependent (loaded assemblies vary by runner/IDE) and may hide missing explicit references. Consider reusing the existing GeneratorTestHelper metadata-reference builder (or mirroring the explicit core-reference list used by GeneratesMethodExecutionRuntimeTests) to keep compilation inputs deterministic.

Suggested change
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (!string.IsNullOrEmpty(assembly.Location) && references.All(reference => reference.Display != assembly.Location))
{
try
{
references.Add(MetadataReference.CreateFromFile(assembly.Location));
}
catch (FileNotFoundException) { }
catch (BadImageFormatException) { }
}
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Switch Generators to use "data abstraction layer"

3 participants