diff --git a/src/main/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunction.java b/src/main/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunction.java index 2c11014de..4ba3d33c3 100644 --- a/src/main/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunction.java +++ b/src/main/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunction.java @@ -24,10 +24,14 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; @Beta public class EagerMacroFunction extends MacroFunction { @@ -212,6 +216,8 @@ public String reconstructImage() { */ public String reconstructImage(String fullName) { String prefix = ""; + StringBuilder result = new StringBuilder(); + String setTagForAliasedVariables = getSetTagForAliasedVariables(fullName); String suffix = ""; JinjavaInterpreter interpreter = JinjavaInterpreter.getCurrent(); Optional importFile = getImportFile(interpreter); @@ -247,7 +253,6 @@ public String reconstructImage(String fullName) { ); } - String result; if ( ( interpreter.getContext().getMacroStack().contains(getName()) && @@ -262,11 +267,19 @@ public String reconstructImage(String fullName) { String evaluation = (String) evaluate( getArguments().stream().map(arg -> DeferredMacroValueImpl.instance()).toArray() ); - result = - (getStartTag(fullName, interpreter) + evaluation + getEndTag(interpreter)); + result + .append(getStartTag(fullName, interpreter)) + .append(setTagForAliasedVariables) + .append(evaluation) + .append(getEndTag(interpreter)); } catch (DeferredValueException e) { // In case something not eager-supported encountered a deferred value - result = super.reconstructImage(); + if (StringUtils.isNotEmpty(setTagForAliasedVariables)) { + throw new DeferredValueException( + "Aliased variables in not eagerly reconstructible macro function" + ); + } + result.append(super.reconstructImage()); } finally { reconstructing = false; interpreter @@ -280,6 +293,26 @@ public String reconstructImage(String fullName) { return prefix + result + suffix; } + private String getSetTagForAliasedVariables(String fullName) { + int lastDotIdx = fullName.lastIndexOf('.'); + if (lastDotIdx > 0) { + String aliasName = fullName.substring(0, lastDotIdx + 1); + Map namesToAlias = localContextScope + .getCombinedScope() + .entrySet() + .stream() + .filter(entry -> entry.getValue() instanceof DeferredValue) + .map(Entry::getKey) + .collect(Collectors.toMap(Function.identity(), name -> aliasName + name)); + return EagerReconstructionUtils.buildSetTag( + namesToAlias, + JinjavaInterpreter.getCurrent(), + false + ); + } + return ""; + } + private boolean differentMacroWithSameNameExists(JinjavaInterpreter interpreter) { Context context = interpreter.getContext(); if (context.getParent() == null) { diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/ForTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/ForTag.java index 49cadd0d1..e38d244a6 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/ForTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/ForTag.java @@ -44,7 +44,6 @@ import java.beans.PropertyDescriptor; import java.util.ConcurrentModificationException; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.regex.Matcher; @@ -151,6 +150,20 @@ public String interpretUnchecked(TagNode tagNode, JinjavaInterpreter interpreter String.format("%s in %s", String.join(", ", loopVars), e.getDeferredEvalResult()) ); } + return renderForCollection( + tagNode, + interpreter, + loopVarsAndExpression.getLeft(), + collection + ); + } + + public String renderForCollection( + TagNode tagNode, + JinjavaInterpreter interpreter, + List loopVars, + Object collection + ) { ForLoop loop = ObjectIterator.getLoop(collection); try (InterpreterScopeClosable c = interpreter.enterScope()) { @@ -190,8 +203,8 @@ public String interpretUnchecked(TagNode tagNode, JinjavaInterpreter interpreter } else { for (int loopVarIndex = 0; loopVarIndex < loopVars.size(); loopVarIndex++) { String loopVar = loopVars.get(loopVarIndex); - if (Map.Entry.class.isAssignableFrom(val.getClass())) { - Map.Entry entry = (Entry) val; + if (Entry.class.isAssignableFrom(val.getClass())) { + Entry entry = (Entry) val; Object entryVal = null; if (loopVars.indexOf(loopVar) == 0) { diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerDoTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerDoTag.java index fb11cd10b..b58103dde 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerDoTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerDoTag.java @@ -40,7 +40,6 @@ public String eagerInterpret( EagerContextWatcher .EagerChildContextConfig.newBuilder() .withTakeNewValue(true) - .withCheckForContextChanges(!interpreter.getContext().isDeferredExecutionMode()) .build() ); PrefixToPreserveState prefixToPreserveState = new PrefixToPreserveState(); diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerExecutionResult.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerExecutionResult.java index f313d4239..e600667b9 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerExecutionResult.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerExecutionResult.java @@ -4,6 +4,7 @@ import static com.hubspot.jinjava.util.EagerReconstructionUtils.buildSetTag; import com.google.common.annotations.Beta; +import com.hubspot.jinjava.interpret.DeferredLazyReferenceSource; import com.hubspot.jinjava.interpret.DeferredValueShadow; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.interpret.LazyReference; @@ -55,8 +56,13 @@ public PrefixToPreserveState getPrefixToPreserveState() { .entrySet() .stream() .filter( - entry -> - !(interpreter.getContext().get(entry.getKey()) instanceof DeferredValueShadow) + entry -> { + Object contextValue = interpreter.getContext().get(entry.getKey()); + if (contextValue instanceof DeferredLazyReferenceSource) { + ((DeferredLazyReferenceSource) contextValue).setReconstructed(true); + } + return (contextValue != null && !(contextValue instanceof DeferredValueShadow)); + } ) .collect(Collectors.toList()); prefixToPreserveState.putAll( diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerForTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerForTag.java index 733886a2f..bdf4cbb64 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerForTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerForTag.java @@ -39,45 +39,63 @@ public EagerForTag(ForTag forTag) { @Override public String innerInterpret(TagNode tagNode, JinjavaInterpreter interpreter) { - Set addedTokens = new HashSet<>(); - EagerExecutionResult result = EagerContextWatcher.executeInChildContext( - eagerInterpreter -> { - EagerExpressionResult expressionResult = EagerExpressionResult.fromSupplier( - () -> getTag().interpretUnchecked(tagNode, eagerInterpreter), - eagerInterpreter - ); - addedTokens.addAll(eagerInterpreter.getContext().getDeferredTokens()); - return expressionResult; - }, + Pair, String> loopVarsAndExpression = getTag() + .getLoopVarsAndExpression((TagToken) tagNode.getMaster()); + EagerExecutionResult collectionResult = EagerContextWatcher.executeInChildContext( + eagerInterpreter -> + EagerExpressionResolver.resolveExpression( + '[' + loopVarsAndExpression.getRight() + ']', + interpreter + ), interpreter, EagerContextWatcher .EagerChildContextConfig.newBuilder() .withCheckForContextChanges(!interpreter.getContext().isDeferredExecutionMode()) .build() ); - if ( - result.getResult().getResolutionState() == ResolutionState.NONE || - ( - !result.getResult().isFullyResolved() && - !result.getSpeculativeBindings().isEmpty() - ) - ) { - EagerReconstructionUtils.resetSpeculativeBindings(interpreter, result); - interpreter.getContext().removeDeferredTokens(addedTokens); - throw new DeferredValueException( - result.getResult().getResolutionState() == ResolutionState.NONE - ? result.getResult().toString() - : "Modification inside partially evaluated for loop" - ); - } - if (result.getResult().isFullyResolved()) { - return result.getResult().toString(true); - } else { - return EagerReconstructionUtils.wrapInChildScope( - result.getResult().toString(true), - interpreter + if (collectionResult.getResult().isFullyResolved()) { + Set addedTokens = new HashSet<>(); + EagerExecutionResult result = EagerContextWatcher.executeInChildContext( + eagerInterpreter -> { + EagerExpressionResult expressionResult = EagerExpressionResult.fromSupplier( + () -> + getTag() + .renderForCollection( + tagNode, + eagerInterpreter, + loopVarsAndExpression.getLeft(), + collectionResult.getResult().toList().get(0) + ), + eagerInterpreter + ); + addedTokens.addAll(eagerInterpreter.getContext().getDeferredTokens()); + return expressionResult; + }, + interpreter, + EagerContextWatcher.EagerChildContextConfig.newBuilder().build() ); + if (result.getResult().getResolutionState() == ResolutionState.NONE) { + EagerReconstructionUtils.resetSpeculativeBindings(interpreter, collectionResult); + EagerReconstructionUtils.resetSpeculativeBindings(interpreter, result); + interpreter.getContext().removeDeferredTokens(addedTokens); + throw new DeferredValueException(result.getResult().toString()); + } + if (result.getResult().isFullyResolved()) { + return result.getResult().toString(true); + } else { + return ( + result + .getPrefixToPreserveState() + .withAllInFront(collectionResult.getPrefixToPreserveState()) + + EagerReconstructionUtils.wrapInChildScope( + result.getResult().toString(true), + interpreter + ) + ); + } } + EagerReconstructionUtils.resetSpeculativeBindings(interpreter, collectionResult); + throw new DeferredValueException(collectionResult.getResult().toString()); } @Override diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java index ece9cd0d0..bbe51be37 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTag.java @@ -191,28 +191,33 @@ private String getFinalOutputWithAlias( ) { return ( newPathSetter + - getSetTagForDeferredChildBindings(interpreter, currentImportAlias, childBindings) + EagerReconstructionUtils.buildSetTag( ImmutableMap.of(currentImportAlias, "{}"), interpreter, true ) + - wrapInChildScopeIfNecessary(interpreter, output, currentImportAlias) + + wrapInChildScope( + interpreter, + getSetTagForDeferredChildBindings( + interpreter, + currentImportAlias, + childBindings + ) + + output, + currentImportAlias + ) + initialPathSetter ); } - private static String wrapInChildScopeIfNecessary( + private static String wrapInChildScope( JinjavaInterpreter interpreter, String output, String currentImportAlias ) { String combined = output + getDoTagToPreserve(interpreter, currentImportAlias); // So that any set variables other than the alias won't exist outside the child's scope - if (interpreter.getContext().isDeferredExecutionMode()) { - return EagerReconstructionUtils.wrapInChildScope(combined, interpreter); - } - return combined; + return EagerReconstructionUtils.wrapInChildScope(combined, interpreter); } private String getSetTagForDeferredChildBindings( @@ -246,7 +251,11 @@ private String getSetTagForDeferredChildBindings( childBindings .entrySet() .stream() - .filter(entry -> entry.getValue() instanceof DeferredValue) + .filter( + entry -> + entry.getValue() instanceof DeferredValue && + ((DeferredValue) entry.getValue()).getOriginalValue() != null + ) .filter(entry -> !interpreter.getContext().containsKey(entry.getKey())) .filter(entry -> !entry.getKey().equals(currentImportAlias)) .collect( diff --git a/src/main/java/com/hubspot/jinjava/util/EagerReconstructionUtils.java b/src/main/java/com/hubspot/jinjava/util/EagerReconstructionUtils.java index f7f342f73..21ab191d2 100644 --- a/src/main/java/com/hubspot/jinjava/util/EagerReconstructionUtils.java +++ b/src/main/java/com/hubspot/jinjava/util/EagerReconstructionUtils.java @@ -790,7 +790,8 @@ public static Map reconstructDeferredReferences( * * When doing some eager execution and then needing to repeat the same execution in deferred execution mode. *

* * When rendering logic which takes place in its own child scope (for tag, macro function, set block) and there - * speculative bindings. These must be deferred and the execution must run again so they don't get reconstructed + * are speculative bindings. + * These must be deferred and the execution must run again, so they don't get reconstructed * within the child scope, and can instead be reconstructed in their original scopes. * @param interpreter The JinjavaInterpreter * @param eagerExecutionResult The execution result which contains information about which bindings were modified diff --git a/src/test/java/com/hubspot/jinjava/EagerTest.java b/src/test/java/com/hubspot/jinjava/EagerTest.java index fc0f96a3a..92a92b0ec 100644 --- a/src/test/java/com/hubspot/jinjava/EagerTest.java +++ b/src/test/java/com/hubspot/jinjava/EagerTest.java @@ -1145,7 +1145,7 @@ public void itHandlesHigherScopeReferenceModificationSecondPass() { @Test public void itHandlesReferenceModificationWhenSourceIsLost() { - expectedTemplateInterpreter.assertExpectedOutput( + expectedTemplateInterpreter.assertExpectedOutputNonIdempotent( "handles-reference-modification-when-source-is-lost" ); } @@ -1335,4 +1335,19 @@ public void itDoesNotReconstructVariableInSetInWrongScope() { "does-not-reconstruct-variable-in-set-in-wrong-scope" ); } + + @Test + public void itRreconstructsValueUsedInDeferredImportedMacro() { + expectedTemplateInterpreter.assertExpectedOutputNonIdempotent( + "reconstructs-value-used-in-deferred-imported-macro" + ); + } + + @Test + public void itRreconstructsValueUsedInDeferredImportedMacroSecondPass() { + interpreter.getContext().put("deferred", "resolved"); + expectedTemplateInterpreter.assertExpectedOutput( + "reconstructs-value-used-in-deferred-imported-macro.expected" + ); + } } diff --git a/src/test/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunctionTest.java b/src/test/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunctionTest.java index 8cf4d1881..149c1d8f5 100644 --- a/src/test/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunctionTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/fn/eager/EagerMacroFunctionTest.java @@ -65,6 +65,7 @@ public void itResolvesFromContext() { @Test public void itReconstructsForAliasedName() { + context.remove("deferred"); String name = "foo"; String fullName = "local." + name; String codeFormat = "{%% macro %s(bar) %%}It's: {{ bar }}{%% endmacro %%}"; diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerForTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerForTagTest.java index 4a7dd16fe..bbfb3c523 100644 --- a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerForTagTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerForTagTest.java @@ -11,6 +11,7 @@ import com.hubspot.jinjava.lib.tag.ForTagTest; import com.hubspot.jinjava.mode.EagerExecutionMode; import com.hubspot.jinjava.tree.parse.TagToken; +import java.util.List; import java.util.Optional; import org.junit.After; import org.junit.Before; @@ -215,7 +216,7 @@ public void itDefersLoopVariable() { } @Test - public void itDoesNotSwallowDeferredValueException() { + public void itCanNowHandleModificationInPartiallyDeferredLoop() { interpreter.getContext().registerTag(new EagerDoTag()); interpreter.getContext().registerTag(new EagerIfTag()); interpreter.getContext().registerTag(new EagerSetTag()); @@ -223,18 +224,29 @@ public void itDoesNotSwallowDeferredValueException() { String input = "{% set my_list = [] %}" + "{% for i in range(401) %}" + - "{{ my_list.append(i) }}" + + "{% do my_list.append(i) %}" + "{% endfor %}" + - "{% for i in my_list.append(1) ? [0, 1] : [0] %}" + + "{% for i in my_list.append(-1) ? [0, 1] : [0] %}" + "{% for j in deferred %}" + "{% if loop.first %}" + - "{% do my_list.append(1) %}" + + "{% do my_list.append(i) %}" + "{% endif %}" + "{% endfor %}" + "{% endfor %}" + "{{ my_list }}"; - interpreter.render(input); - assertThat(interpreter.getContext().getDeferredNodes()).isNotEmpty(); + String initialResult = interpreter.render(input); + assertThat(interpreter.getContext().getDeferredNodes()).isEmpty(); + interpreter.getContext().put("deferred", ImmutableList.of(1, 2)); + interpreter.render(initialResult); + assertThat(interpreter.getContext().get("my_list")).isInstanceOf(List.class); + assertThat((List) interpreter.getContext().get("my_list")) + .as( + "Appends 401 numbers and then appends '-1', running the 'i' loop twice," + + "which runs the 'j' loop, the first time appending the value of 'i', which will be '0', then '1'" + ) + .hasSize(404) + .containsSequence(400L, -1L, 0L, 1L); + assertThat(interpreter.getContext().getDeferredNodes()).isEmpty(); } public static boolean inForLoop() { diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java index 2c9b2c36c..03e1d5ef9 100644 --- a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java @@ -462,7 +462,7 @@ public void itHandlesQuadLayerInDeferredIf() { ); assertThat(result) .isEqualTo( - "{% if deferred %}{% do %}{% set current_path = 'import-tree-b.jinja' %}{% set a,foo_b = {'foo_a': 'a', 'import_resource_path': 'import-tree-a.jinja', 'something': 'somn'} ,null %}{% set b = {} %}{% for __ignored__ in [0] %}{% do %}{% set current_path = 'import-tree-a.jinja' %}{% set a = {} %}{% for __ignored__ in [0] %}{% set something = 'somn' %}{% do a.update({'something': something}) %}\n" + + "{% if deferred %}{% do %}{% set current_path = 'import-tree-b.jinja' %}{% set b = {} %}{% for __ignored__ in [0] %}{% set a = {'foo_a': 'a', 'import_resource_path': 'import-tree-a.jinja', 'something': 'somn'} %}{% do %}{% set current_path = 'import-tree-a.jinja' %}{% set a = {} %}{% for __ignored__ in [0] %}{% set something = 'somn' %}{% do a.update({'something': something}) %}\n" + "{% set foo_a = 'a' %}{% do a.update({'foo_a': foo_a}) %}\n" + "{% do a.update({'foo_a': 'a','import_resource_path': 'import-tree-a.jinja','something': 'somn'}) %}{% endfor %}{% set current_path = 'import-tree-b.jinja' %}{% enddo %}\n" + "{% set foo_b = 'b' + a.foo_a %}{% do b.update({'foo_b': foo_b}) %}\n" + diff --git a/src/test/resources/eager/correctly-defers-with-multiple-loops.expected.jinja b/src/test/resources/eager/correctly-defers-with-multiple-loops.expected.jinja index 234c34f0c..06e64b7fd 100644 --- a/src/test/resources/eager/correctly-defers-with-multiple-loops.expected.jinja +++ b/src/test/resources/eager/correctly-defers-with-multiple-loops.expected.jinja @@ -1,4 +1,8 @@ -{% set my_list = [] %}{% for i in [0, 1] %} +{% set my_list = [] %}{% for __ignored__ in [0] %} +{% for j in deferred %} +{% do my_list.append(0) %} +{% endfor %} + {% for j in deferred %} {% do my_list.append(1) %} {% endfor %} diff --git a/src/test/resources/eager/correctly-defers-with-multiple-loops.jinja b/src/test/resources/eager/correctly-defers-with-multiple-loops.jinja index b4ef48b42..4fcea761c 100644 --- a/src/test/resources/eager/correctly-defers-with-multiple-loops.jinja +++ b/src/test/resources/eager/correctly-defers-with-multiple-loops.jinja @@ -1,7 +1,7 @@ {% set my_list = [] %} {% for i in range(2) %} {% for j in deferred %} -{% do my_list.append(1) %} +{% do my_list.append(i) %} {% endfor %} {% endfor %} {{ my_list }} \ No newline at end of file diff --git a/src/test/resources/eager/does-not-override-import-modification-in-for.expected.jinja b/src/test/resources/eager/does-not-override-import-modification-in-for.expected.jinja index d0600b6cf..dfd7333f5 100644 --- a/src/test/resources/eager/does-not-override-import-modification-in-for.expected.jinja +++ b/src/test/resources/eager/does-not-override-import-modification-in-for.expected.jinja @@ -1,4 +1,23 @@ -{% set foo = 'start' %}{% for i in [0, 1] %} +{% set foo = 'start' %}{% for __ignored__ in [0] %} +{% do %}{% set current_path = 'deferred-modification.jinja' %}{% set bar1 = {} %}{% for __ignored__ in [0] %}{% if deferred %} + +{% set foo = 'starta' %}{% do bar1.update({'foo': foo}) %} + +{% endif %} + +{% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}{% do bar1.update({'foo': foo}) %} +{% do bar1.update({'foo': foo,'import_resource_path': 'deferred-modification.jinja'}) %}{% endfor %}{% set current_path = '' %}{% enddo %} +{{ bar1.foo }} +{% do %}{% set current_path = 'deferred-modification.jinja' %}{% set bar2 = {} %}{% for __ignored__ in [0] %}{% if deferred %} + +{% set foo = filter:join.filter([foo, 'a'], ____int3rpr3t3r____, '') %}{% do bar2.update({'foo': foo}) %} + +{% endif %} + +{% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}{% do bar2.update({'foo': foo}) %} +{% do bar2.update({'import_resource_path': 'deferred-modification.jinja'}) %}{% endfor %}{% set current_path = '' %}{% enddo %} +{{ bar2.foo }} + {% do %}{% set current_path = 'deferred-modification.jinja' %}{% set bar1 = {} %}{% for __ignored__ in [0] %}{% if deferred %} {% set foo = filter:join.filter([foo, 'a'], ____int3rpr3t3r____, '') %}{% do bar1.update({'foo': foo}) %} diff --git a/src/test/resources/eager/handles-deferred-import-vars.expected.jinja b/src/test/resources/eager/handles-deferred-import-vars.expected.jinja index 1b388c631..0e0fad4fb 100644 --- a/src/test/resources/eager/handles-deferred-import-vars.expected.jinja +++ b/src/test/resources/eager/handles-deferred-import-vars.expected.jinja @@ -4,8 +4,8 @@ Hello {{ myname }} {% enddo %}foo: Hello {{ myname }} bar: {{ bar }} --- -{% set myname = deferred + 7 %}{% do %}{% set current_path = 'macro-and-set.jinja' %}{% set simple = {} %} +{% set myname = deferred + 7 %}{% do %}{% set current_path = 'macro-and-set.jinja' %}{% set simple = {} %}{% for __ignored__ in [0] %} {% set bar = myname + 19 %}{% do simple.update({'bar': bar}) %} Hello {{ myname }} -{% do simple.update({'import_resource_path': 'macro-and-set.jinja'}) %}{% set current_path = '' %}{% enddo %}simple.foo: {% set deferred_import_resource_path = 'macro-and-set.jinja' %}{% macro simple.foo() %}Hello {{ myname }}{% endmacro %}{% set deferred_import_resource_path = null %}{{ simple.foo() }} +{% do simple.update({'import_resource_path': 'macro-and-set.jinja'}) %}{% endfor %}{% set current_path = '' %}{% enddo %}simple.foo: {% set deferred_import_resource_path = 'macro-and-set.jinja' %}{% macro simple.foo() %}Hello {{ myname }}{% endmacro %}{% set deferred_import_resource_path = null %}{{ simple.foo() }} simple.bar: {{ simple.bar }} diff --git a/src/test/resources/eager/handles-double-import-modification.expected.jinja b/src/test/resources/eager/handles-double-import-modification.expected.jinja index 2a509a076..aa3ca4b19 100644 --- a/src/test/resources/eager/handles-double-import-modification.expected.jinja +++ b/src/test/resources/eager/handles-double-import-modification.expected.jinja @@ -1,20 +1,20 @@ -{% do %}{% set current_path = 'deferred-modification.jinja' %}{% set foo = null %}{% set bar1 = {} %}{% if deferred %} +{% do %}{% set current_path = 'deferred-modification.jinja' %}{% set bar1 = {} %}{% for __ignored__ in [0] %}{% if deferred %} {% set foo = 'a' %}{% do bar1.update({'foo': foo}) %} {% endif %} {% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}{% do bar1.update({'foo': foo}) %} -{% do bar1.update({'foo': foo,'import_resource_path': 'deferred-modification.jinja'}) %}{% set current_path = '' %}{% enddo %} +{% do bar1.update({'foo': foo,'import_resource_path': 'deferred-modification.jinja'}) %}{% endfor %}{% set current_path = '' %}{% enddo %} --- -{% do %}{% set current_path = 'deferred-modification.jinja' %}{% set foo = null %}{% set bar2 = {} %}{% if deferred %} +{% do %}{% set current_path = 'deferred-modification.jinja' %}{% set bar2 = {} %}{% for __ignored__ in [0] %}{% if deferred %} {% set foo = 'a' %}{% do bar2.update({'foo': foo}) %} {% endif %} {% set foo = filter:join.filter([foo, 'b'], ____int3rpr3t3r____, '') %}{% do bar2.update({'foo': foo}) %} -{% do bar2.update({'foo': foo,'import_resource_path': 'deferred-modification.jinja'}) %}{% set current_path = '' %}{% enddo %} +{% do bar2.update({'foo': foo,'import_resource_path': 'deferred-modification.jinja'}) %}{% endfor %}{% set current_path = '' %}{% enddo %} --- {{ bar1.foo }} {{ bar2.foo }} diff --git a/src/test/resources/eager/handles-higher-scope-reference-modification.expected.jinja b/src/test/resources/eager/handles-higher-scope-reference-modification.expected.jinja index dd63c3d58..742584329 100644 --- a/src/test/resources/eager/handles-higher-scope-reference-modification.expected.jinja +++ b/src/test/resources/eager/handles-higher-scope-reference-modification.expected.jinja @@ -4,7 +4,7 @@ C: {{ c_list }}.{% endmacro %}{{ c(b_list) }}{% set b_list = a_list %}{% do b_li B: {% set b_list = a_list %}{{ b_list }}.{% endset %}{{ __macro_b_125206_temp_variable_0__ }}{% do a_list.append(deferred ? 'A' : '') %} A: {{ a_list }}. --- -{% set a_list = ['a'] %}{% for i in [0] %}{% set b_list = a_list %}{% do b_list.append('b') %}{% for __ignored__ in [0] %}{% set c_list = b_list %}{% do c_list.append(deferred ? 'c' : '') %} -C: {{ c_list }}.{% endfor %}{% do b_list.append(deferred ? 'B' : '') %} -B: {{ b_list }}.{% endfor %}{% do a_list.append(deferred ? 'A' : '') %} +{% set a_list = ['a', 'b'] %}{% for __ignored__ in [0] %}{% for __ignored__ in [0] %}{% set c_list = a_list %}{% do c_list.append(deferred ? 'c' : '') %} +C: {% set c_list = a_list %}{{ c_list }}.{% endfor %}{% set b_list = a_list %}{% do b_list.append(deferred ? 'B' : '') %} +B: {% set b_list = a_list %}{{ b_list }}.{% endfor %}{% do a_list.append(deferred ? 'A' : '') %} A: {{ a_list }}. diff --git a/src/test/resources/eager/handles-reference-modification-when-source-is-lost.expected.jinja b/src/test/resources/eager/handles-reference-modification-when-source-is-lost.expected.jinja index 5d7fc15ac..3001accc0 100644 --- a/src/test/resources/eager/handles-reference-modification-when-source-is-lost.expected.jinja +++ b/src/test/resources/eager/handles-reference-modification-when-source-is-lost.expected.jinja @@ -1,13 +1,13 @@ -{% set a_list = ['a'] %}{% for i in [0] %}{% set b_list = a_list %}{% do b_list.append(deferred) %} +{% set a_list = ['a'] %}{% for __ignored__ in [0] %}{% set b_list = a_list %}{% do b_list.append(deferred) %} {% endfor %} {{ a_list }} --- {% for __ignored__ in [0] %} -{% set a_list = [] %}{% for i in [0] %} +{% set a_list = [] %}{% for __ignored__ in [0] %} {% if deferred %} -{% set b_list = a_list %} -{% do b_list.append(1) %} +{% set b_list = [] %} +{% set b_list = a_list %}{% do b_list.append(1) %} {% endif %} {% endfor %} {{ a_list }} diff --git a/src/test/resources/eager/reconstructs-map-node.expected.expected.jinja b/src/test/resources/eager/reconstructs-map-node.expected.expected.jinja index aea750573..e66867c56 100644 --- a/src/test/resources/eager/reconstructs-map-node.expected.expected.jinja +++ b/src/test/resources/eager/reconstructs-map-node.expected.expected.jinja @@ -1,3 +1,5 @@ +First key is foo. + foo ff bar bb ['resolved', 'resolved'] diff --git a/src/test/resources/eager/reconstructs-map-node.expected.jinja b/src/test/resources/eager/reconstructs-map-node.expected.jinja index a1d272f5d..98b3f99fe 100644 --- a/src/test/resources/eager/reconstructs-map-node.expected.jinja +++ b/src/test/resources/eager/reconstructs-map-node.expected.jinja @@ -1,7 +1,13 @@ -{% set my_list = [] %}{% for key, val in [fn:map_entry('foo', 'ff'), fn:map_entry('bar', 'bb')] %}{% do my_list.append(deferred) %} -{{ key ~ ' ' ~ val }}{% endfor %} +{% if deferred %} +{% set foo = [fn:map_entry('foo', 'ff'), fn:map_entry('bar', 'bb')] %} +{% endif %} +First key is {{ foo[0].key }}. +{% set my_list = [] %}{% for __ignored__ in [0] %}{% do my_list.append(deferred) %} +foo ff{% do my_list.append(deferred) %} +bar bb{% endfor %} {{ my_list }} -{% set my_list = [] %}{% for i in [fn:map_entry('foo', 'ff'), fn:map_entry('bar', 'bb')] %}{% do my_list.append(deferred) %} -{{ i.key }}{% endfor %} -{{ my_list }} +{% set my_list = [] %}{% for __ignored__ in [0] %}{% do my_list.append(deferred) %} +foo{% do my_list.append(deferred) %} +bar{% endfor %} +{{ my_list }} \ No newline at end of file diff --git a/src/test/resources/eager/reconstructs-map-node.jinja b/src/test/resources/eager/reconstructs-map-node.jinja index 1a3858aea..80acfeb80 100644 --- a/src/test/resources/eager/reconstructs-map-node.jinja +++ b/src/test/resources/eager/reconstructs-map-node.jinja @@ -1,4 +1,8 @@ {% set my_list = [] %} +{% if deferred %} +{% set foo = {'foo': 'ff', 'bar': 'bb'}.items() %} +{% endif %} +First key is {{ foo[0].key }}. {% for key, val in {'foo': 'ff', 'bar': 'bb'}.items() -%} {% do my_list.append(deferred) %} {{ key ~ ' ' ~ val }} diff --git a/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.expected.expected.jinja b/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.expected.expected.jinja new file mode 100644 index 000000000..2d6f0822a --- /dev/null +++ b/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.expected.expected.jinja @@ -0,0 +1,4 @@ +resolved 1 + + +resolved resolved diff --git a/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.expected.jinja b/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.expected.jinja new file mode 100644 index 000000000..235a17e0f --- /dev/null +++ b/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.expected.jinja @@ -0,0 +1,10 @@ +{% do %}{% set current_path = 'uses-deferred-value-in-macro.jinja' %}{% set macros = {} %}{% for __ignored__ in [0] %}{% set value = deferred %}{% do macros.update({'value': value}) %} + +{% do macros.update({'import_resource_path': 'uses-deferred-value-in-macro.jinja','value': value}) %}{% endfor %}{% set current_path = '' %}{% enddo %} + +{% set deferred_import_resource_path = 'uses-deferred-value-in-macro.jinja' %}{% macro macros.getValueAnd(other) %}{% set value = macros.value %} +{{ value ~ ' ' ~ other }} +{% endmacro %}{% set deferred_import_resource_path = null %}{{ macros.getValueAnd(1) }} +{% set deferred_import_resource_path = 'uses-deferred-value-in-macro.jinja' %}{% macro macros.getValueAnd(other) %}{% set value = macros.value %} +{{ value ~ ' ' ~ other }} +{% endmacro %}{% set deferred_import_resource_path = null %}{{ macros.getValueAnd(deferred) }} diff --git a/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.jinja b/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.jinja new file mode 100644 index 000000000..d335a751b --- /dev/null +++ b/src/test/resources/eager/reconstructs-value-used-in-deferred-imported-macro.jinja @@ -0,0 +1,4 @@ +{% import 'uses-deferred-value-in-macro.jinja' as macros %} + +{{ macros.getValueAnd(1) }} +{{ macros.getValueAnd(deferred) }} diff --git a/src/test/resources/eager/reverts-modification-with-deferred-loop.expected.jinja b/src/test/resources/eager/reverts-modification-with-deferred-loop.expected.jinja index 0f3b95d12..f1de11b60 100644 --- a/src/test/resources/eager/reverts-modification-with-deferred-loop.expected.jinja +++ b/src/test/resources/eager/reverts-modification-with-deferred-loop.expected.jinja @@ -1,4 +1,10 @@ -{% set my_list = [] %}{% for i in [0, 1] %} +{% set my_list = [] %}{% for __ignored__ in [0] %} +{% for j in deferred %} +{% if loop.first %} +{% do my_list.append(1) %} +{% endif %} +{% endfor %} + {% for j in deferred %} {% if loop.first %} {% do my_list.append(1) %} diff --git a/src/test/resources/tags/macrotag/uses-deferred-value-in-macro.jinja b/src/test/resources/tags/macrotag/uses-deferred-value-in-macro.jinja new file mode 100644 index 000000000..0c19fc60a --- /dev/null +++ b/src/test/resources/tags/macrotag/uses-deferred-value-in-macro.jinja @@ -0,0 +1,4 @@ +{% set value = deferred %} +{% macro getValueAnd(other) %} +{{ value ~ ' ' ~ other }} +{% endmacro %}