Skip to content

Commit 6283e73

Browse files
Fix render named-argument shadowing by evaluating assignments before applying them (#924)
* Initial plan * fix render tag named argument evaluation order Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
1 parent 6e6cd6a commit 6283e73

File tree

2 files changed

+46
-15
lines changed

2 files changed

+46
-15
lines changed

Fluid.Tests/IncludeStatementTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,22 @@ public void RenderTag_With_MultipleNamedArguments()
655655
Assert.Equal("Text: Click Me, Size: large, Color: blue", result);
656656
}
657657

658+
[Fact]
659+
public void RenderTag_NamedArguments_AreEvaluatedBeforeAssignment()
660+
{
661+
var fileProvider = new MockFileProvider();
662+
fileProvider.Add("file.liquid", "{{ value }} {{ key }}");
663+
664+
var options = new TemplateOptions() { FileProvider = fileProvider };
665+
var context = new TemplateContext(options);
666+
context.SetValue("value", new { f1 = "Hello", f2 = "World" });
667+
_parser.TryParse("{% render 'file', value: value.f1, key: value.f2 %}", out var template);
668+
669+
var result = template.Render(context);
670+
671+
Assert.Equal("Hello World", result);
672+
}
673+
658674
[Fact]
659675
public async Task RenderTag_For_And_NamedArguments()
660676
{

Fluid/Ast/RenderStatement.cs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,7 @@ public override async ValueTask<Completion> WriteToAsync(IFluidOutput output, Te
105105
// Evaluate assign statements in the new scope if present
106106
if (AssignStatements.Count > 0)
107107
{
108-
var length = AssignStatements.Count;
109-
for (var i = 0; i < length; i++)
110-
{
111-
await AssignStatements[i].WriteToAsync(output, encoder, context);
112-
}
108+
await EvaluateAssignStatementsAsync(AssignStatements, context);
113109
}
114110

115111
await template.RenderAsync(output, encoder, context);
@@ -133,11 +129,7 @@ public override async ValueTask<Completion> WriteToAsync(IFluidOutput output, Te
133129
// Evaluate assign statements in the new scope before the loop if present
134130
if (AssignStatements.Count > 0)
135131
{
136-
var assignLength = AssignStatements.Count;
137-
for (var j = 0; j < assignLength; j++)
138-
{
139-
await AssignStatements[j].WriteToAsync(output, encoder, context);
140-
}
132+
await EvaluateAssignStatementsAsync(AssignStatements, context);
141133
}
142134

143135
var length = forloop.Length = list.Count;
@@ -174,11 +166,7 @@ public override async ValueTask<Completion> WriteToAsync(IFluidOutput output, Te
174166
}
175167
else if (AssignStatements.Count > 0)
176168
{
177-
var length = AssignStatements.Count;
178-
for (var i = 0; i < length; i++)
179-
{
180-
await AssignStatements[i].WriteToAsync(output, encoder, context);
181-
}
169+
await EvaluateAssignStatementsAsync(AssignStatements, context);
182170

183171
context.LocalScope = new Scope(context.RootScope);
184172
previousScope.CopyTo(context.LocalScope);
@@ -204,6 +192,33 @@ public override async ValueTask<Completion> WriteToAsync(IFluidOutput output, Te
204192

205193
protected internal override Statement Accept(AstVisitor visitor) => visitor.VisitRenderStatement(this);
206194

195+
private static async ValueTask EvaluateAssignStatementsAsync(IReadOnlyList<AssignStatement> assignStatements, TemplateContext context)
196+
{
197+
var length = assignStatements.Count;
198+
var evaluatedValues = new KeyValuePair<string, FluidValue>[length];
199+
200+
for (var i = 0; i < length; i++)
201+
{
202+
context.IncrementSteps();
203+
204+
var assignStatement = assignStatements[i];
205+
var value = await assignStatement.Value.EvaluateAsync(context);
206+
207+
if (context.Assigned != null)
208+
{
209+
value = await context.Assigned.Invoke(assignStatement.Identifier, value, context);
210+
}
211+
212+
evaluatedValues[i] = new KeyValuePair<string, FluidValue>(assignStatement.Identifier, value);
213+
}
214+
215+
for (var i = 0; i < length; i++)
216+
{
217+
var entry = evaluatedValues[i];
218+
context.SetValue(entry.Key, entry.Value);
219+
}
220+
}
221+
207222
private sealed record CachedTemplate(IFluidTemplate Template, string Name);
208223
}
209224
}

0 commit comments

Comments
 (0)