Skip to content

[Clang] Explain why a type is not replaceable. #143265

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 1 commit into from
Jun 9, 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
20 changes: 16 additions & 4 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't see a test for the an array of objects case

"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">;
Expand Down
159 changes: 128 additions & 31 deletions clang/lib/Sema/SemaTypeTraits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand All @@ -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]
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
25 changes: 25 additions & 0 deletions clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));

}


Expand Down
21 changes: 21 additions & 0 deletions clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Loading
Loading