https://github.com/cor3ntin created https://github.com/llvm/llvm-project/pull/143265
As a drive by fix the definition of replaceable, that did not correctly implement https://eel.is/c++draft/class.prop#6.3 >From 7f626192c2636805c890098c6d6bf0e7bef11725 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@gmail.com> Date: Sat, 7 Jun 2025 15:14:43 +0200 Subject: [PATCH] [Clang] Explain why a type is not replaceable. As a drive by fix the definition of replaceable, that did not correctly implement https://eel.is/c++draft/class.prop#6.3 --- .../clang/Basic/DiagnosticSemaKinds.td | 20 ++- clang/lib/Sema/SemaTypeTraits.cpp | 159 ++++++++++++++---- .../SemaCXX/cxx2c-trivially-relocatable.cpp | 25 +++ .../type-traits-unsatisfied-diags-std.cpp | 21 +++ .../SemaCXX/type-traits-unsatisfied-diags.cpp | 131 +++++++++++++++ 5 files changed, 321 insertions(+), 35 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5f44d503580b9..f77ddbcff8ec0 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1763,27 +1763,39 @@ def err_user_defined_msg_constexpr : Error< "constant expression">; // Type traits explanations -def note_unsatisfied_trait : Note<"%0 is not %enum_select<TraitName>{" - "%TriviallyRelocatable{trivially relocatable}|" - "%TriviallyCopyable{trivially copyable}" - "}1">; +def note_unsatisfied_trait + : Note<"%0 is not %enum_select<TraitName>{" + "%TriviallyRelocatable{trivially relocatable}|" + "%Replaceable{replaceable}|" + "%TriviallyCopyable{trivially copyable}" + "}1">; def note_unsatisfied_trait_reason : Note<"because it " "%enum_select<TraitNotSatisfiedReason>{" "%Ref{is a reference type}|" + "%Const{is const}|" + "%Volatile{is volatile}|" "%HasArcLifetime{has an ARC lifetime qualifier}|" "%VLA{is a variably-modified type}|" "%VBase{has a virtual base %1}|" + "%NotScalarOrClass{not %select{a|an array of objects of}1 scalar or " + "class type}|" "%NTRBase{has a non-trivially-relocatable base %1}|" "%NTRField{has a non-trivially-relocatable member %1 of type %2}|" + "%NonReplaceableBase{has a non-replaceable base %1}|" + "%NonReplaceableField{has a non-replaceable member %1 of type %2}|" "%NTCBase{has a non-trivially-copyable base %1}|" "%NTCField{has a non-trivially-copyable member %1 of type %2}|" "%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|" "%UserProvidedCtr{has a user provided %select{copy|move}1 " "constructor}|" + "%DeletedCtr{has a deleted %select{copy|move}1 " + "constructor}|" "%UserProvidedAssign{has a user provided %select{copy|move}1 " "assignment operator}|" + "%DeletedAssign{has a deleted %select{copy|move}1 " + "assignment operator}|" "%UnionWithUserDeclaredSMF{is a union with a user-declared " "%sub{select_special_member_kind}1}" "}0">; diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp index 330f2aa750a09..eba4dbb457ae6 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -104,6 +104,7 @@ static CXXMethodDecl *LookupSpecialMemberFromXValue(Sema &SemaRef, OverloadCandidateSet::iterator Best; switch (OCS.BestViableFunction(SemaRef, LookupLoc, Best)) { case OR_Success: + case OR_Deleted: return cast<CXXMethodDecl>(Best->Function); default: return nullptr; @@ -120,7 +121,8 @@ static bool hasSuitableConstructorForRelocation(Sema &SemaRef, CXXMethodDecl *Decl = LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false); - return Decl && Decl->isUserProvided() == AllowUserDefined; + return Decl && Decl->isUserProvided() == AllowUserDefined && + !Decl->isDeleted(); } static bool hasSuitableMoveAssignmentOperatorForRelocation( @@ -135,7 +137,8 @@ static bool hasSuitableMoveAssignmentOperatorForRelocation( if (!Decl) return false; - return Decl && Decl->isUserProvided() == AllowUserDefined; + return Decl && Decl->isUserProvided() == AllowUserDefined && + !Decl->isDeleted(); } // [C++26][class.prop] @@ -1940,6 +1943,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) { return llvm::StringSwitch<std::optional<TypeTrait>>(Name) .Case("is_trivially_relocatable", TypeTrait::UTT_IsCppTriviallyRelocatable) + .Case("is_replaceable", TypeTrait::UTT_IsReplaceable) .Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable) .Default(std::nullopt); } @@ -2005,35 +2009,8 @@ static ExtractedTypeTraitInfo ExtractTypeTraitFromExpression(const Expr *E) { return std::nullopt; } -static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, - SourceLocation Loc, - const CXXRecordDecl *D) { - for (const CXXBaseSpecifier &B : D->bases()) { - assert(B.getType()->getAsCXXRecordDecl() && "invalid base?"); - if (B.isVirtual()) - SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) - << diag::TraitNotSatisfiedReason::VBase << B.getType() - << B.getSourceRange(); - if (!SemaRef.IsCXXTriviallyRelocatableType(B.getType())) - SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) - << diag::TraitNotSatisfiedReason::NTRBase << B.getType() - << B.getSourceRange(); - } - for (const FieldDecl *Field : D->fields()) { - if (!Field->getType()->isReferenceType() && - !SemaRef.IsCXXTriviallyRelocatableType(Field->getType())) - SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) - << diag::TraitNotSatisfiedReason::NTRField << Field - << Field->getType() << Field->getSourceRange(); - } - if (D->hasDeletedDestructor()) - SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) - << diag::TraitNotSatisfiedReason::DeletedDtr << /*Deleted*/ 0 - << D->getDestructor()->getSourceRange(); - - if (D->hasAttr<TriviallyRelocatableAttr>()) - return; - +static void DiagnoseNonDefaultMovable(Sema &SemaRef, SourceLocation Loc, + const CXXRecordDecl *D) { if (D->isUnion()) { auto DiagSPM = [&](CXXSpecialMemberKind K, bool Has) { if (Has) @@ -2074,6 +2051,37 @@ static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, << Dtr->getSourceRange(); } +static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, + SourceLocation Loc, + const CXXRecordDecl *D) { + for (const CXXBaseSpecifier &B : D->bases()) { + assert(B.getType()->getAsCXXRecordDecl() && "invalid base?"); + if (B.isVirtual()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VBase << B.getType() + << B.getSourceRange(); + if (!SemaRef.IsCXXTriviallyRelocatableType(B.getType())) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NTRBase << B.getType() + << B.getSourceRange(); + } + for (const FieldDecl *Field : D->fields()) { + if (!Field->getType()->isReferenceType() && + !SemaRef.IsCXXTriviallyRelocatableType(Field->getType())) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NTRField << Field + << Field->getType() << Field->getSourceRange(); + } + if (D->hasDeletedDestructor()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::DeletedDtr << /*Deleted*/ 0 + << D->getDestructor()->getSourceRange(); + + if (D->hasAttr<TriviallyRelocatableAttr>()) + return; + DiagnoseNonDefaultMovable(SemaRef, Loc, D); +} + static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, SourceLocation Loc, QualType T) { @@ -2102,6 +2110,92 @@ static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; } +static void DiagnoseNonReplaceableReason(Sema &SemaRef, SourceLocation Loc, + const CXXRecordDecl *D) { + for (const CXXBaseSpecifier &B : D->bases()) { + assert(B.getType()->getAsCXXRecordDecl() && "invalid base?"); + if (!SemaRef.IsCXXReplaceableType(B.getType())) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NonReplaceableBase << B.getType() + << B.getSourceRange(); + } + for (const FieldDecl *Field : D->fields()) { + if (!SemaRef.IsCXXReplaceableType(Field->getType())) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NonReplaceableField << Field + << Field->getType() << Field->getSourceRange(); + } + if (D->hasDeletedDestructor()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::DeletedDtr << /*Deleted*/ 0 + << D->getDestructor()->getSourceRange(); + + if (!D->hasSimpleMoveConstructor() && !D->hasSimpleCopyConstructor()) { + const auto *Decl = cast<CXXConstructorDecl>( + LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false)); + if (Decl && Decl->isDeleted()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::DeletedCtr + << Decl->isMoveConstructor() << Decl->getSourceRange(); + } + if (!D->hasSimpleMoveAssignment() && !D->hasSimpleCopyAssignment()) { + CXXMethodDecl *Decl = + LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true); + if (Decl && Decl->isDeleted()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::DeletedAssign + << Decl->isMoveAssignmentOperator() << Decl->getSourceRange(); + } + + if (D->hasAttr<ReplaceableAttr>()) + return; + DiagnoseNonDefaultMovable(SemaRef, Loc, D); +} + +static void DiagnoseNonReplaceableReason(Sema &SemaRef, SourceLocation Loc, + QualType T) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait) + << T << diag::TraitName::Replaceable; + + if (T->isVariablyModifiedType()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VLA; + + if (T->isReferenceType()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::Ref; + T = T.getNonReferenceType(); + + if (T.isConstQualified()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::Const; + + if (T.isVolatileQualified()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::Volatile; + + bool IsArray = T->isArrayType(); + T = SemaRef.getASTContext().getBaseElementType(T.getUnqualifiedType()); + + if (T->isScalarType()) + return; + + const CXXRecordDecl *D = T->getAsCXXRecordDecl(); + if (!D) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NotScalarOrClass << IsArray; + return; + } + + if (D->isInvalidDecl()) + return; + + if (D->hasDefinition()) + DiagnoseNonReplaceableReason(SemaRef, Loc, D); + + SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; +} + static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef, SourceLocation Loc, const CXXRecordDecl *D) { @@ -2192,6 +2286,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) { case UTT_IsCppTriviallyRelocatable: DiagnoseNonTriviallyRelocatableReason(*this, E->getBeginLoc(), Args[0]); break; + case UTT_IsReplaceable: + DiagnoseNonReplaceableReason(*this, E->getBeginLoc(), Args[0]); + break; case UTT_IsTriviallyCopyable: DiagnoseNonTriviallyCopyableReason(*this, E->getBeginLoc(), Args[0]); break; diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index 062becd4f5776..aff172e0bc70a 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -333,6 +333,31 @@ struct CopyAssign1 { CopyAssign1 & operator=(CopyAssign1 const &) = default; }; +struct UserDeleted1 { + UserDeleted1(const UserDeleted1&) = delete; +}; +static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeleted1)); +static_assert(!__builtin_is_replaceable(UserDeleted1)); + +struct UserDeleted2 { + UserDeleted2(UserDeleted2&&) = delete; +}; +static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeleted2)); +static_assert(!__builtin_is_replaceable(UserDeleted2)); + + +struct UserDeleted3 { + UserDeleted3 operator=(UserDeleted3); +}; +static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeleted3)); +static_assert(!__builtin_is_replaceable(UserDeleted3)); + +struct UserDeleted4 { + UserDeleted4 operator=(UserDeleted4&&); +}; +static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeleted4)); +static_assert(!__builtin_is_replaceable(UserDeleted4)); + } diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp index 498e202e26265..329b611110c1d 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp @@ -171,3 +171,24 @@ void test() { // expected-note@#concept4 {{because it is a reference type}} } } + + +namespace std { +template <typename T> +struct is_replaceable { + static constexpr bool value = __builtin_is_replaceable(T); +}; + +template <typename T> +constexpr bool is_replaceable_v = __builtin_is_replaceable(T); + +} + +static_assert(std::is_replaceable<int&>::value); +// expected-error@-1 {{static assertion failed due to requirement 'std::is_replaceable<int &>::value'}} \ +// expected-note@-1 {{'int &' is not replaceable}} \ +// expected-note@-1 {{because it is a reference type}} +static_assert(std::is_replaceable_v<int&>); +// expected-error@-1 {{static assertion failed due to requirement 'std::is_replaceable_v<int &>'}} \ +// expected-note@-1 {{'int &' is not replaceable}} \ +// expected-note@-1 {{because it is a reference type}} diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp index 0256569fcca5f..0fa3b86a0d214 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -142,6 +142,137 @@ static_assert(__builtin_is_cpp_trivially_relocatable(U2)); // expected-note@-1 {{because it has a deleted destructor}} \ // expected-note@-1 {{because it has a non-trivially-relocatable member 'b' of type 'B'}} \ // expected-note@#tr-U2 {{'U2' defined here}} +} + +namespace replaceable { + +extern int vla_size; +static_assert(__builtin_is_replaceable(int[vla_size])); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(int[vla_size])'}} \ +// expected-note@-1 {{'int[vla_size]' is not replaceable}} \ +// expected-note@-1 {{because it is a variably-modified type}} + +struct S; // expected-note {{forward declaration of 'replaceable::S'}} +static_assert(__builtin_is_replaceable(S)); +// expected-error@-1 {{incomplete type 'S' used in type trait expression}} + +static_assert(__builtin_is_replaceable(const volatile int)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(const volatile int)}} \ +// expected-note@-1 {{'const volatile int' is not replaceable}} \ +// expected-note@-1 {{because it is const}} \ +// expected-note@-1 {{because it is volatile}} + + +static_assert(__builtin_is_replaceable(void())); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(void ())}} \ +// expected-note@-1 {{'void ()' is not replaceable}} \ +// expected-note@-1 {{because it not a scalar or class type}} + +struct B { + virtual ~B(); +}; +struct S : virtual B { // #replaceable-S + S(); + int & a; + const int ci; + B & b; + B c; + ~S(); +}; +static_assert(__builtin_is_replaceable(S)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S)'}} \ +// expected-note@-1 {{'S' is not replaceable}} \ +// expected-note@-1 {{because it has a non-replaceable base 'B'}} \ +// expected-note@-1 {{because it has a non-replaceable member 'a' of type 'int &'}} \ +// expected-note@-1 {{because it has a non-replaceable member 'ci' of type 'const int'}} \ +// expected-note@-1 {{because it has a non-replaceable member 'b' of type 'B &'}} \ +// expected-note@-1 {{because it has a non-replaceable member 'c' of type 'B'}} \ +// expected-note@-1 {{because it has a user-provided destructor}} \ +// expected-note@-1 {{because it has a deleted copy assignment operator}} +// expected-note@#replaceable-S {{'S' defined here}} + +struct S2 { // #replaceable-S2 + S2(S2&&); + S2& operator=(const S2&); +}; +static_assert(__builtin_is_replaceable(S2)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S2)'}} \ +// expected-note@-1 {{'S2' is not replaceable}} \ +// expected-note@-1 {{because it has a user provided move constructor}} \ +// expected-note@-1 {{because it has a user provided copy assignment operator}} \ +// expected-note@#replaceable-S2 {{'S2' defined here}} + + +struct S3 { // #replaceable-S3 + ~S3() = delete; +}; +static_assert(__builtin_is_replaceable(S3)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S3)'}} \ +// expected-note@-1 {{'S3' is not replaceable}} \ +// expected-note@-1 {{because it has a deleted destructor}} \ +// expected-note@#replaceable-S3 {{'S3' defined here}} + + +union U { // #replaceable-U + U(const U&); + U(U&&); + U& operator=(const U&); + U& operator=(U&&); +}; +static_assert(__builtin_is_replaceable(U)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::U)'}} \ +// expected-note@-1 {{'U' is not replaceable}} \ +// expected-note@-1 {{because it is a union with a user-declared copy constructor}} \ +// expected-note@-1 {{because it is a union with a user-declared copy assignment operator}} \ +// expected-note@-1 {{because it is a union with a user-declared move constructor}} \ +// expected-note@-1 {{because it is a union with a user-declared move assignment operator}} +// expected-note@#replaceable-U {{'U' defined here}} +struct S4 replaceable_if_eligible { // #replaceable-S4 + ~S4(); + B b; +}; +static_assert(__builtin_is_replaceable(S4)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S4)'}} \ +// expected-note@-1 {{'S4' is not replaceable}} \ +// expected-note@-1 {{because it has a non-replaceable member 'b' of type 'B'}} \ +// expected-note@#replaceable-S4 {{'S4' defined here}} + +union U2 replaceable_if_eligible { // #replaceable-U2 + U2(const U2&); + U2(U2&&); + B b; +}; +static_assert(__builtin_is_replaceable(U2)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::U2)'}} \ +// expected-note@-1 {{'U2' is not replaceable}} \ +// expected-note@-1 {{because it has a deleted destructor}} \ +// expected-note@-1 {{because it has a non-replaceable member 'b' of type 'B'}} \ +// expected-note@-1 {{because it has a deleted copy assignment operator}} \ +// expected-note@#replaceable-U2 {{'U2' defined here}} + +struct UD1 { // #replaceable-UD1 + UD1(const UD1&) = delete; + UD1 & operator=(const UD1&) = delete; + +}; +static_assert(__builtin_is_replaceable(UD1)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::UD1)'}} \ +// expected-note@-1 {{'UD1' is not replaceable}} \ +// expected-note@-1 {{because it has a deleted copy constructor}} \ +// expected-note@-1 {{because it has a deleted copy assignment operator}} \ +// expected-note@#replaceable-UD1 {{'UD1' defined here}} + + +struct UD2 { // #replaceable-UD2 + UD2(UD2&&) = delete; + UD2 & operator=(UD2&&) = delete; +}; +static_assert(__builtin_is_replaceable(UD2)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::UD2)'}} \ +// expected-note@-1 {{'UD2' is not replaceable}} \ +// expected-note@-1 {{because it has a deleted move constructor}} \ +// expected-note@-1 {{because it has a deleted move assignment operator}} \ +// expected-note@#replaceable-UD2 {{'UD2' defined here}} } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits