Pārlūkot izejas kodu

Split interpreter into smaller modules (#662)

- Move Heap and Frame/Scope to their own headers.
- Move some functions to more appropriate headers (e.g. CopyValue -> value.h).
- Define a separate Bazel rule for each header/cpp pair.
- Modify PrintValue to not print the frames of a ContinuationValue. This was necessary to break a dependency cycle between PrintValue, PrintFrame, and Action::Print.

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
Geoff Romer 4 gadi atpakaļ
vecāks
revīzija
d5124256a2

+ 79 - 24
executable_semantics/interpreter/BUILD

@@ -6,39 +6,19 @@ load("@rules_cc//cc:defs.bzl", "cc_library")
 
 package(default_visibility = ["//executable_semantics:__subpackages__"])
 
-# TODO: It may be helpful to break this apart.
 cc_library(
-    name = "interpreter",
-    srcs = [
-        "action.cpp",
-        "interpreter.cpp",
-        "typecheck.cpp",
-        "value.cpp",
-    ],
-    hdrs = [
-        "action.h",
-        "interpreter.h",
-        "typecheck.h",
-        "value.h",
-    ],
+    name = "action",
+    srcs = ["action.cpp"],
+    hdrs = ["action.h"],
     deps = [
-        ":address",
         ":containers",
-        "//common:check",
-        "//executable_semantics:tracing_flag",
-        "//executable_semantics/ast:declaration",
+        ":value",
         "//executable_semantics/ast:expression",
         "//executable_semantics/ast:function_definition",
-        "//executable_semantics/ast:member",
         "//executable_semantics/ast:statement",
     ],
 )
 
-cc_library(
-    name = "field_path",
-    hdrs = ["field_path.h"],
-)
-
 cc_library(
     name = "address",
     hdrs = ["address.h"],
@@ -58,3 +38,78 @@ cc_library(
     ],
     deps = ["//common:check"],
 )
+
+cc_library(
+    name = "field_path",
+    hdrs = ["field_path.h"],
+)
+
+cc_library(
+    name = "frame",
+    srcs = ["frame.cpp"],
+    hdrs = ["frame.h"],
+    deps = [
+        ":action",
+        ":address",
+        ":containers",
+    ],
+)
+
+cc_library(
+    name = "heap",
+    srcs = ["heap.cpp"],
+    hdrs = ["heap.h"],
+    deps = [
+        ":address",
+        ":value",
+    ],
+)
+
+cc_library(
+    name = "interpreter",
+    srcs = [
+        "interpreter.cpp",
+    ],
+    hdrs = [
+        "interpreter.h",
+    ],
+    deps = [
+        ":action",
+        ":address",
+        ":containers",
+        ":frame",
+        ":heap",
+        ":value",
+        "//common:check",
+        "//executable_semantics:tracing_flag",
+        "//executable_semantics/ast:declaration",
+        "//executable_semantics/ast:expression",
+        "//executable_semantics/ast:function_definition",
+    ],
+)
+
+cc_library(
+    name = "typecheck",
+    srcs = ["typecheck.cpp"],
+    hdrs = ["typecheck.h"],
+    deps = [
+        ":containers",
+        ":interpreter",
+        "//executable_semantics:tracing_flag",
+        "//executable_semantics/ast:expression",
+        "//executable_semantics/ast:function_definition",
+        "//executable_semantics/ast:statement",
+    ],
+)
+
+cc_library(
+    name = "value",
+    srcs = ["value.cpp"],
+    hdrs = ["value.h"],
+    deps = [
+        ":address",
+        ":containers",
+        ":field_path",
+        "//executable_semantics/ast:statement",
+    ],
+)

+ 2 - 2
executable_semantics/interpreter/action.cpp

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+#include "executable_semantics/interpreter/action.h"
+
 #include <iostream>
 #include <iterator>
 #include <map>
@@ -11,9 +13,7 @@
 
 #include "executable_semantics/ast/expression.h"
 #include "executable_semantics/ast/function_definition.h"
-#include "executable_semantics/interpreter/interpreter.h"
 #include "executable_semantics/interpreter/stack.h"
-#include "executable_semantics/interpreter/typecheck.h"
 
 namespace Carbon {
 

+ 20 - 0
executable_semantics/interpreter/frame.cpp

@@ -0,0 +1,20 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "executable_semantics/interpreter/frame.h"
+
+#include <ostream>
+
+#include "executable_semantics/interpreter/action.h"
+
+namespace Carbon {
+
+void PrintFrame(Frame* frame, std::ostream& out) {
+  out << frame->name;
+  out << "{";
+  Action::PrintList(frame->todo, out);
+  out << "}";
+}
+
+}  // namespace Carbon

+ 54 - 0
executable_semantics/interpreter/frame.h

@@ -0,0 +1,54 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_
+#define EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_
+
+#include <list>
+#include <string>
+
+#include "executable_semantics/interpreter/action.h"
+#include "executable_semantics/interpreter/address.h"
+#include "executable_semantics/interpreter/dictionary.h"
+#include "executable_semantics/interpreter/stack.h"
+
+namespace Carbon {
+
+using Env = Dictionary<std::string, Address>;
+
+struct Scope {
+  Scope(Env values, std::list<std::string> l)
+      : values(values), locals(std::move(l)) {}
+  Env values;
+  std::list<std::string> locals;
+};
+
+// A frame represents either a function call or a delimited continuation.
+struct Frame {
+  // The name of the function.
+  std::string name;
+  // If the frame represents a function call, the bottom scope
+  // contains the parameter-argument bindings for this function
+  // call. The rest of the scopes contain local variables defined by
+  // blocks within the function. The scope at the top of the stack is
+  // the current scope and its environment is the one used for looking
+  // up the value associated with a variable.
+  Stack<Scope*> scopes;
+  // The actions that need to be executed in the future of the
+  // current function call. The top of the stack is the action
+  // that is executed first.
+  Stack<Action*> todo;
+  // If this frame is the bottom frame of a continuation, then it stores
+  // the address of the continuation.
+  std::optional<Address> continuation;
+
+  Frame(std::string n, Stack<Scope*> s, Stack<Action*> c)
+      : name(std::move(std::move(n))), scopes(s), todo(c), continuation() {}
+};
+
+void PrintFrame(Frame* frame, std::ostream& out);
+
+}  // namespace Carbon
+
+#endif  // EXECUTABLE_SEMANTICS_INTERPRETER_FRAME_H_

+ 66 - 0
executable_semantics/interpreter/heap.cpp

@@ -0,0 +1,66 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "executable_semantics/interpreter/heap.h"
+
+namespace Carbon {
+
+auto Heap::AllocateValue(const Value* v) -> Address {
+  // Putting the following two side effects together in this function
+  // ensures that we don't do anything else in between, which is really bad!
+  // Consider whether to include a copy of the input v in this function
+  // or to leave it up to the caller.
+  CHECK(v != nullptr);
+  Address a(values_.size());
+  values_.push_back(v);
+  alive_.push_back(true);
+  return a;
+}
+
+auto Heap::Read(const Address& a, int line_num) -> const Value* {
+  this->CheckAlive(a, line_num);
+  return values_[a.index]->GetField(a.field_path, line_num);
+}
+
+auto Heap::Write(const Address& a, const Value* v, int line_num) -> void {
+  CHECK(v != nullptr);
+  this->CheckAlive(a, line_num);
+  values_[a.index] = values_[a.index]->SetField(a.field_path, v, line_num);
+}
+
+void Heap::CheckAlive(const Address& address, int line_num) {
+  if (!alive_[address.index]) {
+    std::cerr << line_num << ": undefined behavior: access to dead value ";
+    PrintValue(values_[address.index], std::cerr);
+    std::cerr << std::endl;
+    exit(-1);
+  }
+}
+
+void Heap::Deallocate(const Address& address) {
+  CHECK(address.field_path.IsEmpty());
+  if (alive_[address.index]) {
+    alive_[address.index] = false;
+  } else {
+    std::cerr << "runtime error, deallocating an already dead value"
+              << std::endl;
+    exit(-1);
+  }
+}
+
+void Heap::PrintHeap(std::ostream& out) {
+  for (size_t i = 0; i < values_.size(); ++i) {
+    PrintAddress(Address(i), out);
+    out << ", ";
+  }
+}
+
+auto Heap::PrintAddress(const Address& a, std::ostream& out) -> void {
+  if (!alive_[a.index]) {
+    out << "!!";
+  }
+  PrintValue(values_[a.index], out);
+}
+
+}  // namespace Carbon

+ 55 - 0
executable_semantics/interpreter/heap.h

@@ -0,0 +1,55 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef EXECUTABLE_SEMANTICS_INTERPRETER_MEMORY_H_
+#define EXECUTABLE_SEMANTICS_INTERPRETER_MEMORY_H_
+
+#include <ostream>
+#include <vector>
+
+#include "executable_semantics/interpreter/address.h"
+#include "executable_semantics/interpreter/value.h"
+
+namespace Carbon {
+
+// A Heap represents the abstract machine's dynamically allocated memory.
+class Heap {
+ public:
+  // Constructs an empty Heap.
+  Heap() = default;
+
+  Heap(const Heap&) = delete;
+  Heap& operator=(const Heap&) = delete;
+
+  // Returns the value at the given address in the heap after
+  // checking that it is alive.
+  auto Read(const Address& a, int line_num) -> const Value*;
+
+  // Writes the given value at the address in the heap after
+  // checking that the address is alive.
+  auto Write(const Address& a, const Value* v, int line_num) -> void;
+
+  // Put the given value on the heap and mark it as alive.
+  auto AllocateValue(const Value* v) -> Address;
+
+  // Marks the object at this address, and all of its sub-objects, as dead.
+  auto Deallocate(const Address& address) -> void;
+
+  // Print the value at the given address to the stream `out`.
+  auto PrintAddress(const Address& a, std::ostream& out) -> void;
+
+  // Print all the values on the heap to the stream `out`.
+  auto PrintHeap(std::ostream& out) -> void;
+
+ private:
+  // Signal an error if the address is no longer alive.
+  void CheckAlive(const Address& address, int line_num);
+
+  std::vector<const Value*> values_;
+  std::vector<bool> alive_;
+};
+
+}  // namespace Carbon
+
+#endif  // EXECUTABLE_SEMANTICS_INTERPRETER_MEMORY_H_

+ 3 - 126
executable_semantics/interpreter/interpreter.cpp

@@ -15,8 +15,9 @@
 #include "common/check.h"
 #include "executable_semantics/ast/expression.h"
 #include "executable_semantics/ast/function_definition.h"
+#include "executable_semantics/interpreter/action.h"
+#include "executable_semantics/interpreter/frame.h"
 #include "executable_semantics/interpreter/stack.h"
-#include "executable_semantics/interpreter/typecheck.h"
 #include "executable_semantics/tracing_flag.h"
 
 namespace Carbon {
@@ -30,109 +31,6 @@ auto Step() -> void;
 // Auxiliary Functions
 //
 
-auto Heap::AllocateValue(const Value* v) -> Address {
-  // Putting the following two side effects together in this function
-  // ensures that we don't do anything else in between, which is really bad!
-  // Consider whether to include a copy of the input v in this function
-  // or to leave it up to the caller.
-  CHECK(v != nullptr);
-  Address a(values_.size());
-  values_.push_back(v);
-  alive_.push_back(true);
-  return a;
-}
-
-auto Heap::Read(const Address& a, int line_num) -> const Value* {
-  this->CheckAlive(a, line_num);
-  return values_[a.index]->GetField(a.field_path, line_num);
-}
-
-auto Heap::Write(const Address& a, const Value* v, int line_num) -> void {
-  CHECK(v != nullptr);
-  this->CheckAlive(a, line_num);
-  values_[a.index] = values_[a.index]->SetField(a.field_path, v, line_num);
-}
-
-void Heap::CheckAlive(const Address& address, int line_num) {
-  if (!alive_[address.index]) {
-    std::cerr << line_num << ": undefined behavior: access to dead value ";
-    PrintValue(values_[address.index], std::cerr);
-    std::cerr << std::endl;
-    exit(-1);
-  }
-}
-
-auto CopyVal(const Value* val, int line_num) -> const Value* {
-  switch (val->tag()) {
-    case ValKind::TupleValue: {
-      std::vector<TupleElement> elements;
-      for (const TupleElement& element : val->GetTupleValue().elements) {
-        elements.push_back(
-            {.name = element.name, .value = CopyVal(element.value, line_num)});
-      }
-      return Value::MakeTupleValue(std::move(elements));
-    }
-    case ValKind::AlternativeValue: {
-      const Value* arg = CopyVal(val->GetAlternativeValue().argument, line_num);
-      return Value::MakeAlternativeValue(val->GetAlternativeValue().alt_name,
-                                         val->GetAlternativeValue().choice_name,
-                                         arg);
-    }
-    case ValKind::StructValue: {
-      const Value* inits = CopyVal(val->GetStructValue().inits, line_num);
-      return Value::MakeStructValue(val->GetStructValue().type, inits);
-    }
-    case ValKind::IntValue:
-      return Value::MakeIntValue(val->GetIntValue());
-    case ValKind::BoolValue:
-      return Value::MakeBoolValue(val->GetBoolValue());
-    case ValKind::FunctionValue:
-      return Value::MakeFunctionValue(val->GetFunctionValue().name,
-                                      val->GetFunctionValue().param,
-                                      val->GetFunctionValue().body);
-    case ValKind::PointerValue:
-      return Value::MakePointerValue(val->GetPointerValue());
-    case ValKind::ContinuationValue:
-      // Copying a continuation is "shallow".
-      return val;
-    case ValKind::FunctionType:
-      return Value::MakeFunctionType(
-          CopyVal(val->GetFunctionType().param, line_num),
-          CopyVal(val->GetFunctionType().ret, line_num));
-
-    case ValKind::PointerType:
-      return Value::MakePointerType(
-          CopyVal(val->GetPointerType().type, line_num));
-    case ValKind::IntType:
-      return Value::MakeIntType();
-    case ValKind::BoolType:
-      return Value::MakeBoolType();
-    case ValKind::TypeType:
-      return Value::MakeTypeType();
-    case ValKind::AutoType:
-      return Value::MakeAutoType();
-    case ValKind::ContinuationType:
-      return Value::MakeContinuationType();
-    case ValKind::StructType:
-    case ValKind::ChoiceType:
-    case ValKind::BindingPlaceholderValue:
-    case ValKind::AlternativeConstructorValue:
-      return val;  // no need to copy these because they are immutable?
-      // No, they need to be copied so they don't get killed. -Jeremy
-  }
-}
-
-void Heap::Deallocate(const Address& address) {
-  CHECK(address.field_path.IsEmpty());
-  if (alive_[address.index]) {
-    alive_[address.index] = false;
-  } else {
-    std::cerr << "runtime error, deallocating an already dead value"
-              << std::endl;
-    exit(-1);
-  }
-}
-
 void PrintEnv(Env values, std::ostream& out) {
   for (const auto& [name, address] : values) {
     out << name << ": ";
@@ -142,16 +40,9 @@ void PrintEnv(Env values, std::ostream& out) {
 }
 
 //
-// Frame and State Operations
+// State Operations
 //
 
-void PrintFrame(Frame* frame, std::ostream& out) {
-  out << frame->name;
-  out << "{";
-  Action::PrintList(frame->todo, out);
-  out << "}";
-}
-
 void PrintStack(Stack<Frame*> ls, std::ostream& out) {
   if (!ls.IsEmpty()) {
     PrintFrame(ls.Pop(), out);
@@ -162,20 +53,6 @@ void PrintStack(Stack<Frame*> ls, std::ostream& out) {
   }
 }
 
-void Heap::PrintHeap(std::ostream& out) {
-  for (size_t i = 0; i < values_.size(); ++i) {
-    PrintAddress(Address(i), out);
-    out << ", ";
-  }
-}
-
-auto Heap::PrintAddress(const Address& a, std::ostream& out) -> void {
-  if (!alive_[a.index]) {
-    out << "!!";
-  }
-  PrintValue(values_[a.index], out);
-}
-
 auto CurrentEnv(State* state) -> Env {
   Frame* frame = state->stack.Top();
   return frame->scopes.Top()->values;

+ 2 - 76
executable_semantics/interpreter/interpreter.h

@@ -10,8 +10,8 @@
 #include <vector>
 
 #include "executable_semantics/ast/declaration.h"
-#include "executable_semantics/interpreter/action.h"
-#include "executable_semantics/interpreter/dictionary.h"
+#include "executable_semantics/interpreter/frame.h"
+#include "executable_semantics/interpreter/heap.h"
 #include "executable_semantics/interpreter/stack.h"
 #include "executable_semantics/interpreter/value.h"
 
@@ -19,77 +19,6 @@ namespace Carbon {
 
 using Env = Dictionary<std::string, Address>;
 
-/***** Scopes *****/
-
-struct Scope {
-  Scope(Env values, std::list<std::string> l)
-      : values(values), locals(std::move(l)) {}
-  Env values;
-  std::list<std::string> locals;
-};
-
-/***** Frames and State *****/
-
-// A frame represents either a function call or a delimited continuation.
-struct Frame {
-  // The name of the function.
-  std::string name;
-  // If the frame represents a function call, the bottom scope
-  // contains the parameter-argument bindings for this function
-  // call. The rest of the scopes contain local variables defined by
-  // blocks within the function. The scope at the top of the stack is
-  // the current scope and its environment is the one used for looking
-  // up the value associated with a variable.
-  Stack<Scope*> scopes;
-  // The actions that need to be executed in the future of the
-  // current function call. The top of the stack is the action
-  // that is executed first.
-  Stack<Action*> todo;
-  // If this frame is the bottom frame of a continuation, then it stores
-  // the address of the continuation.
-  std::optional<Address> continuation;
-
-  Frame(std::string n, Stack<Scope*> s, Stack<Action*> c)
-      : name(std::move(std::move(n))), scopes(s), todo(c), continuation() {}
-};
-
-// A Heap represents the abstract machine's dynamically allocated memory.
-class Heap {
- public:
-  // Constructs an empty Heap.
-  Heap() = default;
-
-  Heap(const Heap&) = delete;
-  Heap& operator=(const Heap&) = delete;
-
-  // Returns the value at the given address in the heap after
-  // checking that it is alive.
-  auto Read(const Address& a, int line_num) -> const Value*;
-
-  // Writes the given value at the address in the heap after
-  // checking that the address is alive.
-  auto Write(const Address& a, const Value* v, int line_num) -> void;
-
-  // Put the given value on the heap and mark it as alive.
-  auto AllocateValue(const Value* v) -> Address;
-
-  // Marks the object at this address, and all of its sub-objects, as dead.
-  auto Deallocate(const Address& address) -> void;
-
-  // Print the value at the given address to the stream `out`.
-  auto PrintAddress(const Address& a, std::ostream& out) -> void;
-
-  // Print all the values on the heap to the stream `out`.
-  auto PrintHeap(std::ostream& out) -> void;
-
- private:
-  // Signal an error if the address is no longer alive.
-  void CheckAlive(const Address& address, int line_num);
-
-  std::vector<const Value*> values_;
-  std::vector<bool> alive_;
-};
-
 struct State {
   Stack<Frame*> stack;
   Heap heap;
@@ -98,11 +27,8 @@ struct State {
 extern State* state;
 
 void InitEnv(const Declaration& d, Env* env);
-void PrintFrame(Frame* frame, std::ostream& out);
 void PrintStack(Stack<Frame*> ls, std::ostream& out);
 void PrintEnv(Env values);
-auto CopyVal(const Value* val, int line_num) -> const Value*;
-auto ToInteger(const Value* v) -> int;
 
 /***** Interpreters *****/
 

+ 63 - 7
executable_semantics/interpreter/value.cpp

@@ -8,7 +8,6 @@
 #include <iostream>
 
 #include "common/check.h"
-#include "executable_semantics/interpreter/interpreter.h"
 
 namespace Carbon {
 
@@ -434,16 +433,73 @@ auto PrintValue(const Value* val, std::ostream& out) -> void {
       out << "choice " << val->GetChoiceType().name;
       break;
     case ValKind::ContinuationValue:
-      out << "continuation[[";
-      for (Frame* frame : val->GetContinuationValue().stack) {
-        PrintFrame(frame, out);
-        out << " :: ";
-      }
-      out << "]]";
+      out << "continuation";
+      // TODO: Find a way to print useful information about the continuation
+      // without creating a dependency cycle.
       break;
   }
 }
 
+auto CopyVal(const Value* val, int line_num) -> const Value* {
+  switch (val->tag()) {
+    case ValKind::TupleValue: {
+      std::vector<TupleElement> elements;
+      for (const TupleElement& element : val->GetTupleValue().elements) {
+        elements.push_back(
+            {.name = element.name, .value = CopyVal(element.value, line_num)});
+      }
+      return Value::MakeTupleValue(std::move(elements));
+    }
+    case ValKind::AlternativeValue: {
+      const Value* arg = CopyVal(val->GetAlternativeValue().argument, line_num);
+      return Value::MakeAlternativeValue(val->GetAlternativeValue().alt_name,
+                                         val->GetAlternativeValue().choice_name,
+                                         arg);
+    }
+    case ValKind::StructValue: {
+      const Value* inits = CopyVal(val->GetStructValue().inits, line_num);
+      return Value::MakeStructValue(val->GetStructValue().type, inits);
+    }
+    case ValKind::IntValue:
+      return Value::MakeIntValue(val->GetIntValue());
+    case ValKind::BoolValue:
+      return Value::MakeBoolValue(val->GetBoolValue());
+    case ValKind::FunctionValue:
+      return Value::MakeFunctionValue(val->GetFunctionValue().name,
+                                      val->GetFunctionValue().param,
+                                      val->GetFunctionValue().body);
+    case ValKind::PointerValue:
+      return Value::MakePointerValue(val->GetPointerValue());
+    case ValKind::ContinuationValue:
+      // Copying a continuation is "shallow".
+      return val;
+    case ValKind::FunctionType:
+      return Value::MakeFunctionType(
+          CopyVal(val->GetFunctionType().param, line_num),
+          CopyVal(val->GetFunctionType().ret, line_num));
+
+    case ValKind::PointerType:
+      return Value::MakePointerType(
+          CopyVal(val->GetPointerType().type, line_num));
+    case ValKind::IntType:
+      return Value::MakeIntType();
+    case ValKind::BoolType:
+      return Value::MakeBoolType();
+    case ValKind::TypeType:
+      return Value::MakeTypeType();
+    case ValKind::AutoType:
+      return Value::MakeAutoType();
+    case ValKind::ContinuationType:
+      return Value::MakeContinuationType();
+    case ValKind::StructType:
+    case ValKind::ChoiceType:
+    case ValKind::BindingPlaceholderValue:
+    case ValKind::AlternativeConstructorValue:
+      // TODO: These should be copied so that they don't get destructed.
+      return val;
+  }
+}
+
 auto TypeEqual(const Value* t1, const Value* t2) -> bool {
   if (t1->tag() != t2->tag()) {
     return false;

+ 2 - 0
executable_semantics/interpreter/value.h

@@ -240,6 +240,8 @@ struct Value {
 
 void PrintValue(const Value* val, std::ostream& out);
 
+auto CopyVal(const Value* val, int line_num) -> const Value*;
+
 auto TypeEqual(const Value* t1, const Value* t2) -> bool;
 auto ValueEqual(const Value* v1, const Value* v2, int line_num) -> bool;
 

+ 1 - 0
executable_semantics/syntax/BUILD

@@ -33,6 +33,7 @@ cc_library(
         "//executable_semantics/ast:declaration",
         "//executable_semantics/ast:expression",
         "//executable_semantics/interpreter",
+        "//executable_semantics/interpreter:typecheck",
     ],
 )