[clang] [Clang] Diagnose unsatisfied `std::is_assignable`. (PR #144836)
Ross Kirsling via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 18 21:55:12 PDT 2025
https://github.com/rkirsling created https://github.com/llvm/llvm-project/pull/144836
Part of the work for #141911.
Checking `is_assignable<T, U>` boils down to checking the well-formedness of `declval<T>() = declval<U>()`; this PR recycles logic from EvaluateBinaryTypeTrait in order to produce the relevant diagnostics.
>From ac74e32ad2045c27c5807ba880c0285872be3f43 Mon Sep 17 00:00:00 2001
From: Ross Kirsling <ross.kirsling at sony.com>
Date: Wed, 18 Jun 2025 21:54:35 -0700
Subject: [PATCH] [Clang] Diagnose unsatisfied `std::is_assignable`.
Part of the work for #141911.
Checking `is_assignable<T, U>` boils down to checking the well-formedness of `declval<T>() = declval<U>()`; this PR recycles logic from EvaluateBinaryTypeTrait in order to produce the relevant diagnostics.
---
clang/lib/Sema/SemaTypeTraits.cpp | 29 ++++++++
.../type-traits-unsatisfied-diags-std.cpp | 65 +++++++++++++++++
.../SemaCXX/type-traits-unsatisfied-diags.cpp | 71 +++++++++++++++++++
3 files changed, 165 insertions(+)
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 22c690bedc1ed..aa3208fe51271 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1949,6 +1949,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
.Case("is_replaceable", TypeTrait::UTT_IsReplaceable)
.Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable)
.Case("is_constructible", TypeTrait::TT_IsConstructible)
+ .Case("is_assignable", TypeTrait::BTT_IsAssignable)
.Default(std::nullopt);
}
@@ -2340,6 +2341,31 @@ static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef,
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
}
+static void DiagnoseNonAssignableReason(Sema &SemaRef, SourceLocation Loc,
+ QualType T, QualType U) {
+ const CXXRecordDecl *D = T->getAsCXXRecordDecl();
+
+ if (T->isObjectType() || T->isFunctionType())
+ T = SemaRef.Context.getRValueReferenceType(T);
+ if (U->isObjectType() || U->isFunctionType())
+ U = SemaRef.Context.getRValueReferenceType(U);
+ OpaqueValueExpr LHS(Loc, T.getNonLValueExprType(SemaRef.Context),
+ Expr::getValueKindForType(T));
+ OpaqueValueExpr RHS(Loc, U.getNonLValueExprType(SemaRef.Context),
+ Expr::getValueKindForType(U));
+
+ EnterExpressionEvaluationContext Unevaluated(
+ SemaRef, Sema::ExpressionEvaluationContext::Unevaluated);
+ Sema::ContextRAII TUContext(SemaRef,
+ SemaRef.Context.getTranslationUnitDecl());
+ SemaRef.BuildBinOp(/*S=*/nullptr, Loc, BO_Assign, &LHS, &RHS);
+
+ if (!D || D->isInvalidDecl())
+ return;
+
+ SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
+}
+
void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
E = E->IgnoreParenImpCasts();
if (E->containsErrors())
@@ -2363,6 +2389,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
case TT_IsConstructible:
DiagnoseNonConstructibleReason(*this, E->getBeginLoc(), Args);
break;
+ case BTT_IsAssignable:
+ DiagnoseNonAssignableReason(*this, E->getBeginLoc(), Args[0], Args[1]);
+ break;
default:
break;
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
index a403a0450607a..23391a799282f 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
@@ -28,6 +28,14 @@ struct is_constructible {
template <typename... Args>
constexpr bool is_constructible_v = __is_constructible(Args...);
+
+template <typename T, typename U>
+struct is_assignable {
+ static constexpr bool value = __is_assignable(T, U);
+};
+
+template <typename T, typename U>
+constexpr bool is_assignable_v = __is_assignable(T, U);
#endif
#ifdef STD2
@@ -63,6 +71,17 @@ using is_constructible = __details_is_constructible<Args...>;
template <typename... Args>
constexpr bool is_constructible_v = __is_constructible(Args...);
+
+template <typename T, typename U>
+struct __details_is_assignable {
+ static constexpr bool value = __is_assignable(T, U);
+};
+
+template <typename T, typename U>
+using is_assignable = __details_is_assignable<T, U>;
+
+template <typename T, typename U>
+constexpr bool is_assignable_v = __is_assignable(T, U);
#endif
@@ -101,6 +120,15 @@ using is_constructible = __details_is_constructible<Args...>;
template <typename... Args>
constexpr bool is_constructible_v = is_constructible<Args...>::value;
+
+template <typename T, typename U>
+struct __details_is_assignable : bool_constant<__is_assignable(T, U)> {};
+
+template <typename T, typename U>
+using is_assignable = __details_is_assignable<T, U>;
+
+template <typename T, typename U>
+constexpr bool is_assignable_v = is_assignable<T, U>::value;
#endif
}
@@ -137,6 +165,15 @@ static_assert(std::is_constructible_v<void>);
// expected-error at -1 {{static assertion failed due to requirement 'std::is_constructible_v<void>'}} \
// expected-note at -1 {{because it is a cv void type}}
+static_assert(std::is_assignable<int&, int>::value);
+
+static_assert(std::is_assignable<int&, void>::value);
+// expected-error-re at -1 {{static assertion failed due to requirement 'std::{{.*}}is_assignable<int &, void>::value'}} \
+// expected-error at -1 {{assigning to 'int' from incompatible type 'void'}}
+static_assert(std::is_assignable_v<int&, void>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_assignable_v<int &, void>'}} \
+// expected-error at -1 {{assigning to 'int' from incompatible type 'void'}}
+
namespace test_namespace {
using namespace std;
static_assert(is_trivially_relocatable<int&>::value);
@@ -163,6 +200,13 @@ namespace test_namespace {
static_assert(is_constructible_v<void>);
// expected-error at -1 {{static assertion failed due to requirement 'is_constructible_v<void>'}} \
// expected-note at -1 {{because it is a cv void type}}
+
+ static_assert(is_assignable<int&, void>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_assignable<int &, void>::value'}} \
+ // expected-error at -1 {{assigning to 'int' from incompatible type 'void'}}
+ static_assert(is_assignable_v<int&, void>);
+ // expected-error at -1 {{static assertion failed due to requirement 'is_assignable_v<int &, void>'}} \
+ // expected-error at -1 {{assigning to 'int' from incompatible type 'void'}}
}
@@ -191,6 +235,14 @@ concept C3 = std::is_constructible_v<Args...>; // #concept6
template <C3 T> void g3(); // #cand6
+template <typename T, typename U>
+requires std::is_assignable<T, U>::value void f4(); // #cand7
+
+template <typename T, typename U>
+concept C4 = std::is_assignable_v<T, U>; // #concept8
+
+template <C4<void> T> void g4(); // #cand8
+
void test() {
f<int&>();
@@ -235,6 +287,19 @@ void test() {
// expected-note@#cand6 {{because 'void' does not satisfy 'C3'}} \
// expected-note@#concept6 {{because 'std::is_constructible_v<void>' evaluated to false}} \
// expected-note@#concept6 {{because it is a cv void type}}
+
+ f4<int&, void>();
+ // expected-error at -1 {{no matching function for call to 'f4'}} \
+ // expected-note@#cand7 {{candidate template ignored: constraints not satisfied [with T = int &, U = void]}} \
+ // expected-note-re@#cand7 {{because '{{.*}}is_assignable<int &, void>::value' evaluated to false}} \
+ // expected-error@#cand7 {{assigning to 'int' from incompatible type 'void'}}
+
+ g4<int&>();
+ // expected-error at -1 {{no matching function for call to 'g4'}} \
+ // expected-note@#cand8 {{candidate template ignored: constraints not satisfied [with T = int &]}} \
+ // expected-note@#cand8 {{because 'C4<int &, void>' evaluated to false}} \
+ // expected-note@#concept8 {{because 'std::is_assignable_v<int &, void>' evaluated to false}} \
+ // expected-error@#concept8 {{assigning to 'int' from incompatible type 'void'}}
}
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index d0b3f294fbcab..adca5938c3759 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -550,3 +550,74 @@ static_assert(__is_constructible(void (int, float)));
// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(void (int, float))'}} \
// expected-note at -1 {{because it is a function type}}
}
+
+namespace assignable {
+struct S1;
+static_assert(__is_assignable(S1&, const S1&));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(assignable::S1 &, const assignable::S1 &)'}} \
+// expected-error at -1 {{no viable overloaded '='}} \
+// expected-note at -1 {{type 'S1' is incomplete}}
+
+static_assert(__is_assignable(void, int));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(void, int)'}} \
+// expected-error at -1 {{expression is not assignable}}
+
+static_assert(__is_assignable(int, int));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(int, int)'}} \
+// expected-error at -1 {{expression is not assignable}}
+
+static_assert(__is_assignable(int*, int));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(int *, int)'}} \
+// expected-error at -1 {{expression is not assignable}}
+
+static_assert(__is_assignable(int[], int));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(int[], int)'}} \
+// expected-error at -1 {{expression is not assignable}}
+
+static_assert(__is_assignable(int&, void));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(int &, void)'}} \
+// expected-error at -1 {{assigning to 'int' from incompatible type 'void'}}
+
+static_assert(__is_assignable(int*&, float*));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(int *&, float *)'}} \
+// expected-error at -1 {{incompatible pointer types assigning to 'int *' from 'float *'}}
+
+static_assert(__is_assignable(const int&, int));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(const int &, int)'}} \
+// expected-error at -1 {{read-only variable is not assignable}}
+
+struct S2 {}; // #a-S2
+static_assert(__is_assignable(const S2, S2));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(const assignable::S2, assignable::S2)'}} \
+// expected-error at -1 {{no viable overloaded '='}} \
+// expected-note@#a-S2 {{candidate function (the implicit copy assignment operator) not viable: 'this' argument has type 'const S2', but method is not marked const}} \
+// expected-note@#a-S2 {{candidate function (the implicit move assignment operator) not viable: 'this' argument has type 'const S2', but method is not marked const}} \
+// expected-note@#a-S2 {{'S2' defined here}}
+
+struct S3 { // #a-S3
+ S3& operator=(const S3&) = delete; // #aca-S3
+ S3& operator=(S3&&) = delete; // #ama-S3
+};
+static_assert(__is_assignable(S3, const S3&));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(assignable::S3, const assignable::S3 &)'}} \
+// expected-error at -1 {{overload resolution selected deleted operator '='}} \
+// expected-note@#aca-S3 {{candidate function has been explicitly deleted}} \
+// expected-note@#ama-S3 {{candidate function not viable: 1st argument ('const S3') would lose const qualifier}} \
+// expected-note@#a-S3 {{'S3' defined here}}
+static_assert(__is_assignable(S3, S3&&));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(assignable::S3, assignable::S3 &&)'}} \
+// expected-error at -1 {{overload resolution selected deleted operator '='}} \
+// expected-note@#aca-S3 {{candidate function has been explicitly deleted}} \
+// expected-note@#ama-S3 {{candidate function has been explicitly deleted}} \
+// expected-note@#a-S3 {{'S3' defined here}}
+
+class C1 { // #a-C1
+ C1& operator=(const C1&) = default;
+ C1& operator=(C1&&) = default; // #ama-C1
+};
+static_assert(__is_assignable(C1, C1));
+// expected-error at -1 {{static assertion failed due to requirement '__is_assignable(assignable::C1, assignable::C1)'}} \
+// expected-error at -1 {{'operator=' is a private member of 'assignable::C1'}} \
+// expected-note@#ama-C1 {{implicitly declared private here}} \
+// expected-note@#a-C1 {{'C1' defined here}}
+}
More information about the cfe-commits
mailing list