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

Merged
merged 3 commits into from
Jun 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions clang/lib/Sema/SemaTypeTraits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1725,14 +1725,15 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT,

// Build expressions that emulate the effect of declval<T>() and
// declval<U>().
if (LhsT->isObjectType() || LhsT->isFunctionType())
LhsT = Self.Context.getRValueReferenceType(LhsT);
if (RhsT->isObjectType() || RhsT->isFunctionType())
RhsT = Self.Context.getRValueReferenceType(RhsT);
OpaqueValueExpr Lhs(KeyLoc, LhsT.getNonLValueExprType(Self.Context),
Expr::getValueKindForType(LhsT));
OpaqueValueExpr Rhs(KeyLoc, RhsT.getNonLValueExprType(Self.Context),
Expr::getValueKindForType(RhsT));
auto createDeclValExpr = [&](QualType Ty) -> OpaqueValueExpr {
if (Ty->isObjectType() || Ty->isFunctionType())
Ty = Self.Context.getRValueReferenceType(Ty);
return {KeyLoc, Ty.getNonLValueExprType(Self.Context),
Expr::getValueKindForType(Ty)};
};

auto Lhs = createDeclValExpr(LhsT);
auto Rhs = createDeclValExpr(RhsT);

// Attempt the assignment in an unevaluated context within a SFINAE
// trap at translation unit scope.
Expand Down Expand Up @@ -1956,6 +1957,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
TypeTrait::UTT_IsCppTriviallyRelocatable)
.Case("is_replaceable", TypeTrait::UTT_IsReplaceable)
.Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable)
.Case("is_assignable", TypeTrait::BTT_IsAssignable)
.Default(std::nullopt);
}

Expand Down Expand Up @@ -2285,6 +2287,32 @@ 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();

auto createDeclValExpr = [&](QualType Ty) -> OpaqueValueExpr {
if (Ty->isObjectType() || Ty->isFunctionType())
Ty = SemaRef.Context.getRValueReferenceType(Ty);
return {Loc, Ty.getNonLValueExprType(SemaRef.Context),
Expr::getValueKindForType(Ty)};
};

auto LHS = createDeclValExpr(T);
auto RHS = createDeclValExpr(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())
Expand All @@ -2305,6 +2333,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
case UTT_IsTriviallyCopyable:
DiagnoseNonTriviallyCopyableReason(*this, E->getBeginLoc(), Args[0]);
break;
case BTT_IsAssignable:
DiagnoseNonAssignableReason(*this, E->getBeginLoc(), Args[0], Args[1]);
break;
default:
break;
}
Expand Down
64 changes: 64 additions & 0 deletions clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ struct is_trivially_copyable {

template <typename T>
constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T);

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
Expand All @@ -44,6 +52,17 @@ using is_trivially_copyable = __details_is_trivially_copyable<T>;

template <typename T>
constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T);

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


Expand Down Expand Up @@ -73,6 +92,15 @@ using is_trivially_copyable = __details_is_trivially_copyable<T>;

template <typename T>
constexpr bool is_trivially_copyable_v = is_trivially_copyable<T>::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

}
Expand All @@ -99,6 +127,14 @@ static_assert(std::is_trivially_copyable_v<int&>);
// expected-note@-1 {{'int &' is not trivially copyable}} \
// expected-note@-1 {{because it is a reference 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'}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike the other DiagnoseTypeTraitDetails diagnostics, this is an "error", not a "note". I'm not sure that actually causes any serious issues right now, but it's a bit confusing, and could cause issues in the future.

Copy link
Contributor Author

@rkirsling rkirsling Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, at first I wondered if I needed to recapitulate all of these existing diagnostic messages somehow, just to ensure that it would all be notes against a single error. But it appears that is_constructible is also incurring a second error (via InitializationSequence::Diagnose), so I took this as precedent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good candidate for nesting; I’ll make a note of this one somewhere. For now I think it’s fine to just emit the error (I’m very busy this week unfortunately, but I’m planning to open a pr for it next week if nothing gets in the way).

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;
Expand All @@ -119,6 +155,13 @@ namespace test_namespace {
// expected-error@-1 {{static assertion failed due to requirement 'is_trivially_copyable_v<int &>'}} \
// expected-note@-1 {{'int &' is not trivially copyable}} \
// expected-note@-1 {{because it is a reference 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'}}
}


Expand All @@ -139,6 +182,14 @@ concept C2 = std::is_trivially_copyable_v<T>; // #concept4

template <C2 T> void g2(); // #cand4

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&>();
// expected-error@-1 {{no matching function for call to 'f'}} \
Expand Down Expand Up @@ -169,6 +220,19 @@ void test() {
// expected-note@#concept4 {{because 'std::is_trivially_copyable_v<int &>' evaluated to false}} \
// expected-note@#concept4 {{'int &' is not trivially copyable}} \
// expected-note@#concept4 {{because it is a reference 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'}}
}
}

Expand Down
71 changes: 71 additions & 0 deletions clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,74 @@ static_assert(__is_trivially_copyable(S12));
// expected-note@-1 {{'S12' is not trivially copyable}} \
// expected-note@#tc-S12 {{'S12' defined here}}
}

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}}
}
Loading