Skip to content

Commit c1e2e0f

Browse files
authored
Revert "Revert "Revert 1055 revert 1048 improve for loop success"" (#1059)
Can confirm this is definitely not the issue. Reverts #1058
1 parent 0099ea9 commit c1e2e0f

26 files changed

Lines changed: 250 additions & 80 deletions

src/main/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunction.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@
2424
import java.util.LinkedHashMap;
2525
import java.util.List;
2626
import java.util.Map;
27+
import java.util.Map.Entry;
2728
import java.util.Optional;
2829
import java.util.StringJoiner;
2930
import java.util.concurrent.atomic.AtomicInteger;
31+
import java.util.function.Function;
3032
import java.util.function.Supplier;
33+
import java.util.stream.Collectors;
34+
import org.apache.commons.lang3.StringUtils;
3135

3236
@Beta
3337
public class EagerMacroFunction extends MacroFunction {
@@ -212,6 +216,8 @@ public String reconstructImage() {
212216
*/
213217
public String reconstructImage(String fullName) {
214218
String prefix = "";
219+
StringBuilder result = new StringBuilder();
220+
String setTagForAliasedVariables = getSetTagForAliasedVariables(fullName);
215221
String suffix = "";
216222
JinjavaInterpreter interpreter = JinjavaInterpreter.getCurrent();
217223
Optional<String> importFile = getImportFile(interpreter);
@@ -247,7 +253,6 @@ public String reconstructImage(String fullName) {
247253
);
248254
}
249255

250-
String result;
251256
if (
252257
(
253258
interpreter.getContext().getMacroStack().contains(getName()) &&
@@ -262,11 +267,19 @@ public String reconstructImage(String fullName) {
262267
String evaluation = (String) evaluate(
263268
getArguments().stream().map(arg -> DeferredMacroValueImpl.instance()).toArray()
264269
);
265-
result =
266-
(getStartTag(fullName, interpreter) + evaluation + getEndTag(interpreter));
270+
result
271+
.append(getStartTag(fullName, interpreter))
272+
.append(setTagForAliasedVariables)
273+
.append(evaluation)
274+
.append(getEndTag(interpreter));
267275
} catch (DeferredValueException e) {
268276
// In case something not eager-supported encountered a deferred value
269-
result = super.reconstructImage();
277+
if (StringUtils.isNotEmpty(setTagForAliasedVariables)) {
278+
throw new DeferredValueException(
279+
"Aliased variables in not eagerly reconstructible macro function"
280+
);
281+
}
282+
result.append(super.reconstructImage());
270283
} finally {
271284
reconstructing = false;
272285
interpreter
@@ -280,6 +293,26 @@ public String reconstructImage(String fullName) {
280293
return prefix + result + suffix;
281294
}
282295

296+
private String getSetTagForAliasedVariables(String fullName) {
297+
int lastDotIdx = fullName.lastIndexOf('.');
298+
if (lastDotIdx > 0) {
299+
String aliasName = fullName.substring(0, lastDotIdx + 1);
300+
Map<String, String> namesToAlias = localContextScope
301+
.getCombinedScope()
302+
.entrySet()
303+
.stream()
304+
.filter(entry -> entry.getValue() instanceof DeferredValue)
305+
.map(Entry::getKey)
306+
.collect(Collectors.toMap(Function.identity(), name -> aliasName + name));
307+
return EagerReconstructionUtils.buildSetTag(
308+
namesToAlias,
309+
JinjavaInterpreter.getCurrent(),
310+
false
311+
);
312+
}
313+
return "";
314+
}
315+
283316
private boolean differentMacroWithSameNameExists(JinjavaInterpreter interpreter) {
284317
Context context = interpreter.getContext();
285318
if (context.getParent() == null) {

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import java.beans.PropertyDescriptor;
4545
import java.util.ConcurrentModificationException;
4646
import java.util.List;
47-
import java.util.Map;
4847
import java.util.Map.Entry;
4948
import java.util.Optional;
5049
import java.util.regex.Matcher;
@@ -151,6 +150,20 @@ public String interpretUnchecked(TagNode tagNode, JinjavaInterpreter interpreter
151150
String.format("%s in %s", String.join(", ", loopVars), e.getDeferredEvalResult())
152151
);
153152
}
153+
return renderForCollection(
154+
tagNode,
155+
interpreter,
156+
loopVarsAndExpression.getLeft(),
157+
collection
158+
);
159+
}
160+
161+
public String renderForCollection(
162+
TagNode tagNode,
163+
JinjavaInterpreter interpreter,
164+
List<String> loopVars,
165+
Object collection
166+
) {
154167
ForLoop loop = ObjectIterator.getLoop(collection);
155168

156169
try (InterpreterScopeClosable c = interpreter.enterScope()) {
@@ -190,8 +203,8 @@ public String interpretUnchecked(TagNode tagNode, JinjavaInterpreter interpreter
190203
} else {
191204
for (int loopVarIndex = 0; loopVarIndex < loopVars.size(); loopVarIndex++) {
192205
String loopVar = loopVars.get(loopVarIndex);
193-
if (Map.Entry.class.isAssignableFrom(val.getClass())) {
194-
Map.Entry<String, Object> entry = (Entry<String, Object>) val;
206+
if (Entry.class.isAssignableFrom(val.getClass())) {
207+
Entry<String, Object> entry = (Entry<String, Object>) val;
195208
Object entryVal = null;
196209

197210
if (loopVars.indexOf(loopVar) == 0) {

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ public String eagerInterpret(
4040
EagerContextWatcher
4141
.EagerChildContextConfig.newBuilder()
4242
.withTakeNewValue(true)
43-
.withCheckForContextChanges(!interpreter.getContext().isDeferredExecutionMode())
4443
.build()
4544
);
4645
PrefixToPreserveState prefixToPreserveState = new PrefixToPreserveState();

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static com.hubspot.jinjava.util.EagerReconstructionUtils.buildSetTag;
55

66
import com.google.common.annotations.Beta;
7+
import com.hubspot.jinjava.interpret.DeferredLazyReferenceSource;
78
import com.hubspot.jinjava.interpret.DeferredValueShadow;
89
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
910
import com.hubspot.jinjava.interpret.LazyReference;
@@ -55,8 +56,13 @@ public PrefixToPreserveState getPrefixToPreserveState() {
5556
.entrySet()
5657
.stream()
5758
.filter(
58-
entry ->
59-
!(interpreter.getContext().get(entry.getKey()) instanceof DeferredValueShadow)
59+
entry -> {
60+
Object contextValue = interpreter.getContext().get(entry.getKey());
61+
if (contextValue instanceof DeferredLazyReferenceSource) {
62+
((DeferredLazyReferenceSource) contextValue).setReconstructed(true);
63+
}
64+
return (contextValue != null && !(contextValue instanceof DeferredValueShadow));
65+
}
6066
)
6167
.collect(Collectors.toList());
6268
prefixToPreserveState.putAll(

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

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,45 +39,63 @@ public EagerForTag(ForTag forTag) {
3939

4040
@Override
4141
public String innerInterpret(TagNode tagNode, JinjavaInterpreter interpreter) {
42-
Set<DeferredToken> addedTokens = new HashSet<>();
43-
EagerExecutionResult result = EagerContextWatcher.executeInChildContext(
44-
eagerInterpreter -> {
45-
EagerExpressionResult expressionResult = EagerExpressionResult.fromSupplier(
46-
() -> getTag().interpretUnchecked(tagNode, eagerInterpreter),
47-
eagerInterpreter
48-
);
49-
addedTokens.addAll(eagerInterpreter.getContext().getDeferredTokens());
50-
return expressionResult;
51-
},
42+
Pair<List<String>, String> loopVarsAndExpression = getTag()
43+
.getLoopVarsAndExpression((TagToken) tagNode.getMaster());
44+
EagerExecutionResult collectionResult = EagerContextWatcher.executeInChildContext(
45+
eagerInterpreter ->
46+
EagerExpressionResolver.resolveExpression(
47+
'[' + loopVarsAndExpression.getRight() + ']',
48+
interpreter
49+
),
5250
interpreter,
5351
EagerContextWatcher
5452
.EagerChildContextConfig.newBuilder()
5553
.withCheckForContextChanges(!interpreter.getContext().isDeferredExecutionMode())
5654
.build()
5755
);
58-
if (
59-
result.getResult().getResolutionState() == ResolutionState.NONE ||
60-
(
61-
!result.getResult().isFullyResolved() &&
62-
!result.getSpeculativeBindings().isEmpty()
63-
)
64-
) {
65-
EagerReconstructionUtils.resetSpeculativeBindings(interpreter, result);
66-
interpreter.getContext().removeDeferredTokens(addedTokens);
67-
throw new DeferredValueException(
68-
result.getResult().getResolutionState() == ResolutionState.NONE
69-
? result.getResult().toString()
70-
: "Modification inside partially evaluated for loop"
71-
);
72-
}
73-
if (result.getResult().isFullyResolved()) {
74-
return result.getResult().toString(true);
75-
} else {
76-
return EagerReconstructionUtils.wrapInChildScope(
77-
result.getResult().toString(true),
78-
interpreter
56+
if (collectionResult.getResult().isFullyResolved()) {
57+
Set<DeferredToken> addedTokens = new HashSet<>();
58+
EagerExecutionResult result = EagerContextWatcher.executeInChildContext(
59+
eagerInterpreter -> {
60+
EagerExpressionResult expressionResult = EagerExpressionResult.fromSupplier(
61+
() ->
62+
getTag()
63+
.renderForCollection(
64+
tagNode,
65+
eagerInterpreter,
66+
loopVarsAndExpression.getLeft(),
67+
collectionResult.getResult().toList().get(0)
68+
),
69+
eagerInterpreter
70+
);
71+
addedTokens.addAll(eagerInterpreter.getContext().getDeferredTokens());
72+
return expressionResult;
73+
},
74+
interpreter,
75+
EagerContextWatcher.EagerChildContextConfig.newBuilder().build()
7976
);
77+
if (result.getResult().getResolutionState() == ResolutionState.NONE) {
78+
EagerReconstructionUtils.resetSpeculativeBindings(interpreter, collectionResult);
79+
EagerReconstructionUtils.resetSpeculativeBindings(interpreter, result);
80+
interpreter.getContext().removeDeferredTokens(addedTokens);
81+
throw new DeferredValueException(result.getResult().toString());
82+
}
83+
if (result.getResult().isFullyResolved()) {
84+
return result.getResult().toString(true);
85+
} else {
86+
return (
87+
result
88+
.getPrefixToPreserveState()
89+
.withAllInFront(collectionResult.getPrefixToPreserveState()) +
90+
EagerReconstructionUtils.wrapInChildScope(
91+
result.getResult().toString(true),
92+
interpreter
93+
)
94+
);
95+
}
8096
}
97+
EagerReconstructionUtils.resetSpeculativeBindings(interpreter, collectionResult);
98+
throw new DeferredValueException(collectionResult.getResult().toString());
8199
}
82100

83101
@Override

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,28 +191,33 @@ private String getFinalOutputWithAlias(
191191
) {
192192
return (
193193
newPathSetter +
194-
getSetTagForDeferredChildBindings(interpreter, currentImportAlias, childBindings) +
195194
EagerReconstructionUtils.buildSetTag(
196195
ImmutableMap.of(currentImportAlias, "{}"),
197196
interpreter,
198197
true
199198
) +
200-
wrapInChildScopeIfNecessary(interpreter, output, currentImportAlias) +
199+
wrapInChildScope(
200+
interpreter,
201+
getSetTagForDeferredChildBindings(
202+
interpreter,
203+
currentImportAlias,
204+
childBindings
205+
) +
206+
output,
207+
currentImportAlias
208+
) +
201209
initialPathSetter
202210
);
203211
}
204212

205-
private static String wrapInChildScopeIfNecessary(
213+
private static String wrapInChildScope(
206214
JinjavaInterpreter interpreter,
207215
String output,
208216
String currentImportAlias
209217
) {
210218
String combined = output + getDoTagToPreserve(interpreter, currentImportAlias);
211219
// So that any set variables other than the alias won't exist outside the child's scope
212-
if (interpreter.getContext().isDeferredExecutionMode()) {
213-
return EagerReconstructionUtils.wrapInChildScope(combined, interpreter);
214-
}
215-
return combined;
220+
return EagerReconstructionUtils.wrapInChildScope(combined, interpreter);
216221
}
217222

218223
private String getSetTagForDeferredChildBindings(
@@ -246,7 +251,11 @@ private String getSetTagForDeferredChildBindings(
246251
childBindings
247252
.entrySet()
248253
.stream()
249-
.filter(entry -> entry.getValue() instanceof DeferredValue)
254+
.filter(
255+
entry ->
256+
entry.getValue() instanceof DeferredValue &&
257+
((DeferredValue) entry.getValue()).getOriginalValue() != null
258+
)
250259
.filter(entry -> !interpreter.getContext().containsKey(entry.getKey()))
251260
.filter(entry -> !entry.getKey().equals(currentImportAlias))
252261
.collect(

src/main/java/com/hubspot/jinjava/util/EagerReconstructionUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,8 @@ public static Map<String, String> reconstructDeferredReferences(
790790
* * When doing some eager execution and then needing to repeat the same execution in deferred execution mode.
791791
* <p>
792792
* * When rendering logic which takes place in its own child scope (for tag, macro function, set block) and there
793-
* speculative bindings. These must be deferred and the execution must run again so they don't get reconstructed
793+
* are speculative bindings.
794+
* These must be deferred and the execution must run again, so they don't get reconstructed
794795
* within the child scope, and can instead be reconstructed in their original scopes.
795796
* @param interpreter The JinjavaInterpreter
796797
* @param eagerExecutionResult The execution result which contains information about which bindings were modified

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1145,7 +1145,7 @@ public void itHandlesHigherScopeReferenceModificationSecondPass() {
11451145

11461146
@Test
11471147
public void itHandlesReferenceModificationWhenSourceIsLost() {
1148-
expectedTemplateInterpreter.assertExpectedOutput(
1148+
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(
11491149
"handles-reference-modification-when-source-is-lost"
11501150
);
11511151
}
@@ -1335,4 +1335,19 @@ public void itDoesNotReconstructVariableInSetInWrongScope() {
13351335
"does-not-reconstruct-variable-in-set-in-wrong-scope"
13361336
);
13371337
}
1338+
1339+
@Test
1340+
public void itRreconstructsValueUsedInDeferredImportedMacro() {
1341+
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(
1342+
"reconstructs-value-used-in-deferred-imported-macro"
1343+
);
1344+
}
1345+
1346+
@Test
1347+
public void itRreconstructsValueUsedInDeferredImportedMacroSecondPass() {
1348+
interpreter.getContext().put("deferred", "resolved");
1349+
expectedTemplateInterpreter.assertExpectedOutput(
1350+
"reconstructs-value-used-in-deferred-imported-macro.expected"
1351+
);
1352+
}
13381353
}

src/test/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunctionTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public void itResolvesFromContext() {
6565

6666
@Test
6767
public void itReconstructsForAliasedName() {
68+
context.remove("deferred");
6869
String name = "foo";
6970
String fullName = "local." + name;
7071
String codeFormat = "{%% macro %s(bar) %%}It's: {{ bar }}{%% endmacro %%}";

0 commit comments

Comments
 (0)