From fa6237dd5416b12ae5d483d4cf542d129665d807 Mon Sep 17 00:00:00 2001 From: jasmith-hs Date: Fri, 18 Sep 2026 15:20:26 -0400 Subject: [PATCH] fix(eager): don't defer meta-context variables during deferred import The flat/no-alias branch of ImportTag.handleDeferredNodesDuringImport wrapped every child binding in a DeferredValue when propagating it to the parent context. This included meta-context variables such as import_resource_path, whose concrete String value was replaced with a value-less DeferredValue, later causing a ClassCastException / loss of the import path in EagerMacroFunction.reconstructImage. Meta-context variables are now skipped entirely, matching the non-deferred integrateChild path which already excludes import_resource_path from parent propagation (it stays alive only in the macro's captured localContextScope). reconstructImage additionally unwraps a DeferredValue defensively before the String cast. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../lib/fn/eager/EagerMacroFunction.java | 10 +++++--- .../hubspot/jinjava/lib/tag/ImportTag.java | 4 ++++ .../lib/tag/eager/EagerImportTagTest.java | 23 +++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) 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 18a649794..d761696d0 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 @@ -237,9 +237,13 @@ public String reconstructImage(String fullName) { String suffix = ""; JinjavaInterpreter interpreter = JinjavaInterpreter.getCurrent(); - Optional importFile = Optional.ofNullable( - (String) localContextScope.get(Context.IMPORT_RESOURCE_PATH_KEY) - ); + Object importPath = localContextScope.get(Context.IMPORT_RESOURCE_PATH_KEY); + if (importPath instanceof DeferredValue) { + importPath = ((DeferredValue) importPath).getOriginalValue(); + } + Optional importFile = importPath instanceof String + ? Optional.of((String) importPath) + : Optional.empty(); Object currentDeferredImportResource = null; if (importFile.isPresent()) { currentDeferredImportResource = diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java index 4ce3c9d27..49832f2c5 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/ImportTag.java @@ -13,6 +13,7 @@ import com.hubspot.jinjava.interpret.DeferredValueException; import com.hubspot.jinjava.interpret.InterpretException; import com.hubspot.jinjava.interpret.JinjavaInterpreter; +import com.hubspot.jinjava.interpret.MetaContextVariables; import com.hubspot.jinjava.interpret.TagCycleException; import com.hubspot.jinjava.interpret.TemplateError; import com.hubspot.jinjava.interpret.TemplateError.ErrorItem; @@ -212,6 +213,9 @@ public static void handleDeferredNodesDuringImport( } childBindings.remove(Context.GLOBAL_MACROS_SCOPE_KEY); childBindings.forEach((key, value) -> { + if (MetaContextVariables.isMetaContextVariable(key, interpreter.getContext())) { + return; + } Object originalValue = value instanceof DeferredValue ? ((DeferredValue) value).getOriginalValue() : null; 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 6e082e9e8..42fdab34f 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 @@ -247,6 +247,29 @@ public void itPreservesOriginalValueOfDeferredImportedBindings() { .isNull(); } + @Test + public void itDoesNotDeferMetaContextVariablesDuringDeferredImport() { + JinjavaInterpreter child = getChildInterpreter(interpreter, ""); + Map childBindings = new HashMap<>(); + childBindings.put(Context.IMPORT_RESOURCE_PATH_KEY, TEMPLATE_FILE); + childBindings.put("request", DeferredValue.instance("the real request")); + + ImportTag.handleDeferredNodesDuringImport( + interpreter.parse(""), + "", + childBindings, + child, + interpreter + ); + + assertThat(interpreter.getContext().get(Context.IMPORT_RESOURCE_PATH_KEY)).isNull(); + assertThat(interpreter.getContext().get("request")).isInstanceOf(DeferredValue.class); + assertThat( + ((DeferredValue) interpreter.getContext().get("request")).getOriginalValue() + ) + .isEqualTo("the real request"); + } + @Test @SuppressWarnings("unchecked") public void itHandlesMultiLayerSomeAliased() {