Implement WithCompileTimeConstants for fluent method body builders#98
Implement WithCompileTimeConstants for fluent method body builders#98Copilot wants to merge 5 commits intofeature/metehod_templatefrom
Conversation
…age5 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
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
|
@copilot can you resolve conflits with |
…stants-implementation Resolve conflict in BodyGenerationDataExtractor.cs by combining both: - HasDelegateBody tracking from base branch - CompileTimeConstants support from this branch When compile-time constants are present, HasDelegateBody is set to false because the delegate body references a 'constants' parameter that doesn't exist in the generated method. Co-authored-by: dex3r <3155725+dex3r@users.noreply.github.com>
Resolved conflicts with |
…letimeconstants-implementation
There was a problem hiding this comment.
Pull request overview
Implements the missing WithCompileTimeConstants support in the fluent method-body builder pipeline by flowing compile-time constants through BodyGenerationData and updating extraction logic so constants can be passed into constant factories / delegates during generator-time evaluation.
Changes:
- Added stage-4 → stage-5 fluent builder tests for
WithCompileTimeConstantsand verified constants are stored onBodyGenerationData. - Introduced
BodyGenerationDataExtractorsupport for readingCompileTimeConstantsand invoking eligible delegates/factories with constants. - Added focused unit tests for extraction behavior with/without constants, including factory-vs-runtime precedence.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs | Adds builder wiring/data-flow tests for WithCompileTimeConstants across the fluent stages. |
| EasySourceGenerators.GeneratorTests/BodyGenerationDataExtractorTests.cs | New test coverage for extractor invocation rules with compile-time constants. |
| EasySourceGenerators.Generators/IncrementalGenerators/BodyGenerationDataExtractor.cs | Extends extraction logic to read CompileTimeConstants and pass them into eligible delegates/factories. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // When compile-time constants are present, the delegate body references a 'constants' parameter | ||
| // that doesn't exist in the generated method, so syntax extraction cannot be used. | ||
| if (compileTimeConstants != null) | ||
| { | ||
| hasDelegateBody = false; | ||
| } | ||
|
|
||
| return TryExtractFromConstantFactory(dataType, bodyGenerationData, isVoid, compileTimeConstants) | ||
| ?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid, hasDelegateBody, compileTimeConstants) | ||
| ?? new FluentBodyResult(null, isVoid, hasDelegateBody); |
There was a problem hiding this comment.
When CompileTimeConstants is non-null, this forces hasDelegateBody=false. For stage5 APIs, RuntimeDelegateBody commonly has signature (constants, methodParam...) (see IMethodBodyBuilderStage5WithConstants), so TryExtractFromRuntimeBody will return null and the pipeline will emit a “simple” method with a default/empty return value, silently ignoring the provided body. Instead of disabling delegate-body extraction, either (1) keep HasDelegateBody=true and implement emitting a constants local initialization in the generated method, or (2) report a diagnostic/error when a constants-aware body cannot be evaluated/extracted so users don’t get a silent default implementation.
| // When compile-time constants are present, the delegate body references a 'constants' parameter | |
| // that doesn't exist in the generated method, so syntax extraction cannot be used. | |
| if (compileTimeConstants != null) | |
| { | |
| hasDelegateBody = false; | |
| } | |
| return TryExtractFromConstantFactory(dataType, bodyGenerationData, isVoid, compileTimeConstants) | |
| ?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid, hasDelegateBody, compileTimeConstants) | |
| ?? new FluentBodyResult(null, isVoid, hasDelegateBody); | |
| var result = | |
| TryExtractFromConstantFactory(dataType, bodyGenerationData, isVoid, compileTimeConstants) | |
| ?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid, hasDelegateBody, compileTimeConstants); | |
| // If compile-time constants and a runtime delegate body are present but we cannot | |
| // extract an implementation, fail fast instead of silently emitting a default body. | |
| if (result == null && compileTimeConstants != null && hasDelegateBody) | |
| { | |
| throw new InvalidOperationException( | |
| "A constants-aware runtime delegate body was provided but could not be extracted. " + | |
| "Ensure that the body can be evaluated with the supplied compile-time constants."); | |
| } | |
| return result ?? new FluentBodyResult(null, isVoid, hasDelegateBody); |
| public void Extract_WithCompileTimeConstants_RuntimeBodyWithAdditionalParams_ReturnsNullValue() | ||
| { | ||
| DataMethodBodyGenerator generator = new DataMethodBodyGenerator( | ||
| new BodyGenerationData( | ||
| ReturnType: typeof(string), | ||
| ParametersTypes: [typeof(int)], | ||
| CompileTimeConstants: 10, | ||
| RuntimeDelegateBody: (Func<int, int, string>)((constants, param) => $"{constants}_{param}"))); | ||
|
|
||
| FluentBodyResult result = BodyGenerationDataExtractor.Extract(generator, false); | ||
|
|
||
| Assert.That(result.ReturnValue, Is.Null); | ||
| Assert.That(result.IsVoid, Is.False); |
There was a problem hiding this comment.
This test currently asserts that a constants-aware delegate with additional method parameters yields ReturnValue == null. That behavior will cause the generator to emit a default/empty implementation without diagnostics (since HasDelegateBody is also forced false when constants exist). Consider changing the expected behavior to either trigger syntax extraction (HasDelegateBody=true) or to fail with a clear diagnostic so this scenario can’t silently succeed.
| 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); |
There was a problem hiding this comment.
This test validates that the builder stores CompileTimeConstants and RuntimeDelegateBody, but it doesn’t assert that the generator can actually produce a correct method body for the stage5 (constants, param) UseProvidedBody form. Adding an integration-style assertion (e.g., run the execution/runtime extraction path and ensure it doesn’t fall back to a default return value) would prevent regressions where constants-aware bodies are silently ignored.
| Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); | |
| Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); | |
| // Execute the runtime body with the stored constants and a sample parameter | |
| object? constants = generator.Data.CompileTimeConstants; | |
| object? runtimeResult = generator.Data.RuntimeDelegateBody!.DynamicInvoke(constants, 23); | |
| Assert.That(runtimeResult, Is.EqualTo(123)); |
The
WithCompileTimeConstantsAPI had interfaces defined in Abstractions and aCompileTimeConstantsslot onBodyGenerationData, but no builder implementations or extractor support.Stage4 builders → Stage5 transitions
Added
WithCompileTimeConstants<TConstants>(Func<TConstants>)to all four Stage4 builders. The factory is invoked immediately and the materialized result stored inBodyGenerationData.CompileTimeConstants. Returns the corresponding Stage5 builder:DataMethodBodyBuilderStage5WithConstants<TParam1, TReturnType, TConstants>DataMethodBodyBuilderStage5NoArgWithConstants<TReturnType, TConstants>DataMethodBodyBuilderStage5ReturnVoidWithConstants<TParam1, TConstants>DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants<TConstants>BodyGenerationDataExtractor
Updated
TryExtractFromConstantFactoryandTryExtractFromRuntimeBodyto passCompileTimeConstantsas the first argument when the delegate expects a parameter and constants are present. Delegates with additional method parameters beyond constants still correctly return null (can't evaluate at compile time).Usage
Per
PiExampleFluent:Tests
23 new tests: Stage5 builder wiring,
WithCompileTimeConstantsdata flow,BodyGenerationDataExtractorwith/without constants, factory invocation timing, and constant factory priority over runtime body.Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.