llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Ross Kirsling (rkirsling) <details> <summary>Changes</summary> 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. --- 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 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}} +} `````````` </details> https://github.com/llvm/llvm-project/pull/144836 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits