From 96c0fbec53a5c090be6deed2815dd9c9aa23bb85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:07:29 +0000 Subject: [PATCH 1/3] Initial plan From b1e18367a4d88b2163c94550558fe47df855618c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:16:08 +0000 Subject: [PATCH 2/3] Implement WithCompileTimeConstants for all Stage4 builders and add Stage5 builders - Add WithCompileTimeConstants methods to DataMethodBodyBuilderStage4, DataMethodBodyBuilderStage4NoArg, DataMethodBodyBuilderStage4ReturnVoid, and DataMethodBodyBuilderStage4ReturnVoidNoArg - Add Stage5 builder records: DataMethodBodyBuilderStage5WithConstants, DataMethodBodyBuilderStage5NoArgWithConstants, DataMethodBodyBuilderStage5ReturnVoidWithConstants, and DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants - Update BodyGenerationDataExtractor to pass CompileTimeConstants when invoking ReturnConstantValueFactory and RuntimeDelegateBody delegates - Add unit tests for all Stage5 builders and WithCompileTimeConstants flow - Add unit tests for BodyGenerationDataExtractor with constants Co-authored-by: dex3r <3155725+dex3r@users.noreply.github.com> Agent-Logs-Url: https://github.com/dex3r/EasySourceGenerators/sessions/c5e01801-855b-4054-bfa6-7860c743dca7 --- .../BodyGenerationDataExtractorTests.cs | 128 ++++++++++++ .../MethodBodyBuilderTests.cs | 194 ++++++++++++++++++ .../DataBuilding/DataMethodBodyBuilders.cs | 38 ++++ .../BodyGenerationDataExtractor.cs | 54 ++++- 4 files changed, 406 insertions(+), 8 deletions(-) create mode 100644 EasySourceGenerators.GeneratorTests/BodyGenerationDataExtractorTests.cs diff --git a/EasySourceGenerators.GeneratorTests/BodyGenerationDataExtractorTests.cs b/EasySourceGenerators.GeneratorTests/BodyGenerationDataExtractorTests.cs new file mode 100644 index 0000000..da242a2 --- /dev/null +++ b/EasySourceGenerators.GeneratorTests/BodyGenerationDataExtractorTests.cs @@ -0,0 +1,128 @@ +using EasySourceGenerators.Generators.DataBuilding; +using EasySourceGenerators.Generators.IncrementalGenerators; + +namespace EasySourceGenerators.GeneratorTests; + +[TestFixture] +public class BodyGenerationDataExtractorTests +{ + [Test] + public void Extract_WithCompileTimeConstants_BodyReturningConstant_InvokesWithConstants() + { + DataMethodBodyGenerator generator = new DataMethodBodyGenerator( + new BodyGenerationData( + ReturnType: typeof(string), + ParametersTypes: [], + CompileTimeConstants: 42, + ReturnConstantValueFactory: (Func)(constants => $"value_{constants}"))); + + FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, false); + + Assert.That(result.ReturnValue, Is.EqualTo("value_42")); + Assert.That(result.IsVoid, Is.False); + } + + [Test] + public void Extract_WithCompileTimeConstants_RuntimeBodyNoArgs_InvokesWithConstants() + { + DataMethodBodyGenerator generator = new DataMethodBodyGenerator( + new BodyGenerationData( + ReturnType: typeof(string), + ParametersTypes: [], + CompileTimeConstants: 10, + RuntimeDelegateBody: (Func)(constants => $"body_{constants}"))); + + FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, false); + + Assert.That(result.ReturnValue, Is.EqualTo("body_10")); + Assert.That(result.IsVoid, Is.False); + } + + [Test] + public void Extract_WithCompileTimeConstants_RuntimeBodyWithAdditionalParams_ReturnsNullValue() + { + DataMethodBodyGenerator generator = new DataMethodBodyGenerator( + new BodyGenerationData( + ReturnType: typeof(string), + ParametersTypes: [typeof(int)], + CompileTimeConstants: 10, + RuntimeDelegateBody: (Func)((constants, param) => $"{constants}_{param}"))); + + FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, false); + + Assert.That(result.ReturnValue, Is.Null); + Assert.That(result.IsVoid, Is.False); + } + + [Test] + public void Extract_WithoutConstants_BodyReturningConstant_InvokesWithoutArgs() + { + DataMethodBodyGenerator generator = new DataMethodBodyGenerator( + new BodyGenerationData( + ReturnType: typeof(int), + ParametersTypes: [], + ReturnConstantValueFactory: (Func)(() => 99))); + + FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, false); + + Assert.That(result.ReturnValue, Is.EqualTo("99")); + } + + [Test] + public void Extract_WithoutConstants_RuntimeBodyNoParams_InvokesDirectly() + { + DataMethodBodyGenerator generator = new DataMethodBodyGenerator( + new BodyGenerationData( + ReturnType: typeof(string), + ParametersTypes: [], + RuntimeDelegateBody: (Func)(() => "hello"))); + + FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, false); + + Assert.That(result.ReturnValue, Is.EqualTo("hello")); + } + + [Test] + public void Extract_VoidReturnType_WithConstants_RuntimeBody_InvokesWithConstants() + { + string captured = ""; + DataMethodBodyGenerator generator = new DataMethodBodyGenerator( + new BodyGenerationData( + ReturnType: typeof(void), + ParametersTypes: [], + CompileTimeConstants: "test", + RuntimeDelegateBody: (Action)(constants => { captured = constants; }))); + + FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, true); + + Assert.That(result.IsVoid, Is.True); + Assert.That(captured, Is.EqualTo("test")); + } + + [Test] + public void Extract_NullBodyGenerationData_ReturnsNullReturnValue() + { + DataMethodBodyGenerator generator = new DataMethodBodyGenerator( + new BodyGenerationData(ReturnType: typeof(string))); + + FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, false); + + Assert.That(result.ReturnValue, Is.Null); + } + + [Test] + public void Extract_WithCompileTimeConstants_ConstantFactoryTakesPriority() + { + DataMethodBodyGenerator generator = new DataMethodBodyGenerator( + new BodyGenerationData( + ReturnType: typeof(string), + ParametersTypes: [], + CompileTimeConstants: 5, + ReturnConstantValueFactory: (Func)(constants => $"factory_{constants}"), + RuntimeDelegateBody: (Func)(constants => $"body_{constants}"))); + + FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, false); + + Assert.That(result.ReturnValue, Is.EqualTo("factory_5")); + } +} diff --git a/EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs b/EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs index a1ad3ed..04486cd 100644 --- a/EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs +++ b/EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs @@ -189,4 +189,198 @@ public void FullFluentChain_UseProvidedBody_ProducesCorrectData() object? bodyValue = generator.Data.RuntimeDelegateBody!.DynamicInvoke(); Assert.That(bodyValue, Is.EqualTo(42)); } + + [Test] + public void WithCompileTimeConstants_WithParam_ReturnsStage5WithConstants() + { + DataMethodBodyBuilderStage4 stage4 = new DataMethodBodyBuilderStage4( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [typeof(int)])); + + IMethodBodyBuilderStage5WithConstants result = stage4.WithCompileTimeConstants(() => 42); + + Assert.That(result, Is.TypeOf>()); + DataMethodBodyBuilderStage5WithConstants stage5 = (DataMethodBodyBuilderStage5WithConstants)result; + Assert.That(stage5.Data.CompileTimeConstants, Is.EqualTo(42)); + } + + [Test] + public void WithCompileTimeConstants_NoArg_ReturnsStage5NoArgWithConstants() + { + DataMethodBodyBuilderStage4NoArg stage4 = new DataMethodBodyBuilderStage4NoArg( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [])); + + IMethodBodyBuilderStage5NoArgWithConstants result = stage4.WithCompileTimeConstants(() => 99); + + Assert.That(result, Is.TypeOf>()); + DataMethodBodyBuilderStage5NoArgWithConstants stage5 = (DataMethodBodyBuilderStage5NoArgWithConstants)result; + Assert.That(stage5.Data.CompileTimeConstants, Is.EqualTo(99)); + } + + [Test] + public void WithCompileTimeConstants_ReturnVoid_ReturnsStage5ReturnVoidWithConstants() + { + DataMethodBodyBuilderStage4ReturnVoid stage4 = new DataMethodBodyBuilderStage4ReturnVoid( + new BodyGenerationData(ReturnType: typeof(void), ParametersTypes: [typeof(int)])); + + IMethodBodyBuilderStage5ReturnVoidWithConstants result = stage4.WithCompileTimeConstants(() => "test"); + + Assert.That(result, Is.TypeOf>()); + DataMethodBodyBuilderStage5ReturnVoidWithConstants stage5 = (DataMethodBodyBuilderStage5ReturnVoidWithConstants)result; + Assert.That(stage5.Data.CompileTimeConstants, Is.EqualTo("test")); + } + + [Test] + public void WithCompileTimeConstants_ReturnVoidNoArg_ReturnsStage5ReturnVoidNoArgWithConstants() + { + DataMethodBodyBuilderStage4ReturnVoidNoArg stage4 = new DataMethodBodyBuilderStage4ReturnVoidNoArg( + new BodyGenerationData(ReturnType: typeof(void), ParametersTypes: [])); + + IMethodBodyBuilderStage5ReturnVoidNoArgWithConstants result = stage4.WithCompileTimeConstants(() => 7); + + Assert.That(result, Is.TypeOf>()); + DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants stage5 = (DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants)result; + Assert.That(stage5.Data.CompileTimeConstants, Is.EqualTo(7)); + } + + [Test] + public void Stage5WithConstants_UseProvidedBody_SetsRuntimeDelegateBody() + { + DataMethodBodyBuilderStage5WithConstants stage5 = new DataMethodBodyBuilderStage5WithConstants( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [typeof(int)], CompileTimeConstants: 42)); + + IMethodBodyGenerator result = stage5.UseProvidedBody((constants, param) => $"{constants}_{param}"); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + Assert.That(generator.Data.CompileTimeConstants, Is.EqualTo(42)); + } + + [Test] + public void Stage5WithConstants_BodyReturningConstant_SetsReturnConstantValueFactory() + { + DataMethodBodyBuilderStage5WithConstants stage5 = new DataMethodBodyBuilderStage5WithConstants( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [typeof(int)], CompileTimeConstants: 42)); + + IMethodBodyGenerator result = stage5.BodyReturningConstant(constants => $"value_{constants}"); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnConstantValueFactory, Is.Not.Null); + Assert.That(generator.Data.CompileTimeConstants, Is.EqualTo(42)); + } + + [Test] + public void Stage5NoArgWithConstants_UseProvidedBody_SetsRuntimeDelegateBody() + { + DataMethodBodyBuilderStage5NoArgWithConstants stage5 = new DataMethodBodyBuilderStage5NoArgWithConstants( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [], CompileTimeConstants: 10)); + + IMethodBodyGenerator result = stage5.UseProvidedBody(constants => $"value_{constants}"); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + Assert.That(generator.Data.CompileTimeConstants, Is.EqualTo(10)); + } + + [Test] + public void Stage5NoArgWithConstants_BodyReturningConstant_SetsReturnConstantValueFactory() + { + DataMethodBodyBuilderStage5NoArgWithConstants stage5 = new DataMethodBodyBuilderStage5NoArgWithConstants( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [], CompileTimeConstants: 10)); + + IMethodBodyGenerator result = stage5.BodyReturningConstant(constants => $"const_{constants}"); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnConstantValueFactory, Is.Not.Null); + Assert.That(generator.Data.CompileTimeConstants, Is.EqualTo(10)); + } + + [Test] + public void Stage5ReturnVoidWithConstants_UseProvidedBody_SetsRuntimeDelegateBody() + { + DataMethodBodyBuilderStage5ReturnVoidWithConstants stage5 = new DataMethodBodyBuilderStage5ReturnVoidWithConstants( + new BodyGenerationData(ReturnType: typeof(void), ParametersTypes: [typeof(int)], CompileTimeConstants: "ctx")); + + IMethodBodyGenerator result = stage5.UseProvidedBody((constants, param) => { }); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + Assert.That(generator.Data.CompileTimeConstants, Is.EqualTo("ctx")); + } + + [Test] + public void Stage5ReturnVoidNoArgWithConstants_UseProvidedBody_SetsRuntimeDelegateBody() + { + DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants stage5 = new DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants( + new BodyGenerationData(ReturnType: typeof(void), ParametersTypes: [], CompileTimeConstants: "ctx")); + + IMethodBodyGenerator result = stage5.UseProvidedBody(constants => { }); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + Assert.That(generator.Data.CompileTimeConstants, Is.EqualTo("ctx")); + } + + [Test] + public void FullFluentChain_WithConstants_NoArg_BodyReturningConstant_ProducesCorrectData() + { + DataGeneratorsFactory factory = new DataGeneratorsFactory(); + + IMethodBodyGenerator result = factory.StartFluentApiBuilderForBody() + .ForMethod() + .WithReturnType() + .WithNoParameters() + .WithCompileTimeConstants(() => 42) + .BodyReturningConstant(constants => $"value_{constants}"); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnType, Is.EqualTo(typeof(string))); + Assert.That(generator.Data.ParametersTypes, Is.Empty); + Assert.That(generator.Data.CompileTimeConstants, Is.EqualTo(42)); + Assert.That(generator.Data.ReturnConstantValueFactory, Is.Not.Null); + object? constantValue = generator.Data.ReturnConstantValueFactory!.DynamicInvoke(42); + Assert.That(constantValue, Is.EqualTo("value_42")); + } + + [Test] + public void FullFluentChain_WithConstants_WithParam_UseProvidedBody_ProducesCorrectData() + { + DataGeneratorsFactory factory = new DataGeneratorsFactory(); + + IMethodBodyGenerator result = factory.StartFluentApiBuilderForBody() + .ForMethod() + .WithReturnType() + .WithParameter() + .WithCompileTimeConstants(() => new { Offset = 100 }) + .UseProvidedBody((constants, param) => constants.Offset + param); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnType, Is.EqualTo(typeof(int))); + Assert.That(generator.Data.ParametersTypes, Is.EqualTo(new[] { typeof(int) })); + Assert.That(generator.Data.CompileTimeConstants, Is.Not.Null); + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + } + + [Test] + public void WithCompileTimeConstants_FactoryIsInvokedImmediately() + { + int invocationCount = 0; + DataMethodBodyBuilderStage4NoArg stage4 = new DataMethodBodyBuilderStage4NoArg( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [])); + + stage4.WithCompileTimeConstants(() => + { + invocationCount++; + return 42; + }); + + Assert.That(invocationCount, Is.EqualTo(1)); + } } diff --git a/EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs b/EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs index cef2554..02e3695 100644 --- a/EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs +++ b/EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs @@ -38,6 +38,9 @@ public IMethodBodyBuilderStage4 WithParameter() => public record DataMethodBodyBuilderStage4(BodyGenerationData Data) : IMethodBodyBuilderStage4 { + public IMethodBodyBuilderStage5WithConstants WithCompileTimeConstants(Func compileTimeConstantsFactory) => + new DataMethodBodyBuilderStage5WithConstants(Data with { CompileTimeConstants = compileTimeConstantsFactory() }); + public IMethodBodyGenerator UseProvidedBody(Func body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body }); public IMethodBodyGenerator BodyReturningConstant(Func constantValueFactory) => @@ -46,6 +49,9 @@ public IMethodBodyGenerator BodyReturningConstant(Func constantValu public record DataMethodBodyBuilderStage4NoArg(BodyGenerationData Data) : IMethodBodyBuilderStage4NoArg { + public IMethodBodyBuilderStage5NoArgWithConstants WithCompileTimeConstants(Func compileTimeConstantsFactory) => + new DataMethodBodyBuilderStage5NoArgWithConstants(Data with { CompileTimeConstants = compileTimeConstantsFactory() }); + public IMethodBodyGenerator UseProvidedBody(Func body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body }); public IMethodBodyGenerator BodyReturningConstant(Func constantValueFactory) => @@ -54,10 +60,42 @@ public IMethodBodyGenerator BodyReturningConstant(Func constantValu public record DataMethodBodyBuilderStage4ReturnVoid(BodyGenerationData BodyGenerationData) : IMethodBodyBuilderStage4ReturnVoid { + public IMethodBodyBuilderStage5ReturnVoidWithConstants WithCompileTimeConstants(Func compileTimeConstantsFactory) => + new DataMethodBodyBuilderStage5ReturnVoidWithConstants(BodyGenerationData with { CompileTimeConstants = compileTimeConstantsFactory() }); + public IMethodBodyGenerator UseProvidedBody(Action body) => new DataMethodBodyGenerator(BodyGenerationData with { RuntimeDelegateBody = body }); } public record DataMethodBodyBuilderStage4ReturnVoidNoArg(BodyGenerationData BodyGenerationData) : IMethodBodyBuilderStage4ReturnVoidNoArg { + public IMethodBodyBuilderStage5ReturnVoidNoArgWithConstants WithCompileTimeConstants(Func compileTimeConstantsFactory) => + new DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants(BodyGenerationData with { CompileTimeConstants = compileTimeConstantsFactory() }); + public IMethodBodyGenerator UseProvidedBody(Action body) => new DataMethodBodyGenerator(BodyGenerationData with { RuntimeDelegateBody = body }); +} + +public record DataMethodBodyBuilderStage5WithConstants(BodyGenerationData Data) : IMethodBodyBuilderStage5WithConstants +{ + public IMethodBodyGenerator UseProvidedBody(Func body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body }); + + public IMethodBodyGenerator BodyReturningConstant(Func constantValueFactory) => + new DataMethodBodyGenerator(Data with { ReturnConstantValueFactory = constantValueFactory }); +} + +public record DataMethodBodyBuilderStage5NoArgWithConstants(BodyGenerationData Data) : IMethodBodyBuilderStage5NoArgWithConstants +{ + public IMethodBodyGenerator UseProvidedBody(Func body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body }); + + public IMethodBodyGenerator BodyReturningConstant(Func constantValueFactory) => + new DataMethodBodyGenerator(Data with { ReturnConstantValueFactory = constantValueFactory }); +} + +public record DataMethodBodyBuilderStage5ReturnVoidWithConstants(BodyGenerationData Data) : IMethodBodyBuilderStage5ReturnVoidWithConstants +{ + public IMethodBodyGenerator UseProvidedBody(Action body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body }); +} + +public record DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants(BodyGenerationData Data) : IMethodBodyBuilderStage5ReturnVoidNoArgWithConstants +{ + public IMethodBodyGenerator UseProvidedBody(Action body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body }); } \ No newline at end of file diff --git a/EasySourceGenerators.Generators/IncrementalGenerators/BodyGenerationDataExtractor.cs b/EasySourceGenerators.Generators/IncrementalGenerators/BodyGenerationDataExtractor.cs index 9130a7a..d45328b 100644 --- a/EasySourceGenerators.Generators/IncrementalGenerators/BodyGenerationDataExtractor.cs +++ b/EasySourceGenerators.Generators/IncrementalGenerators/BodyGenerationDataExtractor.cs @@ -40,18 +40,32 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT Type? dataReturnType = returnTypeProperty?.GetValue(bodyGenerationData) as Type; bool isVoid = dataReturnType == typeof(void); - return TryExtractFromConstantFactory(dataType, bodyGenerationData, isVoid) - ?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid) + object? compileTimeConstants = GetCompileTimeConstants(dataType, bodyGenerationData); + + return TryExtractFromConstantFactory(dataType, bodyGenerationData, isVoid, compileTimeConstants) + ?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid, compileTimeConstants) ?? new FluentBodyResult(null, isVoid); } + /// + /// Retrieves the CompileTimeConstants value from the body generation data, if present. + /// + private static object? GetCompileTimeConstants(Type dataType, object bodyGenerationData) + { + PropertyInfo? constantsProperty = dataType.GetProperty("CompileTimeConstants"); + return constantsProperty?.GetValue(bodyGenerationData); + } + /// /// Attempts to extract a return value by invoking the ReturnConstantValueFactory delegate. + /// If is provided and the factory accepts a parameter, + /// the constants are passed as the first argument. /// private static FluentBodyResult? TryExtractFromConstantFactory( Type dataType, object bodyGenerationData, - bool isVoid) + bool isVoid, + object? compileTimeConstants) { PropertyInfo? constantFactoryProperty = dataType.GetProperty("ReturnConstantValueFactory"); Delegate? constantFactory = constantFactoryProperty?.GetValue(bodyGenerationData) as Delegate; @@ -60,19 +74,37 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT return null; } - object? constantValue = constantFactory.DynamicInvoke(); + ParameterInfo[] factoryParams = constantFactory.Method.GetParameters(); + object? constantValue; + + if (factoryParams.Length == 1 && compileTimeConstants != null) + { + constantValue = constantFactory.DynamicInvoke(compileTimeConstants); + } + else if (factoryParams.Length == 0) + { + constantValue = constantFactory.DynamicInvoke(); + } + else + { + return null; + } + return new FluentBodyResult(constantValue?.ToString(), isVoid); } /// /// Attempts to extract a return value by invoking the RuntimeDelegateBody delegate. - /// Only invokes delegates with zero parameters; parameterized delegates cannot be executed - /// at compile time without concrete values. + /// If the delegate has no parameters, it is invoked directly. + /// If is provided and the delegate has exactly one parameter + /// (the constants), it is invoked with the constants. Delegates with additional parameters + /// (method parameters) cannot be executed at compile time without concrete values. /// private static FluentBodyResult? TryExtractFromRuntimeBody( Type dataType, object bodyGenerationData, - bool isVoid) + bool isVoid, + object? compileTimeConstants) { PropertyInfo? runtimeBodyProperty = dataType.GetProperty("RuntimeDelegateBody"); Delegate? runtimeBody = runtimeBodyProperty?.GetValue(bodyGenerationData) as Delegate; @@ -88,7 +120,13 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT return new FluentBodyResult(bodyResult?.ToString(), isVoid); } - // For delegates with parameters, we can't invoke at compile time without values + if (bodyParams.Length == 1 && compileTimeConstants != null) + { + object? bodyResult = runtimeBody.DynamicInvoke(compileTimeConstants); + return new FluentBodyResult(bodyResult?.ToString(), isVoid); + } + + // For delegates with additional parameters, we can't invoke at compile time without values return new FluentBodyResult(null, isVoid); } } From 062a992014bf23005084fa6c02b2e2c8f2f2c493 Mon Sep 17 00:00:00 2001 From: "mateusz.krzaczek" Date: Thu, 2 Apr 2026 14:41:23 +0200 Subject: [PATCH 3/3] Fixed --- .../EasySourceGenerators.Tests.csproj | 1 + .../PiExampleFluentTests.cs | 179 ------------------ EasySourceGenerators.Tests/PiExampleTests.cs | 69 +++---- EasySourceGenerators.Tests/SlowMath.cs | 20 ++ 4 files changed, 47 insertions(+), 222 deletions(-) delete mode 100644 EasySourceGenerators.Tests/PiExampleFluentTests.cs create mode 100644 EasySourceGenerators.Tests/SlowMath.cs diff --git a/EasySourceGenerators.Tests/EasySourceGenerators.Tests.csproj b/EasySourceGenerators.Tests/EasySourceGenerators.Tests.csproj index 5e00169..7475233 100644 --- a/EasySourceGenerators.Tests/EasySourceGenerators.Tests.csproj +++ b/EasySourceGenerators.Tests/EasySourceGenerators.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/EasySourceGenerators.Tests/PiExampleFluentTests.cs b/EasySourceGenerators.Tests/PiExampleFluentTests.cs deleted file mode 100644 index c86b859..0000000 --- a/EasySourceGenerators.Tests/PiExampleFluentTests.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Fluent switch body generation is commented out pending replacement with a data-driven approach. -// Explicit SwitchCase statements will be replaced in the future. -// See DataMethodBodyBuilders.cs for details on the planned replacement. - -/* -using EasySourceGenerators.Abstractions; -// ReSharper disable ConvertClosureToMethodGroup - -namespace EasySourceGenerators.Tests; - -[TestFixture] -public class PiExampleFluentTests -{ - [TestCase(0, 3)] - [TestCase(1, 1)] - [TestCase(2, 4)] - [TestCase(300, 3)] - [TestCase(301, 7)] - [TestCase(302, 2)] - [TestCase(303, 4)] - [TestCase(5, 9)] - public void PiExampleFluentLikeGenerator_ProducesExpectedRuntimeOutput(int decimalNumber, int expectedDigit) - { - int result = TestPiFluentClass.GetPiDecimal(decimalNumber); - - Assert.That(result, Is.EqualTo(expectedDigit)); - } - - [Test] - public void PiExampleFluentLikeGenerator_ProducesExpectedGeneratedCode() - { - string generatedCode = GeneratedCodeTestHelper.ReadGeneratedCode("TestPiFluentClass_GetPiDecimal.g.cs"); - string expectedCode = """ - namespace EasySourceGenerators.Tests; - - static partial class TestPiFluentClass - { - public static partial int GetPiDecimal(int decimalNumber) - { - switch (decimalNumber) - { - case 0: return 3; - case 1: return 1; - case 2: return 4; - case 300: return 3; - case 301: return 7; - case 302: return 2; - case 303: return 4; - default: return TestSlowMath.CalculatePiDecimal(decimalNumber); - } - } - } - """.ReplaceLineEndings("\n").TrimEnd(); - - Assert.That(generatedCode, Is.EqualTo(expectedCode)); - } - - [TestCase(1, "Dog")] - [TestCase(2, "Cat")] - [TestCase(3, "Unknown")] - public void MapperFluentLikeGenerator_ProducesExpectedRuntimeOutput(int source, string expected) - { - string result = TestMapperFluent.MapToMammal(source); - - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void MapperFluentLikeGenerator_ProducesExpectedGeneratedCode() - { - string generatedCode = GeneratedCodeTestHelper.ReadGeneratedCode("TestMapperFluent_MapToMammal.g.cs"); - string expectedCode = """ - namespace EasySourceGenerators.Tests; - - static partial class TestMapperFluent - { - public static partial string MapToMammal(int animalCode) - { - switch (animalCode) - { - case 1: return "Dog"; - case 2: return "Cat"; - default: return "Unknown"; - } - } - } - """.ReplaceLineEndings("\n").TrimEnd(); - - Assert.That(generatedCode, Is.EqualTo(expectedCode)); - } - [TestCase(TestFourLeggedAnimal.Dog, TestMammalAnimal.Dog)] - [TestCase(TestFourLeggedAnimal.Cat, TestMammalAnimal.Cat)] - public void MapperFluentEnumGenerator_ProducesExpectedRuntimeOutput(TestFourLeggedAnimal source, TestMammalAnimal expected) - { - TestMammalAnimal result = TestMapperFluentEnum.MapToMammal(source); - - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void MapperFluentEnumGenerator_ThrowsForUnmappableValue() - { - Assert.Throws(() => TestMapperFluentEnum.MapToMammal(TestFourLeggedAnimal.Lizard)); - } - - [Test] - public void MapperFluentEnumGenerator_ProducesExpectedGeneratedCode() - { - string generatedCode = GeneratedCodeTestHelper.ReadGeneratedCode("TestMapperFluentEnum_MapToMammal.g.cs"); - string expectedCode = """ - namespace EasySourceGenerators.Tests; - - static partial class TestMapperFluentEnum - { - public static partial EasySourceGenerators.Tests.TestMammalAnimal MapToMammal(EasySourceGenerators.Tests.TestFourLeggedAnimal fourLeggedAnimal) - { - switch (fourLeggedAnimal) - { - case EasySourceGenerators.Tests.TestFourLeggedAnimal.Dog: return EasySourceGenerators.Tests.TestMammalAnimal.Dog; - case EasySourceGenerators.Tests.TestFourLeggedAnimal.Cat: return EasySourceGenerators.Tests.TestMammalAnimal.Cat; - default: throw new ArgumentException($"Cannot map {fourLeggedAnimal} to a mammal"); - } - } - } - """.ReplaceLineEndings("\n").TrimEnd(); - - Assert.That(generatedCode, Is.EqualTo(expectedCode)); - } -} - -public enum TestFourLeggedAnimal { Dog, Cat, Lizard } - -public enum TestMammalAnimal { Dog, Cat } - -public static partial class TestMapperFluentEnum -{ - public static partial TestMammalAnimal MapToMammal(TestFourLeggedAnimal fourLeggedAnimal); - - [MethodBodyGenerator(nameof(MapToMammal))] - static IMethodBodyGenerator MapToMammal_Generator() => - Generate - .MethodBody().WithParameter().WithReturnType() - .GenerateSwitchBody() - .ForCases(GetFourLeggedAnimalsThatAreAlsoMammal()).ReturnConstantValue(a => Enum.Parse(a.ToString(), true)) - .ForDefaultCase().UseBody(fourLeggedAnimal => () => throw new ArgumentException($"Cannot map {fourLeggedAnimal} to a mammal")); - - static TestFourLeggedAnimal[] GetFourLeggedAnimalsThatAreAlsoMammal() => - Enum.GetValues() - .Where(a => Enum.TryParse(typeof(TestMammalAnimal), a.ToString(), true, out _)) - .ToArray(); -} - -public static partial class TestPiFluentClass -{ - public static partial int GetPiDecimal(int decimalNumber); - - [MethodBodyGenerator(nameof(GetPiDecimal))] - static IMethodBodyGenerator GetPiDecimal_Generator() => - Generate - .MethodBody().WithParameter().WithReturnType() - .GenerateSwitchBody() - .ForCases(0, 1, 2, new[]{300, 301, 302, 303}).ReturnConstantValue(decimalNumber => TestSlowMath.CalculatePiDecimal(decimalNumber)) - .ForDefaultCase().UseBody(decimalNumber => () => TestSlowMath.CalculatePiDecimal(decimalNumber)); -} - -public static partial class TestMapperFluent -{ - public static partial string MapToMammal(int animalCode); - - [MethodBodyGenerator(nameof(MapToMammal))] - static IMethodBodyGenerator MapToMammal_Generator() => - Generate - .MethodBody().WithParameter().WithReturnType() - .GenerateSwitchBody() - .ForCases(1).ReturnConstantValue(_ => "Dog") - .ForCases(2).ReturnConstantValue(_ => "Cat") - .ForDefaultCase().UseBody(_ => () => "Unknown"); -} -*/ diff --git a/EasySourceGenerators.Tests/PiExampleTests.cs b/EasySourceGenerators.Tests/PiExampleTests.cs index 435a55a..c39440d 100644 --- a/EasySourceGenerators.Tests/PiExampleTests.cs +++ b/EasySourceGenerators.Tests/PiExampleTests.cs @@ -1,52 +1,31 @@ -// SwitchCase/SwitchDefault attribute-based generation is commented out pending replacement with a data-driven approach. -// See DataMethodBodyBuilders.cs for details on the planned replacement. - -/* using EasySourceGenerators.Abstractions; -// ReSharper disable ConvertClosureToMethodGroup namespace EasySourceGenerators.Tests; [TestFixture] public class PiExampleTests { - [Test] - public void SwitchCaseAttribute_StoresArg1Value() - { - SwitchCase switchCase = new(arg1: 7); - - Assert.That(switchCase.Arg1, Is.EqualTo(7)); - } - - [TestCase(0, 3)] - [TestCase(1, 1)] - [TestCase(2, 4)] - [TestCase(5, 9)] - public void PiExampleLikeGenerator_ProducesExpectedRuntimeOutput(int decimalNumber, int expectedDigit) - { - int result = TestPiClass.GetPiDecimal(decimalNumber); - - Assert.That(result, Is.EqualTo(expectedDigit)); - } - [Test] public void PiExampleLikeGenerator_ProducesExpectedGeneratedCode() { - string generatedCode = GeneratedCodeTestHelper.ReadGeneratedCode("TestPiClass_GetPiDecimal.g.cs"); + string generatedCode = GeneratedCodeTestHelper.ReadGeneratedCode("TestPiExampleFluent_GetPiDecimal.g.cs"); + + //TODO: The specifics here, like the formatting or where the "constants" is declared might be off here, but the general idea must be working string expectedCode = """ namespace EasySourceGenerators.Tests; - static partial class TestPiClass + static partial class TestPiExampleFluent { public static partial int GetPiDecimal(int decimalNumber) { - switch (decimalNumber) + var constants = new { - case 0: return 3; - case 1: return 1; - case 2: return 4; - default: return TestSlowMath.CalculatePiDecimal(decimalNumber); - } + PrecomputedTargets = (new int[] { 0, 1, 2, 300, 301, 302, 303 }).ToDictionary(i => i, i => SlowMath.CalculatePiDecimal(i)) + }; + + if (constants.PrecomputedTargets.TryGetValue(decimalNumber, out int precomputedResult)) return precomputedResult; + + return SlowMath.CalculatePiDecimal(decimalNumber); } } """.ReplaceLineEndings("\n").TrimEnd(); @@ -55,19 +34,23 @@ public static partial int GetPiDecimal(int decimalNumber) } } -public static partial class TestPiClass +public static partial class TestPiExampleFluent { public static partial int GetPiDecimal(int decimalNumber); [MethodBodyGenerator(nameof(GetPiDecimal))] - [SwitchCase(arg1: 0)] - [SwitchCase(arg1: 1)] - [SwitchCase(arg1: 2)] - static int GetPiDecimal_Generator_Specialized(int decimalNumber) => - TestSlowMath.CalculatePiDecimal(decimalNumber); - - [MethodBodyGenerator(nameof(GetPiDecimal))] - [SwitchDefault] - static Func GetPiDecimal_Generator_Fallback() => decimalNumber => TestSlowMath.CalculatePiDecimal(decimalNumber); + static IMethodBodyGenerator GetPiDecimal_Generator() => + Generate.MethodBody() + .ForMethod().WithReturnType().WithParameter() + .WithCompileTimeConstants(() => new + { + PrecomputedTargets = (new int[] { 0, 1, 2, 300, 301, 302, 303 }).ToDictionary(i => i, i => SlowMath.CalculatePiDecimal(i)) + }) + .UseProvidedBody((constants, decimalNumber) => + { + if (constants.PrecomputedTargets.TryGetValue(decimalNumber, out int precomputedResult)) return precomputedResult; + + return SlowMath.CalculatePiDecimal(decimalNumber); + }); } -*/ + diff --git a/EasySourceGenerators.Tests/SlowMath.cs b/EasySourceGenerators.Tests/SlowMath.cs new file mode 100644 index 0000000..6f6aa0f --- /dev/null +++ b/EasySourceGenerators.Tests/SlowMath.cs @@ -0,0 +1,20 @@ +using System.Globalization; +using ExtendedNumerics; + +namespace EasySourceGenerators.Tests; + +public static class SlowMath +{ + public static int CalculatePiDecimal(int decimalNumber) + { + if (decimalNumber < 0) throw new ArgumentOutOfRangeException(nameof(decimalNumber), "Decimal number must be non-negative."); + if (decimalNumber == 0) return 3; + + BigDecimal pi = BigDecimal.ApproximatePi(decimalNumber + 1); + + if(pi.DecimalPlaces < decimalNumber) throw new ArgumentException($"Failed to calculate pi to {decimalNumber} decimal places."); + + string piString = pi.ToString(CultureInfo.InvariantCulture); + return int.Parse(piString.Substring(decimalNumber + 1, 1)); + } +} \ No newline at end of file