Skip to content

Commit 5420b05

Browse files
Copilotdex3r
andcommitted
Remove UseProvidedBody string matching from DelegateBodySyntaxExtractor
Refactor the delegate body extraction to use structural syntax analysis instead of searching for the "UseProvidedBody" method name string. Changes: - DelegateBodySyntaxExtractor now finds the lambda by looking at the outermost invocation's lambda argument in the method's return expression, instead of scanning for "UseProvidedBody" string - FluentBodyResult gets HasDelegateBody flag indicating RuntimeDelegateBody was set in BodyGenerationData - BodyGenerationDataExtractor sets HasDelegateBody when RuntimeDelegateBody is non-null - GeneratesMethodGenerationPipeline executes the method first, then uses HasDelegateBody to decide if syntax extraction is needed - Add missing WithCompileTimeConstants implementations to fix pre-existing build errors in DataMethodBodyBuilders Co-authored-by: dex3r <3155725+dex3r@users.noreply.github.com> Agent-Logs-Url: https://github.com/dex3r/EasySourceGenerators/sessions/ea25cd90-2a07-42c1-8091-f255cb2c6687
1 parent 77250d5 commit 5420b05

5 files changed

Lines changed: 115 additions & 38 deletions

File tree

EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public IMethodBodyBuilderStage4<TParam1, T> WithParameter<TParam1>() =>
3838

3939
public record DataMethodBodyBuilderStage4<TParam1, TReturnType>(BodyGenerationData Data) : IMethodBodyBuilderStage4<TParam1, TReturnType>
4040
{
41+
public IMethodBodyBuilderStage5WithConstants<TParam1, TReturnType, TConstants> WithCompileTimeConstants<TConstants>(Func<TConstants> compileTimeConstantsFactory) =>
42+
new DataMethodBodyBuilderStage5WithConstants<TParam1, TReturnType, TConstants>(Data with { CompileTimeConstants = compileTimeConstantsFactory() });
43+
4144
public IMethodBodyGenerator UseProvidedBody(Func<TParam1, TReturnType> body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body });
4245

4346
public IMethodBodyGenerator BodyReturningConstant(Func<TReturnType> constantValueFactory) =>
@@ -46,6 +49,9 @@ public IMethodBodyGenerator BodyReturningConstant(Func<TReturnType> constantValu
4649

4750
public record DataMethodBodyBuilderStage4NoArg<TReturnType>(BodyGenerationData Data) : IMethodBodyBuilderStage4NoArg<TReturnType>
4851
{
52+
public IMethodBodyBuilderStage5NoArgWithConstants<TReturnType, TConstants> WithCompileTimeConstants<TConstants>(Func<TConstants> compileTimeConstantsFactory) =>
53+
new DataMethodBodyBuilderStage5NoArgWithConstants<TReturnType, TConstants>(Data with { CompileTimeConstants = compileTimeConstantsFactory() });
54+
4955
public IMethodBodyGenerator UseProvidedBody(Func<TReturnType> body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body });
5056

5157
public IMethodBodyGenerator BodyReturningConstant(Func<TReturnType> constantValueFactory) =>
@@ -54,10 +60,42 @@ public IMethodBodyGenerator BodyReturningConstant(Func<TReturnType> constantValu
5460

5561
public record DataMethodBodyBuilderStage4ReturnVoid<TParam1>(BodyGenerationData BodyGenerationData) : IMethodBodyBuilderStage4ReturnVoid<TParam1>
5662
{
63+
public IMethodBodyBuilderStage5ReturnVoidWithConstants<TParam1, TConstants> WithCompileTimeConstants<TConstants>(Func<TConstants> compileTimeConstantsFactory) =>
64+
new DataMethodBodyBuilderStage5ReturnVoidWithConstants<TParam1, TConstants>(BodyGenerationData with { CompileTimeConstants = compileTimeConstantsFactory() });
65+
5766
public IMethodBodyGenerator UseProvidedBody(Action<TParam1> body) => new DataMethodBodyGenerator(BodyGenerationData with { RuntimeDelegateBody = body });
5867
}
5968

6069
public record DataMethodBodyBuilderStage4ReturnVoidNoArg(BodyGenerationData BodyGenerationData) : IMethodBodyBuilderStage4ReturnVoidNoArg
6170
{
71+
public IMethodBodyBuilderStage5ReturnVoidNoArgWithConstants<TConstants> WithCompileTimeConstants<TConstants>(Func<TConstants> compileTimeConstantsFactory) =>
72+
new DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants<TConstants>(BodyGenerationData with { CompileTimeConstants = compileTimeConstantsFactory() });
73+
6274
public IMethodBodyGenerator UseProvidedBody(Action body) => new DataMethodBodyGenerator(BodyGenerationData with { RuntimeDelegateBody = body });
75+
}
76+
77+
public record DataMethodBodyBuilderStage5WithConstants<TParam1, TReturnType, TConstants>(BodyGenerationData Data) : IMethodBodyBuilderStage5WithConstants<TParam1, TReturnType, TConstants>
78+
{
79+
public IMethodBodyGenerator UseProvidedBody(Func<TConstants, TParam1, TReturnType> body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body });
80+
81+
public IMethodBodyGenerator BodyReturningConstant(Func<TConstants, TReturnType> constantValueFactory) =>
82+
new DataMethodBodyGenerator(Data with { ReturnConstantValueFactory = constantValueFactory });
83+
}
84+
85+
public record DataMethodBodyBuilderStage5NoArgWithConstants<TReturnType, TConstants>(BodyGenerationData Data) : IMethodBodyBuilderStage5NoArgWithConstants<TReturnType, TConstants>
86+
{
87+
public IMethodBodyGenerator UseProvidedBody(Func<TConstants, TReturnType> body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body });
88+
89+
public IMethodBodyGenerator BodyReturningConstant(Func<TConstants, TReturnType> constantValueFactory) =>
90+
new DataMethodBodyGenerator(Data with { ReturnConstantValueFactory = constantValueFactory });
91+
}
92+
93+
public record DataMethodBodyBuilderStage5ReturnVoidWithConstants<TParam1, TConstants>(BodyGenerationData Data) : IMethodBodyBuilderStage5ReturnVoidWithConstants<TParam1, TConstants>
94+
{
95+
public IMethodBodyGenerator UseProvidedBody(Action<TConstants, TParam1> body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body });
96+
}
97+
98+
public record DataMethodBodyBuilderStage5ReturnVoidNoArgWithConstants<TConstants>(BodyGenerationData Data) : IMethodBodyBuilderStage5ReturnVoidNoArgWithConstants<TConstants>
99+
{
100+
public IMethodBodyGenerator UseProvidedBody(Action<TConstants> body) => new DataMethodBodyGenerator(Data with { RuntimeDelegateBody = body });
63101
}

EasySourceGenerators.Generators/IncrementalGenerators/BodyGenerationDataExtractor.cs

Lines changed: 23 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,23 +28,35 @@ 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);
46+
4347
return TryExtractFromConstantFactory(dataType, bodyGenerationData, isVoid)
44-
?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid)
45-
?? new FluentBodyResult(null, isVoid);
48+
?? TryExtractFromRuntimeBody(dataType, bodyGenerationData, isVoid, hasDelegateBody)
49+
?? new FluentBodyResult(null, isVoid, hasDelegateBody);
50+
}
51+
52+
/// <summary>
53+
/// Checks whether <c>RuntimeDelegateBody</c> is set (non-null) in the body generation data.
54+
/// </summary>
55+
private static bool HasRuntimeDelegateBody(Type dataType, object bodyGenerationData)
56+
{
57+
PropertyInfo? runtimeBodyProperty = dataType.GetProperty("RuntimeDelegateBody");
58+
Delegate? runtimeBody = runtimeBodyProperty?.GetValue(bodyGenerationData) as Delegate;
59+
return runtimeBody != null;
4660
}
4761

4862
/// <summary>
@@ -61,7 +75,7 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT
6175
}
6276

6377
object? constantValue = constantFactory.DynamicInvoke();
64-
return new FluentBodyResult(constantValue?.ToString(), isVoid);
78+
return new FluentBodyResult(constantValue?.ToString(), isVoid, HasDelegateBody: false);
6579
}
6680

6781
/// <summary>
@@ -72,7 +86,8 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT
7286
private static FluentBodyResult? TryExtractFromRuntimeBody(
7387
Type dataType,
7488
object bodyGenerationData,
75-
bool isVoid)
89+
bool isVoid,
90+
bool hasDelegateBody)
7691
{
7792
PropertyInfo? runtimeBodyProperty = dataType.GetProperty("RuntimeDelegateBody");
7893
Delegate? runtimeBody = runtimeBodyProperty?.GetValue(bodyGenerationData) as Delegate;
@@ -85,10 +100,10 @@ internal static FluentBodyResult Extract(object methodResult, bool isVoidReturnT
85100
if (bodyParams.Length == 0)
86101
{
87102
object? bodyResult = runtimeBody.DynamicInvoke();
88-
return new FluentBodyResult(bodyResult?.ToString(), isVoid);
103+
return new FluentBodyResult(bodyResult?.ToString(), isVoid, hasDelegateBody);
89104
}
90105

91106
// For delegates with parameters, we can't invoke at compile time without values
92-
return new FluentBodyResult(null, isVoid);
107+
return new FluentBodyResult(null, isVoid, hasDelegateBody);
93108
}
94109
}

EasySourceGenerators.Generators/IncrementalGenerators/DelegateBodySyntaxExtractor.cs

Lines changed: 30 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,28 @@ 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+
/// </summary>
56+
private static ExpressionSyntax? GetReturnExpression(MethodDeclarationSyntax method)
57+
{
58+
if (method.ExpressionBody != null)
59+
{
60+
return method.ExpressionBody.Expression;
61+
}
62+
63+
if (method.Body != null)
64+
{
65+
ReturnStatementSyntax? returnStatement = method.Body.Statements
66+
.OfType<ReturnStatementSyntax>()
67+
.FirstOrDefault();
68+
return returnStatement?.Expression;
69+
}
70+
71+
return null;
72+
}
73+
5774
/// <summary>
5875
/// Extracts the content of a block body (between <c>{</c> and <c>}</c>),
5976
/// 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)