Introduce semantic::Info

Will hold the mutable fields that currently reside in the otherwise immutable-AST.

Change the AST string methods to accept a `const semantic::Info&`. This is required as some nodes include type-resolved information in their output strings.

Bug: tint:390
Change-Id: Iba494a9c5645ce2096da0a8cfe63a4309a9d9c3c
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/39003
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 2ba7325..5407a7e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -378,6 +378,8 @@
     "src/reader/reader.cc",
     "src/reader/reader.h",
     "src/scope_stack.h",
+    "src/semantic/info.h",
+    "src/semantic/sem_info.cc",
     "src/source.cc",
     "src/source.h",
     "src/symbol.cc",
@@ -778,7 +780,6 @@
     "src/ast/decoration_test.cc",
     "src/ast/discard_statement_test.cc",
     "src/ast/else_statement_test.cc",
-    "src/ast/expression_test.cc",
     "src/ast/fallthrough_statement_test.cc",
     "src/ast/float_literal_test.cc",
     "src/ast/function_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7000c71..207fe4a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -192,6 +192,8 @@
   reader/reader.cc
   reader/reader.h
   scope_stack.h
+  semantic/info.h
+  semantic/sem_info.cc
   source.cc
   source.h
   symbol.cc
@@ -408,7 +410,6 @@
     ast/decoration_test.cc
     ast/discard_statement_test.cc
     ast/else_statement_test.cc
-    ast/expression_test.cc
     ast/fallthrough_statement_test.cc
     ast/float_literal_test.cc
     ast/function_test.cc
diff --git a/src/ast/access_decoration.cc b/src/ast/access_decoration.cc
index c535160..976ffe2 100644
--- a/src/ast/access_decoration.cc
+++ b/src/ast/access_decoration.cc
@@ -27,7 +27,9 @@
 
 AccessDecoration::~AccessDecoration() = default;
 
-void AccessDecoration::to_str(std::ostream& out, size_t indent) const {
+void AccessDecoration::to_str(const semantic::Info&,
+                              std::ostream& out,
+                              size_t indent) const {
   make_indent(out, indent);
   out << "AccessDecoration{" << value_ << "}" << std::endl;
 }
diff --git a/src/ast/access_decoration.h b/src/ast/access_decoration.h
index e58c13a..2a50e2c 100644
--- a/src/ast/access_decoration.h
+++ b/src/ast/access_decoration.h
@@ -36,9 +36,12 @@
   AccessControl value() const { return value_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/access_decoration_test.cc b/src/ast/access_decoration_test.cc
index 55295fc..e48fe71 100644
--- a/src/ast/access_decoration_test.cc
+++ b/src/ast/access_decoration_test.cc
@@ -37,7 +37,7 @@
 TEST_F(AccessDecorationTest, ToStr) {
   auto* d = create<AccessDecoration>(ast::AccessControl::kReadOnly);
   std::ostringstream out;
-  d->to_str(out, 0);
+  d->to_str(Sem(), out, 0);
   EXPECT_EQ(out.str(), R"(AccessDecoration{read_only}
 )");
 }
diff --git a/src/ast/array_accessor_expression.cc b/src/ast/array_accessor_expression.cc
index be763db..b8855b0 100644
--- a/src/ast/array_accessor_expression.cc
+++ b/src/ast/array_accessor_expression.cc
@@ -47,11 +47,13 @@
   return true;
 }
 
-void ArrayAccessorExpression::to_str(std::ostream& out, size_t indent) const {
+void ArrayAccessorExpression::to_str(const semantic::Info& sem,
+                                     std::ostream& out,
+                                     size_t indent) const {
   make_indent(out, indent);
-  out << "ArrayAccessor[" << result_type_str() << "]{" << std::endl;
-  array_->to_str(out, indent + 2);
-  idx_expr_->to_str(out, indent + 2);
+  out << "ArrayAccessor[" << result_type_str(sem) << "]{" << std::endl;
+  array_->to_str(sem, out, indent + 2);
+  idx_expr_->to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
 }
diff --git a/src/ast/array_accessor_expression.h b/src/ast/array_accessor_expression.h
index ebcf9c6..32911b2 100644
--- a/src/ast/array_accessor_expression.h
+++ b/src/ast/array_accessor_expression.h
@@ -57,9 +57,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   ArrayAccessorExpression(const ArrayAccessorExpression&) = delete;
diff --git a/src/ast/array_accessor_expression_test.cc b/src/ast/array_accessor_expression_test.cc
index be29266..b86266b 100644
--- a/src/ast/array_accessor_expression_test.cc
+++ b/src/ast/array_accessor_expression_test.cc
@@ -93,7 +93,7 @@
 
   auto* exp = create<ArrayAccessorExpression>(ary, idx);
   std::ostringstream out;
-  exp->to_str(out, 2);
+  exp->to_str(Sem(), out, 2);
 
   EXPECT_EQ(demangle(out.str()), R"(  ArrayAccessor[not set]{
     Identifier[not set]{ary}
diff --git a/src/ast/assignment_statement.cc b/src/ast/assignment_statement.cc
index a92c6e3..aec1d64 100644
--- a/src/ast/assignment_statement.cc
+++ b/src/ast/assignment_statement.cc
@@ -45,11 +45,13 @@
   return true;
 }
 
-void AssignmentStatement::to_str(std::ostream& out, size_t indent) const {
+void AssignmentStatement::to_str(const semantic::Info& sem,
+                                 std::ostream& out,
+                                 size_t indent) const {
   make_indent(out, indent);
   out << "Assignment{" << std::endl;
-  lhs_->to_str(out, indent + 2);
-  rhs_->to_str(out, indent + 2);
+  lhs_->to_str(sem, out, indent + 2);
+  rhs_->to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
 }
diff --git a/src/ast/assignment_statement.h b/src/ast/assignment_statement.h
index 01b5ac5..294b8e1 100644
--- a/src/ast/assignment_statement.h
+++ b/src/ast/assignment_statement.h
@@ -54,9 +54,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   AssignmentStatement(const AssignmentStatement&) = delete;
diff --git a/src/ast/assignment_statement_test.cc b/src/ast/assignment_statement_test.cc
index 2d7055d..cbf1813 100644
--- a/src/ast/assignment_statement_test.cc
+++ b/src/ast/assignment_statement_test.cc
@@ -93,7 +93,7 @@
 
   auto* stmt = create<AssignmentStatement>(lhs, rhs);
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
 
   EXPECT_EQ(demangle(out.str()), R"(  Assignment{
     Identifier[not set]{lhs}
diff --git a/src/ast/binary_expression.cc b/src/ast/binary_expression.cc
index 5c59cbd..c1fc0d6 100644
--- a/src/ast/binary_expression.cc
+++ b/src/ast/binary_expression.cc
@@ -47,15 +47,17 @@
   return op_ != BinaryOp::kNone;
 }
 
-void BinaryExpression::to_str(std::ostream& out, size_t indent) const {
+void BinaryExpression::to_str(const semantic::Info& sem,
+                              std::ostream& out,
+                              size_t indent) const {
   make_indent(out, indent);
-  out << "Binary[" << result_type_str() << "]{" << std::endl;
-  lhs_->to_str(out, indent + 2);
+  out << "Binary[" << result_type_str(sem) << "]{" << std::endl;
+  lhs_->to_str(sem, out, indent + 2);
 
   make_indent(out, indent + 2);
   out << op_ << std::endl;
 
-  rhs_->to_str(out, indent + 2);
+  rhs_->to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
 }
diff --git a/src/ast/binary_expression.h b/src/ast/binary_expression.h
index 9bce548..71a1859 100644
--- a/src/ast/binary_expression.h
+++ b/src/ast/binary_expression.h
@@ -120,9 +120,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   BinaryExpression(const BinaryExpression&) = delete;
diff --git a/src/ast/binary_expression_test.cc b/src/ast/binary_expression_test.cc
index 432bc45..5a8f7bf 100644
--- a/src/ast/binary_expression_test.cc
+++ b/src/ast/binary_expression_test.cc
@@ -106,7 +106,7 @@
 
   auto* r = create<BinaryExpression>(BinaryOp::kEqual, lhs, rhs);
   std::ostringstream out;
-  r->to_str(out, 2);
+  r->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Binary[not set]{
     Identifier[not set]{lhs}
     equal
diff --git a/src/ast/binding_decoration.cc b/src/ast/binding_decoration.cc
index 10a6bff..a8714dc 100644
--- a/src/ast/binding_decoration.cc
+++ b/src/ast/binding_decoration.cc
@@ -27,7 +27,9 @@
 
 BindingDecoration::~BindingDecoration() = default;
 
-void BindingDecoration::to_str(std::ostream& out, size_t indent) const {
+void BindingDecoration::to_str(const semantic::Info&,
+                               std::ostream& out,
+                               size_t indent) const {
   make_indent(out, indent);
   out << "BindingDecoration{" << value_ << "}" << std::endl;
 }
diff --git a/src/ast/binding_decoration.h b/src/ast/binding_decoration.h
index e4d73e3..6f9f2bb 100644
--- a/src/ast/binding_decoration.h
+++ b/src/ast/binding_decoration.h
@@ -36,9 +36,12 @@
   uint32_t value() const { return value_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/binding_decoration_test.cc b/src/ast/binding_decoration_test.cc
index ddeff33..a50a048 100644
--- a/src/ast/binding_decoration_test.cc
+++ b/src/ast/binding_decoration_test.cc
@@ -40,7 +40,7 @@
 TEST_F(BindingDecorationTest, ToStr) {
   auto* d = create<BindingDecoration>(2);
   std::ostringstream out;
-  d->to_str(out, 0);
+  d->to_str(Sem(), out, 0);
   EXPECT_EQ(out.str(), R"(BindingDecoration{2}
 )");
 }
diff --git a/src/ast/bitcast_expression.cc b/src/ast/bitcast_expression.cc
index 26e5230..df28119 100644
--- a/src/ast/bitcast_expression.cc
+++ b/src/ast/bitcast_expression.cc
@@ -41,11 +41,13 @@
   return type_ != nullptr;
 }
 
-void BitcastExpression::to_str(std::ostream& out, size_t indent) const {
+void BitcastExpression::to_str(const semantic::Info& sem,
+                               std::ostream& out,
+                               size_t indent) const {
   make_indent(out, indent);
-  out << "Bitcast[" << result_type_str() << "]<" << type_->type_name() << ">{"
-      << std::endl;
-  expr_->to_str(out, indent + 2);
+  out << "Bitcast[" << result_type_str(sem) << "]<" << type_->type_name()
+      << ">{" << std::endl;
+  expr_->to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
 }
diff --git a/src/ast/bitcast_expression.h b/src/ast/bitcast_expression.h
index d1376f2..734be10 100644
--- a/src/ast/bitcast_expression.h
+++ b/src/ast/bitcast_expression.h
@@ -54,9 +54,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   BitcastExpression(const BitcastExpression&) = delete;
diff --git a/src/ast/bitcast_expression_test.cc b/src/ast/bitcast_expression_test.cc
index 56e2568..2dce4b9 100644
--- a/src/ast/bitcast_expression_test.cc
+++ b/src/ast/bitcast_expression_test.cc
@@ -79,7 +79,7 @@
 
   auto* exp = create<BitcastExpression>(ty.f32(), expr);
   std::ostringstream out;
-  exp->to_str(out, 2);
+  exp->to_str(Sem(), out, 2);
 
   EXPECT_EQ(demangle(out.str()), R"(  Bitcast[not set]<__f32>{
     Identifier[not set]{expr}
diff --git a/src/ast/block_statement.cc b/src/ast/block_statement.cc
index 07ff30f..6e4b3f2 100644
--- a/src/ast/block_statement.cc
+++ b/src/ast/block_statement.cc
@@ -44,12 +44,14 @@
   return true;
 }
 
-void BlockStatement::to_str(std::ostream& out, size_t indent) const {
+void BlockStatement::to_str(const semantic::Info& sem,
+                            std::ostream& out,
+                            size_t indent) const {
   make_indent(out, indent);
   out << "Block{" << std::endl;
 
   for (auto* stmt : *this) {
-    stmt->to_str(out, indent + 2);
+    stmt->to_str(sem, out, indent + 2);
   }
 
   make_indent(out, indent);
diff --git a/src/ast/block_statement.h b/src/ast/block_statement.h
index 08e4b06..2af2cf6 100644
--- a/src/ast/block_statement.h
+++ b/src/ast/block_statement.h
@@ -76,9 +76,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   BlockStatement(const BlockStatement&) = delete;
diff --git a/src/ast/block_statement_test.cc b/src/ast/block_statement_test.cc
index 02c536e..5290be8 100644
--- a/src/ast/block_statement_test.cc
+++ b/src/ast/block_statement_test.cc
@@ -87,7 +87,7 @@
   });
 
   std::ostringstream out;
-  b->to_str(out, 2);
+  b->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Block{
     Discard{}
   }
diff --git a/src/ast/bool_literal.cc b/src/ast/bool_literal.cc
index e162d1c..9a9f180 100644
--- a/src/ast/bool_literal.cc
+++ b/src/ast/bool_literal.cc
@@ -27,7 +27,7 @@
 
 BoolLiteral::~BoolLiteral() = default;
 
-std::string BoolLiteral::to_str() const {
+std::string BoolLiteral::to_str(const semantic::Info&) const {
   return value_ ? "true" : "false";
 }
 
diff --git a/src/ast/bool_literal.h b/src/ast/bool_literal.h
index 2896913..05eb137 100644
--- a/src/ast/bool_literal.h
+++ b/src/ast/bool_literal.h
@@ -40,8 +40,9 @@
   /// @returns the name for this literal. This name is unique to this value.
   std::string name() const override;
 
+  /// @param sem the semantic info for the program
   /// @returns the literal as a string
-  std::string to_str() const override;
+  std::string to_str(const semantic::Info& sem) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/bool_literal_test.cc b/src/ast/bool_literal_test.cc
index e07e66e..01c6eac 100644
--- a/src/ast/bool_literal_test.cc
+++ b/src/ast/bool_literal_test.cc
@@ -59,8 +59,8 @@
   auto* t = create<BoolLiteral>(&bool_type, true);
   auto* f = create<BoolLiteral>(&bool_type, false);
 
-  EXPECT_EQ(t->to_str(), "true");
-  EXPECT_EQ(f->to_str(), "false");
+  EXPECT_EQ(t->to_str(Sem()), "true");
+  EXPECT_EQ(f->to_str(Sem()), "false");
 }
 
 }  // namespace
diff --git a/src/ast/break_statement.cc b/src/ast/break_statement.cc
index 6bf034c..15a8e1c 100644
--- a/src/ast/break_statement.cc
+++ b/src/ast/break_statement.cc
@@ -36,7 +36,9 @@
   return true;
 }
 
-void BreakStatement::to_str(std::ostream& out, size_t indent) const {
+void BreakStatement::to_str(const semantic::Info&,
+                            std::ostream& out,
+                            size_t indent) const {
   make_indent(out, indent);
   out << "Break{}" << std::endl;
 }
diff --git a/src/ast/break_statement.h b/src/ast/break_statement.h
index 7c9bd91..23f50e5 100644
--- a/src/ast/break_statement.h
+++ b/src/ast/break_statement.h
@@ -42,9 +42,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   BreakStatement(const BreakStatement&) = delete;
diff --git a/src/ast/break_statement_test.cc b/src/ast/break_statement_test.cc
index 10063a4..ffad482 100644
--- a/src/ast/break_statement_test.cc
+++ b/src/ast/break_statement_test.cc
@@ -42,7 +42,7 @@
 TEST_F(BreakStatementTest, ToStr) {
   auto* stmt = create<BreakStatement>();
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Break{}
 )");
 }
diff --git a/src/ast/builtin_decoration.cc b/src/ast/builtin_decoration.cc
index 1c33150..e275752 100644
--- a/src/ast/builtin_decoration.cc
+++ b/src/ast/builtin_decoration.cc
@@ -27,7 +27,9 @@
 
 BuiltinDecoration::~BuiltinDecoration() = default;
 
-void BuiltinDecoration::to_str(std::ostream& out, size_t indent) const {
+void BuiltinDecoration::to_str(const semantic::Info&,
+                               std::ostream& out,
+                               size_t indent) const {
   make_indent(out, indent);
   out << "BuiltinDecoration{" << builtin_ << "}" << std::endl;
 }
diff --git a/src/ast/builtin_decoration.h b/src/ast/builtin_decoration.h
index 7c437d5..cbe700e 100644
--- a/src/ast/builtin_decoration.h
+++ b/src/ast/builtin_decoration.h
@@ -35,9 +35,12 @@
   Builtin value() const { return builtin_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/builtin_decoration_test.cc b/src/ast/builtin_decoration_test.cc
index df10ec7..b115425 100644
--- a/src/ast/builtin_decoration_test.cc
+++ b/src/ast/builtin_decoration_test.cc
@@ -40,7 +40,7 @@
 TEST_F(BuiltinDecorationTest, ToStr) {
   auto* d = create<BuiltinDecoration>(Builtin::kFragDepth);
   std::ostringstream out;
-  d->to_str(out, 0);
+  d->to_str(Sem(), out, 0);
   EXPECT_EQ(out.str(), R"(BuiltinDecoration{frag_depth}
 )");
 }
diff --git a/src/ast/call_expression.cc b/src/ast/call_expression.cc
index 674350a..a54f9a7 100644
--- a/src/ast/call_expression.cc
+++ b/src/ast/call_expression.cc
@@ -48,15 +48,17 @@
   return true;
 }
 
-void CallExpression::to_str(std::ostream& out, size_t indent) const {
+void CallExpression::to_str(const semantic::Info& sem,
+                            std::ostream& out,
+                            size_t indent) const {
   make_indent(out, indent);
-  out << "Call[" << result_type_str() << "]{" << std::endl;
-  func_->to_str(out, indent + 2);
+  out << "Call[" << result_type_str(sem) << "]{" << std::endl;
+  func_->to_str(sem, out, indent + 2);
 
   make_indent(out, indent + 2);
   out << "(" << std::endl;
   for (auto* param : params_)
-    param->to_str(out, indent + 4);
+    param->to_str(sem, out, indent + 4);
 
   make_indent(out, indent + 2);
   out << ")" << std::endl;
diff --git a/src/ast/call_expression.h b/src/ast/call_expression.h
index d52ab4e..d84f074 100644
--- a/src/ast/call_expression.h
+++ b/src/ast/call_expression.h
@@ -53,9 +53,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   CallExpression(const CallExpression&) = delete;
diff --git a/src/ast/call_expression_test.cc b/src/ast/call_expression_test.cc
index 8769a52..f8f3ee7 100644
--- a/src/ast/call_expression_test.cc
+++ b/src/ast/call_expression_test.cc
@@ -97,7 +97,7 @@
   auto* func = Expr("func");
   auto* stmt = create<CallExpression>(func, ExpressionList{});
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Call[not set]{
     Identifier[not set]{func}
     (
@@ -114,7 +114,7 @@
 
   auto* stmt = create<CallExpression>(func, params);
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Call[not set]{
     Identifier[not set]{func}
     (
diff --git a/src/ast/call_statement.cc b/src/ast/call_statement.cc
index 66b94e5..1e48538 100644
--- a/src/ast/call_statement.cc
+++ b/src/ast/call_statement.cc
@@ -39,8 +39,10 @@
   return call_ != nullptr && call_->IsValid();
 }
 
-void CallStatement::to_str(std::ostream& out, size_t indent) const {
-  call_->to_str(out, indent);
+void CallStatement::to_str(const semantic::Info& sem,
+                           std::ostream& out,
+                           size_t indent) const {
+  call_->to_str(sem, out, indent);
 }
 
 }  // namespace ast
diff --git a/src/ast/call_statement.h b/src/ast/call_statement.h
index c8aa837..dfbb06b 100644
--- a/src/ast/call_statement.h
+++ b/src/ast/call_statement.h
@@ -50,9 +50,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   CallStatement(const CallStatement&) = delete;
diff --git a/src/ast/call_statement_test.cc b/src/ast/call_statement_test.cc
index a9545e2..844d1cb 100644
--- a/src/ast/call_statement_test.cc
+++ b/src/ast/call_statement_test.cc
@@ -58,7 +58,7 @@
       create<CallExpression>(Expr("func"), ExpressionList{}));
 
   std::ostringstream out;
-  c->to_str(out, 2);
+  c->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Call[not set]{
     Identifier[not set]{func}
     (
diff --git a/src/ast/case_statement.cc b/src/ast/case_statement.cc
index ac1a267..5f5934d 100644
--- a/src/ast/case_statement.cc
+++ b/src/ast/case_statement.cc
@@ -40,7 +40,9 @@
   return body_ != nullptr && body_->IsValid();
 }
 
-void CaseStatement::to_str(std::ostream& out, size_t indent) const {
+void CaseStatement::to_str(const semantic::Info& sem,
+                           std::ostream& out,
+                           size_t indent) const {
   make_indent(out, indent);
 
   if (IsDefault()) {
@@ -53,14 +55,14 @@
         out << ", ";
 
       first = false;
-      out << selector->to_str();
+      out << selector->to_str(sem);
     }
     out << "{" << std::endl;
   }
 
   if (body_ != nullptr) {
     for (auto* stmt : *body_) {
-      stmt->to_str(out, indent + 2);
+      stmt->to_str(sem, out, indent + 2);
     }
   }
 
diff --git a/src/ast/case_statement.h b/src/ast/case_statement.h
index b793342..5b5d664 100644
--- a/src/ast/case_statement.h
+++ b/src/ast/case_statement.h
@@ -66,9 +66,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   CaseStatement(const CaseStatement&) = delete;
diff --git a/src/ast/case_statement_test.cc b/src/ast/case_statement_test.cc
index ad54986..2a9779b 100644
--- a/src/ast/case_statement_test.cc
+++ b/src/ast/case_statement_test.cc
@@ -135,7 +135,7 @@
   auto* c = create<CaseStatement>(CaseSelectorList{b}, body);
 
   std::ostringstream out;
-  c->to_str(out, 2);
+  c->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Case -2{
     Discard{}
   }
@@ -152,7 +152,7 @@
   auto* c = create<CaseStatement>(CaseSelectorList{b}, body);
 
   std::ostringstream out;
-  c->to_str(out, 2);
+  c->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Case 2{
     Discard{}
   }
@@ -170,7 +170,7 @@
   auto* c = create<CaseStatement>(b, body);
 
   std::ostringstream out;
-  c->to_str(out, 2);
+  c->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Case 1, 2{
     Discard{}
   }
@@ -184,7 +184,7 @@
   auto* c = create<CaseStatement>(CaseSelectorList{}, body);
 
   std::ostringstream out;
-  c->to_str(out, 2);
+  c->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Default{
     Discard{}
   }
diff --git a/src/ast/constant_id_decoration.cc b/src/ast/constant_id_decoration.cc
index 463e5b3..fec8fe8 100644
--- a/src/ast/constant_id_decoration.cc
+++ b/src/ast/constant_id_decoration.cc
@@ -27,7 +27,9 @@
 
 ConstantIdDecoration::~ConstantIdDecoration() = default;
 
-void ConstantIdDecoration::to_str(std::ostream& out, size_t indent) const {
+void ConstantIdDecoration::to_str(const semantic::Info&,
+                                  std::ostream& out,
+                                  size_t indent) const {
   make_indent(out, indent);
   out << "ConstantIdDecoration{" << value_ << "}" << std::endl;
 }
diff --git a/src/ast/constant_id_decoration.h b/src/ast/constant_id_decoration.h
index 0d2b407..11acca6 100644
--- a/src/ast/constant_id_decoration.h
+++ b/src/ast/constant_id_decoration.h
@@ -35,9 +35,12 @@
   uint32_t value() const { return value_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/constant_id_decoration_test.cc b/src/ast/constant_id_decoration_test.cc
index ea38af3..b536ee6 100644
--- a/src/ast/constant_id_decoration_test.cc
+++ b/src/ast/constant_id_decoration_test.cc
@@ -39,7 +39,7 @@
 TEST_F(ConstantIdDecorationTest, ToStr) {
   auto* d = create<ConstantIdDecoration>(1200);
   std::ostringstream out;
-  d->to_str(out, 0);
+  d->to_str(Sem(), out, 0);
   EXPECT_EQ(out.str(), R"(ConstantIdDecoration{1200}
 )");
 }
diff --git a/src/ast/continue_statement.cc b/src/ast/continue_statement.cc
index 58edfba..3b066a4 100644
--- a/src/ast/continue_statement.cc
+++ b/src/ast/continue_statement.cc
@@ -36,7 +36,9 @@
   return true;
 }
 
-void ContinueStatement::to_str(std::ostream& out, size_t indent) const {
+void ContinueStatement::to_str(const semantic::Info&,
+                               std::ostream& out,
+                               size_t indent) const {
   make_indent(out, indent);
   out << "Continue{}" << std::endl;
 }
diff --git a/src/ast/continue_statement.h b/src/ast/continue_statement.h
index 1d4de24..ab2e870 100644
--- a/src/ast/continue_statement.h
+++ b/src/ast/continue_statement.h
@@ -45,9 +45,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   ContinueStatement(const ContinueStatement&) = delete;
diff --git a/src/ast/continue_statement_test.cc b/src/ast/continue_statement_test.cc
index ab725b1..31c859a 100644
--- a/src/ast/continue_statement_test.cc
+++ b/src/ast/continue_statement_test.cc
@@ -42,7 +42,7 @@
 TEST_F(ContinueStatementTest, ToStr) {
   auto* stmt = create<ContinueStatement>();
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Continue{}
 )");
 }
diff --git a/src/ast/discard_statement.cc b/src/ast/discard_statement.cc
index 3d4ae66..ec2bf65 100644
--- a/src/ast/discard_statement.cc
+++ b/src/ast/discard_statement.cc
@@ -36,7 +36,9 @@
   return true;
 }
 
-void DiscardStatement::to_str(std::ostream& out, size_t indent) const {
+void DiscardStatement::to_str(const semantic::Info&,
+                              std::ostream& out,
+                              size_t indent) const {
   make_indent(out, indent);
   out << "Discard{}" << std::endl;
 }
diff --git a/src/ast/discard_statement.h b/src/ast/discard_statement.h
index 8f17fd7..bb6e95a 100644
--- a/src/ast/discard_statement.h
+++ b/src/ast/discard_statement.h
@@ -42,9 +42,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   DiscardStatement(const DiscardStatement&) = delete;
diff --git a/src/ast/discard_statement_test.cc b/src/ast/discard_statement_test.cc
index 545d732..43e0771 100644
--- a/src/ast/discard_statement_test.cc
+++ b/src/ast/discard_statement_test.cc
@@ -54,7 +54,7 @@
 TEST_F(DiscardStatementTest, ToStr) {
   auto* stmt = create<DiscardStatement>();
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Discard{}
 )");
 }
diff --git a/src/ast/else_statement.cc b/src/ast/else_statement.cc
index 63201a3..30e18a6 100644
--- a/src/ast/else_statement.cc
+++ b/src/ast/else_statement.cc
@@ -43,14 +43,16 @@
   return condition_ == nullptr || condition_->IsValid();
 }
 
-void ElseStatement::to_str(std::ostream& out, size_t indent) const {
+void ElseStatement::to_str(const semantic::Info& sem,
+                           std::ostream& out,
+                           size_t indent) const {
   make_indent(out, indent);
   out << "Else{" << std::endl;
   if (condition_ != nullptr) {
     make_indent(out, indent + 2);
     out << "(" << std::endl;
 
-    condition_->to_str(out, indent + 4);
+    condition_->to_str(sem, out, indent + 4);
 
     make_indent(out, indent + 2);
     out << ")" << std::endl;
@@ -61,7 +63,7 @@
 
   if (body_ != nullptr) {
     for (auto* stmt : *body_) {
-      stmt->to_str(out, indent + 4);
+      stmt->to_str(sem, out, indent + 4);
     }
   }
 
diff --git a/src/ast/else_statement.h b/src/ast/else_statement.h
index 7b08fa5..a432c49 100644
--- a/src/ast/else_statement.h
+++ b/src/ast/else_statement.h
@@ -62,9 +62,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   ElseStatement(const ElseStatement&) = delete;
diff --git a/src/ast/else_statement_test.cc b/src/ast/else_statement_test.cc
index 0855fec..f37fe56 100644
--- a/src/ast/else_statement_test.cc
+++ b/src/ast/else_statement_test.cc
@@ -115,7 +115,7 @@
   });
   auto* e = create<ElseStatement>(cond, body);
   std::ostringstream out;
-  e->to_str(out, 2);
+  e->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Else{
     (
       ScalarConstructor[not set]{true}
@@ -133,7 +133,7 @@
   });
   auto* e = create<ElseStatement>(nullptr, body);
   std::ostringstream out;
-  e->to_str(out, 2);
+  e->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Else{
     {
       Discard{}
diff --git a/src/ast/expression.h b/src/ast/expression.h
index 853a57c..f46d461 100644
--- a/src/ast/expression.h
+++ b/src/ast/expression.h
@@ -38,7 +38,7 @@
 
   /// @returns a string representation of the result type or 'not set' if no
   /// result type present
-  std::string result_type_str() const {
+  std::string result_type_str(const semantic::Info&) const {
     return result_type_ ? result_type_->type_name() : "not set";
   }
 
diff --git a/src/ast/expression_test.cc b/src/ast/expression_test.cc
deleted file mode 100644
index 9dde377..0000000
--- a/src/ast/expression_test.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/ast/expression.h"
-
-#include "src/ast/test_helper.h"
-#include "src/type/alias_type.h"
-#include "src/type/i32_type.h"
-
-namespace tint {
-namespace ast {
-namespace {
-
-class FakeExpr : public Expression {
- public:
-  FakeExpr() : Expression(Source{}) {}
-
-  FakeExpr* Clone(CloneContext*) const override { return nullptr; }
-  bool IsValid() const override { return true; }
-  void to_str(std::ostream&, size_t) const override {}
-};
-
-using ExpressionTest = TestHelper;
-
-TEST_F(ExpressionTest, set_result_type) {
-  FakeExpr e;
-  e.set_result_type(ty.i32());
-  ASSERT_NE(e.result_type(), nullptr);
-  EXPECT_TRUE(e.result_type()->Is<type::I32>());
-}
-
-TEST_F(ExpressionTest, set_result_type_alias) {
-  auto* a = ty.alias("a", ty.i32());
-  auto* b = ty.alias("b", a);
-
-  FakeExpr e;
-  e.set_result_type(b);
-  ASSERT_NE(e.result_type(), nullptr);
-  EXPECT_TRUE(e.result_type()->Is<type::I32>());
-}
-
-}  // namespace
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/fallthrough_statement.cc b/src/ast/fallthrough_statement.cc
index ede2ca6..dcdc26a 100644
--- a/src/ast/fallthrough_statement.cc
+++ b/src/ast/fallthrough_statement.cc
@@ -37,7 +37,9 @@
   return true;
 }
 
-void FallthroughStatement::to_str(std::ostream& out, size_t indent) const {
+void FallthroughStatement::to_str(const semantic::Info&,
+                                  std::ostream& out,
+                                  size_t indent) const {
   make_indent(out, indent);
   out << "Fallthrough{}" << std::endl;
 }
diff --git a/src/ast/fallthrough_statement.h b/src/ast/fallthrough_statement.h
index 25d5870..2db9546 100644
--- a/src/ast/fallthrough_statement.h
+++ b/src/ast/fallthrough_statement.h
@@ -42,9 +42,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   FallthroughStatement(const FallthroughStatement&) = delete;
diff --git a/src/ast/fallthrough_statement_test.cc b/src/ast/fallthrough_statement_test.cc
index a17131a..4021bba 100644
--- a/src/ast/fallthrough_statement_test.cc
+++ b/src/ast/fallthrough_statement_test.cc
@@ -50,7 +50,7 @@
 TEST_F(FallthroughStatementTest, ToStr) {
   auto* stmt = create<FallthroughStatement>();
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Fallthrough{}
 )");
 }
diff --git a/src/ast/float_literal.cc b/src/ast/float_literal.cc
index 6e48199..f7b54f4 100644
--- a/src/ast/float_literal.cc
+++ b/src/ast/float_literal.cc
@@ -30,7 +30,7 @@
 
 FloatLiteral::~FloatLiteral() = default;
 
-std::string FloatLiteral::to_str() const {
+std::string FloatLiteral::to_str(const semantic::Info&) const {
   return std::to_string(value_);
 }
 
diff --git a/src/ast/float_literal.h b/src/ast/float_literal.h
index bbf58f8..b3deb9c 100644
--- a/src/ast/float_literal.h
+++ b/src/ast/float_literal.h
@@ -38,8 +38,9 @@
   /// @returns the name for this literal. This name is unique to this value.
   std::string name() const override;
 
+  /// @param sem the semantic info for the program
   /// @returns the literal as a string
-  std::string to_str() const override;
+  std::string to_str(const semantic::Info& sem) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/float_literal_test.cc b/src/ast/float_literal_test.cc
index fd4dfe7..b329035 100644
--- a/src/ast/float_literal_test.cc
+++ b/src/ast/float_literal_test.cc
@@ -46,7 +46,7 @@
 TEST_F(FloatLiteralTest, ToStr) {
   auto* f = create<FloatLiteral>(ty.f32(), 42.1f);
 
-  EXPECT_EQ(f->to_str(), "42.099998");
+  EXPECT_EQ(f->to_str(Sem()), "42.099998");
 }
 
 TEST_F(FloatLiteralTest, ToName) {
diff --git a/src/ast/function.cc b/src/ast/function.cc
index ceb2d3d..f17421a 100644
--- a/src/ast/function.cc
+++ b/src/ast/function.cc
@@ -247,13 +247,15 @@
   return true;
 }
 
-void Function::to_str(std::ostream& out, size_t indent) const {
+void Function::to_str(const semantic::Info& sem,
+                      std::ostream& out,
+                      size_t indent) const {
   make_indent(out, indent);
   out << "Function " << symbol_.to_str() << " -> " << return_type_->type_name()
       << std::endl;
 
   for (auto* deco : decorations()) {
-    deco->to_str(out, indent);
+    deco->to_str(sem, out, indent);
   }
 
   make_indent(out, indent);
@@ -263,7 +265,7 @@
     out << std::endl;
 
     for (auto* param : params_)
-      param->to_str(out, indent + 2);
+      param->to_str(sem, out, indent + 2);
 
     make_indent(out, indent);
   }
@@ -274,7 +276,7 @@
 
   if (body_ != nullptr) {
     for (auto* stmt : *body_) {
-      stmt->to_str(out, indent + 2);
+      stmt->to_str(sem, out, indent + 2);
     }
   }
 
diff --git a/src/ast/function.h b/src/ast/function.h
index 0f635c1..ff72b14 100644
--- a/src/ast/function.h
+++ b/src/ast/function.h
@@ -184,9 +184,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// @returns the type name for this function
   std::string type_name() const;
diff --git a/src/ast/function_test.cc b/src/ast/function_test.cc
index 1181efa..39b6c62 100644
--- a/src/ast/function_test.cc
+++ b/src/ast/function_test.cc
@@ -245,7 +245,7 @@
                  FunctionDecorationList{});
 
   std::ostringstream out;
-  f->to_str(out, 2);
+  f->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Function func -> __void
   ()
   {
@@ -262,7 +262,7 @@
                  FunctionDecorationList{create<WorkgroupDecoration>(2, 4, 6)});
 
   std::ostringstream out;
-  f->to_str(out, 2);
+  f->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Function func -> __void
   WorkgroupDecoration{2 4 6}
   ()
@@ -283,7 +283,7 @@
                  FunctionDecorationList{});
 
   std::ostringstream out;
-  f->to_str(out, 2);
+  f->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Function func -> __void
   (
     Variable{
diff --git a/src/ast/group_decoration.cc b/src/ast/group_decoration.cc
index 019cc36..5c8171d 100644
--- a/src/ast/group_decoration.cc
+++ b/src/ast/group_decoration.cc
@@ -27,7 +27,9 @@
 
 GroupDecoration::~GroupDecoration() = default;
 
-void GroupDecoration::to_str(std::ostream& out, size_t indent) const {
+void GroupDecoration::to_str(const semantic::Info&,
+                             std::ostream& out,
+                             size_t indent) const {
   make_indent(out, indent);
   out << "GroupDecoration{" << value_ << "}" << std::endl;
 }
diff --git a/src/ast/group_decoration.h b/src/ast/group_decoration.h
index 323012d..1ff24be 100644
--- a/src/ast/group_decoration.h
+++ b/src/ast/group_decoration.h
@@ -35,9 +35,12 @@
   uint32_t value() const { return value_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/group_decoration_test.cc b/src/ast/group_decoration_test.cc
index 95625d3..5052c85 100644
--- a/src/ast/group_decoration_test.cc
+++ b/src/ast/group_decoration_test.cc
@@ -40,7 +40,7 @@
 TEST_F(GroupDecorationTest, ToStr) {
   auto* d = create<GroupDecoration>(2);
   std::ostringstream out;
-  d->to_str(out, 0);
+  d->to_str(Sem(), out, 0);
   EXPECT_EQ(out.str(), R"(GroupDecoration{2}
 )");
 }
diff --git a/src/ast/identifier_expression.cc b/src/ast/identifier_expression.cc
index c6c0779..ba05e64 100644
--- a/src/ast/identifier_expression.cc
+++ b/src/ast/identifier_expression.cc
@@ -38,9 +38,11 @@
   return sym_.IsValid();
 }
 
-void IdentifierExpression::to_str(std::ostream& out, size_t indent) const {
+void IdentifierExpression::to_str(const semantic::Info& sem,
+                                  std::ostream& out,
+                                  size_t indent) const {
   make_indent(out, indent);
-  out << "Identifier[" << result_type_str() << "]{" << sym_.to_str() << "}"
+  out << "Identifier[" << result_type_str(sem) << "]{" << sym_.to_str() << "}"
       << std::endl;
 }
 
diff --git a/src/ast/identifier_expression.h b/src/ast/identifier_expression.h
index d1a2b77..1021bdd 100644
--- a/src/ast/identifier_expression.h
+++ b/src/ast/identifier_expression.h
@@ -76,9 +76,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   IdentifierExpression(const IdentifierExpression&) = delete;
diff --git a/src/ast/identifier_expression_test.cc b/src/ast/identifier_expression_test.cc
index 1059af2..3d504ca 100644
--- a/src/ast/identifier_expression_test.cc
+++ b/src/ast/identifier_expression_test.cc
@@ -49,7 +49,7 @@
 TEST_F(IdentifierExpressionTest, ToStr) {
   auto* i = Expr("ident");
   std::ostringstream out;
-  i->to_str(out, 2);
+  i->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Identifier[not set]{ident}
 )");
 }
diff --git a/src/ast/if_statement.cc b/src/ast/if_statement.cc
index 16a677d..0b018ef 100644
--- a/src/ast/if_statement.cc
+++ b/src/ast/if_statement.cc
@@ -67,7 +67,9 @@
   return true;
 }
 
-void IfStatement::to_str(std::ostream& out, size_t indent) const {
+void IfStatement::to_str(const semantic::Info& sem,
+                         std::ostream& out,
+                         size_t indent) const {
   make_indent(out, indent);
   out << "If{" << std::endl;
 
@@ -75,7 +77,7 @@
   make_indent(out, indent + 2);
   out << "(" << std::endl;
 
-  condition_->to_str(out, indent + 4);
+  condition_->to_str(sem, out, indent + 4);
 
   // Close if conditional
   make_indent(out, indent + 2);
@@ -87,7 +89,7 @@
 
   if (body_ != nullptr) {
     for (auto* stmt : *body_) {
-      stmt->to_str(out, indent + 4);
+      stmt->to_str(sem, out, indent + 4);
     }
   }
 
@@ -100,7 +102,7 @@
   out << "}" << std::endl;
 
   for (auto* e : else_statements_) {
-    e->to_str(out, indent);
+    e->to_str(sem, out, indent);
   }
 }
 
diff --git a/src/ast/if_statement.h b/src/ast/if_statement.h
index 11515ab..10ee4a1 100644
--- a/src/ast/if_statement.h
+++ b/src/ast/if_statement.h
@@ -67,9 +67,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   IfStatement(const IfStatement&) = delete;
diff --git a/src/ast/if_statement_test.cc b/src/ast/if_statement_test.cc
index 6e4bf62..d071f7f 100644
--- a/src/ast/if_statement_test.cc
+++ b/src/ast/if_statement_test.cc
@@ -168,7 +168,7 @@
   auto* stmt = create<IfStatement>(cond, body, ElseStatementList{});
 
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  If{
     (
       Identifier[not set]{cond}
@@ -196,7 +196,7 @@
       });
 
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  If{
     (
       Identifier[not set]{cond}
diff --git a/src/ast/literal.cc b/src/ast/literal.cc
index 05375ad..4c365da 100644
--- a/src/ast/literal.cc
+++ b/src/ast/literal.cc
@@ -28,9 +28,11 @@
   return true;
 }
 
-void Literal::to_str(std::ostream& out, size_t indent) const {
+void Literal::to_str(const semantic::Info& sem,
+                     std::ostream& out,
+                     size_t indent) const {
   make_indent(out, indent);
-  out << to_str();
+  out << to_str(sem);
 }
 
 }  // namespace ast
diff --git a/src/ast/literal.h b/src/ast/literal.h
index 2b08eea..96779d7 100644
--- a/src/ast/literal.h
+++ b/src/ast/literal.h
@@ -35,12 +35,16 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
+  /// @param sem the semantic info for the program
   /// @returns the literal as a string
-  virtual std::string to_str() const = 0;
+  virtual std::string to_str(const semantic::Info& sem) const = 0;
 
   /// @returns the name for this literal. This name is unique to this value.
   virtual std::string name() const = 0;
diff --git a/src/ast/location_decoration.cc b/src/ast/location_decoration.cc
index 1eff8a6..8e25036 100644
--- a/src/ast/location_decoration.cc
+++ b/src/ast/location_decoration.cc
@@ -27,7 +27,9 @@
 
 LocationDecoration::~LocationDecoration() = default;
 
-void LocationDecoration::to_str(std::ostream& out, size_t indent) const {
+void LocationDecoration::to_str(const semantic::Info&,
+                                std::ostream& out,
+                                size_t indent) const {
   make_indent(out, indent);
   out << "LocationDecoration{" << value_ << "}" << std::endl;
 }
diff --git a/src/ast/location_decoration.h b/src/ast/location_decoration.h
index fc24963..3d28c69 100644
--- a/src/ast/location_decoration.h
+++ b/src/ast/location_decoration.h
@@ -36,9 +36,12 @@
   uint32_t value() const { return value_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/location_decoration_test.cc b/src/ast/location_decoration_test.cc
index b8084e3..349e730 100644
--- a/src/ast/location_decoration_test.cc
+++ b/src/ast/location_decoration_test.cc
@@ -42,7 +42,7 @@
 TEST_F(LocationDecorationTest, ToStr) {
   auto* d = create<LocationDecoration>(2);
   std::ostringstream out;
-  d->to_str(out, 0);
+  d->to_str(Sem(), out, 0);
   EXPECT_EQ(out.str(), R"(LocationDecoration{2}
 )");
 }
diff --git a/src/ast/loop_statement.cc b/src/ast/loop_statement.cc
index 68730c4..74cb698 100644
--- a/src/ast/loop_statement.cc
+++ b/src/ast/loop_statement.cc
@@ -46,13 +46,15 @@
   return true;
 }
 
-void LoopStatement::to_str(std::ostream& out, size_t indent) const {
+void LoopStatement::to_str(const semantic::Info& sem,
+                           std::ostream& out,
+                           size_t indent) const {
   make_indent(out, indent);
   out << "Loop{" << std::endl;
 
   if (body_ != nullptr) {
     for (auto* stmt : *body_) {
-      stmt->to_str(out, indent + 2);
+      stmt->to_str(sem, out, indent + 2);
     }
   }
 
@@ -61,7 +63,7 @@
     out << "continuing {" << std::endl;
 
     for (auto* stmt : *continuing_) {
-      stmt->to_str(out, indent + 4);
+      stmt->to_str(sem, out, indent + 4);
     }
 
     make_indent(out, indent + 2);
diff --git a/src/ast/loop_statement.h b/src/ast/loop_statement.h
index e80eb94..b4f7f74 100644
--- a/src/ast/loop_statement.h
+++ b/src/ast/loop_statement.h
@@ -64,9 +64,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   LoopStatement(const LoopStatement&) = delete;
diff --git a/src/ast/loop_statement_test.cc b/src/ast/loop_statement_test.cc
index d3dca64..0f0b4f4 100644
--- a/src/ast/loop_statement_test.cc
+++ b/src/ast/loop_statement_test.cc
@@ -171,7 +171,7 @@
 
   auto* l = create<LoopStatement>(body, nullptr);
   std::ostringstream out;
-  l->to_str(out, 2);
+  l->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Loop{
     Discard{}
   }
@@ -187,7 +187,7 @@
 
   auto* l = create<LoopStatement>(body, continuing);
   std::ostringstream out;
-  l->to_str(out, 2);
+  l->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Loop{
     Discard{}
     continuing {
diff --git a/src/ast/member_accessor_expression.cc b/src/ast/member_accessor_expression.cc
index 20e6ee6..e58ad98 100644
--- a/src/ast/member_accessor_expression.cc
+++ b/src/ast/member_accessor_expression.cc
@@ -48,11 +48,13 @@
   return true;
 }
 
-void MemberAccessorExpression::to_str(std::ostream& out, size_t indent) const {
+void MemberAccessorExpression::to_str(const semantic::Info& sem,
+                                      std::ostream& out,
+                                      size_t indent) const {
   make_indent(out, indent);
-  out << "MemberAccessor[" << result_type_str() << "]{" << std::endl;
-  struct_->to_str(out, indent + 2);
-  member_->to_str(out, indent + 2);
+  out << "MemberAccessor[" << result_type_str(sem) << "]{" << std::endl;
+  struct_->to_str(sem, out, indent + 2);
+  member_->to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
 }
diff --git a/src/ast/member_accessor_expression.h b/src/ast/member_accessor_expression.h
index d9aef79..3e6fd9b 100644
--- a/src/ast/member_accessor_expression.h
+++ b/src/ast/member_accessor_expression.h
@@ -57,9 +57,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   MemberAccessorExpression(const MemberAccessorExpression&) = delete;
diff --git a/src/ast/member_accessor_expression_test.cc b/src/ast/member_accessor_expression_test.cc
index c7d1dd5..cc17106 100644
--- a/src/ast/member_accessor_expression_test.cc
+++ b/src/ast/member_accessor_expression_test.cc
@@ -78,7 +78,7 @@
   auto* stmt =
       create<MemberAccessorExpression>(Expr("structure"), Expr("member"));
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  MemberAccessor[not set]{
     Identifier[not set]{structure}
     Identifier[not set]{member}
diff --git a/src/ast/module.cc b/src/ast/module.cc
index 9d99c17..e9e3a63 100644
--- a/src/ast/module.cc
+++ b/src/ast/module.cc
@@ -81,7 +81,9 @@
                                   ctx->Clone(global_variables_));
 }
 
-void Module::to_str(std::ostream& out, size_t indent) const {
+void Module::to_str(const semantic::Info& sem,
+                    std::ostream& out,
+                    size_t indent) const {
   make_indent(out, indent);
   out << "Module{" << std::endl;
   indent += 2;
@@ -91,25 +93,25 @@
       out << alias->symbol().to_str() << " -> " << alias->type()->type_name()
           << std::endl;
       if (auto* str = alias->type()->As<type::Struct>()) {
-        str->impl()->to_str(out, indent);
+        str->impl()->to_str(sem, out, indent);
       }
     } else if (auto* str = ty->As<type::Struct>()) {
       out << str->symbol().to_str() << " ";
-      str->impl()->to_str(out, indent);
+      str->impl()->to_str(sem, out, indent);
     }
   }
   for (auto* var : global_variables_) {
-    var->to_str(out, indent);
+    var->to_str(sem, out, indent);
   }
   for (auto* func : functions_) {
-    func->to_str(out, indent);
+    func->to_str(sem, out, indent);
   }
   out << "}" << std::endl;
 }
 
-std::string Module::to_str() const {
+std::string Module::to_str(const semantic::Info& sem) const {
   std::ostringstream out;
-  to_str(out, 0);
+  to_str(sem, out, 0);
   return out.str();
 }
 
diff --git a/src/ast/module.h b/src/ast/module.h
index bbc3c0b..c094c7c 100644
--- a/src/ast/module.h
+++ b/src/ast/module.h
@@ -89,12 +89,16 @@
   Module* Clone(CloneContext* ctx) const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
+  /// @param sem the semantic info for the program
   /// @returns a string representation of the Builder
-  std::string to_str() const;
+  std::string to_str(const semantic::Info& sem) const;
 
  private:
   std::vector<type::Type*> constructed_types_;
diff --git a/src/ast/module_clone_test.cc b/src/ast/module_clone_test.cc
index d2d72a1..8fa8350 100644
--- a/src/ast/module_clone_test.cc
+++ b/src/ast/module_clone_test.cc
@@ -122,8 +122,8 @@
 
   // Expect the AST printed with to_str() to match
   Demangler demanger;
-  EXPECT_EQ(demanger.Demangle(src.Symbols(), src.AST().to_str()),
-            demanger.Demangle(dst.Symbols(), dst.AST().to_str()));
+  EXPECT_EQ(demanger.Demangle(src.Symbols(), src.AST().to_str(src.Sem())),
+            demanger.Demangle(dst.Symbols(), dst.AST().to_str(dst.Sem())));
 
   // Check that none of the AST nodes or type pointers in dst are found in src
   std::unordered_set<ast::Node*> src_nodes;
@@ -135,7 +135,7 @@
     src_types.emplace(src_type);
   }
   for (auto* dst_node : dst.Nodes().Objects()) {
-    ASSERT_EQ(src_nodes.count(dst_node), 0u) << dst_node->str();
+    ASSERT_EQ(src_nodes.count(dst_node), 0u) << dst_node->str(dst.Sem());
   }
   for (auto* dst_type : dst.Types()) {
     ASSERT_EQ(src_types.count(dst_type), 0u) << dst_type->type_name();
diff --git a/src/ast/module_test.cc b/src/ast/module_test.cc
index f987023..2f62568 100644
--- a/src/ast/module_test.cc
+++ b/src/ast/module_test.cc
@@ -36,7 +36,7 @@
 }
 
 TEST_F(ModuleTest, ToStrEmitsPreambleAndPostamble) {
-  const auto str = Program(std::move(*this)).AST().to_str();
+  const auto str = Program(std::move(*this)).to_str();
   auto* const expected = "Module{\n}\n";
   EXPECT_EQ(str, expected);
 }
diff --git a/src/ast/node.cc b/src/ast/node.cc
index 8d0f1e0..41480b2 100644
--- a/src/ast/node.cc
+++ b/src/ast/node.cc
@@ -32,9 +32,9 @@
     out << " ";
 }
 
-std::string Node::str() const {
+std::string Node::str(const semantic::Info& sem) const {
   std::ostringstream out;
-  to_str(out, 0);
+  to_str(sem, out, 0);
   return out.str();
 }
 
diff --git a/src/ast/node.h b/src/ast/node.h
index 1c215e4..e7b9262 100644
--- a/src/ast/node.h
+++ b/src/ast/node.h
@@ -29,6 +29,9 @@
 namespace type {
 class Type;
 }
+namespace semantic {
+class Info;
+}
 
 namespace ast {
 
@@ -52,13 +55,17 @@
   virtual bool IsValid() const = 0;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  virtual void to_str(std::ostream& out, size_t indent) const = 0;
+  virtual void to_str(const semantic::Info& sem,
+                      std::ostream& out,
+                      size_t indent) const = 0;
 
   /// Convenience wrapper around the to_str() method.
+  /// @param sem the semantic info for the program
   /// @returns the node as a string
-  std::string str() const;
+  std::string str(const semantic::Info& sem) const;
 
  protected:
   /// Create a new node
diff --git a/src/ast/null_literal.cc b/src/ast/null_literal.cc
index edcf896..835ae29 100644
--- a/src/ast/null_literal.cc
+++ b/src/ast/null_literal.cc
@@ -27,7 +27,7 @@
 
 NullLiteral::~NullLiteral() = default;
 
-std::string NullLiteral::to_str() const {
+std::string NullLiteral::to_str(const semantic::Info&) const {
   return "null " + type()->type_name();
 }
 
diff --git a/src/ast/null_literal.h b/src/ast/null_literal.h
index 31ada3b..de6f7fc 100644
--- a/src/ast/null_literal.h
+++ b/src/ast/null_literal.h
@@ -34,8 +34,9 @@
   /// @returns the name for this literal. This name is unique to this value.
   std::string name() const override;
 
+  /// @param sem the semantic info for the program
   /// @returns the literal as a string
-  std::string to_str() const override;
+  std::string to_str(const semantic::Info& sem) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/null_literal_test.cc b/src/ast/null_literal_test.cc
index e50bd42..eac5bb0 100644
--- a/src/ast/null_literal_test.cc
+++ b/src/ast/null_literal_test.cc
@@ -40,7 +40,7 @@
 TEST_F(NullLiteralTest, ToStr) {
   auto* i = create<NullLiteral>(ty.i32());
 
-  EXPECT_EQ(i->to_str(), "null __i32");
+  EXPECT_EQ(i->to_str(Sem()), "null __i32");
 }
 
 TEST_F(NullLiteralTest, Name_I32) {
diff --git a/src/ast/return_statement.cc b/src/ast/return_statement.cc
index 38133c2..2f85773 100644
--- a/src/ast/return_statement.cc
+++ b/src/ast/return_statement.cc
@@ -44,7 +44,9 @@
   return true;
 }
 
-void ReturnStatement::to_str(std::ostream& out, size_t indent) const {
+void ReturnStatement::to_str(const semantic::Info& sem,
+                             std::ostream& out,
+                             size_t indent) const {
   make_indent(out, indent);
   out << "Return{";
 
@@ -54,7 +56,7 @@
     make_indent(out, indent + 2);
     out << "{" << std::endl;
 
-    value_->to_str(out, indent + 4);
+    value_->to_str(sem, out, indent + 4);
 
     make_indent(out, indent + 2);
     out << "}" << std::endl;
diff --git a/src/ast/return_statement.h b/src/ast/return_statement.h
index 5a56ba6..31c4fb5 100644
--- a/src/ast/return_statement.h
+++ b/src/ast/return_statement.h
@@ -55,9 +55,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   ReturnStatement(const ReturnStatement&) = delete;
diff --git a/src/ast/return_statement_test.cc b/src/ast/return_statement_test.cc
index 68506c0..d09352a 100644
--- a/src/ast/return_statement_test.cc
+++ b/src/ast/return_statement_test.cc
@@ -76,7 +76,7 @@
   auto* expr = Expr("expr");
   auto* r = create<ReturnStatement>(expr);
   std::ostringstream out;
-  r->to_str(out, 2);
+  r->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Return{
     {
       Identifier[not set]{expr}
@@ -88,7 +88,7 @@
 TEST_F(ReturnStatementTest, ToStr_WithoutValue) {
   auto* r = create<ReturnStatement>();
   std::ostringstream out;
-  r->to_str(out, 2);
+  r->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  Return{}
 )");
 }
diff --git a/src/ast/scalar_constructor_expression.cc b/src/ast/scalar_constructor_expression.cc
index ebbecc6..4c90f19 100644
--- a/src/ast/scalar_constructor_expression.cc
+++ b/src/ast/scalar_constructor_expression.cc
@@ -41,11 +41,12 @@
   return literal_ != nullptr;
 }
 
-void ScalarConstructorExpression::to_str(std::ostream& out,
+void ScalarConstructorExpression::to_str(const semantic::Info& sem,
+                                         std::ostream& out,
                                          size_t indent) const {
   make_indent(out, indent);
-  out << "ScalarConstructor[" << result_type_str() << "]{" << literal_->to_str()
-      << "}" << std::endl;
+  out << "ScalarConstructor[" << result_type_str(sem) << "]{"
+      << literal_->to_str(sem) << "}" << std::endl;
 }
 
 }  // namespace ast
diff --git a/src/ast/scalar_constructor_expression.h b/src/ast/scalar_constructor_expression.h
index 5c96ef9..052eb05 100644
--- a/src/ast/scalar_constructor_expression.h
+++ b/src/ast/scalar_constructor_expression.h
@@ -51,9 +51,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   ScalarConstructorExpression(const ScalarConstructorExpression&) = delete;
diff --git a/src/ast/scalar_constructor_expression_test.cc b/src/ast/scalar_constructor_expression_test.cc
index c4a4559..46fde36 100644
--- a/src/ast/scalar_constructor_expression_test.cc
+++ b/src/ast/scalar_constructor_expression_test.cc
@@ -50,7 +50,7 @@
 TEST_F(ScalarConstructorExpressionTest, ToStr) {
   auto* c = Expr(true);
   std::ostringstream out;
-  c->to_str(out, 2);
+  c->to_str(Sem(), out, 2);
   EXPECT_EQ(out.str(), R"(  ScalarConstructor[not set]{true}
 )");
 }
diff --git a/src/ast/sint_literal.cc b/src/ast/sint_literal.cc
index bc78d50..3c946ac 100644
--- a/src/ast/sint_literal.cc
+++ b/src/ast/sint_literal.cc
@@ -27,7 +27,7 @@
 
 SintLiteral::~SintLiteral() = default;
 
-std::string SintLiteral::to_str() const {
+std::string SintLiteral::to_str(const semantic::Info&) const {
   return std::to_string(value_);
 }
 
diff --git a/src/ast/sint_literal.h b/src/ast/sint_literal.h
index 2df717d..61fa740 100644
--- a/src/ast/sint_literal.h
+++ b/src/ast/sint_literal.h
@@ -38,8 +38,9 @@
   /// @returns the name for this literal. This name is unique to this value.
   std::string name() const override;
 
+  /// @param sem the semantic info for the program
   /// @returns the literal as a string
-  std::string to_str() const override;
+  std::string to_str(const semantic::Info& sem) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/sint_literal_test.cc b/src/ast/sint_literal_test.cc
index 2fc5993..a6d39a4 100644
--- a/src/ast/sint_literal_test.cc
+++ b/src/ast/sint_literal_test.cc
@@ -46,7 +46,7 @@
 TEST_F(SintLiteralTest, ToStr) {
   auto* i = create<SintLiteral>(ty.i32(), -42);
 
-  EXPECT_EQ(i->to_str(), "-42");
+  EXPECT_EQ(i->to_str(Sem()), "-42");
 }
 
 TEST_F(SintLiteralTest, Name_I32) {
diff --git a/src/ast/stage_decoration.cc b/src/ast/stage_decoration.cc
index 53cb362..bd68fe2 100644
--- a/src/ast/stage_decoration.cc
+++ b/src/ast/stage_decoration.cc
@@ -27,7 +27,9 @@
 
 StageDecoration::~StageDecoration() = default;
 
-void StageDecoration::to_str(std::ostream& out, size_t indent) const {
+void StageDecoration::to_str(const semantic::Info&,
+                             std::ostream& out,
+                             size_t indent) const {
   make_indent(out, indent);
   out << "StageDecoration{" << stage_ << "}" << std::endl;
 }
diff --git a/src/ast/stage_decoration.h b/src/ast/stage_decoration.h
index 4208c7d..f34bd21 100644
--- a/src/ast/stage_decoration.h
+++ b/src/ast/stage_decoration.h
@@ -34,9 +34,12 @@
   PipelineStage value() const { return stage_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/stage_decoration_test.cc b/src/ast/stage_decoration_test.cc
index 82a5ccd..142f26d 100644
--- a/src/ast/stage_decoration_test.cc
+++ b/src/ast/stage_decoration_test.cc
@@ -39,7 +39,7 @@
 TEST_F(StageDecorationTest, ToStr) {
   auto* d = create<StageDecoration>(PipelineStage::kFragment);
   std::ostringstream out;
-  d->to_str(out, 0);
+  d->to_str(Sem(), out, 0);
   EXPECT_EQ(out.str(), R"(StageDecoration{fragment}
 )");
 }
diff --git a/src/ast/stride_decoration.cc b/src/ast/stride_decoration.cc
index 0dd1990..52f3ef5 100644
--- a/src/ast/stride_decoration.cc
+++ b/src/ast/stride_decoration.cc
@@ -27,7 +27,9 @@
 
 StrideDecoration::~StrideDecoration() = default;
 
-void StrideDecoration::to_str(std::ostream& out, size_t indent) const {
+void StrideDecoration::to_str(const semantic::Info&,
+                              std::ostream& out,
+                              size_t indent) const {
   make_indent(out, indent);
   out << "stride " << stride_;
 }
diff --git a/src/ast/stride_decoration.h b/src/ast/stride_decoration.h
index ad99778..7e4031f 100644
--- a/src/ast/stride_decoration.h
+++ b/src/ast/stride_decoration.h
@@ -35,9 +35,12 @@
   uint32_t stride() const { return stride_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/struct.cc b/src/ast/struct.cc
index cf696c5..0493e3a 100644
--- a/src/ast/struct.cc
+++ b/src/ast/struct.cc
@@ -66,16 +66,18 @@
   return true;
 }
 
-void Struct::to_str(std::ostream& out, size_t indent) const {
+void Struct::to_str(const semantic::Info& sem,
+                    std::ostream& out,
+                    size_t indent) const {
   out << "Struct{" << std::endl;
   for (auto* deco : decorations_) {
     make_indent(out, indent + 2);
     out << "[[";
-    deco->to_str(out, 0);
+    deco->to_str(sem, out, 0);
     out << "]]" << std::endl;
   }
   for (auto* member : members_) {
-    member->to_str(out, indent + 2);
+    member->to_str(sem, out, indent + 2);
   }
   make_indent(out, indent);
   out << "}" << std::endl;
diff --git a/src/ast/struct.h b/src/ast/struct.h
index 9b4cb38..36b0062 100644
--- a/src/ast/struct.h
+++ b/src/ast/struct.h
@@ -67,9 +67,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   Struct(const Struct&) = delete;
diff --git a/src/ast/struct_block_decoration.cc b/src/ast/struct_block_decoration.cc
index 2e1cf92..5c69f81 100644
--- a/src/ast/struct_block_decoration.cc
+++ b/src/ast/struct_block_decoration.cc
@@ -27,7 +27,9 @@
 
 StructBlockDecoration::~StructBlockDecoration() = default;
 
-void StructBlockDecoration::to_str(std::ostream& out, size_t indent) const {
+void StructBlockDecoration::to_str(const semantic::Info&,
+                                   std::ostream& out,
+                                   size_t indent) const {
   make_indent(out, indent);
   out << "block";
 }
diff --git a/src/ast/struct_block_decoration.h b/src/ast/struct_block_decoration.h
index 732fa5e..c9e7d8a 100644
--- a/src/ast/struct_block_decoration.h
+++ b/src/ast/struct_block_decoration.h
@@ -34,9 +34,12 @@
   ~StructBlockDecoration() override;
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/struct_member.cc b/src/ast/struct_member.cc
index 0cc7a71..b6aa642 100644
--- a/src/ast/struct_member.cc
+++ b/src/ast/struct_member.cc
@@ -72,13 +72,15 @@
   return true;
 }
 
-void StructMember::to_str(std::ostream& out, size_t indent) const {
+void StructMember::to_str(const semantic::Info& sem,
+                          std::ostream& out,
+                          size_t indent) const {
   make_indent(out, indent);
   out << "StructMember{";
   if (decorations_.size() > 0) {
     out << "[[ ";
     for (auto* deco : decorations_)
-      out << deco->str() << " ";
+      out << deco->str(sem) << " ";
     out << "]] ";
   }
 
diff --git a/src/ast/struct_member.h b/src/ast/struct_member.h
index 9f06562..c087b2b 100644
--- a/src/ast/struct_member.h
+++ b/src/ast/struct_member.h
@@ -70,9 +70,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   StructMember(const StructMember&) = delete;
diff --git a/src/ast/struct_member_offset_decoration.cc b/src/ast/struct_member_offset_decoration.cc
index 09ebef6..87f13a6 100644
--- a/src/ast/struct_member_offset_decoration.cc
+++ b/src/ast/struct_member_offset_decoration.cc
@@ -28,7 +28,8 @@
 
 StructMemberOffsetDecoration::~StructMemberOffsetDecoration() = default;
 
-void StructMemberOffsetDecoration::to_str(std::ostream& out,
+void StructMemberOffsetDecoration::to_str(const semantic::Info&,
+                                          std::ostream& out,
                                           size_t indent) const {
   make_indent(out, indent);
   out << "offset " << std::to_string(offset_);
diff --git a/src/ast/struct_member_offset_decoration.h b/src/ast/struct_member_offset_decoration.h
index 445483b..031a758 100644
--- a/src/ast/struct_member_offset_decoration.h
+++ b/src/ast/struct_member_offset_decoration.h
@@ -36,9 +36,12 @@
   uint32_t offset() const { return offset_; }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/struct_member_test.cc b/src/ast/struct_member_test.cc
index 55ffc64..44c2bb9 100644
--- a/src/ast/struct_member_test.cc
+++ b/src/ast/struct_member_test.cc
@@ -75,14 +75,14 @@
 TEST_F(StructMemberTest, ToStr) {
   auto* st = Member("a", ty.i32(), {MemberOffset(4)});
   std::ostringstream out;
-  st->to_str(out, 2);
+  st->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), "  StructMember{[[ offset 4 ]] a: __i32}\n");
 }
 
 TEST_F(StructMemberTest, ToStrNoDecorations) {
   auto* st = Member("a", ty.i32());
   std::ostringstream out;
-  st->to_str(out, 2);
+  st->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), "  StructMember{a: __i32}\n");
 }
 
diff --git a/src/ast/struct_test.cc b/src/ast/struct_test.cc
index 8274c5a..103c22b 100644
--- a/src/ast/struct_test.cc
+++ b/src/ast/struct_test.cc
@@ -93,7 +93,7 @@
   auto* s = create<Struct>(StructMemberList{Member("a", ty.i32())}, decos);
 
   std::ostringstream out;
-  s->to_str(out, 2);
+  s->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(Struct{
     [[block]]
     StructMember{a: __i32}
diff --git a/src/ast/switch_statement.cc b/src/ast/switch_statement.cc
index 4bc34b3..89dc22d 100644
--- a/src/ast/switch_statement.cc
+++ b/src/ast/switch_statement.cc
@@ -49,16 +49,18 @@
   return true;
 }
 
-void SwitchStatement::to_str(std::ostream& out, size_t indent) const {
+void SwitchStatement::to_str(const semantic::Info& sem,
+                             std::ostream& out,
+                             size_t indent) const {
   make_indent(out, indent);
   out << "Switch{" << std::endl;
-  condition_->to_str(out, indent + 2);
+  condition_->to_str(sem, out, indent + 2);
 
   make_indent(out, indent + 2);
   out << "{" << std::endl;
 
   for (auto* stmt : body_) {
-    stmt->to_str(out, indent + 4);
+    stmt->to_str(sem, out, indent + 4);
   }
 
   make_indent(out, indent + 2);
diff --git a/src/ast/switch_statement.h b/src/ast/switch_statement.h
index b964724..6c180cd 100644
--- a/src/ast/switch_statement.h
+++ b/src/ast/switch_statement.h
@@ -60,9 +60,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   SwitchStatement(const SwitchStatement&) = delete;
diff --git a/src/ast/switch_statement_test.cc b/src/ast/switch_statement_test.cc
index d51eb55..86d0b90 100644
--- a/src/ast/switch_statement_test.cc
+++ b/src/ast/switch_statement_test.cc
@@ -137,7 +137,7 @@
 
   auto* stmt = create<SwitchStatement>(ident, CaseStatementList{});
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Switch{
     Identifier[not set]{ident}
     {
@@ -157,7 +157,7 @@
 
   auto* stmt = create<SwitchStatement>(ident, body);
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Switch{
     Identifier[not set]{ident}
     {
diff --git a/src/ast/type_constructor_expression.cc b/src/ast/type_constructor_expression.cc
index 2afde7b..532e0d0 100644
--- a/src/ast/type_constructor_expression.cc
+++ b/src/ast/type_constructor_expression.cc
@@ -53,14 +53,16 @@
   return true;
 }
 
-void TypeConstructorExpression::to_str(std::ostream& out, size_t indent) const {
+void TypeConstructorExpression::to_str(const semantic::Info& sem,
+                                       std::ostream& out,
+                                       size_t indent) const {
   make_indent(out, indent);
-  out << "TypeConstructor[" << result_type_str() << "]{" << std::endl;
+  out << "TypeConstructor[" << result_type_str(sem) << "]{" << std::endl;
   make_indent(out, indent + 2);
   out << type_->type_name() << std::endl;
 
   for (auto* val : values_) {
-    val->to_str(out, indent + 2);
+    val->to_str(sem, out, indent + 2);
   }
   make_indent(out, indent);
   out << "}" << std::endl;
diff --git a/src/ast/type_constructor_expression.h b/src/ast/type_constructor_expression.h
index 1fdbeec..9190a84 100644
--- a/src/ast/type_constructor_expression.h
+++ b/src/ast/type_constructor_expression.h
@@ -56,9 +56,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   TypeConstructorExpression(const TypeConstructorExpression&) = delete;
diff --git a/src/ast/type_constructor_expression_test.cc b/src/ast/type_constructor_expression_test.cc
index 4f1b3b1..e4421a4 100644
--- a/src/ast/type_constructor_expression_test.cc
+++ b/src/ast/type_constructor_expression_test.cc
@@ -107,7 +107,7 @@
 
   auto* t = create<TypeConstructorExpression>(&vec, expr);
   std::ostringstream out;
-  t->to_str(out, 2);
+  t->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  TypeConstructor[not set]{
     __vec_3__f32
     Identifier[not set]{expr_1}
diff --git a/src/ast/uint_literal.cc b/src/ast/uint_literal.cc
index 076b896..e9dd464 100644
--- a/src/ast/uint_literal.cc
+++ b/src/ast/uint_literal.cc
@@ -27,7 +27,7 @@
 
 UintLiteral::~UintLiteral() = default;
 
-std::string UintLiteral::to_str() const {
+std::string UintLiteral::to_str(const semantic::Info&) const {
   return std::to_string(value_);
 }
 
diff --git a/src/ast/uint_literal.h b/src/ast/uint_literal.h
index 3464a88..c3ff8cf 100644
--- a/src/ast/uint_literal.h
+++ b/src/ast/uint_literal.h
@@ -38,8 +38,9 @@
   /// @returns the name for this literal. This name is unique to this value.
   std::string name() const override;
 
+  /// @param sem the semantic info for the program
   /// @returns the literal as a string
-  std::string to_str() const override;
+  std::string to_str(const semantic::Info& sem) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/uint_literal_test.cc b/src/ast/uint_literal_test.cc
index 1216bb2..6628269 100644
--- a/src/ast/uint_literal_test.cc
+++ b/src/ast/uint_literal_test.cc
@@ -44,7 +44,7 @@
 
 TEST_F(UintLiteralTest, ToStr) {
   auto* u = create<UintLiteral>(ty.u32(), 42);
-  EXPECT_EQ(u->to_str(), "42");
+  EXPECT_EQ(u->to_str(Sem()), "42");
 }
 
 }  // namespace
diff --git a/src/ast/unary_op_expression.cc b/src/ast/unary_op_expression.cc
index 890e1ed..495e49a 100644
--- a/src/ast/unary_op_expression.cc
+++ b/src/ast/unary_op_expression.cc
@@ -40,12 +40,14 @@
   return expr_ != nullptr && expr_->IsValid();
 }
 
-void UnaryOpExpression::to_str(std::ostream& out, size_t indent) const {
+void UnaryOpExpression::to_str(const semantic::Info& sem,
+                               std::ostream& out,
+                               size_t indent) const {
   make_indent(out, indent);
-  out << "UnaryOp[" << result_type_str() << "]{" << std::endl;
+  out << "UnaryOp[" << result_type_str(sem) << "]{" << std::endl;
   make_indent(out, indent + 2);
   out << op_ << std::endl;
-  expr_->to_str(out, indent + 2);
+  expr_->to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
 }
diff --git a/src/ast/unary_op_expression.h b/src/ast/unary_op_expression.h
index 0a9d352..c7d8017 100644
--- a/src/ast/unary_op_expression.h
+++ b/src/ast/unary_op_expression.h
@@ -54,9 +54,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   UnaryOpExpression(const UnaryOpExpression&) = delete;
diff --git a/src/ast/unary_op_expression_test.cc b/src/ast/unary_op_expression_test.cc
index 9b0717a..6abc1bb 100644
--- a/src/ast/unary_op_expression_test.cc
+++ b/src/ast/unary_op_expression_test.cc
@@ -69,7 +69,7 @@
   auto* ident = Expr("ident");
   auto* u = create<UnaryOpExpression>(UnaryOp::kNot, ident);
   std::ostringstream out;
-  u->to_str(out, 2);
+  u->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  UnaryOp[not set]{
     not
     Identifier[not set]{ident}
diff --git a/src/ast/variable.cc b/src/ast/variable.cc
index 52e869b..cc0e676 100644
--- a/src/ast/variable.cc
+++ b/src/ast/variable.cc
@@ -110,7 +110,9 @@
   return true;
 }
 
-void Variable::info_to_str(std::ostream& out, size_t indent) const {
+void Variable::info_to_str(const semantic::Info&,
+                           std::ostream& out,
+                           size_t indent) const {
   make_indent(out, indent);
   out << symbol_.to_str() << std::endl;
   make_indent(out, indent);
@@ -119,20 +121,24 @@
   out << type_->type_name() << std::endl;
 }
 
-void Variable::constructor_to_str(std::ostream& out, size_t indent) const {
+void Variable::constructor_to_str(const semantic::Info& sem,
+                                  std::ostream& out,
+                                  size_t indent) const {
   if (constructor_ == nullptr)
     return;
 
   make_indent(out, indent);
   out << "{" << std::endl;
 
-  constructor_->to_str(out, indent + 2);
+  constructor_->to_str(sem, out, indent + 2);
 
   make_indent(out, indent);
   out << "}" << std::endl;
 }
 
-void Variable::to_str(std::ostream& out, size_t indent) const {
+void Variable::to_str(const semantic::Info& sem,
+                      std::ostream& out,
+                      size_t indent) const {
   make_indent(out, indent);
   out << "Variable";
   if (is_const()) {
@@ -144,14 +150,14 @@
     make_indent(out, indent + 2);
     out << "Decorations{" << std::endl;
     for (auto* deco : decorations_) {
-      deco->to_str(out, indent + 4);
+      deco->to_str(sem, out, indent + 4);
     }
     make_indent(out, indent + 2);
     out << "}" << std::endl;
   }
 
-  info_to_str(out, indent + 2);
-  constructor_to_str(out, indent + 2);
+  info_to_str(sem, out, indent + 2);
+  constructor_to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
 }
diff --git a/src/ast/variable.h b/src/ast/variable.h
index e721ad0..fb337b1 100644
--- a/src/ast/variable.h
+++ b/src/ast/variable.h
@@ -150,19 +150,28 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  protected:
   /// Output information for this variable.
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void info_to_str(std::ostream& out, size_t indent) const;
+  void info_to_str(const semantic::Info& sem,
+                   std::ostream& out,
+                   size_t indent) const;
   /// Output constructor for this variable.
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void constructor_to_str(std::ostream& out, size_t indent) const;
+  void constructor_to_str(const semantic::Info& sem,
+                          std::ostream& out,
+                          size_t indent) const;
 
  private:
   Variable(const Variable&) = delete;
diff --git a/src/ast/variable_decl_statement.cc b/src/ast/variable_decl_statement.cc
index 8bdddcb..2dd0a77 100644
--- a/src/ast/variable_decl_statement.cc
+++ b/src/ast/variable_decl_statement.cc
@@ -39,10 +39,12 @@
   return variable_ != nullptr && variable_->IsValid();
 }
 
-void VariableDeclStatement::to_str(std::ostream& out, size_t indent) const {
+void VariableDeclStatement::to_str(const semantic::Info& sem,
+                                   std::ostream& out,
+                                   size_t indent) const {
   make_indent(out, indent);
   out << "VariableDeclStatement{" << std::endl;
-  variable_->to_str(out, indent + 2);
+  variable_->to_str(sem, out, indent + 2);
   make_indent(out, indent);
   out << "}" << std::endl;
 }
diff --git a/src/ast/variable_decl_statement.h b/src/ast/variable_decl_statement.h
index bc2bed7..5acafd4 100644
--- a/src/ast/variable_decl_statement.h
+++ b/src/ast/variable_decl_statement.h
@@ -52,9 +52,12 @@
   bool IsValid() const override;
 
   /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
  private:
   VariableDeclStatement(const VariableDeclStatement&) = delete;
diff --git a/src/ast/variable_decl_statement_test.cc b/src/ast/variable_decl_statement_test.cc
index 62294fa..6524c0e 100644
--- a/src/ast/variable_decl_statement_test.cc
+++ b/src/ast/variable_decl_statement_test.cc
@@ -71,7 +71,7 @@
   auto* stmt =
       create<VariableDeclStatement>(Source{Source::Location{20, 2}}, var);
   std::ostringstream out;
-  stmt->to_str(out, 2);
+  stmt->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  VariableDeclStatement{
     Variable{
       a
diff --git a/src/ast/variable_test.cc b/src/ast/variable_test.cc
index a7d2319..15a930f 100644
--- a/src/ast/variable_test.cc
+++ b/src/ast/variable_test.cc
@@ -103,7 +103,7 @@
   auto* v = Var("my_var", StorageClass::kFunction, ty.f32(), nullptr,
                 ast::VariableDecorationList{});
   std::ostringstream out;
-  v->to_str(out, 2);
+  v->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Variable{
     my_var
     function
@@ -146,7 +146,7 @@
                   });
 
   std::ostringstream out;
-  var->to_str(out, 2);
+  var->to_str(Sem(), out, 2);
   EXPECT_EQ(demangle(out.str()), R"(  Variable{
     Decorations{
       BindingDecoration{2}
diff --git a/src/ast/workgroup_decoration.cc b/src/ast/workgroup_decoration.cc
index 914278a..0d49f10 100644
--- a/src/ast/workgroup_decoration.cc
+++ b/src/ast/workgroup_decoration.cc
@@ -38,7 +38,9 @@
 
 WorkgroupDecoration::~WorkgroupDecoration() = default;
 
-void WorkgroupDecoration::to_str(std::ostream& out, size_t indent) const {
+void WorkgroupDecoration::to_str(const semantic::Info&,
+                                 std::ostream& out,
+                                 size_t indent) const {
   make_indent(out, indent);
   out << "WorkgroupDecoration{" << x_ << " " << y_ << " " << z_ << "}"
       << std::endl;
diff --git a/src/ast/workgroup_decoration.h b/src/ast/workgroup_decoration.h
index d394215..af6e949 100644
--- a/src/ast/workgroup_decoration.h
+++ b/src/ast/workgroup_decoration.h
@@ -51,9 +51,12 @@
   }
 
   /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
   /// @param out the stream to write to
   /// @param indent number of spaces to indent the node when writing
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 
   /// Clones this node and all transitive child nodes using the `CloneContext`
   /// `ctx`.
diff --git a/src/ast/workgroup_decoration_test.cc b/src/ast/workgroup_decoration_test.cc
index 4a749f1..df28db1 100644
--- a/src/ast/workgroup_decoration_test.cc
+++ b/src/ast/workgroup_decoration_test.cc
@@ -66,7 +66,7 @@
 TEST_F(WorkgroupDecorationTest, ToStr) {
   auto* d = create<WorkgroupDecoration>(2, 4, 6);
   std::ostringstream out;
-  d->to_str(out, 0);
+  d->to_str(Sem(), out, 0);
   EXPECT_EQ(out.str(), R"(WorkgroupDecoration{2 4 6}
 )");
 }
diff --git a/src/clone_context_test.cc b/src/clone_context_test.cc
index 6460eae..55d0f52 100644
--- a/src/clone_context_test.cc
+++ b/src/clone_context_test.cc
@@ -39,7 +39,7 @@
   }
 
   bool IsValid() const override { return true; }
-  void to_str(std::ostream&, size_t) const override {}
+  void to_str(const semantic::Info&, std::ostream&, size_t) const override {}
 };
 
 struct Replaceable : public Castable<Replaceable, Cloneable> {
diff --git a/src/demangler.cc b/src/demangler.cc
index aca97a0..cdfcaa5 100644
--- a/src/demangler.cc
+++ b/src/demangler.cc
@@ -59,7 +59,7 @@
 }
 
 std::string Demangler::Demangle(const Program& program) const {
-  return Demangle(program.Symbols(), program.AST().to_str());
+  return Demangle(program.Symbols(), program.AST().to_str(program.Sem()));
 }
 
 }  // namespace tint
diff --git a/src/program.cc b/src/program.cc
index 6b25bdf..39bf5a0 100644
--- a/src/program.cc
+++ b/src/program.cc
@@ -30,6 +30,7 @@
     : types_(std::move(program.types_)),
       nodes_(std::move(program.nodes_)),
       ast_(std::move(program.ast_)),
+      sem_(std::move(program.sem_)),
       symbols_(std::move(program.symbols_)),
       diagnostics_(std::move(program.diagnostics_)),
       is_valid_(program.is_valid_) {
@@ -53,6 +54,7 @@
   ast_ = nodes_.Create<ast::Module>(Source{}, builder.AST().ConstructedTypes(),
                                     builder.AST().Functions(),
                                     builder.AST().GlobalVariables());
+  sem_ = std::move(builder.Sem());
   symbols_ = std::move(builder.Symbols());
   diagnostics_ = std::move(builder.Diagnostics());
   builder.MarkAsMoved();
@@ -73,6 +75,7 @@
   types_ = std::move(program.types_);
   nodes_ = std::move(program.nodes_);
   ast_ = std::move(program.ast_);
+  sem_ = std::move(program.sem_);
   symbols_ = std::move(program.symbols_);
   is_valid_ = program.is_valid_;
   return *this;
@@ -97,7 +100,7 @@
 
 std::string Program::to_str() const {
   AssertNotMoved();
-  return ast_->to_str();
+  return ast_->to_str(Sem());
 }
 
 void Program::AssertNotMoved() const {
diff --git a/src/program.h b/src/program.h
index bddc73d..0e624e7 100644
--- a/src/program.h
+++ b/src/program.h
@@ -19,6 +19,7 @@
 
 #include "src/ast/function.h"
 #include "src/diagnostic/diagnostic.h"
+#include "src/semantic/info.h"
 #include "src/symbol_table.h"
 #include "src/type/type_manager.h"
 
@@ -76,6 +77,12 @@
     return *ast_;
   }
 
+  /// @returns a reference to the program's semantic info
+  const semantic::Info& Sem() const {
+    AssertNotMoved();
+    return sem_;
+  }
+
   /// @returns a reference to the program's SymbolTable
   const SymbolTable& Symbols() const {
     AssertNotMoved();
@@ -110,6 +117,7 @@
   type::Manager types_;
   ASTNodes nodes_;
   ast::Module* ast_;
+  semantic::Info sem_;
   SymbolTable symbols_;
   diag::List diagnostics_;
   bool is_valid_ = true;
diff --git a/src/program_builder.cc b/src/program_builder.cc
index af61240..5f21743 100644
--- a/src/program_builder.cc
+++ b/src/program_builder.cc
@@ -31,6 +31,7 @@
       types_(std::move(rhs.types_)),
       nodes_(std::move(rhs.nodes_)),
       ast_(rhs.ast_),
+      sem_(std::move(rhs.sem_)),
       symbols_(std::move(rhs.symbols_)) {
   rhs.MarkAsMoved();
 }
@@ -44,6 +45,7 @@
   types_ = std::move(rhs.types_);
   nodes_ = std::move(rhs.nodes_);
   ast_ = rhs.ast_;
+  sem_ = std::move(rhs.sem_);
   symbols_ = std::move(rhs.symbols_);
   return *this;
 }
diff --git a/src/program_builder.h b/src/program_builder.h
index dfac2c9..582717d 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -37,6 +37,7 @@
 #include "src/ast/variable.h"
 #include "src/diagnostic/diagnostic.h"
 #include "src/program.h"
+#include "src/semantic/info.h"
 #include "src/symbol_table.h"
 #include "src/type/alias_type.h"
 #include "src/type/array_type.h"
@@ -101,18 +102,48 @@
     return types_;
   }
 
+  /// @returns a reference to the program's types
+  const type::Manager& Types() const {
+    AssertNotMoved();
+    return types_;
+  }
+
   /// @returns a reference to the program's AST nodes storage
   ASTNodes& Nodes() {
     AssertNotMoved();
     return nodes_;
   }
 
+  /// @returns a reference to the program's AST nodes storage
+  const ASTNodes& Nodes() const {
+    AssertNotMoved();
+    return nodes_;
+  }
+
   /// @returns a reference to the program's AST root Module
   ast::Module& AST() {
     AssertNotMoved();
     return *ast_;
   }
 
+  /// @returns a reference to the program's AST root Module
+  const ast::Module& AST() const {
+    AssertNotMoved();
+    return *ast_;
+  }
+
+  /// @returns a reference to the program's semantic info
+  semantic::Info& Sem() {
+    AssertNotMoved();
+    return sem_;
+  }
+
+  /// @returns a reference to the program's semantic info
+  const semantic::Info& Sem() const {
+    AssertNotMoved();
+    return sem_;
+  }
+
   /// @returns a reference to the program's SymbolTable
   SymbolTable& Symbols() {
     AssertNotMoved();
@@ -125,6 +156,12 @@
     return diagnostics_;
   }
 
+  /// @returns a reference to the program's diagnostics
+  const diag::List& Diagnostics() const {
+    AssertNotMoved();
+    return diagnostics_;
+  }
+
   /// Controls whether the TypeDeterminer will be run on the program when it is
   /// built.
   /// @param enable the new flag value (defaults to true)
@@ -853,6 +890,7 @@
   type::Manager types_;
   ASTNodes nodes_;
   ast::Module* ast_;
+  semantic::Info sem_;
   SymbolTable symbols_;
   diag::List diagnostics_;
 
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index d00da55..fd3aff3 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -709,7 +709,9 @@
 ast::Node* StatementBuilder::Clone(CloneContext*) const {
   return nullptr;
 }
-void StatementBuilder::to_str(std::ostream& out, size_t indent) const {
+void StatementBuilder::to_str(const semantic::Info&,
+                              std::ostream& out,
+                              size_t indent) const {
   make_indent(out, indent);
   out << "StatementBuilder" << std::endl;
 }
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index acc01d9..eaafa68 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -353,7 +353,9 @@
  private:
   bool IsValid() const override;
   Node* Clone(CloneContext*) const override;
-  void to_str(std::ostream& out, size_t indent) const override;
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
 };
 
 /// A FunctionEmitter emits a SPIR-V function onto a Tint AST module.
diff --git a/src/reader/spirv/function_arithmetic_test.cc b/src/reader/spirv/function_arithmetic_test.cc
index dd17a2d..cfe77bc 100644
--- a/src/reader/spirv/function_arithmetic_test.cc
+++ b/src/reader/spirv/function_arithmetic_test.cc
@@ -139,7 +139,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -151,7 +151,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_Int_Uint) {
@@ -166,7 +166,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -180,7 +180,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_Uint_Int) {
@@ -195,7 +195,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -209,7 +209,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_Uint_Uint) {
@@ -224,7 +224,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -240,7 +240,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_SignedVec_SignedVec) {
@@ -255,7 +255,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -271,7 +271,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_SignedVec_UnsignedVec) {
@@ -286,7 +286,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -304,7 +304,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_SignedVec) {
@@ -319,7 +319,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -337,7 +337,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_UnsignedVec) {
@@ -352,7 +352,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -372,7 +372,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, FNegate_Scalar) {
@@ -387,7 +387,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -399,7 +399,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, FNegate_Vector) {
@@ -414,7 +414,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -430,7 +430,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 struct BinaryData {
@@ -478,8 +478,7 @@
      << GetParam().ast_type << "\n    {\n      Binary[not set]{"
      << "\n        " << GetParam().ast_lhs << "\n        " << GetParam().ast_op
      << "\n        " << GetParam().ast_rhs;
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(ss.str()))
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(ss.str()))
       << assembly;
 }
 
@@ -702,7 +701,7 @@
       << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -737,7 +736,7 @@
       << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -760,7 +759,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -848,7 +847,7 @@
       << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -883,7 +882,7 @@
       << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -906,7 +905,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -936,8 +935,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_10
     none
     __vec_2__f32
@@ -949,7 +947,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesScalar) {
@@ -966,8 +964,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_10
     none
     __mat_2_2__f32
@@ -979,7 +976,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, VectorTimesMatrix) {
@@ -996,8 +993,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_10
     none
     __mat_2_2__f32
@@ -1009,7 +1005,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesVector) {
@@ -1026,8 +1022,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_10
     none
     __mat_2_2__f32
@@ -1039,7 +1034,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesMatrix) {
@@ -1056,8 +1051,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_10
     none
     __mat_2_2__f32
@@ -1069,7 +1063,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, Dot) {
@@ -1086,8 +1080,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_3
     none
     __f32
@@ -1101,7 +1094,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, OuterProduct) {
@@ -1120,7 +1113,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(got, HasSubstr(R"(VariableConst{
     x_3
     none
diff --git a/src/reader/spirv/function_bit_test.cc b/src/reader/spirv/function_bit_test.cc
index 57d400a..000ef59 100644
--- a/src/reader/spirv/function_bit_test.cc
+++ b/src/reader/spirv/function_bit_test.cc
@@ -163,8 +163,7 @@
      << GetParam().ast_type << "\n    {\n      Binary[not set]{"
      << "\n        " << GetParam().ast_lhs << "\n        " << GetParam().ast_op
      << "\n        " << GetParam().ast_rhs;
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(ss.str()))
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(ss.str()))
       << assembly;
 }
 
@@ -402,7 +401,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -414,7 +413,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_Int_Uint) {
@@ -429,7 +428,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -443,7 +442,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_Uint_Int) {
@@ -458,7 +457,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -472,7 +471,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_Uint_Uint) {
@@ -487,7 +486,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -499,7 +498,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_SignedVec_SignedVec) {
@@ -514,7 +513,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -530,7 +529,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_SignedVec_UnsignedVec) {
@@ -545,7 +544,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -563,7 +562,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_UnsignedVec_SignedVec) {
@@ -578,7 +577,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -596,7 +595,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 TEST_F(SpvUnaryBitTest, Not_UnsignedVec_UnsignedVec) {
   const auto assembly = CommonTypes() + R"(
@@ -610,7 +609,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -626,7 +625,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 std::string BitTestPreamble() {
@@ -665,7 +664,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -693,7 +692,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -723,7 +722,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -753,7 +752,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -781,7 +780,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -809,7 +808,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -839,7 +838,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -869,7 +868,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -897,7 +896,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -925,7 +924,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -955,7 +954,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -985,7 +984,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1013,7 +1012,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1041,7 +1040,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1071,7 +1070,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1101,7 +1100,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
diff --git a/src/reader/spirv/function_call_test.cc b/src/reader/spirv/function_call_test.cc
index 5b8ce72..0184d16 100644
--- a/src/reader/spirv/function_call_test.cc
+++ b/src/reader/spirv/function_call_test.cc
@@ -46,7 +46,7 @@
      OpFunctionEnd
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  const auto got = p->program().AST().to_str();
+  const auto got = p->program().to_str();
   const char* expect = R"(Module{
   Function tint_symbol_1 -> __void
   ()
@@ -91,7 +91,7 @@
   {
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
     EXPECT_TRUE(fe.EmitBody());
-    EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+    EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
                 HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -107,18 +107,17 @@
   }
 }
 Return{})"))
-        << ToString(p->builder().Symbols(), fe.ast_body());
+        << ToString(p->builder(), fe.ast_body());
   }
 
   {
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 50));
     EXPECT_TRUE(fe.EmitBody());
-    EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-                HasSubstr(R"(Return{
+    EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Return{
   {
     ScalarConstructor[not set]{42}
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
   }
 }
 
@@ -149,7 +148,7 @@
   {
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
     EXPECT_TRUE(fe.EmitBody()) << p->error();
-    EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+    EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
                 HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_10
@@ -180,17 +179,16 @@
   Identifier[not set]{x_1}
 }
 Return{})"))
-        << ToString(p->builder().Symbols(), fe.ast_body());
+        << ToString(p->builder(), fe.ast_body());
   }
   {
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 50));
     EXPECT_TRUE(fe.EmitBody()) << p->error();
-    EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-                HasSubstr(R"(Return{
+    EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Return{
   {
     ScalarConstructor[not set]{42}
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
   }
 }
 
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index 57af7e9..2cc9a84 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -7361,7 +7361,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -7473,7 +7473,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -7600,7 +7600,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -7791,7 +7791,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -7827,7 +7827,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -7875,7 +7875,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -7931,7 +7931,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -7998,7 +7998,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8070,7 +8070,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8134,7 +8134,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8219,7 +8219,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8309,7 +8309,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8364,7 +8364,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8415,7 +8415,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8458,7 +8458,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8509,7 +8509,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8574,7 +8574,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8648,7 +8648,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8720,7 +8720,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8778,7 +8778,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -8838,7 +8838,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -8905,7 +8905,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -8979,7 +8979,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   If{
     (
@@ -9037,7 +9037,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9086,7 +9086,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9142,7 +9142,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9204,7 +9204,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9266,7 +9266,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9319,7 +9319,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9364,7 +9364,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9418,7 +9418,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9478,7 +9478,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9544,7 +9544,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9615,7 +9615,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9687,7 +9687,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9757,7 +9757,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9809,7 +9809,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Return{}
 )";
   ASSERT_EQ(expect, got);
@@ -9835,7 +9835,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -9875,7 +9875,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Return{}
 }
@@ -9905,7 +9905,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Return{
   {
     ScalarConstructor[not set]{2}
@@ -9944,7 +9944,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10001,7 +10001,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Return{
     {
@@ -10031,7 +10031,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Discard{}
 )";
   ASSERT_EQ(expect, got);
@@ -10057,7 +10057,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10097,7 +10097,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Discard{}
 }
@@ -10119,7 +10119,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Return{}
 )";
   ASSERT_EQ(expect, got);
@@ -10145,7 +10145,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10185,7 +10185,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Return{}
 }
@@ -10215,7 +10215,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Return{
   {
     ScalarConstructor[not set]{0}
@@ -10249,7 +10249,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   continuing {
     Assignment{
@@ -10284,7 +10284,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -10321,7 +10321,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -10381,7 +10381,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -10452,7 +10452,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -10534,7 +10534,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   continuing {
     Assignment{
@@ -10576,7 +10576,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -10630,7 +10630,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   If{
     (
@@ -10699,7 +10699,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -10769,7 +10769,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10812,7 +10812,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10864,7 +10864,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -10915,7 +10915,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11019,7 +11019,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11062,7 +11062,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11113,7 +11113,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11171,7 +11171,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11227,7 +11227,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11287,7 +11287,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11348,7 +11348,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11431,7 +11431,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11526,7 +11526,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11611,7 +11611,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11680,7 +11680,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11747,7 +11747,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11821,7 +11821,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11890,7 +11890,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11948,7 +11948,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12021,7 +12021,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12117,7 +12117,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12256,7 +12256,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12337,7 +12337,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12405,7 +12405,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12462,7 +12462,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12534,7 +12534,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12622,7 +12622,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12701,7 +12701,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -12789,7 +12789,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12886,7 +12886,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12982,7 +12982,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13091,7 +13091,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13188,7 +13188,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13269,7 +13269,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13333,7 +13333,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13418,7 +13418,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -13509,7 +13509,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -13578,7 +13578,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = "unhandled case";
   ASSERT_EQ(expect, got);
 }
@@ -13613,7 +13613,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = "unhandled case";
   ASSERT_EQ(expect, got);
 }
diff --git a/src/reader/spirv/function_composite_test.cc b/src/reader/spirv/function_composite_test.cc
index e607076..e5ff9b4 100644
--- a/src/reader/spirv/function_composite_test.cc
+++ b/src/reader/spirv/function_composite_test.cc
@@ -87,7 +87,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -129,7 +129,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_Composite_Construct, Matrix) {
@@ -144,7 +144,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -170,7 +170,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_Composite_Construct, Array) {
@@ -185,7 +185,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -201,7 +201,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_Composite_Construct, Struct) {
@@ -216,7 +216,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -234,7 +234,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 using SpvParserTest_CompositeExtract = SpvParserTest;
@@ -251,7 +251,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -267,7 +267,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Vector_IndexTooBigError) {
@@ -302,7 +302,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -314,7 +314,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Matrix_IndexTooBigError) {
@@ -353,7 +353,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -368,7 +368,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Array) {
@@ -387,7 +387,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -399,7 +399,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, RuntimeArray_IsError) {
@@ -438,7 +438,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -450,7 +450,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Struct_DifferOnlyInMemberName) {
@@ -481,7 +481,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
   auto got = fe.ast_body();
-  EXPECT_THAT(ToString(p->builder().Symbols(), got), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), got), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -493,8 +493,8 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), got);
-  EXPECT_THAT(ToString(p->builder().Symbols(), got), HasSubstr(R"(
+      << ToString(p->builder(), got);
+  EXPECT_THAT(ToString(p->builder(), got), HasSubstr(R"(
   VariableConst{
     x_4
     none
@@ -506,7 +506,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), got);
+      << ToString(p->builder(), got);
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Struct_IndexTooBigError) {
@@ -547,7 +547,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -568,7 +568,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 using SpvParserTest_CopyObject = SpvParserTest;
@@ -586,7 +586,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -606,7 +606,7 @@
       Identifier[not set]{x_1}
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CopyObject, Pointer) {
@@ -625,7 +625,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -645,7 +645,7 @@
       Identifier[not set]{x_1}
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 using SpvParserTest_VectorShuffle = SpvParserTest;
@@ -666,8 +666,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_10
     none
     __vec_4__u32
@@ -693,7 +692,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_UseBoth) {
@@ -709,8 +708,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_10
     none
     __vec_4__u32
@@ -752,7 +750,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_AllOnesMapToNull) {
@@ -769,8 +767,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_10
     none
     __vec_2__u32
@@ -785,7 +782,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_VectorShuffle, IndexTooBig_IsError) {
@@ -822,7 +819,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto got = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(got, HasSubstr(R"(VariableConst{
     x_10
     none
@@ -852,7 +849,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto got = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(got, HasSubstr(R"(VariableConst{
     x_10
     none
diff --git a/src/reader/spirv/function_conversion_test.cc b/src/reader/spirv/function_conversion_test.cc
index 352ee99..ec1bf40 100644
--- a/src/reader/spirv/function_conversion_test.cc
+++ b/src/reader/spirv/function_conversion_test.cc
@@ -82,7 +82,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -93,7 +93,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, Bitcast_Vector) {
@@ -108,7 +108,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -123,7 +123,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_BadArg) {
@@ -238,8 +238,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __f32
@@ -250,7 +249,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_Scalar_FromUnsigned) {
@@ -266,8 +265,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __f32
@@ -280,7 +278,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromSigned) {
@@ -296,8 +294,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __vec_2__f32
@@ -308,7 +305,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromUnsigned) {
@@ -324,8 +321,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __vec_2__f32
@@ -338,7 +334,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_BadArgType) {
@@ -387,8 +383,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __f32
@@ -401,7 +396,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_FromUnsigned) {
@@ -417,8 +412,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __f32
@@ -429,7 +423,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromSigned) {
@@ -445,8 +439,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __vec_2__f32
@@ -459,7 +452,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromUnsigned) {
@@ -475,8 +468,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __vec_2__f32
@@ -487,7 +479,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_BadArgType) {
@@ -537,8 +529,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __i32
@@ -549,7 +540,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_ToUnsigned) {
@@ -565,8 +556,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __u32
@@ -579,7 +569,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToSigned) {
@@ -595,8 +585,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __vec_2__i32
@@ -607,7 +596,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToUnsigned) {
@@ -623,8 +612,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __vec_2__u32
@@ -637,7 +625,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_BadArgType) {
@@ -687,8 +675,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __i32
@@ -701,7 +688,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_ToUnsigned) {
@@ -717,8 +704,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __u32
@@ -729,7 +715,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_ToSigned) {
@@ -745,8 +731,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __vec_2__i32
@@ -759,7 +744,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_ToUnsigned) {
@@ -775,8 +760,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(VariableConst{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableConst{
     x_1
     none
     __vec_2__u32
@@ -787,7 +771,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 // TODO(dneto): OpSConvert // only if multiple widths
diff --git a/src/reader/spirv/function_glsl_std_450_test.cc b/src/reader/spirv/function_glsl_std_450_test.cc
index 9158de9..8352396 100644
--- a/src/reader/spirv/function_glsl_std_450_test.cc
+++ b/src/reader/spirv/function_glsl_std_450_test.cc
@@ -183,7 +183,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -191,14 +191,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{f1}
         )
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Float_Floating, Vector) {
@@ -212,7 +212,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -220,14 +220,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2f1}
         )
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Scalar) {
@@ -241,7 +241,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -249,7 +249,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{f2}
@@ -257,7 +257,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Vector) {
@@ -271,7 +271,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -279,7 +279,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2f2}
@@ -287,7 +287,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Scalar) {
@@ -301,7 +301,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -309,14 +309,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{f1}
         )
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Vector) {
@@ -330,7 +330,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -338,14 +338,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2f1}
         )
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Scalar) {
@@ -359,7 +359,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -367,7 +367,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{f2}
@@ -375,7 +375,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Vector) {
@@ -389,7 +389,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -397,7 +397,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2f2}
@@ -405,7 +405,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Scalar) {
@@ -419,7 +419,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -427,7 +427,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{f2}
@@ -436,7 +436,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Vector) {
@@ -451,7 +451,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -459,7 +459,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2f2}
@@ -468,7 +468,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingUinting, Scalar) {
@@ -482,7 +482,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -490,7 +490,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{u1}
@@ -498,7 +498,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingUinting, Vector) {
@@ -513,7 +513,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -521,7 +521,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2u1}
@@ -529,7 +529,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingInting, Scalar) {
@@ -543,7 +543,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -551,7 +551,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{i1}
@@ -559,7 +559,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingInting, Vector) {
@@ -574,7 +574,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -582,7 +582,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2i1}
@@ -590,7 +590,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Float3_Float3Float3, Samples) {
@@ -605,7 +605,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -613,7 +613,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v3f1}
           Identifier[not set]{v3f2}
@@ -621,7 +621,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(Samples,
@@ -709,7 +709,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -717,14 +717,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{i1}
         )
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_Inting, Vector) {
@@ -739,7 +739,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -747,14 +747,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2i1}
         )
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_IntingInting, Scalar) {
@@ -769,7 +769,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -777,7 +777,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{i1}
           Identifier[not set]{i2}
@@ -785,7 +785,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_IntingInting, Vector) {
@@ -800,7 +800,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -808,7 +808,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2i1}
           Identifier[not set]{v2i2}
@@ -816,7 +816,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_IntingIntingInting, Scalar) {
@@ -831,7 +831,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -839,7 +839,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{i1}
           Identifier[not set]{i2}
@@ -848,7 +848,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_IntingIntingInting, Vector) {
@@ -863,7 +863,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -871,7 +871,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2i1}
           Identifier[not set]{v2i2}
@@ -880,7 +880,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(Samples,
@@ -907,7 +907,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -915,7 +915,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{u1}
           Identifier[not set]{u2}
@@ -923,7 +923,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUinting, Vector) {
@@ -938,7 +938,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -946,7 +946,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2u1}
           Identifier[not set]{v2u2}
@@ -954,7 +954,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUintingUinting, Scalar) {
@@ -968,7 +968,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -976,7 +976,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{u1}
           Identifier[not set]{u2}
@@ -985,7 +985,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUintingUinting, Vector) {
@@ -1000,7 +1000,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -1008,7 +1008,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                         R"(}
+                                                               R"(}
         (
           Identifier[not set]{v2u1}
           Identifier[not set]{v2u2}
@@ -1017,7 +1017,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(Samples,
@@ -1043,7 +1043,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1095,7 +1095,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1153,7 +1153,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1211,7 +1211,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1271,7 +1271,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1329,7 +1329,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1387,7 +1387,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->builder().Symbols(), fe.ast_body());
+  auto body = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
diff --git a/src/reader/spirv/function_logical_test.cc b/src/reader/spirv/function_logical_test.cc
index dc40f0c..e1c12c8 100644
--- a/src/reader/spirv/function_logical_test.cc
+++ b/src/reader/spirv/function_logical_test.cc
@@ -206,7 +206,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -218,7 +218,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryLogicalTest, LogicalNot_Vector) {
@@ -233,7 +233,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -249,7 +249,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 struct BinaryData {
@@ -296,8 +296,7 @@
      << GetParam().ast_type << "\n    {\n      Binary[not set]{"
      << "\n        " << GetParam().ast_lhs << "\n        " << GetParam().ast_op
      << "\n        " << GetParam().ast_rhs;
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(ss.str()))
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(ss.str()))
       << assembly;
 }
 
@@ -703,7 +702,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -719,7 +718,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordEqual_Vector) {
@@ -734,7 +733,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -758,7 +757,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordNotEqual_Scalar) {
@@ -773,7 +772,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -789,7 +788,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordNotEqual_Vector) {
@@ -804,7 +803,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -828,7 +827,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThan_Scalar) {
@@ -843,7 +842,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -859,7 +858,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThan_Vector) {
@@ -874,7 +873,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -898,7 +897,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThanEqual_Scalar) {
@@ -913,7 +912,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -929,7 +928,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThanEqual_Vector) {
@@ -944,7 +943,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -968,7 +967,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThan_Scalar) {
@@ -983,7 +982,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -999,7 +998,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThan_Vector) {
@@ -1014,7 +1013,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -1038,7 +1037,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Scalar) {
@@ -1053,7 +1052,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -1069,7 +1068,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Vector) {
@@ -1084,7 +1083,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -1108,7 +1107,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_BoolCond_BoolParams) {
@@ -1123,7 +1122,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1140,7 +1139,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_BoolCond_IntScalarParams) {
@@ -1155,7 +1154,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1172,7 +1171,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_BoolCond_FloatScalarParams) {
@@ -1187,7 +1186,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1204,7 +1203,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_BoolCond_VectorParams) {
@@ -1219,7 +1218,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1244,7 +1243,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_VecBoolCond_VectorParams) {
@@ -1259,7 +1258,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1288,7 +1287,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 using SpvLogicalTest = SpvParserTestBase<::testing::Test>;
@@ -1305,7 +1304,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1324,7 +1323,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, All) {
@@ -1339,7 +1338,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1358,7 +1357,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, IsNan_Scalar) {
@@ -1373,7 +1372,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1388,7 +1387,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, IsNan_Vector) {
@@ -1403,7 +1402,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1422,7 +1421,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, IsInf_Scalar) {
@@ -1437,7 +1436,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1452,7 +1451,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, IsInf_Vector) {
@@ -1467,7 +1466,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1486,7 +1485,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 // TODO(dneto): Kernel-guarded instructions.
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
index 379286c..89cfe2b 100644
--- a/src/reader/spirv/function_memory_test.cc
+++ b/src/reader/spirv/function_memory_test.cc
@@ -51,8 +51,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{true}
 }
@@ -84,8 +83,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{42}
 }
@@ -113,8 +111,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{42}
 }
@@ -142,8 +139,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{42.000000}
 }
@@ -172,7 +168,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -201,7 +197,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_2
@@ -243,7 +239,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_2
@@ -281,8 +277,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{42}
 })"));
@@ -349,14 +344,13 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
     Identifier[not set]{z}
   }
   ScalarConstructor[not set]{42}
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitStatement_AccessChain_VectorConstOutOfBounds) {
@@ -413,8 +407,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     Identifier[not set]{myvar}
     Identifier[not set]{x_11}
@@ -451,8 +444,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     Identifier[not set]{myvar}
     ScalarConstructor[not set]{2}
@@ -495,8 +487,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     Identifier[not set]{myvar}
     ScalarConstructor[not set]{2}
@@ -538,8 +529,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
     Identifier[not set]{age}
@@ -587,8 +577,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
     Identifier[not set]{age}
@@ -601,7 +590,7 @@
     Identifier[not set]{ancientness}
   }
   ScalarConstructor[not set]{420.000000}
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitStatement_AccessChain_StructNonConstIndex) {
@@ -698,8 +687,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     MemberAccessor[not set]{
       Identifier[not set]{myvar}
@@ -739,8 +727,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     ArrayAccessor[not set]{
       Identifier[not set]{myvar}
@@ -848,7 +835,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto got = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(got, HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
@@ -865,8 +852,7 @@
     ScalarConstructor[not set]{1}
   }
   ScalarConstructor[not set]{0}
-})")) << got
-      << p->error();
+})"));
 }
 
 TEST_F(SpvParserTest,
@@ -891,7 +877,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto got = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(got, HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
@@ -931,8 +917,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
-              HasSubstr(R"(Assignment{
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     MemberAccessor[not set]{
       Identifier[not set]{myvar}
@@ -941,7 +926,7 @@
     ScalarConstructor[not set]{1}
   }
   ScalarConstructor[not set]{0}
-})")) << ToString(p->builder().Symbols(), fe.ast_body())
+})")) << ToString(p->builder(), fe.ast_body())
       << p->error();
 }
 
@@ -965,7 +950,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_2
@@ -985,7 +970,7 @@
 Assignment{
   Identifier[not set]{x_2}
   ScalarConstructor[not set]{0}
-})")) << ToString(p->builder().Symbols(), fe.ast_body())
+})")) << ToString(p->builder(), fe.ast_body())
       << p->error();
 }
 
@@ -1021,7 +1006,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               Eq(R"(VariableDeclStatement{
   Variable{
     x_2
@@ -1056,7 +1041,7 @@
   ScalarConstructor[not set]{0}
 }
 Return{}
-)")) << ToString(p->builder().Symbols(), fe.ast_body())
+)")) << ToString(p->builder(), fe.ast_body())
      << p->error();
 }
 
@@ -1109,7 +1094,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body_str = ToString(p->builder().Symbols(), fe.ast_body());
+  const auto body_str = ToString(p->builder(), fe.ast_body());
   EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
diff --git a/src/reader/spirv/function_misc_test.cc b/src/reader/spirv/function_misc_test.cc
index 6df487a..809cf38 100644
--- a/src/reader/spirv/function_misc_test.cc
+++ b/src/reader/spirv/function_misc_test.cc
@@ -68,7 +68,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -108,7 +108,7 @@
       ScalarConstructor[not set]{0.000000}
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Vector) {
@@ -129,7 +129,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -171,7 +171,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Matrix) {
@@ -190,7 +190,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -212,7 +212,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Array) {
@@ -232,7 +232,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -246,7 +246,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Struct) {
@@ -265,7 +265,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -281,7 +281,7 @@
       }
     }
   }
-})")) << ToString(p->builder().Symbols(), fe.ast_body());
+})")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpNop) {
@@ -297,8 +297,8 @@
       << p->error() << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), Eq(R"(Return{}
-)")) << ToString(p->builder().Symbols(), fe.ast_body());
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()), Eq(R"(Return{}
+)")) << ToString(p->builder(), fe.ast_body());
 }
 
 // Test swizzle generation.
@@ -325,11 +325,12 @@
 
   auto* result = fe.Swizzle(GetParam().index);
   if (GetParam().expected_error.empty()) {
+    Program program(p->program());
     EXPECT_TRUE(fe.success());
     ASSERT_NE(result, nullptr);
     std::ostringstream ss;
-    result->to_str(ss, 0);
-    auto str = Demangler().Demangle(p->program().Symbols(), ss.str());
+    result->to_str(program.Sem(), ss, 0);
+    auto str = Demangler().Demangle(program.Symbols(), ss.str());
     EXPECT_THAT(str, Eq(GetParam().expected_expr));
   } else {
     EXPECT_EQ(result, nullptr);
diff --git a/src/reader/spirv/function_var_test.cc b/src/reader/spirv/function_var_test.cc
index 288dbe2..ca6f49a 100644
--- a/src/reader/spirv/function_var_test.cc
+++ b/src/reader/spirv/function_var_test.cc
@@ -91,7 +91,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_1
@@ -130,7 +130,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     a
@@ -169,7 +169,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     a
@@ -211,7 +211,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     a
@@ -285,7 +285,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     a
@@ -345,7 +345,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -360,7 +360,7 @@
     }
   }
 }
-)")) << ToString(p->builder().Symbols(), fe.ast_body());
+)")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_MatrixInitializer) {
@@ -384,7 +384,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -431,7 +431,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -446,7 +446,7 @@
     }
   }
 }
-)")) << ToString(p->builder().Symbols(), fe.ast_body());
+)")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_ArrayInitializer_Alias) {
@@ -466,7 +466,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   const char* expect = R"(VariableDeclStatement{
   Variable{
     x_200
@@ -501,7 +501,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -516,7 +516,7 @@
     }
   }
 }
-)")) << ToString(p->builder().Symbols(), fe.ast_body());
+)")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_ArrayInitializer_Alias_Null) {
@@ -536,7 +536,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -551,7 +551,7 @@
     }
   }
 }
-)")) << ToString(p->builder().Symbols(), fe.ast_body());
+)")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_StructInitializer) {
@@ -571,7 +571,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -591,7 +591,7 @@
     }
   }
 }
-)")) << ToString(p->builder().Symbols(), fe.ast_body());
+)")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_StructInitializer_Null) {
@@ -611,7 +611,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -631,7 +631,7 @@
     }
   }
 }
-)")) << ToString(p->builder().Symbols(), fe.ast_body());
+)")) << ToString(p->builder(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest,
@@ -656,7 +656,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect =
       R"(VariableDeclStatement{
   Variable{
@@ -704,7 +704,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   Variable{
     x_25
@@ -776,7 +776,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   Variable{
     x_25
@@ -875,7 +875,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{0}
@@ -993,7 +993,7 @@
 
   // We don't hoist x_1 into its own mutable variable. It is emitted as
   // a const definition.
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1078,7 +1078,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{true}
@@ -1165,7 +1165,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{true}
@@ -1248,7 +1248,7 @@
 
   // We don't hoist x_1 into its own mutable variable. It is emitted as
   // a const definition.
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1322,7 +1322,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   VariableDeclStatement{
     Variable{
@@ -1466,7 +1466,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(Loop{
   VariableDeclStatement{
     Variable{
@@ -1624,7 +1624,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_101
@@ -1793,7 +1793,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_101
@@ -1915,7 +1915,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_101
@@ -2023,7 +2023,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  auto got = ToString(p->builder(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   Variable{
     x_101_phi
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 8c0b629..3173fd6 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1504,7 +1504,8 @@
   }
   auto* type = expr.type;
   if (!type) {
-    Fail() << "internal error: unmapped type for: " << expr.expr->str() << "\n";
+    Fail() << "internal error: unmapped type for: "
+           << expr.expr->str(builder_.Sem()) << "\n";
     return {};
   }
   if (requires_unsigned) {
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
index 12d67b9..02dead2 100644
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/reader/spirv/parser_impl_convert_type_test.cc
@@ -564,10 +564,12 @@
   auto* type = p->ConvertType(10);
   ASSERT_NE(type, nullptr);
   EXPECT_TRUE(type->Is<type::Struct>());
+
+  Program program = p->program();
+
   std::stringstream ss;
-  type->As<type::Struct>()->impl()->to_str(ss, 0);
-  EXPECT_THAT(Demangler().Demangle(p->program().Symbols(), ss.str()),
-              Eq(R"(Struct{
+  type->As<type::Struct>()->impl()->to_str(program.Sem(), ss, 0);
+  EXPECT_THAT(Demangler().Demangle(program.Symbols(), ss.str()), Eq(R"(Struct{
   StructMember{field0: __u32}
   StructMember{field1: __f32}
 }
@@ -586,10 +588,12 @@
   auto* type = p->ConvertType(10);
   ASSERT_NE(type, nullptr);
   EXPECT_TRUE(type->Is<type::Struct>());
+
+  Program program = p->program();
+
   std::stringstream ss;
-  type->As<type::Struct>()->impl()->to_str(ss, 0);
-  EXPECT_THAT(Demangler().Demangle(p->program().Symbols(), ss.str()),
-              Eq(R"(Struct{
+  type->As<type::Struct>()->impl()->to_str(program.Sem(), ss, 0);
+  EXPECT_THAT(Demangler().Demangle(program.Symbols(), ss.str()), Eq(R"(Struct{
   [[block]]
   StructMember{field0: __u32}
 }
@@ -612,10 +616,12 @@
   auto* type = p->ConvertType(10);
   ASSERT_NE(type, nullptr);
   EXPECT_TRUE(type->Is<type::Struct>());
+
+  Program program = p->program();
+
   std::stringstream ss;
-  type->As<type::Struct>()->impl()->to_str(ss, 0);
-  EXPECT_THAT(Demangler().Demangle(p->program().Symbols(), ss.str()),
-              Eq(R"(Struct{
+  type->As<type::Struct>()->impl()->to_str(program.Sem(), ss, 0);
+  EXPECT_THAT(Demangler().Demangle(program.Symbols(), ss.str()), Eq(R"(Struct{
   StructMember{[[ offset 0 ]] field0: __f32}
   StructMember{[[ offset 8 ]] field1: __vec_2__f32}
   StructMember{[[ offset 16 ]] field2: __mat_2_2__f32}
diff --git a/src/reader/spirv/parser_impl_function_decl_test.cc b/src/reader/spirv/parser_impl_function_decl_test.cc
index d9f5458..80c47be 100644
--- a/src/reader/spirv/parser_impl_function_decl_test.cc
+++ b/src/reader/spirv/parser_impl_function_decl_test.cc
@@ -55,7 +55,7 @@
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
-  const auto program_ast = program.AST().to_str();
+  const auto program_ast = program.to_str();
   EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
 }
 
@@ -67,7 +67,7 @@
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
-  const auto program_ast = program.AST().to_str();
+  const auto program_ast = program.to_str();
   EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
 }
 
@@ -83,7 +83,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule());
   ASSERT_TRUE(p->error().empty()) << p->error();
   Program program = p->program();
-  const auto program_ast = program.AST().to_str();
+  const auto program_ast = program.to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function )" + program.Symbols().Get("main").to_str() +
                                      R"( -> __void
@@ -104,7 +104,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule());
   ASSERT_TRUE(p->error().empty()) << p->error();
   Program program = p->program();
-  const auto program_ast = program.AST().to_str();
+  const auto program_ast = program.to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function )" + program.Symbols().Get("main").to_str() +
                                      R"( -> __void
@@ -125,7 +125,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule());
   ASSERT_TRUE(p->error().empty()) << p->error();
   Program program = p->program();
-  const auto program_ast = program.AST().to_str();
+  const auto program_ast = program.to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function )" + program.Symbols().Get("main").to_str() +
                                      R"( -> __void
@@ -148,7 +148,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule());
   ASSERT_TRUE(p->error().empty()) << p->error();
   Program program = p->program();
-  const auto program_ast = program.AST().to_str();
+  const auto program_ast = program.to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function )" + program.Symbols().Get("frag_main").to_str() +
                                      R"( -> __void
@@ -173,7 +173,7 @@
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
-  const auto program_ast = program.AST().to_str();
+  const auto program_ast = program.to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function )" + program.Symbols().Get("main").to_str() +
                                      R"( -> __void
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index 2977958..78422fd 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -3788,7 +3788,7 @@
       for (auto* expr : result) {
         ASSERT_NE(expr, nullptr);
         result_strings.push_back(
-            Demangler().Demangle(program.Symbols(), expr->str()));
+            Demangler().Demangle(program.Symbols(), expr->str(program.Sem())));
       }
       EXPECT_THAT(result_strings,
                   ::testing::ContainerEq(GetParam().expected_expressions));
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 13e6c0f..1acf2d1 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -71,7 +71,7 @@
   auto p = parser(test::Assemble(""));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_ast = p->program().AST().to_str();
+  const auto module_ast = p->program().to_str();
   EXPECT_THAT(module_ast, Not(HasSubstr("Variable")));
 }
 
@@ -2002,7 +2002,10 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
+
+  Program program = p->program();
+
+  EXPECT_THAT(ToString(program, fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -2015,7 +2018,7 @@
       }
     }
   })"))
-      << ToString(p->builder().Symbols(), fe.ast_body());
+      << ToString(program, fe.ast_body());
 }
 
 }  // namespace
diff --git a/src/reader/spirv/parser_impl_test_helper.h b/src/reader/spirv/parser_impl_test_helper.h
index 3a3acad..89aa2af 100644
--- a/src/reader/spirv/parser_impl_test_helper.h
+++ b/src/reader/spirv/parser_impl_test_helper.h
@@ -62,16 +62,29 @@
 using SpvParserTest = SpvParserTestBase<::testing::Test>;
 
 /// Returns the string dump of a statement list.
-/// @param symbols the SymbolTable
+/// @param program the Program
 /// @param stmts the statement list
 /// @returns the string dump of a statement list.
-inline std::string ToString(const SymbolTable& symbols,
+inline std::string ToString(const Program& program,
                             const ast::StatementList& stmts) {
   std::ostringstream outs;
   for (const auto* stmt : stmts) {
-    stmt->to_str(outs, 0);
+    stmt->to_str(program.Sem(), outs, 0);
   }
-  return Demangler().Demangle(symbols, outs.str());
+  return Demangler().Demangle(program.Symbols(), outs.str());
+}
+
+/// Returns the string dump of a statement list.
+/// @param builder the ProgramBuilder
+/// @param stmts the statement list
+/// @returns the string dump of a statement list.
+inline std::string ToString(ProgramBuilder& builder,
+                            const ast::StatementList& stmts) {
+  std::ostringstream outs;
+  for (const auto* stmt : stmts) {
+    stmt->to_str(builder.Sem(), outs, 0);
+  }
+  return Demangler().Demangle(builder.Symbols(), outs.str());
 }
 
 }  // namespace spirv
diff --git a/src/reader/wgsl/parser_impl_for_stmt_test.cc b/src/reader/wgsl/parser_impl_for_stmt_test.cc
index 42af07d..ad2a0b7 100644
--- a/src/reader/wgsl/parser_impl_for_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -37,8 +37,8 @@
     EXPECT_FALSE(e_for.errored);
     EXPECT_FALSE(p_for->has_error()) << p_for->error();
 
-    std::string loop = ast::BlockStatement({}, e_loop.value).str();
-    std::string for_ = ast::BlockStatement({}, e_for.value).str();
+    std::string loop = ast::BlockStatement({}, e_loop.value).str(Sem());
+    std::string for_ = ast::BlockStatement({}, e_for.value).str(Sem());
     EXPECT_EQ(loop, for_);
   }
 };
diff --git a/src/reader/wgsl/parser_impl_switch_body_test.cc b/src/reader/wgsl/parser_impl_switch_body_test.cc
index ba98d01..b596149 100644
--- a/src/reader/wgsl/parser_impl_switch_body_test.cc
+++ b/src/reader/wgsl/parser_impl_switch_body_test.cc
@@ -116,8 +116,8 @@
   EXPECT_FALSE(e->IsDefault());
   ASSERT_EQ(e->body()->size(), 0u);
   ASSERT_EQ(e->selectors().size(), 2u);
-  ASSERT_EQ(e->selectors()[0]->to_str(), "1");
-  ASSERT_EQ(e->selectors()[1]->to_str(), "2");
+  ASSERT_EQ(e->selectors()[0]->to_str(Sem()), "1");
+  ASSERT_EQ(e->selectors()[1]->to_str(Sem()), "2");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsMissingColon) {
diff --git a/src/semantic/info.h b/src/semantic/info.h
new file mode 100644
index 0000000..fff6558
--- /dev/null
+++ b/src/semantic/info.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_SEMANTIC_INFO_H_
+#define SRC_SEMANTIC_INFO_H_
+
+namespace tint {
+
+namespace semantic {
+
+/// Info will hold all the resolved semantic information for a Program.
+class Info {
+ public:
+  /// Constructor
+  Info();
+
+  /// Move constructor
+  Info(Info&&);
+
+  /// Destructor
+  ~Info();
+
+  /// Move assignment operator
+  /// @param rhs the Program to move
+  /// @return this Program
+  Info& operator=(Info&& rhs);
+};
+
+}  // namespace semantic
+}  // namespace tint
+
+#endif  // SRC_SEMANTIC_INFO_H_
diff --git a/src/semantic/sem_info.cc b/src/semantic/sem_info.cc
new file mode 100644
index 0000000..ddd3de5
--- /dev/null
+++ b/src/semantic/sem_info.cc
@@ -0,0 +1,29 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/semantic/info.h"
+
+namespace tint {
+namespace semantic {
+
+Info::Info() = default;
+
+Info::Info(Info&&) = default;
+
+Info::~Info() = default;
+
+Info& Info::operator=(Info&&) = default;
+
+}  // namespace semantic
+}  // namespace tint
diff --git a/src/type_determiner.cc b/src/type_determiner.cc
index 518f5c9..65ec83f 100644
--- a/src/type_determiner.cc
+++ b/src/type_determiner.cc
@@ -288,8 +288,8 @@
     return DetermineResultType(v->variable()->constructor());
   }
 
-  set_error(stmt->source(),
-            "unknown statement type for type determination: " + stmt->str());
+  set_error(stmt->source(), "unknown statement type for type determination: " +
+                                stmt->str(builder_->Sem()));
   return false;
 }
 
diff --git a/src/type_determiner_test.cc b/src/type_determiner_test.cc
index 1dd3d6e..50d3f27 100644
--- a/src/type_determiner_test.cc
+++ b/src/type_determiner_test.cc
@@ -76,7 +76,9 @@
   explicit FakeStmt(Source source) : ast::Statement(source) {}
   FakeStmt* Clone(CloneContext*) const override { return nullptr; }
   bool IsValid() const override { return true; }
-  void to_str(std::ostream& out, size_t) const override { out << "Fake"; }
+  void to_str(const semantic::Info&, std::ostream& out, size_t) const override {
+    out << "Fake";
+  }
 };
 
 class FakeExpr : public ast::Expression {
@@ -84,7 +86,7 @@
   explicit FakeExpr(Source source) : ast::Expression(source) {}
   FakeExpr* Clone(CloneContext*) const override { return nullptr; }
   bool IsValid() const override { return true; }
-  void to_str(std::ostream&, size_t) const override {}
+  void to_str(const semantic::Info&, std::ostream&, size_t) const override {}
 };
 
 class TypeDeterminerHelper : public ProgramBuilder {
diff --git a/src/validator/validator_impl.cc b/src/validator/validator_impl.cc
index 6beadbf..0c418a1 100644
--- a/src/validator/validator_impl.cc
+++ b/src/validator/validator_impl.cc
@@ -360,9 +360,10 @@
                                    ? selector->As<ast::UintLiteral>()->value()
                                    : selector->As<ast::SintLiteral>()->value());
       if (selector_set.count(v)) {
-        auto v_str = selector->type()->Is<type::U32>()
-                         ? selector->As<ast::UintLiteral>()->to_str()
-                         : selector->As<ast::SintLiteral>()->to_str();
+        auto v_str =
+            selector->type()->Is<type::U32>()
+                ? selector->As<ast::UintLiteral>()->to_str(program_->Sem())
+                : selector->As<ast::SintLiteral>()->to_str(program_->Sem());
         add_error(case_stmt->source(), "v-0027",
                   "a literal value must not appear more than once in "
                   "the case selectors for a switch statement: '" +
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index ac24076..e7ec1ce 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -1141,7 +1141,7 @@
     return EmitUnaryOp(pre, out, u);
   }
 
-  error_ = "unknown expression type: " + expr->str();
+  error_ = "unknown expression type: " + expr->str(program_->Sem());
   return false;
 }
 
@@ -2179,7 +2179,7 @@
     return EmitVariable(out, v->variable(), false);
   }
 
-  error_ = "unknown statement type: " + stmt->str();
+  error_ = "unknown statement type: " + stmt->str(program_->Sem());
   return false;
 }
 
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 34a1390..eaae6c0 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -1177,7 +1177,7 @@
     return EmitUnaryOp(u);
   }
 
-  error_ = "unknown expression type: " + expr->str();
+  error_ = "unknown expression type: " + expr->str(program_->Sem());
   return false;
 }
 
@@ -1844,7 +1844,7 @@
     return EmitVariable(v->variable(), false);
   }
 
-  error_ = "unknown statement type: " + stmt->str();
+  error_ = "unknown statement type: " + stmt->str(program_->Sem());
   return false;
 }
 
@@ -2032,7 +2032,7 @@
         }
         current_offset = offset;
       } else {
-        error_ = "unsupported member decoration: " + deco->str();
+        error_ = "unsupported member decoration: " + deco->str(program_->Sem());
         return false;
       }
     }
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 3d1b071..4fb1b76 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -533,7 +533,7 @@
     return GenerateUnaryOpExpression(u);
   }
 
-  error_ = "unknown expression type: " + expr->str();
+  error_ = "unknown expression type: " + expr->str(program_->Sem());
   return 0;
 }
 
@@ -1091,7 +1091,7 @@
       }
 
     } else {
-      error_ = "invalid accessor in list: " + accessor->str();
+      error_ = "invalid accessor in list: " + accessor->str(program_->Sem());
       return 0;
     }
   }
@@ -2763,7 +2763,7 @@
     return GenerateVariableDeclStatement(v);
   }
 
-  error_ = "Unknown statement: " + stmt->str();
+  error_ = "Unknown statement: " + stmt->str(program_->Sem());
   return false;
 }
 
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index e2add77..20561e1 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -550,7 +550,7 @@
   auto* impl = str->impl();
   for (auto* deco : impl->decorations()) {
     out_ << "[[";
-    deco->to_str(out_, 0);
+    deco->to_str(program_->Sem(), out_, 0);
     out_ << "]]" << std::endl;
   }
   out_ << "struct " << program_->Symbols().NameFor(str->symbol()) << " {"
@@ -817,7 +817,7 @@
     return EmitVariable(v->variable());
   }
 
-  error_ = "unknown statement type: " + stmt->str();
+  error_ = "unknown statement type: " + stmt->str(program_->Sem());
   return false;
 }