From a872123a73eef24415889e0184dc96ac068acd07 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Wed, 19 Aug 2026 01:00:13 +0000 Subject: [PATCH] Let `Printable` children default their comparison operators (#7642) A defaulted `operator==` or `operator<=>` compares every base class subobject, so children of `Printable` couldn't default their comparisons: `Printable` had no comparison operators of its own, which made the defaulted operator implicitly deleted. Children that want member-wise comparison had to write it out by hand instead. `Printable` is empty, so it now provides comparisons that always compare equal. Its operands are constrained template parameter rather than `const Printable&` so that they're only viable for comparing the base class subobjects themselves. An overload taking `const Printable&` would also be viable when comparing two `DerivedT` objects by converting them to the base class, and would then both make children that provide no comparison silently compare equal and displace the comparisons of children that provide them through a conversion of their own, as `EnumBase` does. Some hand-written comparisons stay, for reasons unrelated to `Printable`: using `= default` would change their meaning. Adds `common/ostream_test.cpp`, which covers both the member and friend forms of defaulting, the resulting comparison categories, and both of the hazards above. Assisted-by: Claude Code --------- Co-authored-by: Richard Smith --- common/BUILD | 13 ++ common/ostream.h | 23 ++- common/ostream_test.cpp | 253 +++++++++++++++++++++++++ common/raw_hashtable_test_helpers.h | 10 +- toolchain/sem_ir/clang_decl.h | 10 +- toolchain/sem_ir/declared_facet_type.h | 13 +- 6 files changed, 293 insertions(+), 29 deletions(-) create mode 100644 common/ostream_test.cpp diff --git a/common/BUILD b/common/BUILD index c438381f5ff5..f7d08dabbb19 100644 --- a/common/BUILD +++ b/common/BUILD @@ -506,6 +506,19 @@ cc_library( ], ) +cc_test( + name = "ostream_test", + size = "small", + srcs = ["ostream_test.cpp"], + deps = [ + ":ostream", + ":raw_string_ostream", + "//testing/base:gtest_main", + "@googletest//:gtest", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "pretty_stack_trace_function", hdrs = ["pretty_stack_trace_function.h"], diff --git a/common/ostream.h b/common/ostream.h index d9fd397bacbc..35b2209b401c 100644 --- a/common/ostream.h +++ b/common/ostream.h @@ -7,6 +7,7 @@ // Libraries should include this header instead of raw_ostream. +#include #include #include #include @@ -17,11 +18,31 @@ namespace Carbon { -// CRTP base class for printable types. Children (DerivedT) must implement: +// CRTP base class for printable types. Derived classes (DerivedT) must +// implement: // - auto Print(llvm::raw_ostream& out) const -> void template // NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility) class Printable { + // Comparisons of the base class itself, which is empty and so always compares + // equal, allowing derived classes to default their own comparison operators. + // + // These are templated so that they are only used when the types of the + // arguments are exactly `Printable`, rather than a derived class, and are + // hidden friends so that they aren't candidates for unrelated comparisons. + template + requires std::same_as + friend constexpr auto operator==(const T& /*lhs*/, const T& /*rhs*/) noexcept + -> bool { + return true; + } + template + requires std::same_as + friend constexpr auto operator<=>(const T& /*lhs*/, const T& /*rhs*/) noexcept + -> std::strong_ordering { + return std::strong_ordering::equal; + } + // Supports printing to llvm::raw_ostream. friend auto operator<<(llvm::raw_ostream& out, const DerivedT& obj) -> llvm::raw_ostream& { diff --git a/common/ostream_test.cpp b/common/ostream_test.cpp new file mode 100644 index 000000000000..8f6bba7eed5a --- /dev/null +++ b/common/ostream_test.cpp @@ -0,0 +1,253 @@ +// 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/ostream.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common/raw_string_ostream.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" + +namespace Carbon::Testing { +namespace { + +using ::testing::ElementsAre; + +// Whether two types can be compared with both `==` and `<=>`. +template +concept Comparable = requires(const LhsT& lhs, const RhsT& rhs) { + lhs == rhs; + lhs <=> rhs; +}; + +// A child that defaults its comparisons with member declarations. +struct Point : Printable { + int x; + int y; + + constexpr Point(int x, int y) : x(x), y(y) {} + + auto Print(llvm::raw_ostream& out) const -> void { + out << "(" << x << ", " << y << ")"; + } + + auto operator<=>(const Point& rhs) const = default; +}; + +// A child that defaults its comparisons with friend declarations, and whose +// comparisons are neither trivial nor `noexcept`. +struct Label : Printable