From 96c0dcb30a111fc23eef3d90def803ad285fa2a0 Mon Sep 17 00:00:00 2001 From: Jesse Costello-Good Date: Wed, 20 May 2026 10:31:51 -0700 Subject: [PATCH] Internal PiperOrigin-RevId: 918525223 --- .../soy/passes/LocalVariablesNodeVisitor.java | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/java/src/com/google/template/soy/passes/LocalVariablesNodeVisitor.java b/java/src/com/google/template/soy/passes/LocalVariablesNodeVisitor.java index 16eb6342e..a7c0a32bd 100644 --- a/java/src/com/google/template/soy/passes/LocalVariablesNodeVisitor.java +++ b/java/src/com/google/template/soy/passes/LocalVariablesNodeVisitor.java @@ -52,6 +52,7 @@ import com.google.template.soy.soytree.defn.SymbolVar.SymbolKind; import com.google.template.soy.soytree.defn.TemplateHeaderVarDefn; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Deque; import java.util.LinkedHashMap; import java.util.List; @@ -194,25 +195,38 @@ protected void visitSoyFileNode(SoyFileNode node) { localVariables.errorReporter = getErrorReporter(); localVariables.enterScope(); - // Define all templates in scope before visiting them so that any template can reference - // any other template. - for (TemplateNode template : node.getTemplates()) { - localVariables.define(template.asVarDefn(), template); - } - - // Allow forward references between externs. - for (ExternNode extern : node.getExterns()) { - VarDefn var = extern.getVar(); - VarDefn preexisting = localVariables.lookup(var.refName()); - if (preexisting instanceof SymbolVar symbolVar - && symbolVar.getSymbolKind() == SymbolKind.EXTERN) { - // Allow multiple externs with the same name. - continue; + List constNodes = new ArrayList<>(); + List scopedChildren = new ArrayList<>(); + for (SoyNode child : node.getChildren()) { + // Step 1: "Hoist" (like TSJS) function-like Soy nodes -- {template} and {extern} -- before + // visiting their bodies. Allows for forward and backward references between these nodes. + if (child instanceof TemplateNode template) { + localVariables.define(template.asVarDefn(), template); + scopedChildren.add(template); + } else if (child instanceof ExternNode extern) { + // Allow forward references between externs. + VarDefn var = extern.getVar(); + VarDefn preexisting = localVariables.lookup(var.refName()); + if (preexisting instanceof SymbolVar symbolVar + && symbolVar.getSymbolKind() == SymbolKind.EXTERN) { + // Allow multiple externs with the same name. + continue; + } + localVariables.define(var, node); + scopedChildren.add(extern); + } else if (child instanceof ConstNode constNode) { + constNodes.add(constNode); + } else { + visit(child); } - localVariables.define(var, node); } - super.visitSoyFileNode(node); + // Step 2: Define file-level constants. + constNodes.forEach(this::visit); + + // Step 3: Visit the bodies of templates and externs. + scopedChildren.forEach(this::visit); + if (cleanUpFileScope()) { localVariables.exitScope(); localVariables = null;