bazel syntax: avoid StarlarkThread.getGlobals within lib.syntax

Another baby step to decoupling these classes: this change exposes a
Module parameter from various lib.syntax API functions instead of
using thread.getGlobals(), in most cases pushing the call into the
callers.

Construction of the thread and module is still entangled so we can't
do more yet.

Also:
- rename parseAndValidate{,Skylark} and
  replace thread by semantics parameter.
  Now ValidatorTest doesn't depend on Thread.

This is a breaking API change for copybara.

PiperOrigin-RevId: 294764678
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index b807a83..237f0fa 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -983,10 +983,10 @@
               .setImportedExtensions(imports)
               .build();
       thread.setPrintHandler(StarlarkThread.makeDebugPrintHandler(pkgContext.eventHandler));
+      Module module = thread.getGlobals();
 
       // Validate.
-      ValidationEnvironment.validateFile(
-          file, thread.getGlobals(), semantics, /*isBuildFile=*/ true);
+      ValidationEnvironment.validateFile(file, module, semantics, /*isBuildFile=*/ true);
       if (!file.ok()) {
         Event.replayEventsOn(pkgContext.eventHandler, file.errors());
         return false;
@@ -1041,7 +1041,7 @@
 
       // Execute.
       try {
-        EvalUtils.exec(file, thread);
+        EvalUtils.exec(file, module, thread);
       } catch (EvalException ex) {
         pkgContext.eventHandler.handle(Event.error(ex.getLocation(), ex.getMessage()));
         return false;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
index b3cc560..ad69a34 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
@@ -171,6 +171,7 @@
     thread.setThreadLocal(
         PackageFactory.PackageContext.class,
         new PackageFactory.PackageContext(builder, null, localReporter));
+    Module module = thread.getGlobals();
 
     // The workspace environment doesn't need the tools repository or the fragment map
     // because executing workspace rules happens before analysis and it doesn't need a
@@ -194,7 +195,7 @@
     } else if (PackageFactory.checkBuildSyntax(
         file, globs, globs, new HashMap<>(), localReporter)) {
       try {
-        EvalUtils.exec(file, thread);
+        EvalUtils.exec(file, module, thread);
       } catch (EvalException ex) {
         localReporter.handle(Event.error(ex.getLocation(), ex.getMessage()));
       }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/ResolvedFileFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/ResolvedFileFunction.java
index 33fbc46..fbd824e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/repository/ResolvedFileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/ResolvedFileFunction.java
@@ -82,7 +82,7 @@
                   .build();
           resolvedModule = thread.getGlobals();
           try {
-            EvalUtils.exec(file, thread);
+            EvalUtils.exec(file, resolvedModule, thread);
           } catch (EvalException ex) {
             env.getListener().handle(Event.error(ex.getLocation(), ex.getMessage()));
             throw resolvedValueError("Failed to evaluate resolved file " + key.getPath());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
index 59edf49..cc95398 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
@@ -635,7 +635,7 @@
         });
 
     try {
-      EvalUtils.exec(file, thread);
+      EvalUtils.exec(file, thread.getGlobals(), thread);
     } catch (EvalException ex) {
       handler.handle(Event.error(ex.getLocation(), ex.getMessage()));
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
index 9033bbf..3848eba 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
@@ -298,7 +298,7 @@
       servicingEvalRequest.set(true);
 
       ParserInput input = ParserInput.create(content, "<debug eval>");
-      Object x = EvalUtils.execAndEvalOptionalFinalExpression(input, thread);
+      Object x = EvalUtils.execAndEvalOptionalFinalExpression(input, thread.getGlobals(), thread);
       return x != null ? x : Starlark.NONE;
     } finally {
       servicingEvalRequest.set(false);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
index 2635576..2fa19df 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
@@ -745,36 +745,36 @@
   }
 
   /**
-   * Parses the input as a file, validates it in the {@code thread.getGlobals} environment using
-   * options defined by {@code thread.getSemantics}, and returns the syntax tree. It uses Starlark
-   * (not BUILD) validation semantics.
+   * Parses the input as a file, validates it in the module environment using options defined by
+   * {@code thread.getSemantics}, and returns the syntax tree. It uses Starlark (not BUILD)
+   * validation semantics.
    *
    * <p>The thread is primarily used for its Module. Scan/parse/validate errors are recorded in the
    * StarlarkFile. It is the caller's responsibility to inspect them.
    */
-  public static StarlarkFile parseAndValidateSkylark(ParserInput input, StarlarkThread thread) {
+  public static StarlarkFile parseAndValidate(
+      ParserInput input, Module module, StarlarkSemantics semantics) {
     StarlarkFile file = StarlarkFile.parse(input);
-    ValidationEnvironment.validateFile(
-        file, thread.getGlobals(), thread.getSemantics(), /*isBuildFile=*/ false);
+    ValidationEnvironment.validateFile(file, module, semantics, /*isBuildFile=*/ false);
     return file;
   }
 
   /**
-   * Parses the input as a file, validates it in the {@code thread.getGlobals} environment using
-   * options defined by {@code thread.getSemantics}, and executes it. It uses Starlark (not BUILD)
-   * validation semantics.
+   * Parses the input as a file, validates it in the module environment using options defined by
+   * {@code thread.getSemantics}, and executes it. It uses Starlark (not BUILD) validation
+   * semantics.
    */
-  public static void exec(ParserInput input, StarlarkThread thread)
+  public static void exec(ParserInput input, Module module, StarlarkThread thread)
       throws SyntaxError, EvalException, InterruptedException {
-    StarlarkFile file = parseAndValidateSkylark(input, thread);
+    StarlarkFile file = parseAndValidate(input, module, thread.getSemantics());
     if (!file.ok()) {
       throw new SyntaxError(file.errors());
     }
-    exec(file, thread);
+    exec(file, module, thread);
   }
 
   /** Executes a parsed, validated Starlark file in a given StarlarkThread. */
-  public static void exec(StarlarkFile file, StarlarkThread thread)
+  public static void exec(StarlarkFile file, Module module, StarlarkThread thread)
       throws EvalException, InterruptedException {
     StarlarkFunction toplevel =
         new StarlarkFunction(
@@ -783,7 +783,7 @@
             FunctionSignature.NOARGS,
             /*defaultValues=*/ Tuple.empty(),
             file.getStatements(),
-            thread.getGlobals());
+            module);
     // Hack: assume unresolved identifiers are globals.
     toplevel.isToplevel = true;
 
@@ -791,14 +791,14 @@
   }
 
   /**
-   * Parses the input as an expression, validates it in the {@code thread.getGlobals} environment
-   * using options defined by {@code thread.getSemantics}, and evaluates it. It uses Starlark (not
-   * BUILD) validation semantics.
+   * Parses the input as an expression, validates it in the module environment using options defined
+   * by {@code thread.getSemantics}, and evaluates it. It uses Starlark (not BUILD) validation
+   * semantics.
    */
-  public static Object eval(ParserInput input, StarlarkThread thread)
+  public static Object eval(ParserInput input, Module module, StarlarkThread thread)
       throws SyntaxError, EvalException, InterruptedException {
     Expression expr = Expression.parse(input);
-    ValidationEnvironment.validateExpr(expr, thread.getGlobals(), thread.getSemantics());
+    ValidationEnvironment.validateExpr(expr, module, thread.getSemantics());
 
     // Turn expression into a no-arg StarlarkFunction and call it.
     StarlarkFunction fn =
@@ -808,27 +808,27 @@
             FunctionSignature.NOARGS,
             /*defaultValues=*/ Tuple.empty(),
             ImmutableList.<Statement>of(new ReturnStatement(expr)),
-            thread.getGlobals());
+            module);
 
     return Starlark.fastcall(thread, fn, NOARGS, NOARGS);
   }
 
   /**
-   * Parses the input as a file, validates it in the {@code thread.getGlobals} environment using
-   * options defined by {@code thread.getSemantics}, and executes it. The function uses Starlark
-   * (not BUILD) validation semantics. If the final statement is an expression statement, it returns
-   * the value of that expression, otherwise it returns null.
+   * Parses the input as a file, validates it in the module environment using options defined by
+   * {@code thread.getSemantics}, and executes it. The function uses Starlark (not BUILD) validation
+   * semantics. If the final statement is an expression statement, it returns the value of that
+   * expression, otherwise it returns null.
    *
    * <p>The function's name is intentionally unattractive. Don't call it unless you're accepting
    * strings from an interactive user interface such as a REPL or debugger; use {@link #exec} or
    * {@link #eval} instead.
    */
   @Nullable
-  public static Object execAndEvalOptionalFinalExpression(ParserInput input, StarlarkThread thread)
+  public static Object execAndEvalOptionalFinalExpression(
+      ParserInput input, Module module, StarlarkThread thread)
       throws SyntaxError, EvalException, InterruptedException {
     StarlarkFile file = StarlarkFile.parse(input);
-    ValidationEnvironment.validateFile(
-        file, thread.getGlobals(), thread.getSemantics(), /*isBuildFile=*/ false);
+    ValidationEnvironment.validateFile(file, module, thread.getSemantics(), /*isBuildFile=*/ false);
     if (!file.ok()) {
       throw new SyntaxError(file.errors());
     }
@@ -853,7 +853,7 @@
             FunctionSignature.NOARGS,
             /*defaultValues=*/ Tuple.empty(),
             stmts,
-            thread.getGlobals());
+            module);
     // Hack: assume unresolved identifiers are globals.
     toplevel.isToplevel = true;
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java b/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java
index 22a2483..b05c709 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java
@@ -191,6 +191,7 @@
               .useDefaultSemantics()
               .setGlobals(Module.createForBuiltins(Starlark.UNIVERSE))
               .build();
+      Module module = thread.getGlobals();
 
       // Disable polling of the java.lang.Thread.interrupt flag during
       // Starlark evaluation. Assuming the expression does not call a
@@ -214,7 +215,7 @@
       // See https://docs.oracle.com/javase/specs/jls/se12/html/jls-12.html#jls-12.4
       thread.ignoreThreadInterrupts();
 
-      x = EvalUtils.eval(ParserInput.fromLines(expr), thread);
+      x = EvalUtils.eval(ParserInput.fromLines(expr), module, thread);
     } catch (InterruptedException ex) {
       throw new IllegalStateException(ex); // can't happen
     } catch (SyntaxError | EvalException ex) {
diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
index b834d8f..efb8ecc 100644
--- a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
+++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
@@ -512,9 +512,10 @@
             semantics,
             globalFrame(ruleInfoList, providerInfoList, aspectInfoList),
             imports);
+    Module module = thread.getGlobals();
 
     try {
-      EvalUtils.exec(file, thread);
+      EvalUtils.exec(file, module, thread);
     } catch (EvalException | InterruptedException ex) {
       // This exception class seems a bit unnecessary. Replace with EvalException?
       throw new StarlarkEvaluationException("Starlark evaluation error", ex);
diff --git a/src/main/java/com/google/devtools/starlark/cmd/Starlark.java b/src/main/java/com/google/devtools/starlark/cmd/Starlark.java
index 907dba6..91ea8c6 100644
--- a/src/main/java/com/google/devtools/starlark/cmd/Starlark.java
+++ b/src/main/java/com/google/devtools/starlark/cmd/Starlark.java
@@ -43,17 +43,19 @@
   private static final Charset CHARSET = StandardCharsets.ISO_8859_1;
   private final BufferedReader reader =
       new BufferedReader(new InputStreamReader(System.in, CHARSET));
-  private final Mutability mutability = Mutability.create("interpreter");
   private final StarlarkThread thread;
+  private final Module module;
 
   {
     thread =
-        StarlarkThread.builder(mutability)
+        StarlarkThread.builder(Mutability.create("interpreter"))
             .useDefaultSemantics()
             .setGlobals(
                 Module.createForBuiltins(com.google.devtools.build.lib.syntax.Starlark.UNIVERSE))
             .build();
     thread.setPrintHandler((th, msg) -> System.out.println(msg));
+
+    module = thread.getGlobals();
   }
 
   private String prompt() {
@@ -92,7 +94,7 @@
     while ((line = prompt()) != null) {
       ParserInput input = ParserInput.create(line, "<stdin>");
       try {
-        Object result = EvalUtils.execAndEvalOptionalFinalExpression(input, thread);
+        Object result = EvalUtils.execAndEvalOptionalFinalExpression(input, module, thread);
         if (result != null) {
           System.out.println(com.google.devtools.build.lib.syntax.Starlark.repr(result));
         }
@@ -123,7 +125,7 @@
   /** Execute a Starlark file. */
   private int execute(String filename, String content) {
     try {
-      EvalUtils.exec(ParserInput.create(content, filename), thread);
+      EvalUtils.exec(ParserInput.create(content, filename), module, thread);
       return 0;
     } catch (SyntaxError ex) {
       for (Event ev : ex.errors()) {
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContextTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContextTest.java
index e886a73..11c4116 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContextTest.java
@@ -42,6 +42,7 @@
 import com.google.devtools.build.lib.syntax.Dict;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
+import com.google.devtools.build.lib.syntax.Module;
 import com.google.devtools.build.lib.syntax.Mutability;
 import com.google.devtools.build.lib.syntax.ParserInput;
 import com.google.devtools.build.lib.syntax.StarlarkFunction;
@@ -103,8 +104,10 @@
 
   private static Object execAndEval(String... lines) {
     try (Mutability mu = Mutability.create("impl")) {
+      StarlarkThread thread = StarlarkThread.builder(mu).useDefaultSemantics().build();
+      Module module = thread.getGlobals();
       return EvalUtils.execAndEvalOptionalFinalExpression(
-          ParserInput.fromLines(lines), StarlarkThread.builder(mu).useDefaultSemantics().build());
+          ParserInput.fromLines(lines), module, thread);
     } catch (Exception ex) { // SyntaxError | EvalException | InterruptedException
       throw new AssertionError("exec failed", ex);
     }
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SelectTest.java b/src/test/java/com/google/devtools/build/lib/packages/SelectTest.java
index 2cbdff7..acd0be1 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SelectTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SelectTest.java
@@ -40,7 +40,8 @@
             .setGlobals(Module.createForBuiltins(StarlarkLibrary.COMMON)) // select et al
             .useDefaultSemantics()
             .build();
-    return EvalUtils.eval(input, thread);
+    Module module = thread.getGlobals();
+    return EvalUtils.eval(input, module, thread);
   }
 
   private static void assertFails(String expr, String wantError) {
diff --git a/src/test/java/com/google/devtools/build/lib/profiler/memory/AllocationTrackerTest.java b/src/test/java/com/google/devtools/build/lib/profiler/memory/AllocationTrackerTest.java
index a24052c..41fc7c6 100644
--- a/src/test/java/com/google/devtools/build/lib/profiler/memory/AllocationTrackerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/profiler/memory/AllocationTrackerTest.java
@@ -199,7 +199,8 @@
                         "myrule", new MyRuleFunction())))
             .build();
     ParserInput input = ParserInput.create(Joiner.on("\n").join(lines), "a.star");
-    EvalUtils.exec(input, thread);
+    Module module = thread.getGlobals();
+    EvalUtils.exec(input, module, thread);
   }
 
   // A fake Bazel rule. The allocation tracker reports retained memory broken down by rule class.
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
index b9c9be4..3feba044 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -55,6 +55,7 @@
 import com.google.devtools.build.lib.syntax.ParserInput;
 import com.google.devtools.build.lib.syntax.StarlarkFile;
 import com.google.devtools.build.lib.syntax.StarlarkList;
+import com.google.devtools.build.lib.syntax.StarlarkThread;
 import com.google.devtools.build.lib.syntax.SyntaxError;
 import com.google.devtools.build.lib.syntax.Tuple;
 import com.google.devtools.build.lib.testutil.MoreAsserts;
@@ -708,12 +709,13 @@
   // TODO(adonovan): rename execAndExport
   private void evalAndExport(String... lines) throws Exception {
     ParserInput input = ParserInput.fromLines(lines);
-    StarlarkFile file = EvalUtils.parseAndValidateSkylark(input, ev.getStarlarkThread());
+    StarlarkThread thread = ev.getStarlarkThread();
+    StarlarkFile file =
+        EvalUtils.parseAndValidate(input, thread.getGlobals(), thread.getSemantics());
     if (!file.ok()) {
       throw new SyntaxError(file.errors());
     }
-    SkylarkImportLookupFunction.execAndExport(
-        file, FAKE_LABEL, ev.getEventHandler(), ev.getStarlarkThread());
+    SkylarkImportLookupFunction.execAndExport(file, FAKE_LABEL, ev.getEventHandler(), thread);
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java b/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
index 0f6ff61..c571553 100644
--- a/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
@@ -152,7 +152,7 @@
 
   @Test
   public void testPausedUntilStartDebuggingRequestReceived() throws Exception {
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]");
     StarlarkThread thread = newStarlarkThread();
 
     Thread evaluationThread = execInWorkerThread(buildFile, thread);
@@ -183,7 +183,7 @@
   @Test
   public void testResumeAllThreads() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
 
     Location breakpoint =
         Location.newBuilder().setLineNumber(2).setPath("/a/build/file/BUILD").build();
@@ -218,7 +218,7 @@
   @Test
   public void testPauseAtBreakpoint() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location breakpoint =
@@ -247,7 +247,7 @@
   public void testDoNotPauseAtUnsatisfiedConditionalBreakpoint() throws Exception {
     sendStartDebuggingRequest();
     StarlarkFile buildFile =
-        parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]", "z = 1");
+        parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]", "z = 1");
     StarlarkThread thread = newStarlarkThread();
 
     ImmutableList<Breakpoint> breakpoints =
@@ -282,7 +282,7 @@
   @Test
   public void testPauseAtSatisfiedConditionalBreakpoint() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location location =
@@ -312,7 +312,7 @@
   @Test
   public void testPauseAtInvalidConditionBreakpointWithError() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location location =
@@ -357,7 +357,7 @@
   @Test
   public void testSimpleListFramesRequest() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location breakpoint =
@@ -387,7 +387,7 @@
   @Test
   public void testGetChildrenRequest() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location breakpoint =
@@ -418,7 +418,7 @@
   public void testListFramesShadowedBinding() throws Exception {
     sendStartDebuggingRequest();
     StarlarkFile bzlFile =
-        parseSkylarkFile(
+        parseFile(
             "/a/build/file/test.bzl",
             "a = 1",
             "c = 3",
@@ -480,7 +480,7 @@
   @Test
   public void testEvaluateRequestWithExpression() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location breakpoint =
@@ -507,7 +507,7 @@
   @Test
   public void testEvaluateRequestWithAssignmentStatement() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location breakpoint =
@@ -541,7 +541,7 @@
   @Test
   public void testEvaluateRequestWithExpressionStatementMutatingState() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location breakpoint =
@@ -575,7 +575,7 @@
   @Test
   public void testEvaluateRequestThrowingException() throws Exception {
     sendStartDebuggingRequest();
-    StarlarkFile buildFile = parseBuildFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
+    StarlarkFile buildFile = parseFile("/a/build/file/BUILD", "x = [1,2,3]", "y = [2,3,4]");
     StarlarkThread thread = newStarlarkThread();
 
     Location breakpoint =
@@ -603,7 +603,7 @@
   public void testStepIntoFunction() throws Exception {
     sendStartDebuggingRequest();
     StarlarkFile bzlFile =
-        parseSkylarkFile(
+        parseFile(
             "/a/build/file/test.bzl",
             "def fn():",
             "  a = 2",
@@ -651,7 +651,7 @@
   public void testStepOverFunction() throws Exception {
     sendStartDebuggingRequest();
     StarlarkFile bzlFile =
-        parseSkylarkFile(
+        parseFile(
             "/a/build/file/test.bzl",
             "def fn():",
             "  a = 2",
@@ -694,7 +694,7 @@
   public void testStepOutOfFunction() throws Exception {
     sendStartDebuggingRequest();
     StarlarkFile bzlFile =
-        parseSkylarkFile(
+        parseFile(
             "/a/build/file/test.bzl",
             "def fn():",
             "  a = 2",
@@ -778,7 +778,7 @@
     return StarlarkThread.builder(mutability).useDefaultSemantics().build();
   }
 
-  private StarlarkFile parseBuildFile(String filename, String... lines) throws IOException {
+  private StarlarkFile parseFile(String filename, String... lines) throws IOException {
     Path path = scratch.file(filename, lines);
     byte[] bytes = FileSystemUtils.readWithKnownFileSize(path, path.getFileSize());
     ParserInput input = ParserInput.create(bytes, filename);
@@ -787,10 +787,6 @@
     return file;
   }
 
-  private StarlarkFile parseSkylarkFile(String path, String... lines) throws IOException {
-    return parseBuildFile(path, lines); // TODO(adonovan): combine these functions
-  }
-
   /**
    * Creates and starts a worker thread executing the given {@link StarlarkFile} in the given
    * environment.
@@ -800,7 +796,7 @@
         new Thread(
             () -> {
               try {
-                EvalUtils.exec(file, thread);
+                EvalUtils.exec(file, thread.getGlobals(), thread);
               } catch (EvalException | InterruptedException ex) {
                 throw new AssertionError(ex);
               }
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/CpuProfilerTest.java b/src/test/java/com/google/devtools/build/lib/syntax/CpuProfilerTest.java
index 71f782d..8b79dc9 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/CpuProfilerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/CpuProfilerTest.java
@@ -62,15 +62,12 @@
             "f()");
 
     // Execute the workload.
-    Mutability mu = Mutability.create("test");
     StarlarkThread thread =
-        StarlarkThread.builder(mu)
+        StarlarkThread.builder(Mutability.create("test"))
             .setGlobals(Module.createForBuiltins(Starlark.UNIVERSE))
             .useDefaultSemantics()
             .build();
-    StarlarkFile file = StarlarkFile.parse(input);
-    ValidationEnvironment.validateFile(file, thread.getGlobals(), thread.getSemantics(), false);
-    EvalUtils.exec(file, thread);
+    EvalUtils.exec(input, thread.getGlobals(), thread);
 
     Starlark.stopCpuProfile();
 
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
index d4459f7..5203ffe 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -35,8 +35,9 @@
     EventCollector printEvents = new EventCollector();
     StarlarkThread thread = createStarlarkThread(StarlarkThread.makeDebugPrintHandler(printEvents));
     ParserInput input = ParserInput.fromLines("print('hello'); x = 1//0; print('goodbye')");
+    Module module = thread.getGlobals();
 
-    assertThrows(EvalException.class, () -> EvalUtils.exec(input, thread));
+    assertThrows(EvalException.class, () -> EvalUtils.exec(input, module, thread));
 
     // Only expect hello, should have been an error before goodbye.
     assertThat(printEvents).hasSize(1);
@@ -48,10 +49,11 @@
     EventCollector printEvents = new EventCollector();
     StarlarkThread thread = createStarlarkThread(StarlarkThread.makeDebugPrintHandler(printEvents));
     ParserInput input = ParserInput.fromLines("print('hello');");
+    Module module = thread.getGlobals();
 
     try {
       Thread.currentThread().interrupt();
-      assertThrows(InterruptedException.class, () -> EvalUtils.exec(input, thread));
+      assertThrows(InterruptedException.class, () -> EvalUtils.exec(input, module, thread));
     } finally {
       // Reset interrupt bit in case the test failed to do so.
       Thread.interrupted();
@@ -64,7 +66,8 @@
   public void testForLoopAbortedOnInterrupt() throws Exception {
     StarlarkThread thread = createStarlarkThread((th, msg) -> {});
     InterruptFunction interruptFunction = new InterruptFunction();
-    thread.getGlobals().put("interrupt", interruptFunction);
+    Module module = thread.getGlobals();
+    module.put("interrupt", interruptFunction);
 
     ParserInput input =
         ParserInput.fromLines(
@@ -74,7 +77,7 @@
             "foo()");
 
     try {
-      assertThrows(InterruptedException.class, () -> EvalUtils.exec(input, thread));
+      assertThrows(InterruptedException.class, () -> EvalUtils.exec(input, module, thread));
     } finally {
       // Reset interrupt bit in case the test failed to do so.
       Thread.interrupted();
@@ -86,13 +89,14 @@
   @Test
   public void testForComprehensionAbortedOnInterrupt() throws Exception {
     StarlarkThread thread = createStarlarkThread((th, msg) -> {});
+    Module module = thread.getGlobals();
     InterruptFunction interruptFunction = new InterruptFunction();
-    thread.getGlobals().put("interrupt", interruptFunction);
+    module.put("interrupt", interruptFunction);
 
     ParserInput input = ParserInput.fromLines("[interrupt(i == 5) for i in range(100)]");
 
     try {
-      assertThrows(InterruptedException.class, () -> EvalUtils.exec(input, thread));
+      assertThrows(InterruptedException.class, () -> EvalUtils.exec(input, module, thread));
     } finally {
       // Reset interrupt bit in case the test failed to do so.
       Thread.interrupted();
@@ -104,14 +108,15 @@
   @Test
   public void testFunctionCallsNotStartedOnInterrupt() throws Exception {
     StarlarkThread thread = createStarlarkThread((th, msg) -> {});
+    Module module = thread.getGlobals();
     InterruptFunction interruptFunction = new InterruptFunction();
-    thread.getGlobals().put("interrupt", interruptFunction);
+    module.put("interrupt", interruptFunction);
 
     ParserInput input =
         ParserInput.fromLines("interrupt(False); interrupt(True); interrupt(False);");
 
     try {
-      assertThrows(InterruptedException.class, () -> EvalUtils.exec(input, thread));
+      assertThrows(InterruptedException.class, () -> EvalUtils.exec(input, module, thread));
     } finally {
       // Reset interrupt bit in case the test failed to do so.
       Thread.interrupted();
@@ -503,11 +508,12 @@
     StarlarkSemantics semantics = StarlarkSemantics.DEFAULT_SEMANTICS;
     StarlarkThread thread =
         StarlarkThread.builder(Mutability.create("test")).setSemantics(semantics).build();
-    ValidationEnvironment.validateFile(file, thread.getGlobals(), semantics, /*isBuildFile=*/ true);
+    Module module = thread.getGlobals();
+    ValidationEnvironment.validateFile(file, module, semantics, /*isBuildFile=*/ true);
     if (!file.ok()) {
       throw new SyntaxError(file.errors());
     }
-    EvalUtils.exec(file, thread);
+    EvalUtils.exec(file, module, thread);
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/StarlarkFileTest.java b/src/test/java/com/google/devtools/build/lib/syntax/StarlarkFileTest.java
index 6853ae0..4cd28bc 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/StarlarkFileTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/StarlarkFileTest.java
@@ -50,7 +50,8 @@
             "",
             "x = [1,2,'foo',4] + [1,2, \"%s%d\" % ('foo', 1)]");
     StarlarkThread thread = newThread();
-    EvalUtils.exec(file, thread);
+    Module module = thread.getGlobals();
+    EvalUtils.exec(file, module, thread);
 
     // Test final environment is correctly modified:
     //
@@ -65,8 +66,9 @@
     StarlarkFile file = parseFile("x = 1", "y = [2,3]", "", "z = x + y");
 
     StarlarkThread thread = newThread();
+    Module module = thread.getGlobals();
     try {
-      EvalUtils.exec(file, thread);
+      EvalUtils.exec(file, module, thread);
       throw new AssertionError("execution succeeded unexpectedly");
     } catch (EvalException ex) {
       assertThat(ex.getMessage()).contains("unsupported binary operation: int + list");
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/StarlarkThreadDebuggingTest.java b/src/test/java/com/google/devtools/build/lib/syntax/StarlarkThreadDebuggingTest.java
index e667499..a510a75 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/StarlarkThreadDebuggingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/StarlarkThreadDebuggingTest.java
@@ -39,7 +39,8 @@
   // Executes the definition of a trivial function f in the specified thread,
   // and returns the function value.
   private static StarlarkFunction defineFunc(StarlarkThread thread) throws Exception {
-    EvalUtils.exec(ParserInput.fromLines("def f(): pass"), thread);
+    Module module = thread.getGlobals();
+    EvalUtils.exec(ParserInput.fromLines("def f(): pass"), module, thread);
     return (StarlarkFunction) thread.getGlobals().lookup("f");
   }
 
@@ -81,9 +82,10 @@
 
     // Set up global environment.
     StarlarkThread thread = newStarlarkThread();
-    thread.getGlobals().put("a", 1);
-    thread.getGlobals().put("b", 2);
-    thread.getGlobals().put("f", f);
+    Module module = thread.getGlobals();
+    module.put("a", 1);
+    module.put("b", 2);
+    module.put("f", f);
 
     // Execute a small file that calls f.
     ParserInput input =
@@ -92,7 +94,7 @@
                 + "  f()\n"
                 + "g(4, 5, 6)",
             "main.star");
-    EvalUtils.exec(input, thread);
+    EvalUtils.exec(input, module, thread);
 
     @SuppressWarnings("unchecked")
     ImmutableList<Debug.Frame> stack = (ImmutableList<Debug.Frame>) result[0];
@@ -210,37 +212,46 @@
   @Test
   public void testEvaluateVariableInScope() throws Exception {
     StarlarkThread thread = newStarlarkThread();
-    thread.getGlobals().put("a", 1);
+    Module module = thread.getGlobals();
+    module.put("a", 1);
 
-    Object a = EvalUtils.execAndEvalOptionalFinalExpression(ParserInput.fromLines("a"), thread);
+    Object a =
+        EvalUtils.execAndEvalOptionalFinalExpression(ParserInput.fromLines("a"), module, thread);
     assertThat(a).isEqualTo(1);
   }
 
   @Test
   public void testEvaluateVariableNotInScopeFails() throws Exception {
     StarlarkThread thread = newStarlarkThread();
-    thread.getGlobals().put("a", 1);
+    Module module = thread.getGlobals();
+    module.put("a", 1);
 
     SyntaxError e =
         assertThrows(
             SyntaxError.class,
-            () -> EvalUtils.execAndEvalOptionalFinalExpression(ParserInput.fromLines("b"), thread));
-
+            () ->
+                EvalUtils.execAndEvalOptionalFinalExpression(
+                    ParserInput.fromLines("b"), module, thread));
     assertThat(e).hasMessageThat().isEqualTo("name 'b' is not defined");
   }
 
   @Test
   public void testEvaluateExpressionOnVariableInScope() throws Exception {
     StarlarkThread thread = newStarlarkThread();
-    thread.getGlobals().put("a", "string");
+    Module module = thread.getGlobals();
+    module.put("a", "string");
 
     assertThat(
             EvalUtils.execAndEvalOptionalFinalExpression(
-                ParserInput.fromLines("a.startswith('str')"), thread))
+                ParserInput.fromLines("a.startswith('str')"), module, thread))
         .isEqualTo(true);
     EvalUtils.exec(
-        EvalUtils.parseAndValidateSkylark(ParserInput.fromLines("a = 1"), thread), thread);
-    assertThat(EvalUtils.execAndEvalOptionalFinalExpression(ParserInput.fromLines("a"), thread))
+        EvalUtils.parseAndValidate(ParserInput.fromLines("a = 1"), module, thread.getSemantics()),
+        module,
+        thread);
+    assertThat(
+            EvalUtils.execAndEvalOptionalFinalExpression(
+                ParserInput.fromLines("a"), module, thread))
         .isEqualTo(1);
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/StarlarkThreadTest.java b/src/test/java/com/google/devtools/build/lib/syntax/StarlarkThreadTest.java
index 700d9e3..a6ec3d9 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/StarlarkThreadTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/StarlarkThreadTest.java
@@ -173,19 +173,21 @@
   @Test
   public void testBuiltinsCanBeShadowed() throws Exception {
     StarlarkThread thread = newStarlarkThread();
-    EvalUtils.exec(ParserInput.fromLines("True = 123"), thread);
+    EvalUtils.exec(ParserInput.fromLines("True = 123"), thread.getGlobals(), thread);
     assertThat(thread.getGlobals().lookup("True")).isEqualTo(123);
   }
 
   @Test
   public void testVariableIsReferencedBeforeAssignment() throws Exception {
     StarlarkThread thread = newStarlarkThread();
-    thread.getGlobals().put("global_var", 666);
+    Module module = thread.getGlobals();
+    module.put("global_var", 666);
     try {
       EvalUtils.exec(
           ParserInput.fromLines(
               "def foo(x): x += global_var; global_var = 36; return x", //
               "foo(1)"),
+          module,
           thread);
       throw new AssertionError("failed to fail");
     } catch (EvalExceptionWithStackTrace e) {
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
index 67e21b3..7f2aa10 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
@@ -39,16 +39,8 @@
   // Validates a file using the current semantics.
   private StarlarkFile validateFile(String... lines) throws SyntaxError {
     ParserInput input = ParserInput.fromLines(lines);
-
-    // TODO(adonovan): make parseAndValidateSkylark not depend on StarlarkThread.
-    // All we should need is the StarlarkSemantics and Module.
-    StarlarkThread thread =
-        StarlarkThread.builder(Mutability.create("test"))
-            .setGlobals(Module.createForBuiltins(Starlark.UNIVERSE))
-            .setSemantics(semantics)
-            .build();
-
-    return EvalUtils.parseAndValidateSkylark(input, thread);
+    Module module = Module.createForBuiltins(Starlark.UNIVERSE);
+    return EvalUtils.parseAndValidate(input, module, semantics);
   }
 
   // Assertions that parsing and validation succeeds.
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
index 10c391c..27d0b04 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
@@ -74,11 +74,10 @@
     ImmutableMap.Builder<String, Object> envBuilder = ImmutableMap.builder();
     SkylarkModules.addSkylarkGlobalsToBuilder(envBuilder); // TODO(adonovan): break bad dependency
     envBuilder.putAll(extraPredeclared);
-    Module module = Module.createForBuiltins(envBuilder.build());
 
     StarlarkThread thread =
         StarlarkThread.builder(Mutability.create("test"))
-            .setGlobals(module)
+            .setGlobals(Module.createForBuiltins(envBuilder.build()))
             .setSemantics(semantics)
             .build();
     thread.setPrintHandler(StarlarkThread.makeDebugPrintHandler(getEventHandler()));
@@ -128,13 +127,13 @@
   /** Joins the lines, parses them as an expression, and evaluates it. */
   public final Object eval(String... lines) throws Exception {
     ParserInput input = ParserInput.fromLines(lines);
-    return EvalUtils.eval(input, thread);
+    return EvalUtils.eval(input, thread.getGlobals(), thread);
   }
 
   /** Joins the lines, parses them as a file, and executes it. */
   public final void exec(String... lines) throws SyntaxError, EvalException, InterruptedException {
     ParserInput input = ParserInput.fromLines(lines);
-    EvalUtils.exec(input, thread);
+    EvalUtils.exec(input, thread.getGlobals(), thread);
   }
 
   public void checkEvalError(String msg, String... input) throws Exception {