Skip to content

[Clang] Diagnose unsatisfied std::is_assignable. #144836

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

rkirsling
Copy link
Contributor

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.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jun 19, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 19, 2025

@llvm/pr-subscribers-clang

Author: Ross Kirsling (rkirsling)

Changes

Part of the work for #141911.

Checking is_assignable&lt;T, U&gt; boils down to checking the well-formedness of declval&lt;T&gt;() = declval&lt;U&gt;(); this PR recycles logic from EvaluateBinaryTypeTrait in order to produce the relevant diagnostics.


Full diff: https://github.com/llvm/llvm-project/pull/144836.diff

3 Files Affected:

  • (modified) clang/lib/Sema/SemaTypeTraits.cpp (+29)
  • (modified) clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp (+65)
  • (modified) clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp (+71)
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@-1 {{static assertion failed due to requirement 'std::is_constructible_v<void>'}} \
 // expected-note@-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@-1 {{static assertion failed due to requirement 'std::{{.*}}is_assignable<int &, void>::value'}} \
+// expected-error@-1 {{assigning to 'int' from incompatible type 'void'}}
+static_assert(std::is_assignable_v<int&, void>);
+// expected-error@-1 {{static assertion failed due to requirement 'std::is_assignable_v<int &, void>'}} \
+// expected-error@-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@-1 {{static assertion failed due to requirement 'is_constructible_v<void>'}} \
     // expected-note@-1 {{because it is a cv void type}}
+
+    static_assert(is_assignable<int&, void>::value);
+    // expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_assignable<int &, void>::value'}} \
+    // expected-error@-1 {{assigning to 'int' from incompatible type 'void'}}
+    static_assert(is_assignable_v<int&, void>);
+    // expected-error@-1 {{static assertion failed due to requirement 'is_assignable_v<int &, void>'}} \
+    // expected-error@-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@-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@-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@-1 {{static assertion failed due to requirement '__is_constructible(void (int, float))'}} \
 // expected-note@-1 {{because it is a function type}}
 }
+
+namespace assignable {
+struct S1;
+static_assert(__is_assignable(S1&, const S1&));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(assignable::S1 &, const assignable::S1 &)'}} \
+// expected-error@-1 {{no viable overloaded '='}} \
+// expected-note@-1 {{type 'S1' is incomplete}}
+
+static_assert(__is_assignable(void, int));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(void, int)'}} \
+// expected-error@-1 {{expression is not assignable}}
+
+static_assert(__is_assignable(int, int));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(int, int)'}} \
+// expected-error@-1 {{expression is not assignable}}
+
+static_assert(__is_assignable(int*, int));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(int *, int)'}} \
+// expected-error@-1 {{expression is not assignable}}
+
+static_assert(__is_assignable(int[], int));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(int[], int)'}} \
+// expected-error@-1 {{expression is not assignable}}
+
+static_assert(__is_assignable(int&, void));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(int &, void)'}} \
+// expected-error@-1 {{assigning to 'int' from incompatible type 'void'}}
+
+static_assert(__is_assignable(int*&, float*));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(int *&, float *)'}} \
+// expected-error@-1 {{incompatible pointer types assigning to 'int *' from 'float *'}}
+
+static_assert(__is_assignable(const int&, int));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(const int &, int)'}} \
+// expected-error@-1 {{read-only variable is not assignable}}
+
+struct S2 {}; // #a-S2
+static_assert(__is_assignable(const S2, S2));
+// expected-error@-1 {{static assertion failed due to requirement '__is_assignable(const assignable::S2, assignable::S2)'}} \
+// expected-error@-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@-1 {{static assertion failed due to requirement '__is_assignable(assignable::S3, const assignable::S3 &)'}} \
+// expected-error@-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@-1 {{static assertion failed due to requirement '__is_assignable(assignable::S3, assignable::S3 &&)'}} \
+// expected-error@-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@-1 {{static assertion failed due to requirement '__is_assignable(assignable::C1, assignable::C1)'}} \
+// expected-error@-1 {{'operator=' is a private member of 'assignable::C1'}} \
+// expected-note@#ama-C1 {{implicitly declared private here}} \
+// expected-note@#a-C1 {{'C1' defined here}}
+}

Part of the work for llvm#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.
@rkirsling rkirsling force-pushed the diagnose-unsatisfied-is_assignable branch from ac74e32 to 5807503 Compare June 19, 2025 05:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants