Skip to content

Commit bb62cab

Browse files
authored
Merge pull request #692 from HubSpot/separate-if-branches
[Eager Execution] Execute deferred branches of if tag in separate contexts
2 parents 772f559 + 294fc98 commit bb62cab

9 files changed

Lines changed: 131 additions & 43 deletions

File tree

src/main/java/com/hubspot/jinjava/lib/tag/SetTag.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ private void setVariable(JinjavaInterpreter interpreter, String var, Object valu
184184
((Namespace) namespace).put(varArray[1], value);
185185
return;
186186
}
187+
if (namespace instanceof DeferredValue) {
188+
throw new DeferredValueException("Deferred Namespace");
189+
}
187190
}
188191
interpreter.getContext().put(var, value);
189192
}

src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerIfTag.java

Lines changed: 100 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.hubspot.jinjava.lib.tag.eager;
22

3+
import com.hubspot.jinjava.interpret.DeferredValue;
34
import com.hubspot.jinjava.interpret.DeferredValueException;
45
import com.hubspot.jinjava.interpret.InterpretException;
56
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
@@ -98,51 +99,113 @@ public String eagerRenderBranches(
9899
boolean definitelyExecuted = false;
99100
StringBuilder sb = new StringBuilder();
100101
sb.append(getEagerImage(buildToken(tagNode, e, deferredLineNumber), interpreter));
102+
int branchStart = 0;
103+
int childrenSize = tagNode.getChildren().size();
104+
while (branchStart < childrenSize) {
105+
int branchEnd = findNextElseToken(tagNode, branchStart);
106+
if (!definitelyDrop) {
107+
int finalBranchStart = branchStart;
108+
EagerExecutionResult result = executeInChildContext(
109+
eagerInterpreter ->
110+
EagerExpressionResult.fromString(
111+
evaluateBranch(tagNode, finalBranchStart, branchEnd, interpreter)
112+
),
113+
interpreter,
114+
false,
115+
false,
116+
true
117+
);
118+
sb.append(result.getResult());
119+
resetBindingsForNextBranch(interpreter, result);
120+
}
121+
if (branchEnd >= childrenSize || definitelyExecuted) {
122+
break;
123+
}
124+
TagNode caseNode = (TagNode) tagNode.getChildren().get(branchEnd);
125+
definitelyDrop =
126+
caseNode.getName().equals(ElseIfTag.TAG_NAME) &&
127+
shouldDropBranch(caseNode, interpreter, deferredLineNumber);
128+
if (!definitelyDrop) {
129+
definitelyExecuted =
130+
caseNode.getName().equals(ElseTag.TAG_NAME) ||
131+
isDefinitelyExecuted(caseNode, interpreter, deferredLineNumber);
132+
if (definitelyExecuted) {
133+
sb.append(
134+
String.format(
135+
"%s else %s",
136+
caseNode.getSymbols().getExpressionStartWithTag(),
137+
caseNode.getSymbols().getExpressionEndWithTag()
138+
)
139+
);
140+
} else {
141+
sb.append(
142+
getEagerImage(buildToken(caseNode, e, deferredLineNumber), interpreter)
143+
);
144+
}
145+
}
146+
branchStart = branchEnd + 1;
147+
}
148+
return sb.toString();
149+
}
101150

102-
for (Node child : tagNode.getChildren()) {
103-
if (TagNode.class.isAssignableFrom(child.getClass())) {
104-
TagNode childTagNode = (TagNode) child;
105-
if (
106-
childTagNode.getName().equals(ElseIfTag.TAG_NAME) ||
107-
childTagNode.getName().equals(ElseTag.TAG_NAME)
108-
) {
109-
if (definitelyExecuted) {
110-
break;
111-
}
112-
definitelyDrop =
113-
childTagNode.getName().equals(ElseIfTag.TAG_NAME) &&
114-
shouldDropBranch(childTagNode, interpreter, deferredLineNumber);
115-
if (!definitelyDrop) {
116-
definitelyExecuted =
117-
childTagNode.getName().equals(ElseTag.TAG_NAME) ||
118-
isDefinitelyExecuted(childTagNode, interpreter, deferredLineNumber);
119-
if (definitelyExecuted) {
120-
sb.append(
121-
String.format(
122-
"%s else %s",
123-
childTagNode.getSymbols().getExpressionStartWithTag(),
124-
childTagNode.getSymbols().getExpressionEndWithTag()
125-
)
126-
);
127-
} else {
128-
sb.append(
129-
getEagerImage(
130-
buildToken(childTagNode, e, deferredLineNumber),
131-
interpreter
132-
)
151+
private void resetBindingsForNextBranch(
152+
JinjavaInterpreter interpreter,
153+
EagerExecutionResult result
154+
) {
155+
result
156+
.getSpeculativeBindings()
157+
.keySet()
158+
.stream()
159+
.filter(
160+
key ->
161+
interpreter.getContext().containsKey(key) &&
162+
interpreter.getContext().get(key) instanceof DeferredValue
163+
)
164+
.forEach(
165+
key -> {
166+
if (
167+
((DeferredValue) interpreter.getContext().get(key)).getOriginalValue() != null
168+
) {
169+
interpreter
170+
.getContext()
171+
.put(
172+
key,
173+
((DeferredValue) interpreter.getContext().get(key)).getOriginalValue()
133174
);
134-
}
135175
}
136-
continue;
137176
}
138-
}
139-
if (!definitelyDrop) {
140-
sb.append(child.render(interpreter).getValue());
141-
}
177+
);
178+
}
179+
180+
private String evaluateBranch(
181+
TagNode tagNode,
182+
int startIdx,
183+
int endIdx,
184+
JinjavaInterpreter interpreter
185+
) {
186+
StringBuilder sb = new StringBuilder();
187+
for (int i = startIdx; i < endIdx; i++) {
188+
Node child = tagNode.getChildren().get(i);
189+
sb.append(child.render(interpreter).getValue());
142190
}
143191
return sb.toString();
144192
}
145193

194+
private int findNextElseToken(TagNode tagNode, int startIdx) {
195+
int i;
196+
for (i = startIdx; i < tagNode.getChildren().size(); i++) {
197+
Node childNode = tagNode.getChildren().get(i);
198+
if (
199+
(TagNode.class.isAssignableFrom(childNode.getClass())) &&
200+
childNode.getName().equals(ElseIfTag.TAG_NAME) ||
201+
childNode.getName().equals(ElseTag.TAG_NAME)
202+
) {
203+
return i;
204+
}
205+
}
206+
return i;
207+
}
208+
146209
private boolean shouldDropBranch(
147210
TagNode tagNode,
148211
JinjavaInterpreter eagerInterpreter,

src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerSetTag.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,21 @@ public String getEagerTagImage(TagToken tagToken, JinjavaInterpreter interpreter
133133
)
134134
);
135135
}
136+
if (
137+
eagerExecutionResult.getResult().isFullyResolved() &&
138+
interpreter.getContext().isDeferredExecutionMode()
139+
) {
140+
try {
141+
getTag()
142+
.executeSet(
143+
tagToken,
144+
interpreter,
145+
varTokens,
146+
eagerExecutionResult.getResult().toList(),
147+
true
148+
);
149+
} catch (DeferredValueException ignored) {}
150+
}
136151
return wrapInAutoEscapeIfNeeded(
137152
prefixToPreserveState + joiner.toString() + suffixToPreserveState.toString(),
138153
interpreter

src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagDecorator.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,13 @@ public static EagerExecutionResult executeInChildContext(
267267
.entrySet()
268268
.stream()
269269
.filter(
270-
entry -> !entry.getValue().equals(interpreter.getContext().get(entry.getKey()))
270+
entry ->
271+
entry.getValue() != null &&
272+
!entry.getValue().equals(interpreter.getContext().get(entry.getKey()))
273+
)
274+
.filter(
275+
entry ->
276+
!(interpreter.getContext().get(entry.getKey()) instanceof DeferredValue)
271277
)
272278
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
273279
if (checkForContextChanges) {

src/test/java/com/hubspot/jinjava/EagerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ public void itMarksVariablesSetInDeferredBlockAsDeferred() {
423423
assertThat(localContext).containsKey("varSetInside");
424424
Object varSetInside = localContext.get("varSetInside");
425425
assertThat(varSetInside).isInstanceOf(DeferredValue.class);
426-
assertThat(output).contains("{{ varSetInside }}");
426+
assertThat(output).contains("{{ varSetInside }}").contains("xyz");
427427
assertThat(context.get("a")).isInstanceOf(DeferredValue.class);
428428
assertThat(context.get("b")).isInstanceOf(DeferredValue.class);
429429
assertThat(context.get("c")).isInstanceOf(DeferredValue.class);
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{% set reference = deferredValue %}
22
{% if reference == 'resolved' %}
33
{% set varSetInside = 'set inside' %}
4-
{% set a, b, c = ['x','y','z'] %}
4+
{% set a, b, c = 'x','y','z' %}
5+
{{ a ~ b ~ c }}
56
{% endif %}
67
{{ varSetInside }}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% if deferred %}
22
{% set foo = 1 %}
3-
{{ foo }}
3+
1
44
{% endif %}
55
We don't know if someone wanted to access {{ foo }} here!

src/test/resources/eager/defers-on-immutable-mode.expected.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% set foo = 1 %}{% if deferred %}
22
{% set foo = 2 %}
33
{% else %}
4-
{% set foo = foo + 2 %}//best we can do
4+
{% set foo = 3 %}
55
{% endif %}
66
{{ foo }}
77

src/test/resources/eager/defers-on-immutable-mode.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{% if deferred %}
33
{% set foo = foo + 1 %}
44
{% else %}
5-
{% set foo = foo + 2 %}//best we can do
5+
{% set foo = foo + 2 %}
66
{% endif %}
77
{{ foo }}
88

0 commit comments

Comments
 (0)