Просмотр исходного кода

Provide a Printable CRTP parent to replace HasPrintable templates. (#3166)

With the toolchain splitting namespaces, ostream.h's `operator<<`
templates aren't reliably found with name lookup, likely due to the loss
of associated namespaces (zygoloid commented on this at
https://github.com/carbon-language/carbon-lang/pull/3161#discussion_r1307941999).
This is especially a barrier to moving the lex files into `Carbon::Lex`;
versus other parts of the toolchain, they contain more printable types
which are used cross-namespace, including `Carbon::Testing`. As a
consequence, I'm looking at migrating ostream.h to a more reliable
approach that doesn't rely as much on everything being in the `Carbon`
namespace.
Jon Ross-Perkins 2 лет назад
Родитель
Сommit
53af8f04b2

+ 0 - 17
common/BUILD

@@ -122,27 +122,10 @@ cc_test(
     ],
 )
 
-cc_library(
-    name = "metaprogramming",
-    hdrs = ["metaprogramming.h"],
-)
-
-cc_test(
-    name = "metaprogramming_test",
-    srcs = ["metaprogramming_test.cpp"],
-    deps = [
-        ":metaprogramming",
-        "//testing/base:gtest_main",
-        "@com_google_googletest//:gtest",
-        "@llvm-project//llvm:Support",
-    ],
-)
-
 cc_library(
     name = "ostream",
     hdrs = ["ostream.h"],
     deps = [
-        ":metaprogramming",
         "@llvm-project//llvm:Support",
     ],
 )

+ 1 - 9
common/enum_base.h

@@ -52,7 +52,7 @@ namespace Carbon::Internal {
 //   };
 //   ```
 template <typename DerivedT, typename EnumT, const llvm::StringLiteral Names[]>
-class EnumBase {
+class EnumBase : public Printable<DerivedT> {
  public:
   // An alias for the raw enum type. This is an implementation detail and
   // should rarely be used directly, only when an actual enum type is needed.
@@ -83,14 +83,6 @@ class EnumBase {
   // Prints this value using its name.
   auto Print(llvm::raw_ostream& out) const -> void { out << name(); }
 
-  // TODO: common/ostream.h tries to provide this, but does not reliably work on
-  // EnumBase children. Try to find a more generic solution.
-  friend auto operator<<(llvm::raw_ostream& out, const DerivedT& obj)
-      -> llvm::raw_ostream& {
-    obj.Print(out);
-    return out;
-  }
-
  protected:
   // The default constructor is explicitly defaulted (and constexpr) as a
   // protected constructor to allow derived classes to be constructed but not

+ 5 - 5
common/enum_base_test.cpp

@@ -8,7 +8,8 @@
 
 #include "testing/base/test_raw_ostream.h"
 
-namespace Carbon {
+namespace Carbon::Testing {
+namespace {
 
 // These are directly in the Carbon namespace because the defines require it.
 CARBON_DEFINE_RAW_ENUM_CLASS(TestKind, uint8_t) {
@@ -34,8 +35,8 @@ CARBON_DEFINE_ENUM_CLASS_NAMES(TestKind) = {
 #include "common/enum_base_test.def"
 };
 
-namespace Testing {
-namespace {
+static_assert(sizeof(TestKind) == sizeof(uint8_t),
+              "Class size doesn't match enum size!");
 
 TEST(EnumBaseTest, NamesAndConstants) {
   EXPECT_EQ("Beep", TestKind::Beep.name());
@@ -108,5 +109,4 @@ TEST(EnumBaseTest, IntConversion) {
 }
 
 }  // namespace
-}  // namespace Testing
-}  // namespace Carbon
+}  // namespace Carbon::Testing

+ 1 - 1
common/error.h

@@ -20,7 +20,7 @@ namespace Carbon {
 struct Success {};
 
 // Tracks an error message.
-class [[nodiscard]] Error {
+class [[nodiscard]] Error : public Printable<Error> {
  public:
   // Represents an error state.
   explicit Error(llvm::Twine location, llvm::Twine message)

+ 0 - 34
common/metaprogramming.h

@@ -1,34 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#ifndef CARBON_COMMON_METAPROGRAMMING_H_
-#define CARBON_COMMON_METAPROGRAMMING_H_
-
-#include <type_traits>
-
-namespace Carbon {
-
-// C++17-compatible emulation of a C++20 `requires` expression, which queries
-// whether a given expression is well-formed. The syntax is best explained
-// in terms of an example:
-//
-// template <typename T>
-// static constexpr bool IsStreamableToRawOstream =
-//     Requires<const T, llvm::raw_ostream>(
-//         [](auto&& t, auto&& out) -> decltype(out << t) {});
-//
-// The expression enclosed in `decltype` is the expression whose validity the
-// trait queries. The lambda parameters declare the names that are used in
-// that expression, and the types of those names are specified by the
-// corresponding template arguments of the `Requires` call. The lambda
-// parameters should always have type `auto&&`, and the template arguments
-// should not have `&` or `&&` qualifiers.
-template <typename... T, typename F>
-constexpr auto Requires(F /* f */) -> bool {
-  return std::is_invocable_v<F, T...>;
-}
-
-}  // namespace Carbon
-
-#endif  // CARBON_COMMON_METAPROGRAMMING_H_

+ 0 - 32
common/metaprogramming_test.cpp

@@ -1,32 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#include "common/metaprogramming.h"
-
-#include <gtest/gtest.h>
-
-#include <string>
-
-#include "llvm/Support/raw_ostream.h"
-
-namespace Carbon::Testing {
-namespace {
-
-TEST(MetaProgrammingTest, RequiresBasic) {
-  bool result = Requires<int, int>([](int a, int b) { return a + b; });
-  EXPECT_TRUE(result);
-}
-
-struct TypeWithPrint {
-  void Print(llvm::raw_ostream& os) const { os << "Test"; }
-};
-
-TEST(MetaProgrammingTest, RequiresPrintMethod) {
-  bool result = Requires<const TypeWithPrint, llvm::raw_ostream>(
-      [](auto&& t, auto&& out) -> decltype(t.Print(out)) {});
-  EXPECT_TRUE(result);
-}
-
-}  // namespace
-}  // namespace Carbon::Testing

+ 38 - 50
common/ostream.h

@@ -7,60 +7,48 @@
 
 #include <ostream>
 
-#include "common/metaprogramming.h"
 #include "llvm/Support/raw_os_ostream.h"
 // Libraries should include this header instead of raw_ostream.
+#include "llvm/Support/Compiler.h"
 #include "llvm/Support/raw_ostream.h"  // IWYU pragma: export
 
 namespace Carbon {
 
-// True if T has a method `void Print(llvm::raw_ostream& out) const`.
-template <typename T>
-static constexpr bool HasPrintMethod = Requires<const T, llvm::raw_ostream>(
-    [](auto&& t, auto&& out) -> decltype(t.Print(out)) {});
-
-// Support raw_ostream << for types which implement:
-//   void Print(llvm::raw_ostream& out) const;
-template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
-auto operator<<(llvm::raw_ostream& out, const T& obj) -> llvm::raw_ostream& {
-  obj.Print(out);
-  return out;
-}
-
-// Prevents raw_ostream << for pointers to printable types.
-template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
-__attribute__((unavailable(
-    "Received a pointer to a printable type, are you missing a `*`? "
-    "To print as a pointer, cast to void*."))) auto
-operator<<(llvm::raw_ostream& out, const T* /*obj*/) -> llvm::raw_ostream&;
-
-// Support std::ostream << for types which implement:
-//   void Print(llvm::raw_ostream& out) const;
-template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
-auto operator<<(std::ostream& out, const T& obj) -> std::ostream& {
-  llvm::raw_os_ostream raw_os(out);
-  obj.Print(raw_os);
-  return out;
-}
+// CRTP base class for printable types. Children (DerivedT) must implement:
+// - auto Print(llvm::raw_ostream& out) -> void
+template <typename DerivedT>
+class Printable {
+  // Provides simple printing for debuggers.
+  LLVM_DUMP_METHOD void Dump() const {
+    static_cast<const DerivedT*>(this)->Print(llvm::errs());
+  }
 
-// Prevents std::ostream << for pointers to printable types.
-template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
-__attribute__((unavailable(
-    "Received a pointer to a printable type, are you missing a `*`? "
-    "To print as a pointer, cast to void*."))) auto
-operator<<(std::ostream& out, const T* /*obj*/) -> std::ostream&;
+  // Supports printing to llvm::raw_ostream.
+  friend auto operator<<(llvm::raw_ostream& out, const DerivedT& obj)
+      -> llvm::raw_ostream& {
+    obj.Print(out);
+    return out;
+  }
 
-// Allow GoogleTest and GoogleMock to print even pointers by dereferencing them.
-// This is important to allow automatic printing of arguments of mocked APIs.
-template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
-void PrintTo(T* p, std::ostream* out) {
-  *out << static_cast<const void*>(p);
+  // Supports printing to std::ostream.
+  friend auto operator<<(std::ostream& out, const DerivedT& obj)
+      -> std::ostream& {
+    llvm::raw_os_ostream raw_os(out);
+    obj.Print(raw_os);
+    return out;
+  }
 
-  // Also print the object if non-null.
-  if (p) {
-    *out << " pointing to " << *p;
+  // Allows GoogleTest and GoogleMock to print pointers by dereferencing them.
+  // This is important to allow automatic printing of arguments of mocked
+  // APIs.
+  friend auto PrintTo(DerivedT* p, std::ostream* out) -> void {
+    *out << static_cast<const void*>(p);
+    // Also print the object if non-null.
+    if (p) {
+      *out << " pointing to " << *p;
+    }
   }
-}
+};
 
 }  // namespace Carbon
 
@@ -82,12 +70,12 @@ namespace llvm {
 // `raw_os_ostream.h` so that we wouldn't need to inject into LLVM's namespace,
 // but supporting `std::ostream` isn't a priority for LLVM so we handle it
 // locally instead.
-template <typename S, typename T,
-          typename = std::enable_if_t<std::is_base_of_v<
-              std::ostream, std::remove_reference_t<std::remove_cv_t<S>>>>,
-          typename = std::enable_if_t<!std::is_same_v<
-              std::remove_reference_t<std::remove_cv_t<T>>, raw_ostream>>>
-auto operator<<(S& standard_out, const T& value) -> S& {
+template <typename StreamT, typename ClassT,
+          typename = std::enable_if_t<
+              std::is_base_of_v<std::ostream, std::decay_t<StreamT>>>,
+          typename = std::enable_if_t<
+              !std::is_same_v<std::decay_t<ClassT>, raw_ostream>>>
+auto operator<<(StreamT& standard_out, const ClassT& value) -> StreamT& {
   raw_os_ostream(standard_out) << value;
   return standard_out;
 }

+ 2 - 5
explorer/ast/address.h

@@ -12,14 +12,13 @@
 #include "common/check.h"
 #include "common/ostream.h"
 #include "explorer/ast/element_path.h"
-#include "llvm/Support/Compiler.h"
 
 namespace Carbon {
 
 // An AllocationId identifies an _allocation_ produced by a Heap. An allocation
 // is analogous to the C++ notion of a complete object: the `Value` in an
 // allocation is not a sub-part of any other `Value`.
-class AllocationId {
+class AllocationId : public Printable<AllocationId> {
  public:
   AllocationId(const AllocationId&) = default;
   auto operator=(const AllocationId&) -> AllocationId& = default;
@@ -52,7 +51,7 @@ class AllocationId {
 // An Address represents a memory address in the Carbon virtual machine.
 // Addresses are used to access values stored in a Heap. Unlike an
 // AllocationId, an Address can refer to a sub-Value of some larger Value.
-class Address {
+class Address : public Printable<Address> {
  public:
   // Constructs an `Address` that refers to the value stored in `allocation`.
   explicit Address(AllocationId allocation) : allocation_(allocation) {}
@@ -70,8 +69,6 @@ class Address {
     out << allocation_ << element_path_;
   }
 
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
-
   // If *this represents the address of an object with a field named
   // `field_name`, this method returns the address of that field.
   auto ElementAddress(Nonnull<const Element*> element) const -> Address {

+ 1 - 2
explorer/ast/ast_node.h

@@ -46,7 +46,7 @@ class CloneContext;
 //
 // TODO: To support generic traversal, add children() method, and ensure that
 //   all AstNodes are reachable from a root AstNode.
-class AstNode {
+class AstNode : public Printable<AstNode> {
  public:
   AstNode(AstNode&&) = delete;
   auto operator=(AstNode&&) -> AstNode& = delete;
@@ -56,7 +56,6 @@ class AstNode {
   virtual void Print(llvm::raw_ostream& out) const = 0;
   // Print identifying information about the node, such as its name.
   virtual void PrintID(llvm::raw_ostream& out) const = 0;
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
 
   // Returns an enumerator specifying the concrete type of this node.
   //

+ 1 - 2
explorer/ast/bindings.h

@@ -31,7 +31,7 @@ using ImplWitnessMap =
 // These are shared by a context and all unparameterized entities within that
 // context. For example, a class and the name of a method within that class
 // will have the same set of bindings.
-class Bindings {
+class Bindings : public Printable<Bindings> {
  public:
   // Gets an empty set of bindings.
   static auto None() -> Nonnull<const Bindings*>;
@@ -66,7 +66,6 @@ class Bindings {
   }
 
   void Print(llvm::raw_ostream& out) const;
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
 
   // Add a value, and perhaps a witness, for a generic binding.
   void Add(Nonnull<const GenericBinding*> binding, Nonnull<const Value*> value,

+ 1 - 1
explorer/ast/declaration.h

@@ -143,7 +143,7 @@ inline auto DeclaresSameEntity(const Declaration& first,
 }
 
 // A name being declared in a named declaration.
-class DeclaredName {
+class DeclaredName : public Printable<DeclaredName> {
  public:
   struct NameComponent {
     SourceLocation source_loc;

+ 1 - 1
explorer/ast/element.h

@@ -44,7 +44,7 @@ struct NamedValue : HashFromDecompose<NamedValue> {
 // types. As a result, all Elements must be immutable, and all their constructor
 // arguments must be copyable, equality-comparable, and hashable. See
 // Arena's documentation for details.
-class Element {
+class Element : public Printable<Element> {
  protected:
   explicit Element(ElementKind kind) : kind_(kind) {}
 

+ 2 - 4
explorer/ast/element_path.h

@@ -33,7 +33,7 @@ class Witness;
 // there is only one kind of step, a string specifying a child field by name,
 // but that may change as Carbon develops. Note that an empty ElementPath
 // refers to the initial Value itself.
-class ElementPath {
+class ElementPath : public Printable<ElementPath> {
  public:
   // Constructs an empty ElementPath.
   ElementPath() = default;
@@ -42,7 +42,7 @@ class ElementPath {
   // of a field. However, inside a generic, when there is a field
   // access on something of a generic type, e.g., `T`, then we also
   // need `witness`, a pointer to the witness table containing that field.
-  class Component {
+  class Component : public Printable<Component> {
    public:
     explicit Component(Nonnull<const Element*> element) : element_(element) {}
     Component(Nonnull<const Element*> element,
@@ -128,8 +128,6 @@ class ElementPath {
     }
   }
 
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
-
  private:
   // The representation of ElementPath describes how to locate a Value within
   // another Value, so its implementation details are tied to the implementation

+ 1 - 2
explorer/ast/return_term.h

@@ -25,7 +25,7 @@ class Value;
 // - An _auto_ term consists of `-> auto`.
 // - An _omitted_ term consists of no tokens at all.
 // Each of these forms has a corresponding factory function.
-class ReturnTerm {
+class ReturnTerm : public Printable<ReturnTerm> {
  public:
   explicit ReturnTerm(CloneContext& context, const ReturnTerm& other)
       : kind_(other.kind_),
@@ -80,7 +80,6 @@ class ReturnTerm {
   auto source_loc() const -> SourceLocation { return source_loc_; }
 
   void Print(llvm::raw_ostream& out) const;
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
 
  private:
   enum class ReturnKind { Omitted, Auto, Expression };

+ 15 - 10
explorer/ast/value.h

@@ -64,7 +64,7 @@ inline auto EmptyVTable() -> Nonnull<const VTable*> {
 // As a result, all Values must be immutable, and all their constructor
 // arguments must be copyable, equality-comparable, and hashable. See
 // Arena's documentation for details.
-class Value {
+class Value : public Printable<Value> {
  public:
   using EnableCanonicalizedAllocation = void;
   enum class Kind {
@@ -81,7 +81,6 @@ class Value {
   auto Visit(F f) const -> R;
 
   void Print(llvm::raw_ostream& out) const;
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
 
   // Returns the sub-Value specified by `path`, which must be a valid element
   // path for *this. If the sub-Value is a method and its self_pattern is an
@@ -1062,7 +1061,19 @@ struct ImplsConstraint : public HashFromDecompose<ImplsConstraint> {
 };
 
 // A constraint that requires an intrinsic property of a type.
-struct IntrinsicConstraint : public HashFromDecompose<IntrinsicConstraint> {
+struct IntrinsicConstraint : public HashFromDecompose<IntrinsicConstraint>,
+                             public Printable<IntrinsicConstraint> {
+  enum Kind {
+    // `type` intrinsically implicitly converts to `parameters[0]`.
+    // TODO: Split ImplicitAs into more specific constraints (such as
+    // derived-to-base pointer conversions).
+    ImplicitAs,
+  };
+
+  explicit IntrinsicConstraint(Nonnull<const Value*> type, Kind kind,
+                               std::vector<Nonnull<const Value*>> arguments)
+      : type(type), kind(kind), arguments(std::move(arguments)) {}
+
   template <typename F>
   auto Decompose(F f) const {
     return f(type, kind, arguments);
@@ -1074,12 +1085,6 @@ struct IntrinsicConstraint : public HashFromDecompose<IntrinsicConstraint> {
   // The type that is required to satisfy the intrinsic property.
   Nonnull<const Value*> type;
   // The kind of the intrinsic property.
-  enum Kind {
-    // `type` intrinsically implicitly converts to `parameters[0]`.
-    // TODO: Split ImplicitAs into more specific constraints (such as
-    // derived-to-base pointer conversions).
-    ImplicitAs,
-  };
   Kind kind;
   // Arguments for the intrinsic property. The meaning of these depends on
   // `kind`.
@@ -1466,7 +1471,7 @@ class ParameterizedEntityName : public Value {
 // These values are used to represent the second operand of a compound member
 // access expression: `x.(A.B)`, and can also be the value of an alias
 // declaration, but cannot be used in most other contexts.
-class MemberName : public Value {
+class MemberName : public Value, public Printable<MemberName> {
  public:
   MemberName(std::optional<Nonnull<const Value*>> base_type,
              std::optional<Nonnull<const InterfaceType*>> interface,

+ 1 - 1
explorer/ast/value_node.h

@@ -56,7 +56,7 @@ template <typename T>
 static constexpr bool
     ImplementsValueNode<T, typename T::ImplementsCarbonValueNode> = true;
 
-class ValueNodeView {
+class ValueNodeView : public Printable<ValueNodeView> {
  public:
   template <typename NodeType,
             typename = std::enable_if_t<ImplementsValueNode<NodeType>>>

+ 1 - 3
explorer/base/source_location.h

@@ -16,7 +16,7 @@ namespace Carbon {
 // Describes the kind of file that the source location is within.
 enum class FileKind { Main, Prelude, Import, Unknown, Last = Unknown };
 
-class SourceLocation {
+class SourceLocation : public Printable<SourceLocation> {
  public:
   // Produce a source location that is known to not be used, because it is fed
   // into an operation that creates no AST nodes and whose diagnostics are
@@ -62,8 +62,6 @@ class SourceLocation {
     return result;
   }
 
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
-
  private:
   std::string_view filename_;
   int line_num_;

+ 2 - 4
explorer/interpreter/action.h

@@ -30,7 +30,7 @@ namespace Carbon {
 
 // A RuntimeScope manages and provides access to the storage for names that are
 // not compile-time constants.
-class RuntimeScope {
+class RuntimeScope : public Printable<RuntimeScope> {
  public:
   // Returns a RuntimeScope whose Get() operation for a given name returns the
   // storage owned by the first entry in `scopes` that defines that name. This
@@ -48,7 +48,6 @@ class RuntimeScope {
   auto operator=(RuntimeScope&&) noexcept -> RuntimeScope&;
 
   void Print(llvm::raw_ostream& out) const;
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
 
   // Allocates storage for `value_node` in `heap`, and initializes it with
   // `value`.
@@ -109,7 +108,7 @@ class RuntimeScope {
 // The actual behavior of an Action step is defined by Interpreter::Step, not by
 // Action or its subclasses.
 // TODO: consider moving this logic to a virtual method `Step`.
-class Action {
+class Action : public Printable<Action> {
  public:
   enum class Kind {
     LocationAction,
@@ -131,7 +130,6 @@ class Action {
   virtual ~Action() = default;
 
   void Print(llvm::raw_ostream& out) const;
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
 
   // Resets this Action to its initial state.
   void Clear() {

+ 1 - 2
explorer/interpreter/action_stack.h

@@ -21,7 +21,7 @@ namespace Carbon {
 enum class Phase { CompileTime, RunTime };
 
 // The stack of Actions currently being executed by the interpreter.
-class ActionStack {
+class ActionStack : public Printable<ActionStack> {
  public:
   // Constructs an empty compile-time ActionStack.
   explicit ActionStack(Nonnull<TraceStream*> trace_stream)
@@ -36,7 +36,6 @@ class ActionStack {
         trace_stream_(trace_stream) {}
 
   void Print(llvm::raw_ostream& out) const;
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
 
   // Starts execution with `action` at the top of the stack. Cannot be called
   // when IsEmpty() is false.

+ 1 - 3
explorer/interpreter/heap.h

@@ -19,7 +19,7 @@
 namespace Carbon {
 
 // A Heap represents the abstract machine's dynamically allocated memory.
-class Heap : public HeapAllocationInterface {
+class Heap : public HeapAllocationInterface, public Printable<Heap> {
  public:
   enum class ValueState {
     Uninitialized,
@@ -70,8 +70,6 @@ class Heap : public HeapAllocationInterface {
   // Print all the values on the heap to the stream `out`.
   void Print(llvm::raw_ostream& out) const;
 
-  LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
-
   auto arena() const -> Arena& override { return *arena_; }
 
  private:

+ 1 - 2
explorer/interpreter/impl_scope.cpp

@@ -183,8 +183,7 @@ auto ImplScope::TryResolve(Nonnull<const Value*> constraint_type,
         CARBON_ASSIGN_OR_RETURN(
             Nonnull<const Value*> type,
             type_checker.Substitute(local_bindings, intrinsic.type));
-        IntrinsicConstraint converted = {
-            .type = type, .kind = intrinsic.kind, .arguments = {}};
+        IntrinsicConstraint converted(type, intrinsic.kind, {});
         converted.arguments.reserve(intrinsic.arguments.size());
         for (Nonnull<const Value*> argument : intrinsic.arguments) {
           CARBON_ASSIGN_OR_RETURN(

+ 1 - 1
explorer/interpreter/impl_scope.h

@@ -41,7 +41,7 @@ class TypeChecker;
 //
 // `ImplScope` also tracks the type equalities that are known in a particular
 // scope.
-class ImplScope {
+class ImplScope : public Printable<ImplScope> {
  public:
   // The `ImplFact` struct is a key-value pair where the key is the
   // combination of a type and an interface, e.g., `List` and `Container`,

+ 2 - 4
explorer/interpreter/interpreter.cpp

@@ -1723,10 +1723,8 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           self_binding->set_symbolic_identity(self);
           self_binding->set_value(self);
           self_binding->set_impl_binding(impl_binding);
-          IntrinsicConstraint constraint = {
-              .type = self,
-              .kind = IntrinsicConstraint::ImplicitAs,
-              .arguments = args};
+          IntrinsicConstraint constraint(self, IntrinsicConstraint::ImplicitAs,
+                                         args);
           auto* result = arena_->New<ConstraintType>(
               self_binding, std::vector<ImplsConstraint>{},
               std::vector<IntrinsicConstraint>{std::move(constraint)},

+ 1 - 2
explorer/interpreter/type_checker.cpp

@@ -1744,8 +1744,7 @@ class TypeChecker::ConstraintTypeBuilder {
       CARBON_ASSIGN_OR_RETURN(
           Nonnull<const Value*> type,
           type_checker.Substitute(local_bindings, intrinsic_constraint.type));
-      IntrinsicConstraint converted = {
-          .type = type, .kind = intrinsic_constraint.kind, .arguments = {}};
+      IntrinsicConstraint converted(type, intrinsic_constraint.kind, {});
       converted.arguments.reserve(intrinsic_constraint.arguments.size());
       for (Nonnull<const Value*> argument : intrinsic_constraint.arguments) {
         CARBON_ASSIGN_OR_RETURN(

+ 1 - 1
explorer/interpreter/type_structure.h

@@ -27,7 +27,7 @@ class Value;
 // This class extracts the relevant information needed to order type
 // structures, and provides a weak ordering over type structures in which type
 // structures that provide a better match compare earlier.
-class TypeStructureSortKey {
+class TypeStructureSortKey : public Printable<TypeStructureSortKey> {
  public:
   // Compute the sort key for `impl type as interface`.
   static auto ForImpl(Nonnull<const Value*> type,

+ 1 - 1
toolchain/base/index_base.h

@@ -18,7 +18,7 @@ class DataIterator;
 //
 // DataIndex is designed to be passed by value, not reference or pointer. They
 // are also designed to be small and efficient to store in data structures.
-struct IndexBase {
+struct IndexBase : public Printable<IndexBase> {
   static constexpr int32_t InvalidIndex = -1;
 
   IndexBase() = delete;

+ 4 - 3
toolchain/lexer/tokenized_buffer.h

@@ -33,7 +33,7 @@ class TokenizedBuffer;
 //
 // Lexing errors result in a potentially incomplete sequence of tokens and
 // `HasError` returning true.
-class TokenizedBuffer {
+class TokenizedBuffer : public Printable<TokenizedBuffer> {
  public:
   // A lightweight handle to a lexed token in a `TokenizedBuffer`.
   //
@@ -87,7 +87,8 @@ class TokenizedBuffer {
   // Random-access iterator over tokens within the buffer.
   class TokenIterator
       : public llvm::iterator_facade_base<
-            TokenIterator, std::random_access_iterator_tag, const Token, int> {
+            TokenIterator, std::random_access_iterator_tag, const Token, int>,
+        public Printable<TokenIterator> {
    public:
     TokenIterator() = delete;
 
@@ -132,7 +133,7 @@ class TokenizedBuffer {
   //
   // The `TokenizedBuffer` must outlive any `RealLiteralValue`s referring to
   // its tokens.
-  class RealLiteralValue {
+  class RealLiteralValue : public Printable<RealLiteralValue> {
    public:
     // The mantissa, represented as an unsigned integer.
     [[nodiscard]] auto Mantissa() const -> const llvm::APInt& {

+ 2 - 13
toolchain/lexer/tokenized_buffer_test_helpers.h

@@ -11,17 +11,7 @@
 #include "llvm/Support/YAMLParser.h"
 #include "toolchain/lexer/tokenized_buffer.h"
 
-namespace Carbon {
-
-inline void PrintTo(const TokenizedBuffer& buffer, std::ostream* output) {
-  std::string message;
-  llvm::raw_string_ostream message_stream(message);
-  message_stream << "\n";
-  buffer.Print(message_stream);
-  *output << message_stream.str();
-}
-
-namespace Testing {
+namespace Carbon::Testing {
 
 struct ExpectedToken {
   friend auto operator<<(std::ostream& output, const ExpectedToken& expected)
@@ -150,7 +140,6 @@ MATCHER_P(HasTokens, raw_all_expected, "") {
   return matches;
 }
 
-}  // namespace Testing
-}  // namespace Carbon
+}  // namespace Carbon::Testing
 
 #endif  // CARBON_TOOLCHAIN_LEXER_TOKENIZED_BUFFER_TEST_HELPERS_H_

+ 5 - 3
toolchain/parser/parse_tree.h

@@ -57,7 +57,7 @@ constexpr Node Node::Invalid = Node(Node::InvalidIndex);
 // The tree is immutable once built, but is designed to support reasonably
 // efficient patterns that build a new tree with a specific transformation
 // applied.
-class Tree {
+class Tree : public Printable<Tree> {
  public:
   class PostorderIterator;
   class SiblingIterator;
@@ -255,7 +255,8 @@ class Tree {
 class Tree::PostorderIterator
     : public llvm::iterator_facade_base<PostorderIterator,
                                         std::random_access_iterator_tag, Node,
-                                        int, Node*, Node> {
+                                        int, Node*, Node>,
+      public Printable<Tree::PostorderIterator> {
  public:
   PostorderIterator() = delete;
 
@@ -305,7 +306,8 @@ class Tree::PostorderIterator
 // relative order of siblings matches their RPO order.
 class Tree::SiblingIterator
     : public llvm::iterator_facade_base<
-          SiblingIterator, std::forward_iterator_tag, Node, int, Node*, Node> {
+          SiblingIterator, std::forward_iterator_tag, Node, int, Node*, Node>,
+      public Printable<Tree::SiblingIterator> {
  public:
   explicit SiblingIterator() = delete;
 

+ 1 - 1
toolchain/parser/parser_context.h

@@ -40,7 +40,7 @@ class Context {
   };
 
   // Used to track state on state_stack_.
-  struct StateStackEntry {
+  struct StateStackEntry : public Printable<StateStackEntry> {
     explicit StateStackEntry(State state, PrecedenceGroup ambient_precedence,
                              PrecedenceGroup lhs_precedence,
                              TokenizedBuffer::Token token,

+ 3 - 5
toolchain/semantics/semantics_ir.h

@@ -15,7 +15,7 @@
 namespace Carbon::SemIR {
 
 // A function.
-struct Function {
+struct Function : public Printable<Function> {
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "{name: " << name_id << ", "
         << "param_refs: " << param_refs_id;
@@ -32,7 +32,6 @@ struct Function {
     }
     out << "}";
   }
-  LLVM_DUMP_METHOD void Dump() const { llvm::errs() << *this; }
 
   // The function name.
   StringId name_id;
@@ -52,12 +51,11 @@ struct Function {
   llvm::SmallVector<NodeBlockId> body_block_ids;
 };
 
-struct RealLiteral {
+struct RealLiteral : public Printable<RealLiteral> {
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "{mantissa: " << mantissa << ", exponent: " << exponent
         << ", is_decimal: " << is_decimal << "}";
   }
-  LLVM_DUMP_METHOD void Dump() const { llvm::errs() << *this; }
 
   llvm::APInt mantissa;
   llvm::APInt exponent;
@@ -68,7 +66,7 @@ struct RealLiteral {
 };
 
 // Provides semantic analysis on a Parse::Tree.
-class File {
+class File : public Printable<File> {
  public:
   // Produces the builtins.
   static auto MakeBuiltinIR() -> File;

+ 14 - 13
toolchain/semantics/semantics_node.h

@@ -17,7 +17,7 @@
 namespace Carbon::SemIR {
 
 // The ID of a node.
-struct NodeId : public IndexBase {
+struct NodeId : public IndexBase, public Printable<NodeId> {
   // An explicitly invalid node ID.
   static const NodeId Invalid;
 
@@ -50,7 +50,7 @@ constexpr NodeId NodeId::Invalid = NodeId(NodeId::InvalidIndex);
 #include "toolchain/semantics/semantics_builtin_kind.def"
 
 // The ID of a function.
-struct FunctionId : public IndexBase {
+struct FunctionId : public IndexBase, public Printable<FunctionId> {
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "function";
@@ -59,7 +59,8 @@ struct FunctionId : public IndexBase {
 };
 
 // The ID of a cross-referenced IR.
-struct CrossReferenceIRId : public IndexBase {
+struct CrossReferenceIRId : public IndexBase,
+                            public Printable<CrossReferenceIRId> {
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "ir";
@@ -68,7 +69,7 @@ struct CrossReferenceIRId : public IndexBase {
 };
 
 // A boolean value.
-struct BoolValue : public IndexBase {
+struct BoolValue : public IndexBase, public Printable<BoolValue> {
   static const BoolValue False;
   static const BoolValue True;
 
@@ -91,7 +92,7 @@ constexpr BoolValue BoolValue::False = BoolValue(0);
 constexpr BoolValue BoolValue::True = BoolValue(1);
 
 // The ID of an integer literal.
-struct IntegerLiteralId : public IndexBase {
+struct IntegerLiteralId : public IndexBase, public Printable<IntegerLiteralId> {
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "int";
@@ -100,7 +101,7 @@ struct IntegerLiteralId : public IndexBase {
 };
 
 // The ID of a name scope.
-struct NameScopeId : public IndexBase {
+struct NameScopeId : public IndexBase, public Printable<NameScopeId> {
   // An explicitly invalid ID.
   static const NameScopeId Invalid;
 
@@ -115,7 +116,7 @@ constexpr NameScopeId NameScopeId::Invalid =
     NameScopeId(NameScopeId::InvalidIndex);
 
 // The ID of a node block.
-struct NodeBlockId : public IndexBase {
+struct NodeBlockId : public IndexBase, public Printable<NodeBlockId> {
   // All File instances must provide the 0th node block as empty.
   static const NodeBlockId Empty;
 
@@ -143,7 +144,7 @@ constexpr NodeBlockId NodeBlockId::Unreachable =
     NodeBlockId(NodeBlockId::InvalidIndex - 1);
 
 // The ID of a real literal.
-struct RealLiteralId : public IndexBase {
+struct RealLiteralId : public IndexBase, public Printable<RealLiteralId> {
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "real";
@@ -152,7 +153,7 @@ struct RealLiteralId : public IndexBase {
 };
 
 // The ID of a string.
-struct StringId : public IndexBase {
+struct StringId : public IndexBase, public Printable<StringId> {
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "str";
@@ -161,7 +162,7 @@ struct StringId : public IndexBase {
 };
 
 // The ID of a node block.
-struct TypeId : public IndexBase {
+struct TypeId : public IndexBase, public Printable<TypeId> {
   // The builtin TypeType.
   static const TypeId TypeType;
 
@@ -189,7 +190,7 @@ constexpr TypeId TypeId::Error = TypeId(TypeId::InvalidIndex - 1);
 constexpr TypeId TypeId::Invalid = TypeId(TypeId::InvalidIndex);
 
 // The ID of a type block.
-struct TypeBlockId : public IndexBase {
+struct TypeBlockId : public IndexBase, public Printable<TypeBlockId> {
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "typeBlock";
@@ -198,7 +199,7 @@ struct TypeBlockId : public IndexBase {
 };
 
 // An index for member access.
-struct MemberIndex : public IndexBase {
+struct MemberIndex : public IndexBase, public Printable<MemberIndex> {
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "member";
@@ -226,7 +227,7 @@ struct MemberIndex : public IndexBase {
 //
 // Internally, each Kind uses the `Factory*` types to provide a boilerplate
 // `Make` and `Get` methods.
-class Node {
+class Node : public Printable<Node> {
  public:
   struct NoArgs {};