[clang] [Clang] Introduce __builtin_meow_synthesises_from_spaceship (PR #155612)
Nikolas Klauser via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 1 07:34:27 PDT 2025
https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/155612
>From e2125e4c9a9d8f3b45ba28af3a54cf13626a910f Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Wed, 27 Aug 2025 14:28:22 +0200
Subject: [PATCH] [Clang] Introduce __builtin_meow_synthesises_from_spaceship
---
clang/docs/LanguageExtensions.rst | 3 +
clang/docs/ReleaseNotes.rst | 11 +-
clang/include/clang/Basic/TokenKinds.def | 4 +
clang/lib/Sema/SemaTypeTraits.cpp | 45 ++++
.../type-trait-synthesises-from-spaceship.cpp | 212 ++++++++++++++++++
5 files changed, 272 insertions(+), 3 deletions(-)
create mode 100644 clang/test/SemaCXX/type-trait-synthesises-from-spaceship.cpp
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index cbe59124d5b99..9767fde2c65a1 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -2049,6 +2049,9 @@ The following type trait primitives are supported by Clang. Those traits marked
Returns true if a reference ``T`` can be copy-initialized from a temporary of type
a non-cv-qualified ``U``.
* ``__underlying_type`` (C++, GNU, Microsoft)
+* ``__builtin_lt_synthesises_from_spaceship``, ``__builtin_gt_synthesises_from_spaceship``,
+ ``__builtin_le_synthesises_from_spaceship``, ``__builtin_ge_synthesises_from_spaceship`` (Clang):
+ These builtins can be used to determine whether the corresponding operator is synthesised from a spaceship operator.
In addition, the following expression traits are supported:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 046e6a51a1ebd..39fd1e396cdb8 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -109,6 +109,11 @@ What's New in Clang |release|?
C++ Language Changes
--------------------
+- A new family of builtins ``__builtin_*_synthesises_from_spaceship`` has been added. These can be queried to know
+ whether the ``<`` (``lt``), ``>`` (``gt``), ``<=`` (``le``), or ``>=`` (``ge``) operators are synthesised from a
+ ``<=>``. This makes it possible to optimize certain facilities by using the ``<=>`` operation directly instead of
+ doing multiple comparisons.
+
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
@@ -241,7 +246,7 @@ Improvements to Clang's diagnostics
"format specifies type 'unsigned int' but the argument has type 'int', which differs in signedness [-Wformat-signedness]"
"signedness of format specifier 'u' is incompatible with 'c' [-Wformat-signedness]"
and the API-visible diagnostic id will be appropriate.
-
+
- Fixed false positives in ``-Waddress-of-packed-member`` diagnostics when
potential misaligned members get processed before they can get discarded.
(#GH144729)
@@ -275,7 +280,7 @@ Bug Fixes in This Version
-------------------------
- Fix a crash when marco name is empty in ``#pragma push_macro("")`` or
``#pragma pop_macro("")``. (#GH149762).
-- Fix a crash in variable length array (e.g. ``int a[*]``) function parameter type
+- Fix a crash in variable length array (e.g. ``int a[*]``) function parameter type
being used in ``_Countof`` expression. (#GH152826).
- ``-Wunreachable-code`` now diagnoses tautological or contradictory
comparisons such as ``x != 0 || x != 1.0`` and ``x == 0 && x == 1.0`` on
@@ -360,7 +365,7 @@ X86 Support
arithmetic can now be used in C++ constant expressions.
- Some SSE, AVX and AVX512 intrinsics have been converted to wrap
generic __builtin intrinsics.
-- NOTE: Please avoid use of the __builtin_ia32_* intrinsics - these are not
+- NOTE: Please avoid use of the __builtin_ia32_* intrinsics - these are not
guaranteed to exist in future releases, or match behaviour with previous
releases of clang or other compilers.
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 94e72fea56a68..9d1a23d1af218 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -552,6 +552,10 @@ TYPE_TRAIT_1(__can_pass_in_regs, CanPassInRegs, KEYCXX)
TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX)
TYPE_TRAIT_2(__reference_constructs_from_temporary, ReferenceConstructsFromTemporary, KEYCXX)
TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary, KEYCXX)
+TYPE_TRAIT_2(__builtin_lt_synthesises_from_spaceship, LtSynthesisesFromSpaceship, KEYCXX)
+TYPE_TRAIT_2(__builtin_le_synthesises_from_spaceship, LeSynthesisesFromSpaceship, KEYCXX)
+TYPE_TRAIT_2(__builtin_gt_synthesises_from_spaceship, GtSynthesisesFromSpaceship, KEYCXX)
+TYPE_TRAIT_2(__builtin_ge_synthesises_from_spaceship, GeSynthesisesFromSpaceship, KEYCXX)
// IsDeducible is only used internally by clang for CTAD implementation and
// is not exposed to users.
TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX)
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 37552331478f1..b77975945c18c 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1825,6 +1825,51 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT,
return Self.HLSL().IsScalarizedLayoutCompatible(LhsT, RhsT);
}
+ case BTT_LtSynthesisesFromSpaceship:
+ case BTT_LeSynthesisesFromSpaceship:
+ case BTT_GtSynthesisesFromSpaceship:
+ case BTT_GeSynthesisesFromSpaceship: {
+ EnterExpressionEvaluationContext UnevaluatedContext(
+ Self, Sema::ExpressionEvaluationContext::Unevaluated);
+ Sema::SFINAETrap SFINAE(Self, /*ForValidityCheck=*/true);
+ Sema::ContextRAII TUContext(Self, Self.Context.getTranslationUnitDecl());
+
+ OpaqueValueExpr LHS(KeyLoc, LhsT.getNonReferenceType(),
+ LhsT->isLValueReferenceType() ? ExprValueKind::VK_LValue
+ : LhsT->isRValueReferenceType()
+ ? ExprValueKind::VK_XValue
+ : ExprValueKind::VK_PRValue);
+ OpaqueValueExpr RHS(KeyLoc, RhsT.getNonReferenceType(),
+ RhsT->isLValueReferenceType() ? ExprValueKind::VK_LValue
+ : RhsT->isRValueReferenceType()
+ ? ExprValueKind::VK_XValue
+ : ExprValueKind::VK_PRValue);
+
+ auto OpKind = [&] {
+ switch (BTT) {
+ case BTT_LtSynthesisesFromSpaceship:
+ return BinaryOperatorKind::BO_LT;
+ case BTT_LeSynthesisesFromSpaceship:
+ return BinaryOperatorKind::BO_LE;
+ case BTT_GtSynthesisesFromSpaceship:
+ return BinaryOperatorKind::BO_GT;
+ case BTT_GeSynthesisesFromSpaceship:
+ return BinaryOperatorKind::BO_GE;
+ default:
+ llvm_unreachable("Trying to Synthesize non-comparison operator?");
+ }
+ }();
+
+ UnresolvedSet<16> Functions;
+ Self.LookupBinOp(Self.TUScope, KeyLoc, OpKind, Functions);
+
+ ExprResult Result =
+ Self.CreateOverloadedBinOp(KeyLoc, OpKind, Functions, &LHS, &RHS);
+ if (Result.isInvalid() || SFINAE.hasErrorOccurred())
+ return false;
+
+ return isa<CXXRewrittenBinaryOperator>(Result.get());
+ }
default:
llvm_unreachable("not a BTT");
}
diff --git a/clang/test/SemaCXX/type-trait-synthesises-from-spaceship.cpp b/clang/test/SemaCXX/type-trait-synthesises-from-spaceship.cpp
new file mode 100644
index 0000000000000..ba581475bb4c7
--- /dev/null
+++ b/clang/test/SemaCXX/type-trait-synthesises-from-spaceship.cpp
@@ -0,0 +1,212 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
+
+static_assert(!__builtin_lt_synthesises_from_spaceship()); // expected-error {{expected a type}}
+static_assert(!__builtin_lt_synthesises_from_spaceship(int)); // expected-error {{type trait requires 2 arguments; have 1 argument}}
+static_assert(!__builtin_lt_synthesises_from_spaceship(int, int, int)); // expected-error {{type trait requires 2 arguments; have 3 argument}}
+static_assert(!__builtin_lt_synthesises_from_spaceship(int, 0)); // expected-error {{expected a type}}
+
+static_assert(!__builtin_le_synthesises_from_spaceship()); // expected-error {{expected a type}}
+static_assert(!__builtin_le_synthesises_from_spaceship(int)); // expected-error {{type trait requires 2 arguments; have 1 argument}}
+static_assert(!__builtin_le_synthesises_from_spaceship(int, int, int)); // expected-error {{type trait requires 2 arguments; have 3 argument}}
+static_assert(!__builtin_le_synthesises_from_spaceship(int, 0)); // expected-error {{expected a type}}
+
+static_assert(!__builtin_gt_synthesises_from_spaceship()); // expected-error {{expected a type}}
+static_assert(!__builtin_gt_synthesises_from_spaceship(int)); // expected-error {{type trait requires 2 arguments; have 1 argument}}
+static_assert(!__builtin_gt_synthesises_from_spaceship(int, int, int)); // expected-error {{type trait requires 2 arguments; have 3 argument}}
+static_assert(!__builtin_gt_synthesises_from_spaceship(int, 0)); // expected-error {{expected a type}}
+
+static_assert(!__builtin_ge_synthesises_from_spaceship()); // expected-error {{expected a type}}
+static_assert(!__builtin_ge_synthesises_from_spaceship(int)); // expected-error {{type trait requires 2 arguments; have 1 argument}}
+static_assert(!__builtin_ge_synthesises_from_spaceship(int, int, int)); // expected-error {{type trait requires 2 arguments; have 3 argument}}
+static_assert(!__builtin_ge_synthesises_from_spaceship(int, 0)); // expected-error {{expected a type}}
+
+namespace std {
+ struct strong_ordering {
+ int n;
+ constexpr operator int() const { return n; }
+ static const strong_ordering less, equal, greater;
+ };
+ constexpr strong_ordering strong_ordering::less = {-1};
+ constexpr strong_ordering strong_ordering::equal = {0};
+ constexpr strong_ordering strong_ordering::greater = {1};
+}
+
+struct DefaultSpaceship {
+ friend auto operator<=>(DefaultSpaceship, DefaultSpaceship) = default;
+};
+
+static_assert(__builtin_lt_synthesises_from_spaceship(const DefaultSpaceship&, const DefaultSpaceship&));
+static_assert(__builtin_le_synthesises_from_spaceship(const DefaultSpaceship&, const DefaultSpaceship&));
+static_assert(__builtin_gt_synthesises_from_spaceship(const DefaultSpaceship&, const DefaultSpaceship&));
+static_assert(__builtin_ge_synthesises_from_spaceship(const DefaultSpaceship&, const DefaultSpaceship&));
+
+struct CustomSpaceship {
+ int i;
+
+ friend auto operator<=>(CustomSpaceship lhs, CustomSpaceship rhs) {
+ return rhs.i <=> lhs.i;
+ }
+};
+
+static_assert(__builtin_lt_synthesises_from_spaceship(const CustomSpaceship&, const CustomSpaceship&));
+static_assert(__builtin_le_synthesises_from_spaceship(const CustomSpaceship&, const CustomSpaceship&));
+static_assert(__builtin_gt_synthesises_from_spaceship(const CustomSpaceship&, const CustomSpaceship&));
+static_assert(__builtin_ge_synthesises_from_spaceship(const CustomSpaceship&, const CustomSpaceship&));
+
+struct CustomLT {
+ int i;
+
+ friend auto operator<(CustomLT lhs, CustomLT rhs) {
+ return rhs.i < lhs.i;
+ }
+};
+
+static_assert(!__builtin_lt_synthesises_from_spaceship(const CustomLT&, const CustomLT&));
+static_assert(!__builtin_le_synthesises_from_spaceship(const CustomLT&, const CustomLT&));
+static_assert(!__builtin_gt_synthesises_from_spaceship(const CustomLT&, const CustomLT&));
+static_assert(!__builtin_ge_synthesises_from_spaceship(const CustomLT&, const CustomLT&));
+
+struct CustomLE {
+ int i;
+
+ friend auto operator<=(CustomLE lhs, CustomLE rhs) {
+ return rhs.i < lhs.i;
+ }
+};
+
+static_assert(!__builtin_lt_synthesises_from_spaceship(const CustomLE&, const CustomLE&));
+static_assert(!__builtin_le_synthesises_from_spaceship(const CustomLE&, const CustomLE&));
+static_assert(!__builtin_gt_synthesises_from_spaceship(const CustomLE&, const CustomLE&));
+static_assert(!__builtin_ge_synthesises_from_spaceship(const CustomLE&, const CustomLE&));
+
+struct CustomGT {
+ int i;
+
+ friend auto operator>(CustomGT lhs, CustomGT rhs) {
+ return rhs.i < lhs.i;
+ }
+};
+
+static_assert(!__builtin_lt_synthesises_from_spaceship(const CustomGT&, const CustomGT&));
+static_assert(!__builtin_le_synthesises_from_spaceship(const CustomGT&, const CustomGT&));
+static_assert(!__builtin_gt_synthesises_from_spaceship(const CustomGT&, const CustomGT&));
+static_assert(!__builtin_ge_synthesises_from_spaceship(const CustomGT&, const CustomGT&));
+
+struct CustomGE {
+ int i;
+
+ friend auto operator>=(CustomGE lhs, CustomGE rhs) {
+ return rhs.i < lhs.i;
+ }
+};
+
+static_assert(!__builtin_lt_synthesises_from_spaceship(const CustomGE&, const CustomGE&));
+static_assert(!__builtin_le_synthesises_from_spaceship(const CustomGE&, const CustomGE&));
+static_assert(!__builtin_gt_synthesises_from_spaceship(const CustomGE&, const CustomGE&));
+static_assert(!__builtin_ge_synthesises_from_spaceship(const CustomGE&, const CustomGE&));
+
+struct CustomLTAndSpaceship {
+ int i;
+
+ friend auto operator<=>(CustomLTAndSpaceship lhs, CustomLTAndSpaceship rhs) {
+ return rhs.i <=> lhs.i;
+ }
+
+ friend auto operator<(CustomLTAndSpaceship lhs, CustomLTAndSpaceship rhs) {
+ return rhs.i < lhs.i;
+ }
+};
+
+static_assert(!__builtin_lt_synthesises_from_spaceship(const CustomLTAndSpaceship&, const CustomLTAndSpaceship&));
+static_assert(__builtin_le_synthesises_from_spaceship(const CustomLTAndSpaceship&, const CustomLTAndSpaceship&));
+static_assert(__builtin_gt_synthesises_from_spaceship(const CustomLTAndSpaceship&, const CustomLTAndSpaceship&));
+static_assert(__builtin_ge_synthesises_from_spaceship(const CustomLTAndSpaceship&, const CustomLTAndSpaceship&));
+
+struct CustomLEAndSpaceship {
+ int i;
+
+ friend auto operator<=>(CustomLEAndSpaceship lhs, CustomLEAndSpaceship rhs) {
+ return rhs.i <=> lhs.i;
+ }
+
+ friend auto operator<=(CustomLEAndSpaceship lhs, CustomLEAndSpaceship rhs) {
+ return rhs.i < lhs.i;
+ }
+};
+
+static_assert(__builtin_lt_synthesises_from_spaceship(const CustomLEAndSpaceship&, const CustomLEAndSpaceship&));
+static_assert(!__builtin_le_synthesises_from_spaceship(const CustomLEAndSpaceship&, const CustomLEAndSpaceship&));
+static_assert(__builtin_gt_synthesises_from_spaceship(const CustomLEAndSpaceship&, const CustomLEAndSpaceship&));
+static_assert(__builtin_ge_synthesises_from_spaceship(const CustomLEAndSpaceship&, const CustomLEAndSpaceship&));
+
+struct CustomGTAndSpaceship {
+ int i;
+
+ friend auto operator<=>(CustomGTAndSpaceship lhs, CustomGTAndSpaceship rhs) {
+ return rhs.i <=> lhs.i;
+ }
+
+ friend auto operator>(CustomGTAndSpaceship lhs, CustomGTAndSpaceship rhs) {
+ return rhs.i < lhs.i;
+ }
+};
+
+static_assert(__builtin_lt_synthesises_from_spaceship(const CustomGTAndSpaceship&, const CustomGTAndSpaceship&));
+static_assert(__builtin_le_synthesises_from_spaceship(const CustomGTAndSpaceship&, const CustomGTAndSpaceship&));
+static_assert(!__builtin_gt_synthesises_from_spaceship(const CustomGTAndSpaceship&, const CustomGTAndSpaceship&));
+static_assert(__builtin_ge_synthesises_from_spaceship(const CustomGTAndSpaceship&, const CustomGTAndSpaceship&));
+
+struct CustomGEAndSpaceship {
+ int i;
+
+ friend auto operator<=>(CustomGEAndSpaceship lhs, CustomGEAndSpaceship rhs) {
+ return rhs.i <=> lhs.i;
+ }
+
+ friend auto operator>=(CustomGEAndSpaceship lhs, CustomGEAndSpaceship rhs) {
+ return rhs.i < lhs.i;
+ }
+};
+
+static_assert(__builtin_lt_synthesises_from_spaceship(const CustomGEAndSpaceship&, const CustomGEAndSpaceship&));
+static_assert(__builtin_le_synthesises_from_spaceship(const CustomGEAndSpaceship&, const CustomGEAndSpaceship&));
+static_assert(__builtin_gt_synthesises_from_spaceship(const CustomGEAndSpaceship&, const CustomGEAndSpaceship&));
+static_assert(!__builtin_ge_synthesises_from_spaceship(const CustomGEAndSpaceship&, const CustomGEAndSpaceship&));
+
+struct DefaultedCmpAndSpaceship {
+ int i;
+
+ friend auto operator<=>(DefaultedCmpAndSpaceship lhs, DefaultedCmpAndSpaceship rhs) {
+ return rhs.i <=> lhs.i;
+ }
+
+ friend bool operator<(DefaultedCmpAndSpaceship lhs, DefaultedCmpAndSpaceship rhs) = default;
+ friend bool operator<=(DefaultedCmpAndSpaceship lhs, DefaultedCmpAndSpaceship rhs) = default;
+ friend bool operator>(DefaultedCmpAndSpaceship lhs, DefaultedCmpAndSpaceship rhs) = default;
+ friend bool operator>=(DefaultedCmpAndSpaceship lhs, DefaultedCmpAndSpaceship rhs) = default;
+};
+
+// TODO: This should probably return true
+static_assert(!__builtin_lt_synthesises_from_spaceship(const DefaultedCmpAndSpaceship&, const DefaultedCmpAndSpaceship&));
+static_assert(!__builtin_le_synthesises_from_spaceship(const DefaultedCmpAndSpaceship&, const DefaultedCmpAndSpaceship&));
+static_assert(!__builtin_gt_synthesises_from_spaceship(const DefaultedCmpAndSpaceship&, const DefaultedCmpAndSpaceship&));
+static_assert(!__builtin_ge_synthesises_from_spaceship(const DefaultedCmpAndSpaceship&, const DefaultedCmpAndSpaceship&));
+
+struct DifferentTypes {
+ int i;
+
+ friend auto operator<=>(DifferentTypes lhs, int rhs) {
+ return rhs <=> lhs.i;
+ }
+};
+
+static_assert(__builtin_lt_synthesises_from_spaceship(const DifferentTypes&, const int&));
+static_assert(__builtin_le_synthesises_from_spaceship(const DifferentTypes&, const int&));
+static_assert(__builtin_gt_synthesises_from_spaceship(const DifferentTypes&, const int&));
+static_assert(__builtin_ge_synthesises_from_spaceship(const DifferentTypes&, const int&));
+
+// TODO: Should this return true? It's technically not synthesized from spaceship, but it behaves exactly as-if it was
+static_assert(!__builtin_lt_synthesises_from_spaceship(int, int));
+static_assert(!__builtin_le_synthesises_from_spaceship(int, int));
+static_assert(!__builtin_gt_synthesises_from_spaceship(int, int));
+static_assert(!__builtin_ge_synthesises_from_spaceship(int, int));
More information about the cfe-commits
mailing list