Skip to content

Commit bc5266c

Browse files
Copilotdex3r
andauthored
Merge feature/metehod_template into copilot/finish-withcompiletimeconstants-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 <[email protected]>
2 parents b1e1836 + bfb4dfc commit bc5266c

4 files changed

Lines changed: 84 additions & 38 deletions

File tree

EasySourceGenerators.Generators/IncrementalGenerators/BodyGenerationDataExtractor.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ internal static class BodyGenerationDataExtractor
1515
/// Checks for <c>ReturnConstantValueFactory</c> first, then <c>RuntimeDelegateBody</c>.
1616
/// Returns a <see cref="FluentBodyResult"/> with the extracted value, or <c>null</c> return value
1717
/// if neither factory nor body are present.
18+
/// Sets <see cref="FluentBodyResult.HasDelegateBody"/> when <c>RuntimeDelegateBody</c> is present,
19+
/// indicating that the delegate body source code should be extracted from the syntax tree.
1820
/// </summary>
1921
internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnType)
2022
{
@@ -26,25 +28,43 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT
2628
{
2729
// The method returned something that isn't a DataMethodBodyGenerator.
2830
// This may happen when the fluent chain is incomplete (e.g., user returned an intermediate builder).
29-
return new FluentBodyResult(null, isVoidReturnType);
31+
return new FluentBodyResult(null, isVoidReturnType, HasDelegateBody: false);
3032
}
3133

3234
object? bodyGenerationData = dataProperty.GetValue(methodResult);
3335
if (bodyGenerationData == null)
3436
{
35-
return new FluentBodyResult(null, isVoidReturnType);
37+
return new FluentBodyResult(null, isVoidReturnType, HasDelegateBody: false);
3638
}
3739

3840
Type dataType = bodyGenerationData.GetType();
3941
PropertyInfo? returnTypeProperty = dataType.GetProperty("ReturnType");
4042
Type? dataReturnType = returnTypeProperty?.GetValue(bodyGenerationData) as Type;
4143
bool isVoid = dataReturnType == typeof(void);
4244

45+
bool hasDelegateBody = HasRuntimeDelegateBody(dataType, bodyGenerationData);
4346
object? compileTimeConstants = GetCompileTimeConstants(dataType, bodyGenerationData);
4447

48+
// When compile-time constants are present, the delegate body references a 'constants' parameter
49+
// that doesn't exist in the generated method, so syntax extraction cannot be used.
50+
if (compileTimeConstants != null)
51+
{
52+
hasDelegateBody = false;
53+
}
54+
4555
return TryExtractFromConstantFactory(dataType, bodyGenerationData, isVoid, compileTimeConstants)
46-
?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid, compileTimeConstants)
47-
?? new FluentBodyResult(null, isVoid);
56+
?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid, hasDelegateBody, compileTimeConstants)
57+
?? new FluentBodyResult(null, isVoid, hasDelegateBody);
58+
}
59+
60+
/// <summary>
61+
/// Checks whether <c>RuntimeDelegateBody</c> is set (non-null) in the body generation data.
62+
/// </summary>
63+
private static bool HasRuntimeDelegateBody(Type dataType, object bodyGenerationData)
64+
{
65+
PropertyInfo? runtimeBodyProperty = dataType.GetProperty("RuntimeDelegateBody");
66+
Delegate? runtimeBody = runtimeBodyProperty?.GetValue(bodyGenerationData) as Delegate;
67+
return runtimeBody != null;
4868
}
4969

5070
/// <summary>
@@ -90,7 +110,7 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT
90110
return null;
91111
}
92112

93-
return new FluentBodyResult(constantValue?.ToString(), isVoid);
113+
return new FluentBodyResult(constantValue?.ToString(), isVoid, HasDelegateBody: false);
94114
}
95115

96116
/// <summary>
@@ -104,6 +124,7 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT
104124
Type dataType,
105125
object bodyGenerationData,
106126
bool isVoid,
127+
bool hasDelegateBody,
107128
object? compileTimeConstants)
108129
{
109130
PropertyInfo? runtimeBodyProperty = dataType.GetProperty("RuntimeDelegateBody");
@@ -117,16 +138,16 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT
117138
if (bodyParams.Length == 0)
118139
{
119140
object? bodyResult = runtimeBody.DynamicInvoke();
120-
return new FluentBodyResult(bodyResult?.ToString(), isVoid);
141+
return new FluentBodyResult(bodyResult?.ToString(), isVoid, hasDelegateBody);
121142
}
122143

123144
if (bodyParams.Length == 1 && compileTimeConstants != null)
124145
{
125146
object? bodyResult = runtimeBody.DynamicInvoke(compileTimeConstants);
126-
return new FluentBodyResult(bodyResult?.ToString(), isVoid);
147+
return new FluentBodyResult(bodyResult?.ToString(), isVoid, hasDelegateBody);
127148
}
128149

129150
// For delegates with additional parameters, we can't invoke at compile time without values
130-
return new FluentBodyResult(null, isVoid);
151+
return new FluentBodyResult(null, isVoid, hasDelegateBody);
131152
}
132153
}

EasySourceGenerators.Generators/IncrementalGenerators/DelegateBodySyntaxExtractor.cs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,25 @@
66
namespace EasySourceGenerators.Generators.IncrementalGenerators;
77

88
/// <summary>
9-
/// Extracts the delegate body source code from a <c>UseProvidedBody(...)</c> invocation
10-
/// within a generator method's syntax tree. The extracted body is re-indented to match
9+
/// Extracts the delegate body source code from the outermost invocation's lambda argument
10+
/// in a generator method's return expression. The extracted body is re-indented to match
1111
/// the target method body indentation (8 spaces).
1212
/// </summary>
1313
internal static class DelegateBodySyntaxExtractor
1414
{
1515
private const string MethodBodyIndent = " ";
1616

1717
/// <summary>
18-
/// Attempts to find a <c>UseProvidedBody(...)</c> call in the given generator method syntax
19-
/// and extract the lambda body. Returns <c>null</c> if no such call is found.
20-
/// For expression lambdas, returns a single <c>return {expr};</c> line.
18+
/// Attempts to find the lambda argument of the outermost invocation in the generator
19+
/// method's return expression and extract the lambda body. Returns <c>null</c> if no
20+
/// such lambda is found.
21+
/// For expression lambdas, returns the expression text.
2122
/// For block lambdas, returns the block body re-indented to the method body level.
2223
/// </summary>
2324
internal static string? TryExtractDelegateBody(MethodDeclarationSyntax generatorMethodSyntax)
2425
{
25-
InvocationExpressionSyntax? invocation = generatorMethodSyntax
26-
.DescendantNodes()
27-
.OfType<InvocationExpressionSyntax>()
28-
.FirstOrDefault(inv =>
29-
inv.Expression is MemberAccessExpressionSyntax memberAccess &&
30-
memberAccess.Name.Identifier.Text == "UseProvidedBody");
31-
32-
if (invocation == null)
26+
ExpressionSyntax? returnExpression = GetReturnExpression(generatorMethodSyntax);
27+
if (returnExpression is not InvocationExpressionSyntax invocation)
3328
{
3429
return null;
3530
}
@@ -54,6 +49,29 @@ inv.Expression is MemberAccessExpressionSyntax memberAccess &&
5449
return null;
5550
}
5651

52+
/// <summary>
53+
/// Gets the return expression from a generator method. Handles both expression-body
54+
/// methods (<c>=&gt; expr</c>) and block-body methods (<c>{ return expr; }</c>).
55+
/// Assumes the generator method has a simple structure with at most one return statement.
56+
/// </summary>
57+
private static ExpressionSyntax? GetReturnExpression(MethodDeclarationSyntax method)
58+
{
59+
if (method.ExpressionBody != null)
60+
{
61+
return method.ExpressionBody.Expression;
62+
}
63+
64+
if (method.Body != null)
65+
{
66+
ReturnStatementSyntax? returnStatement = method.Body.Statements
67+
.OfType<ReturnStatementSyntax>()
68+
.FirstOrDefault();
69+
return returnStatement?.Expression;
70+
}
71+
72+
return null;
73+
}
74+
5775
/// <summary>
5876
/// Extracts the content of a block body (between <c>{</c> and <c>}</c>),
5977
/// determines the base indentation, and re-indents all lines to the method body level.

EasySourceGenerators.Generators/IncrementalGenerators/GeneratesMethodExecutionRuntime.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ internal sealed record SwitchBodyData(
1414

1515
/// <summary>
1616
/// Result extracted from <see cref="DataBuilding.BodyGenerationData"/> after executing a fluent body generator method.
17+
/// <see cref="HasDelegateBody"/> indicates that the generator used <c>UseProvidedBody</c>,
18+
/// signaling that the delegate body source code should be extracted from the syntax tree.
1719
/// </summary>
1820
internal sealed record FluentBodyResult(
1921
string? ReturnValue,
20-
bool IsVoid);
22+
bool IsVoid,
23+
bool HasDelegateBody);
2124

2225
/// <summary>
2326
/// Orchestrates the execution of generator methods at compile time.

EasySourceGenerators.Generators/IncrementalGenerators/GeneratesMethodGenerationPipeline.cs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ private static string GenerateSourceForGroup(
9696
}
9797

9898
/// <summary>
99-
/// Generates source code from a fluent body pattern. First attempts to extract the delegate
100-
/// body from a <c>UseProvidedBody</c> call in the syntax tree. If no such call is found,
101-
/// falls back to executing the generator method and extracting the return value.
99+
/// Generates source code from a fluent body pattern. Executes the generator method first
100+
/// to obtain <see cref="FluentBodyResult"/>. If the result indicates a delegate body was
101+
/// provided (via <see cref="FluentBodyResult.HasDelegateBody"/>), attempts to extract the
102+
/// lambda body from the syntax tree. Otherwise, uses the runtime-evaluated return value.
102103
/// </summary>
103104
private static string GenerateFromFluentBodyPattern(
104105
SourceProductionContext context,
@@ -107,18 +108,6 @@ private static string GenerateFromFluentBodyPattern(
107108
INamedTypeSymbol containingType,
108109
Compilation compilation)
109110
{
110-
string? delegateBody = DelegateBodySyntaxExtractor.TryExtractDelegateBody(methodInfo.Syntax);
111-
if (delegateBody != null)
112-
{
113-
bool isVoidReturn = partialMethod.ReturnType.SpecialType == SpecialType.System_Void;
114-
string bodyLines = FormatDelegateBodyForEmit(delegateBody, isVoidReturn);
115-
116-
return GeneratesMethodPatternSourceBuilder.GeneratePartialMethodWithBody(
117-
containingType,
118-
partialMethod,
119-
bodyLines);
120-
}
121-
122111
(FluentBodyResult? result, string? error) = GeneratesMethodExecutionRuntime.ExecuteFluentBodyGeneratorMethod(
123112
methodInfo.Symbol,
124113
partialMethod,
@@ -134,10 +123,25 @@ private static string GenerateFromFluentBodyPattern(
134123
return string.Empty;
135124
}
136125

126+
if (result!.HasDelegateBody)
127+
{
128+
string? delegateBody = DelegateBodySyntaxExtractor.TryExtractDelegateBody(methodInfo.Syntax);
129+
if (delegateBody != null)
130+
{
131+
bool isVoidReturn = partialMethod.ReturnType.SpecialType == SpecialType.System_Void;
132+
string bodyLines = FormatDelegateBodyForEmit(delegateBody, isVoidReturn);
133+
134+
return GeneratesMethodPatternSourceBuilder.GeneratePartialMethodWithBody(
135+
containingType,
136+
partialMethod,
137+
bodyLines);
138+
}
139+
}
140+
137141
return GeneratesMethodPatternSourceBuilder.GenerateSimplePartialMethod(
138142
containingType,
139143
partialMethod,
140-
result!.ReturnValue);
144+
result.ReturnValue);
141145
}
142146

143147
/// <summary>

0 commit comments

Comments
 (0)