https://github.com/cor3ntin created https://github.com/llvm/llvm-project/pull/141238
`static_assert(std::is_xx_v<MyType>);` is a common pattern to check that a type meets a requirement. This patch produces diagnostics notes when such assertion fails. The first type trait for which we provide detailed explanation is std::is_trivially_relocatable. We employ the same mechanisn when a type trait appears an an unsatisfied atomic constraint. I plan to also support `std::is_trivially_replaceable` in a follow up PR, and hopefully, over time we can support more type traits. >From 5ffeedb51facf82dc446f852ead90ba040d4d659 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@gmail.com> Date: Wed, 21 May 2025 23:10:36 +0200 Subject: [PATCH] [Clang] Explain why a type trait evaluated to false. `static_assert(std::is_xx_v<MyType>);` is a common pattern to check that a type meets a requirement. This patch produces diagnostics notes when such assertion fails. The first type trait for which we provide detailed explanation is std::is_trivially_relocatable. We employ the same mechanisn when a type trait appears an an unsatisfied atomic constraint. I plan to also support `std::is_trivially_replaceable` in a follow up PR, and hopefully, over time we can support more type traits. --- .../clang/Basic/DiagnosticSemaKinds.td | 23 + clang/include/clang/Sema/Sema.h | 5 + clang/lib/Sema/CMakeLists.txt | 1 + clang/lib/Sema/SemaConcept.cpp | 1 + clang/lib/Sema/SemaDeclCXX.cpp | 275 +-------- clang/lib/Sema/SemaExprCXX.cpp | 65 +-- clang/lib/Sema/SemaTypeTraits.cpp | 539 ++++++++++++++++++ .../type-traits-unsatisfied-diags-std.cpp | 101 ++++ .../SemaCXX/type-traits-unsatisfied-diags.cpp | 146 +++++ .../test/SemaObjCXX/objc-weak-type-traits.mm | 7 +- 10 files changed, 826 insertions(+), 337 deletions(-) create mode 100644 clang/lib/Sema/SemaTypeTraits.cpp create mode 100644 clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp create mode 100644 clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 2835e3a9d9960..f2fcfe5d351f4 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1762,6 +1762,29 @@ def err_user_defined_msg_constexpr : Error< "%sub{subst_user_defined_msg}0 must be produced by a " "constant expression">; +// Type traits explanations +def note_unsatisfied_trait : Note<"%0 is not %enum_select<TraitName>{" + "%TriviallyRelocatable{trivially relocatable}" + "}1">; + +def note_unsatisfied_trait_reason + : Note<"because it " + "%enum_select<TraitNotSatisfiedReason>{" + "%Ref{is a reference type}|" + "%HasArcLifetime{has an ARC lifetime qualifier}|" + "%VLA{is a variably-modified type}|" + "%VBase{has a virtual base %1}|" + "%NRBase{has a non-trivially-relocatable base %1}|" + "%NRField{has a non-trivially-relocatable member %1 of type %2}|" + "%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|" + "%UserProvidedCtr{has a user provided %select{copy|move}1 " + "constructor}|" + "%UserProvidedAssign{has a user provided %select{copy|move}1 " + "assignment operator}|" + "%UnionWithUserDeclaredSMF{is a union with a user-declared " + "%sub{select_special_member_kind}1}" + "}0">; + def warn_consteval_if_always_true : Warning< "consteval if is always true in an %select{unevaluated|immediate}0 context">, InGroup<DiagGroup<"redundant-consteval-if">>; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 1091a7f504b57..bbc5c181c6a10 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5910,6 +5910,11 @@ class Sema final : public SemaBase { /// with expression \E void DiagnoseStaticAssertDetails(const Expr *E); + /// If E represents a built-in type trait, or a known standard type trait, + /// try to print more information about why the type type-trait failed. + /// This assumes we already evaluated the expression to a false boolean value. + void DiagnoseTypeTraitDetails(const Expr *E); + /// Handle a friend type declaration. This works in tandem with /// ActOnTag. /// diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index 4b87004e4b8ea..51e0ee10b080b 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -96,6 +96,7 @@ add_clang_library(clangSema SemaTemplateInstantiateDecl.cpp SemaTemplateVariadic.cpp SemaType.cpp + SemaTypeTraits.cpp SemaWasm.cpp SemaX86.cpp TypeLocBuilder.cpp diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp index 7da8e696c90bd..c6a54dc141ded 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1320,6 +1320,7 @@ static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S, S.Diag(SubstExpr->getSourceRange().getBegin(), diag::note_atomic_constraint_evaluated_to_false) << (int)First << SubstExpr; + S.DiagnoseTypeTraitDetails(SubstExpr); } template <typename SubstitutionDiagnostic> diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index fe92191b6a687..770ac9839eb98 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7368,279 +7368,6 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { CheckMismatchedTypeAwareAllocators(OO_Array_New, OO_Array_Delete); } -static CXXMethodDecl *LookupSpecialMemberFromXValue(Sema &SemaRef, - const CXXRecordDecl *RD, - bool Assign) { - RD = RD->getDefinition(); - SourceLocation LookupLoc = RD->getLocation(); - - CanQualType CanTy = SemaRef.getASTContext().getCanonicalType( - SemaRef.getASTContext().getTagDeclType(RD)); - DeclarationName Name; - Expr *Arg = nullptr; - unsigned NumArgs; - - QualType ArgType = CanTy; - ExprValueKind VK = clang::VK_XValue; - - if (Assign) - Name = - SemaRef.getASTContext().DeclarationNames.getCXXOperatorName(OO_Equal); - else - Name = - SemaRef.getASTContext().DeclarationNames.getCXXConstructorName(CanTy); - - OpaqueValueExpr FakeArg(LookupLoc, ArgType, VK); - NumArgs = 1; - Arg = &FakeArg; - - // Create the object argument - QualType ThisTy = CanTy; - Expr::Classification Classification = - OpaqueValueExpr(LookupLoc, ThisTy, VK_LValue) - .Classify(SemaRef.getASTContext()); - - // Now we perform lookup on the name we computed earlier and do overload - // resolution. Lookup is only performed directly into the class since there - // will always be a (possibly implicit) declaration to shadow any others. - OverloadCandidateSet OCS(LookupLoc, OverloadCandidateSet::CSK_Normal); - DeclContext::lookup_result R = RD->lookup(Name); - - if (R.empty()) - return nullptr; - - // Copy the candidates as our processing of them may load new declarations - // from an external source and invalidate lookup_result. - SmallVector<NamedDecl *, 8> Candidates(R.begin(), R.end()); - - for (NamedDecl *CandDecl : Candidates) { - if (CandDecl->isInvalidDecl()) - continue; - - DeclAccessPair Cand = DeclAccessPair::make(CandDecl, clang::AS_none); - auto CtorInfo = getConstructorInfo(Cand); - if (CXXMethodDecl *M = dyn_cast<CXXMethodDecl>(Cand->getUnderlyingDecl())) { - if (Assign) - SemaRef.AddMethodCandidate(M, Cand, const_cast<CXXRecordDecl *>(RD), - ThisTy, Classification, - llvm::ArrayRef(&Arg, NumArgs), OCS, true); - else { - assert(CtorInfo); - SemaRef.AddOverloadCandidate(CtorInfo.Constructor, CtorInfo.FoundDecl, - llvm::ArrayRef(&Arg, NumArgs), OCS, - /*SuppressUserConversions*/ true); - } - } else if (FunctionTemplateDecl *Tmpl = - dyn_cast<FunctionTemplateDecl>(Cand->getUnderlyingDecl())) { - if (Assign) - SemaRef.AddMethodTemplateCandidate( - Tmpl, Cand, const_cast<CXXRecordDecl *>(RD), nullptr, ThisTy, - Classification, llvm::ArrayRef(&Arg, NumArgs), OCS, true); - else { - assert(CtorInfo); - SemaRef.AddTemplateOverloadCandidate( - CtorInfo.ConstructorTmpl, CtorInfo.FoundDecl, nullptr, - llvm::ArrayRef(&Arg, NumArgs), OCS, true); - } - } - } - - OverloadCandidateSet::iterator Best; - switch (OCS.BestViableFunction(SemaRef, LookupLoc, Best)) { - case OR_Success: - return cast<CXXMethodDecl>(Best->Function); - default: - return nullptr; - } -} - -static bool hasSuitableConstructorForRelocation(Sema &SemaRef, - const CXXRecordDecl *D, - bool AllowUserDefined) { - assert(D->hasDefinition() && !D->isInvalidDecl()); - - if (D->hasSimpleMoveConstructor() || D->hasSimpleCopyConstructor()) - return true; - - CXXMethodDecl *Decl = - LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false); - return Decl && Decl->isUserProvided() == AllowUserDefined; -} - -static bool hasSuitableMoveAssignmentOperatorForRelocation( - Sema &SemaRef, const CXXRecordDecl *D, bool AllowUserDefined) { - assert(D->hasDefinition() && !D->isInvalidDecl()); - - if (D->hasSimpleMoveAssignment() || D->hasSimpleCopyAssignment()) - return true; - - CXXMethodDecl *Decl = - LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true); - if (!Decl) - return false; - - return Decl && Decl->isUserProvided() == AllowUserDefined; -} - -// [C++26][class.prop] -// A class C is default-movable if -// - overload resolution for direct-initializing an object of type C -// from an xvalue of type C selects a constructor that is a direct member of C -// and is neither user-provided nor deleted, -// - overload resolution for assigning to an lvalue of type C from an xvalue of -// type C selects an assignment operator function that is a direct member of C -// and is neither user-provided nor deleted, and C has a destructor that is -// neither user-provided nor deleted. -static bool IsDefaultMovable(Sema &SemaRef, const CXXRecordDecl *D) { - if (!hasSuitableConstructorForRelocation(SemaRef, D, - /*AllowUserDefined=*/false)) - return false; - - if (!hasSuitableMoveAssignmentOperatorForRelocation( - SemaRef, D, /*AllowUserDefined=*/false)) - return false; - - CXXDestructorDecl *Dtr = D->getDestructor(); - - if (!Dtr) - return true; - - if (Dtr->isUserProvided() && (!Dtr->isDefaulted() || Dtr->isDeleted())) - return false; - - return !Dtr->isDeleted(); -} - -// [C++26][class.prop] -// A class is eligible for trivial relocation unless it... -static bool IsEligibleForTrivialRelocation(Sema &SemaRef, - const CXXRecordDecl *D) { - - for (const CXXBaseSpecifier &B : D->bases()) { - const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); - if (!BaseDecl) - continue; - // ... has any virtual base classes - // ... has a base class that is not a trivially relocatable class - if (B.isVirtual() || (!BaseDecl->isDependentType() && - !SemaRef.IsCXXTriviallyRelocatableType(B.getType()))) - return false; - } - - for (const FieldDecl *Field : D->fields()) { - if (Field->getType()->isDependentType()) - continue; - if (Field->getType()->isReferenceType()) - continue; - // ... has a non-static data member of an object type that is not - // of a trivially relocatable type - if (!SemaRef.IsCXXTriviallyRelocatableType(Field->getType())) - return false; - } - return !D->hasDeletedDestructor(); -} - -// [C++26][class.prop] -// A class C is eligible for replacement unless -static bool IsEligibleForReplacement(Sema &SemaRef, const CXXRecordDecl *D) { - - for (const CXXBaseSpecifier &B : D->bases()) { - const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); - if (!BaseDecl) - continue; - // it has a base class that is not a replaceable class - if (!BaseDecl->isDependentType() && - !SemaRef.IsCXXReplaceableType(B.getType())) - return false; - } - - for (const FieldDecl *Field : D->fields()) { - if (Field->getType()->isDependentType()) - continue; - - // it has a non-static data member that is not of a replaceable type, - if (!SemaRef.IsCXXReplaceableType(Field->getType())) - return false; - } - return !D->hasDeletedDestructor(); -} - -ASTContext::CXXRecordDeclRelocationInfo -Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { - ASTContext::CXXRecordDeclRelocationInfo Info{false, false}; - - if (!getLangOpts().CPlusPlus || D->isInvalidDecl()) - return Info; - - assert(D->hasDefinition()); - - // This is part of "eligible for replacement", however we defer it - // to avoid extraneous computations. - auto HasSuitableSMP = [&] { - return hasSuitableConstructorForRelocation(*this, D, - /*AllowUserDefined=*/true) && - hasSuitableMoveAssignmentOperatorForRelocation( - *this, D, /*AllowUserDefined=*/true); - }; - - auto IsUnion = [&, Is = std::optional<bool>{}]() mutable { - if (!Is.has_value()) - Is = D->isUnion() && !D->hasUserDeclaredCopyConstructor() && - !D->hasUserDeclaredCopyAssignment() && - !D->hasUserDeclaredMoveOperation() && - !D->hasUserDeclaredDestructor(); - return *Is; - }; - - auto IsDefaultMovable = [&, Is = std::optional<bool>{}]() mutable { - if (!Is.has_value()) - Is = ::IsDefaultMovable(*this, D); - return *Is; - }; - - Info.IsRelocatable = [&] { - if (D->isDependentType()) - return false; - - // if it is eligible for trivial relocation - if (!IsEligibleForTrivialRelocation(*this, D)) - return false; - - // has the trivially_relocatable_if_eligible class-property-specifier, - if (D->hasAttr<TriviallyRelocatableAttr>()) - return true; - - // is a union with no user-declared special member functions, or - if (IsUnion()) - return true; - - // is default-movable. - return IsDefaultMovable(); - }(); - - Info.IsReplaceable = [&] { - if (D->isDependentType()) - return false; - - // A class C is a replaceable class if it is eligible for replacement - if (!IsEligibleForReplacement(*this, D)) - return false; - - // has the replaceable_if_eligible class-property-specifier - if (D->hasAttr<ReplaceableAttr>()) - return HasSuitableSMP(); - - // is a union with no user-declared special member functions, or - if (IsUnion()) - return HasSuitableSMP(); - - // is default-movable. - return IsDefaultMovable(); - }(); - - return Info; -} - /// Look up the special member function that would be called by a special /// member function for a subobject of class type. /// @@ -17967,6 +17694,8 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) { << DiagSide[0].ValueString << Op->getOpcodeStr() << DiagSide[1].ValueString << Op->getSourceRange(); } + } else { + DiagnoseTypeTraitDetails(E); } } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index b53877c40668d..3640483fd3706 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5590,69 +5590,8 @@ static bool isTriviallyEqualityComparableType(Sema &S, QualType Type, SourceLoca CanonicalType, /*CheckIfTriviallyCopyable=*/false); } -static bool IsCXXTriviallyRelocatableType(Sema &S, const CXXRecordDecl *RD) { - if (std::optional<ASTContext::CXXRecordDeclRelocationInfo> Info = - S.getASTContext().getRelocationInfoForCXXRecord(RD)) - return Info->IsRelocatable; - ASTContext::CXXRecordDeclRelocationInfo Info = - S.CheckCXX2CRelocatableAndReplaceable(RD); - S.getASTContext().setRelocationInfoForCXXRecord(RD, Info); - return Info.IsRelocatable; -} - -bool Sema::IsCXXTriviallyRelocatableType(QualType Type) { - - QualType BaseElementType = getASTContext().getBaseElementType(Type); - - if (Type->isVariableArrayType()) - return false; - - if (BaseElementType.hasNonTrivialObjCLifetime()) - return false; - - if (BaseElementType.hasAddressDiscriminatedPointerAuth()) - return false; - - if (BaseElementType->isIncompleteType()) - return false; - - if (BaseElementType->isScalarType() || BaseElementType->isVectorType()) - return true; - - if (const auto *RD = BaseElementType->getAsCXXRecordDecl()) - return ::IsCXXTriviallyRelocatableType(*this, RD); - - return false; -} - -static bool IsCXXReplaceableType(Sema &S, const CXXRecordDecl *RD) { - if (std::optional<ASTContext::CXXRecordDeclRelocationInfo> Info = - S.getASTContext().getRelocationInfoForCXXRecord(RD)) - return Info->IsReplaceable; - ASTContext::CXXRecordDeclRelocationInfo Info = - S.CheckCXX2CRelocatableAndReplaceable(RD); - S.getASTContext().setRelocationInfoForCXXRecord(RD, Info); - return Info.IsReplaceable; -} - -bool Sema::IsCXXReplaceableType(QualType Type) { - if (Type.isConstQualified() || Type.isVolatileQualified()) - return false; - - if (Type->isVariableArrayType()) - return false; - - QualType BaseElementType = - getASTContext().getBaseElementType(Type.getUnqualifiedType()); - if (BaseElementType->isIncompleteType()) - return false; - if (BaseElementType->isScalarType()) - return true; - if (const auto *RD = BaseElementType->getAsCXXRecordDecl()) - return ::IsCXXReplaceableType(*this, RD); - return false; -} - +// FIXME : Move the type traits logic to SemaTypeTraits.h +extern bool IsCXXTriviallyRelocatableType(Sema &S, const CXXRecordDecl *RD); static bool IsTriviallyRelocatableType(Sema &SemaRef, QualType T) { QualType BaseElementType = SemaRef.getASTContext().getBaseElementType(T); diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp new file mode 100644 index 0000000000000..d16989e8dfdbe --- /dev/null +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -0,0 +1,539 @@ +//===----- SemaTypeTraits.cpp - Semantic Analysis for C++ Type Traits -----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements semantic analysis for C++ type traits. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/DeclCXX.h" +#include "clang/Basic/DiagnosticSema.h" +#include "clang/Sema/Overload.h" +#include "clang/Sema/Sema.h" + +using namespace clang; + +static CXXMethodDecl *LookupSpecialMemberFromXValue(Sema &SemaRef, + const CXXRecordDecl *RD, + bool Assign) { + RD = RD->getDefinition(); + SourceLocation LookupLoc = RD->getLocation(); + + CanQualType CanTy = SemaRef.getASTContext().getCanonicalType( + SemaRef.getASTContext().getTagDeclType(RD)); + DeclarationName Name; + Expr *Arg = nullptr; + unsigned NumArgs; + + QualType ArgType = CanTy; + ExprValueKind VK = clang::VK_XValue; + + if (Assign) + Name = + SemaRef.getASTContext().DeclarationNames.getCXXOperatorName(OO_Equal); + else + Name = + SemaRef.getASTContext().DeclarationNames.getCXXConstructorName(CanTy); + + OpaqueValueExpr FakeArg(LookupLoc, ArgType, VK); + NumArgs = 1; + Arg = &FakeArg; + + // Create the object argument + QualType ThisTy = CanTy; + Expr::Classification Classification = + OpaqueValueExpr(LookupLoc, ThisTy, VK_LValue) + .Classify(SemaRef.getASTContext()); + + // Now we perform lookup on the name we computed earlier and do overload + // resolution. Lookup is only performed directly into the class since there + // will always be a (possibly implicit) declaration to shadow any others. + OverloadCandidateSet OCS(LookupLoc, OverloadCandidateSet::CSK_Normal); + DeclContext::lookup_result R = RD->lookup(Name); + + if (R.empty()) + return nullptr; + + // Copy the candidates as our processing of them may load new declarations + // from an external source and invalidate lookup_result. + SmallVector<NamedDecl *, 8> Candidates(R.begin(), R.end()); + + for (NamedDecl *CandDecl : Candidates) { + if (CandDecl->isInvalidDecl()) + continue; + + DeclAccessPair Cand = DeclAccessPair::make(CandDecl, clang::AS_none); + auto CtorInfo = getConstructorInfo(Cand); + if (CXXMethodDecl *M = dyn_cast<CXXMethodDecl>(Cand->getUnderlyingDecl())) { + if (Assign) + SemaRef.AddMethodCandidate(M, Cand, const_cast<CXXRecordDecl *>(RD), + ThisTy, Classification, + llvm::ArrayRef(&Arg, NumArgs), OCS, true); + else { + assert(CtorInfo); + SemaRef.AddOverloadCandidate(CtorInfo.Constructor, CtorInfo.FoundDecl, + llvm::ArrayRef(&Arg, NumArgs), OCS, + /*SuppressUserConversions*/ true); + } + } else if (FunctionTemplateDecl *Tmpl = + dyn_cast<FunctionTemplateDecl>(Cand->getUnderlyingDecl())) { + if (Assign) + SemaRef.AddMethodTemplateCandidate( + Tmpl, Cand, const_cast<CXXRecordDecl *>(RD), nullptr, ThisTy, + Classification, llvm::ArrayRef(&Arg, NumArgs), OCS, true); + else { + assert(CtorInfo); + SemaRef.AddTemplateOverloadCandidate( + CtorInfo.ConstructorTmpl, CtorInfo.FoundDecl, nullptr, + llvm::ArrayRef(&Arg, NumArgs), OCS, true); + } + } + } + + OverloadCandidateSet::iterator Best; + switch (OCS.BestViableFunction(SemaRef, LookupLoc, Best)) { + case OR_Success: + return cast<CXXMethodDecl>(Best->Function); + default: + return nullptr; + } +} + +static bool hasSuitableConstructorForRelocation(Sema &SemaRef, + const CXXRecordDecl *D, + bool AllowUserDefined) { + assert(D->hasDefinition() && !D->isInvalidDecl()); + + if (D->hasSimpleMoveConstructor() || D->hasSimpleCopyConstructor()) + return true; + + CXXMethodDecl *Decl = + LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false); + return Decl && Decl->isUserProvided() == AllowUserDefined; +} + +static bool hasSuitableMoveAssignmentOperatorForRelocation( + Sema &SemaRef, const CXXRecordDecl *D, bool AllowUserDefined) { + assert(D->hasDefinition() && !D->isInvalidDecl()); + + if (D->hasSimpleMoveAssignment() || D->hasSimpleCopyAssignment()) + return true; + + CXXMethodDecl *Decl = + LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true); + if (!Decl) + return false; + + return Decl && Decl->isUserProvided() == AllowUserDefined; +} + +// [C++26][class.prop] +// A class C is default-movable if +// - overload resolution for direct-initializing an object of type C +// from an xvalue of type C selects a constructor that is a direct member of C +// and is neither user-provided nor deleted, +// - overload resolution for assigning to an lvalue of type C from an xvalue of +// type C selects an assignment operator function that is a direct member of C +// and is neither user-provided nor deleted, and C has a destructor that is +// neither user-provided nor deleted. +static bool IsDefaultMovable(Sema &SemaRef, const CXXRecordDecl *D) { + if (!hasSuitableConstructorForRelocation(SemaRef, D, + /*AllowUserDefined=*/false)) + return false; + + if (!hasSuitableMoveAssignmentOperatorForRelocation( + SemaRef, D, /*AllowUserDefined=*/false)) + return false; + + CXXDestructorDecl *Dtr = D->getDestructor(); + + if (!Dtr) + return true; + + if (Dtr->isUserProvided() && (!Dtr->isDefaulted() || Dtr->isDeleted())) + return false; + + return !Dtr->isDeleted(); +} + +// [C++26][class.prop] +// A class is eligible for trivial relocation unless it... +static bool IsEligibleForTrivialRelocation(Sema &SemaRef, + const CXXRecordDecl *D) { + + for (const CXXBaseSpecifier &B : D->bases()) { + const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); + if (!BaseDecl) + continue; + // ... has any virtual base classes + // ... has a base class that is not a trivially relocatable class + if (B.isVirtual() || (!BaseDecl->isDependentType() && + !SemaRef.IsCXXTriviallyRelocatableType(B.getType()))) + return false; + } + + for (const FieldDecl *Field : D->fields()) { + if (Field->getType()->isDependentType()) + continue; + if (Field->getType()->isReferenceType()) + continue; + // ... has a non-static data member of an object type that is not + // of a trivially relocatable type + if (!SemaRef.IsCXXTriviallyRelocatableType(Field->getType())) + return false; + } + return !D->hasDeletedDestructor(); +} + +// [C++26][class.prop] +// A class C is eligible for replacement unless +static bool IsEligibleForReplacement(Sema &SemaRef, const CXXRecordDecl *D) { + + for (const CXXBaseSpecifier &B : D->bases()) { + const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); + if (!BaseDecl) + continue; + // it has a base class that is not a replaceable class + if (!BaseDecl->isDependentType() && + !SemaRef.IsCXXReplaceableType(B.getType())) + return false; + } + + for (const FieldDecl *Field : D->fields()) { + if (Field->getType()->isDependentType()) + continue; + + // it has a non-static data member that is not of a replaceable type, + if (!SemaRef.IsCXXReplaceableType(Field->getType())) + return false; + } + return !D->hasDeletedDestructor(); +} + +ASTContext::CXXRecordDeclRelocationInfo +Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { + ASTContext::CXXRecordDeclRelocationInfo Info{false, false}; + + if (!getLangOpts().CPlusPlus || D->isInvalidDecl()) + return Info; + + assert(D->hasDefinition()); + + // This is part of "eligible for replacement", however we defer it + // to avoid extraneous computations. + auto HasSuitableSMP = [&] { + return hasSuitableConstructorForRelocation(*this, D, + /*AllowUserDefined=*/true) && + hasSuitableMoveAssignmentOperatorForRelocation( + *this, D, /*AllowUserDefined=*/true); + }; + + auto IsUnion = [&, Is = std::optional<bool>{}]() mutable { + if (!Is.has_value()) + Is = D->isUnion() && !D->hasUserDeclaredCopyConstructor() && + !D->hasUserDeclaredCopyAssignment() && + !D->hasUserDeclaredMoveOperation() && + !D->hasUserDeclaredDestructor(); + return *Is; + }; + + auto IsDefaultMovable = [&, Is = std::optional<bool>{}]() mutable { + if (!Is.has_value()) + Is = ::IsDefaultMovable(*this, D); + return *Is; + }; + + Info.IsRelocatable = [&] { + if (D->isDependentType()) + return false; + + // if it is eligible for trivial relocation + if (!IsEligibleForTrivialRelocation(*this, D)) + return false; + + // has the trivially_relocatable_if_eligible class-property-specifier, + if (D->hasAttr<TriviallyRelocatableAttr>()) + return true; + + // is a union with no user-declared special member functions, or + if (IsUnion()) + return true; + + // is default-movable. + return IsDefaultMovable(); + }(); + + Info.IsReplaceable = [&] { + if (D->isDependentType()) + return false; + + // A class C is a replaceable class if it is eligible for replacement + if (!IsEligibleForReplacement(*this, D)) + return false; + + // has the replaceable_if_eligible class-property-specifier + if (D->hasAttr<ReplaceableAttr>()) + return HasSuitableSMP(); + + // is a union with no user-declared special member functions, or + if (IsUnion()) + return HasSuitableSMP(); + + // is default-movable. + return IsDefaultMovable(); + }(); + + return Info; +} + +bool IsCXXTriviallyRelocatableType(Sema &S, const CXXRecordDecl *RD) { + if (std::optional<ASTContext::CXXRecordDeclRelocationInfo> Info = + S.getASTContext().getRelocationInfoForCXXRecord(RD)) + return Info->IsRelocatable; + ASTContext::CXXRecordDeclRelocationInfo Info = + S.CheckCXX2CRelocatableAndReplaceable(RD); + S.getASTContext().setRelocationInfoForCXXRecord(RD, Info); + return Info.IsRelocatable; +} + +bool Sema::IsCXXTriviallyRelocatableType(QualType Type) { + + QualType BaseElementType = getASTContext().getBaseElementType(Type); + + if (Type->isVariableArrayType()) + return false; + + if (BaseElementType.hasNonTrivialObjCLifetime()) + return false; + + if (BaseElementType.hasAddressDiscriminatedPointerAuth()) + return false; + + if (BaseElementType->isIncompleteType()) + return false; + + if (BaseElementType->isScalarType() || BaseElementType->isVectorType()) + return true; + + if (const auto *RD = BaseElementType->getAsCXXRecordDecl()) + return ::IsCXXTriviallyRelocatableType(*this, RD); + + return false; +} + +static bool IsCXXReplaceableType(Sema &S, const CXXRecordDecl *RD) { + if (std::optional<ASTContext::CXXRecordDeclRelocationInfo> Info = + S.getASTContext().getRelocationInfoForCXXRecord(RD)) + return Info->IsReplaceable; + ASTContext::CXXRecordDeclRelocationInfo Info = + S.CheckCXX2CRelocatableAndReplaceable(RD); + S.getASTContext().setRelocationInfoForCXXRecord(RD, Info); + return Info.IsReplaceable; +} + +bool Sema::IsCXXReplaceableType(QualType Type) { + if (Type.isConstQualified() || Type.isVolatileQualified()) + return false; + + if (Type->isVariableArrayType()) + return false; + + QualType BaseElementType = + getASTContext().getBaseElementType(Type.getUnqualifiedType()); + if (BaseElementType->isIncompleteType()) + return false; + if (BaseElementType->isScalarType()) + return true; + if (const auto *RD = BaseElementType->getAsCXXRecordDecl()) + return ::IsCXXReplaceableType(*this, RD); + return false; +} + +static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) { + return llvm::StringSwitch<std::optional<TypeTrait>>(Name) + .Case("is_trivially_relocatable", + TypeTrait::UTT_IsCppTriviallyRelocatable) + .Default(std::nullopt); +} + +// Recognize type traits that are builting type traits, or known standard +// type traits in <type_traits>. Note that at this point we assume the +// trait evaluated to false, so we need only to recognize the shape of the +// outer-most symbol. +static std::optional<std::pair<TypeTrait, llvm::SmallVector<QualType, 1>>> +ExtractTypeTraitFromExpression(const Expr *E) { + llvm::SmallVector<QualType, 1> Args; + std::optional<TypeTrait> Trait; + + // builtins + if (const auto *TraitExpr = dyn_cast<TypeTraitExpr>(E)) { + Trait = TraitExpr->getTrait(); + for (const auto *Arg : TraitExpr->getArgs()) + Args.push_back(Arg->getType()); + return {{Trait.value(), std::move(Args)}}; + } + const auto *Ref = dyn_cast<DeclRefExpr>(E); + if (!Ref) + return std::nullopt; + + // std::is_xxx_v<> + if (const auto *VD = + dyn_cast<VarTemplateSpecializationDecl>(Ref->getDecl())) { + if (!VD->isInStdNamespace()) + return std::nullopt; + StringRef Name = VD->getIdentifier()->getName(); + if (!Name.consume_back("_v")) + return std::nullopt; + Trait = StdNameToTypeTrait(Name); + if (!Trait) + return std::nullopt; + for (const auto &Arg : VD->getTemplateArgs().asArray()) + Args.push_back(Arg.getAsType()); + return {{Trait.value(), std::move(Args)}}; + } + + // std::is_xxx<>::value + if (const auto *VD = dyn_cast<VarDecl>(Ref->getDecl()); + Ref->hasQualifier() && VD && VD->getIdentifier()->isStr("value")) { + const Type *T = Ref->getQualifier()->getAsType(); + if (!T) + return std::nullopt; + const TemplateSpecializationType *Ts = + T->getAs<TemplateSpecializationType>(); + if (!Ts) + return std::nullopt; + const TemplateDecl *D = Ts->getTemplateName().getAsTemplateDecl(); + if (!D || !D->isInStdNamespace()) + return std::nullopt; + Trait = StdNameToTypeTrait(D->getIdentifier()->getName()); + if (!Trait) + return std::nullopt; + for (const auto &Arg : Ts->template_arguments()) + Args.push_back(Arg.getAsType()); + return {{Trait.value(), std::move(Args)}}; + } + return std::nullopt; +} + +static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, + SourceLocation Loc, + const CXXRecordDecl *D) { + for (const CXXBaseSpecifier &B : D->bases()) { + const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); + if (!BaseDecl) + continue; + 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::NRBase << B.getType() + << B.getSourceRange(); + } + for (const FieldDecl *Field : D->fields()) { + if (Field->getType()->isReferenceType()) + continue; + if (!SemaRef.IsCXXTriviallyRelocatableType(Field->getType())) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NRField << 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; + + if (D->isUnion()) { + auto DiagSPM = [&](CXXSpecialMemberKind K, bool Has) { + if (Has) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::UnionWithUserDeclaredSMF << K; + }; + DiagSPM(CXXSpecialMemberKind::CopyConstructor, + D->hasUserDeclaredCopyConstructor()); + DiagSPM(CXXSpecialMemberKind::CopyAssignment, + D->hasUserDeclaredCopyAssignment()); + DiagSPM(CXXSpecialMemberKind::MoveConstructor, + D->hasUserDeclaredMoveConstructor()); + DiagSPM(CXXSpecialMemberKind::MoveAssignment, + D->hasUserDeclaredMoveAssignment()); + return; + } + + if (!D->hasSimpleMoveConstructor() && !D->hasSimpleCopyConstructor()) { + const auto *Decl = cast<CXXConstructorDecl>( + LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false)); + if (Decl && Decl->isUserProvided()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::UserProvidedCtr + << Decl->isMoveConstructor() << Decl->getSourceRange(); + } + if (!D->hasSimpleMoveAssignment() && !D->hasSimpleCopyAssignment()) { + CXXMethodDecl *Decl = + LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true); + if (Decl && Decl->isUserProvided()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::UserProvidedAssign + << Decl->isMoveAssignmentOperator() << Decl->getSourceRange(); + } + CXXDestructorDecl *Dtr = D->getDestructor(); + if (Dtr && Dtr->isUserProvided() && !Dtr->isDefaulted()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::DeletedDtr << /*User Provided*/ 1 + << Dtr->getSourceRange(); +} + +static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, + SourceLocation Loc, + QualType T) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait) + << T << diag::TraitName::TriviallyRelocatable; + 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.hasNonTrivialObjCLifetime()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::HasArcLifetime; + + const CXXRecordDecl *D = T->getAsCXXRecordDecl(); + if (!D) + return; + + if (D->hasDefinition()) + DiagnoseNonTriviallyRelocatableReason(SemaRef, Loc, D); + + SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; +} + +void Sema::DiagnoseTypeTraitDetails(const Expr *E) { + E = E->IgnoreParenImpCasts(); + if (E->containsErrors()) + return; + + auto TraitInfo = ExtractTypeTraitFromExpression(E); + if (!TraitInfo) + return; + + const auto &[Trait, Args] = TraitInfo.value(); + switch (Trait) { + case UTT_IsCppTriviallyRelocatable: + DiagnoseNonTriviallyRelocatableReason(*this, E->getBeginLoc(), Args[0]); + 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 new file mode 100644 index 0000000000000..90cff1e66000c --- /dev/null +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp @@ -0,0 +1,101 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD1 %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD2 %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD3 %s + +namespace std { + +#ifdef STD1 +template <typename T> +struct is_trivially_relocatable { + static constexpr bool value = __builtin_is_cpp_trivially_relocatable(T); +}; + +template <typename T> +constexpr bool is_trivially_relocatable_v = __builtin_is_cpp_trivially_relocatable(T); +#endif + +#ifdef STD2 +template <typename T> +struct __details_is_trivially_relocatable { + static constexpr bool value = __builtin_is_cpp_trivially_relocatable(T); +}; + +template <typename T> +using is_trivially_relocatable = __details_is_trivially_relocatable<T>; + +template <typename T> +constexpr bool is_trivially_relocatable_v = __builtin_is_cpp_trivially_relocatable(T); +#endif + + +#ifdef STD3 +template< class T, T v > +struct integral_constant { + static constexpr T value = v; +}; + +template< bool B > +using bool_constant = integral_constant<bool, B>; + +template <typename T> +struct __details_is_trivially_relocatable : bool_constant<__builtin_is_cpp_trivially_relocatable(T)> {}; + +template <typename T> +using is_trivially_relocatable = __details_is_trivially_relocatable<T>; + +template <typename T> +constexpr bool is_trivially_relocatable_v = is_trivially_relocatable<T>::value; +#endif + +} + +static_assert(std::is_trivially_relocatable<int>::value); + +static_assert(std::is_trivially_relocatable<int&>::value); +// expected-error-re@-1 {{static assertion failed due to requirement 'std::{{.*}}is_trivially_relocatable<int &>::value'}} \ +// expected-note@-1 {{'int &' is not trivially relocatable}} \ +// expected-note@-1 {{because it is a reference type}} +static_assert(std::is_trivially_relocatable_v<int&>); +// expected-error@-1 {{static assertion failed due to requirement 'std::is_trivially_relocatable_v<int &>'}} \ +// expected-note@-1 {{'int &' is not trivially relocatable}} \ +// expected-note@-1 {{because it is a reference type}} + +namespace test_namespace { + using namespace std; + static_assert(is_trivially_relocatable<int&>::value); + // expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_trivially_relocatable<int &>::value'}} \ + // expected-note@-1 {{'int &' is not trivially relocatable}} \ + // expected-note@-1 {{because it is a reference type}} + static_assert(is_trivially_relocatable_v<int&>); + // expected-error@-1 {{static assertion failed due to requirement 'is_trivially_relocatable_v<int &>'}} \ + // expected-note@-1 {{'int &' is not trivially relocatable}} \ + // expected-note@-1 {{because it is a reference type}} +} + + +namespace concepts { +template <typename T> +requires std::is_trivially_relocatable<T>::value void f(); // #cand1 + +template <typename T> +concept C = std::is_trivially_relocatable_v<T>; // #concept2 + +template <C T> void g(); // #cand2 + +void test() { + f<int&>(); + // expected-error@-1 {{no matching function for call to 'f'}} \ + // expected-note@#cand1 {{candidate template ignored: constraints not satisfied [with T = int &]}} \ + // expected-note-re@#cand1 {{because '{{.*}}is_trivially_relocatable<int &>::value' evaluated to false}} \ + // expected-note@#cand1 {{'int &' is not trivially relocatable}} \ + // expected-note@#cand1 {{because it is a reference type}} + + g<int&>(); + // expected-error@-1 {{no matching function for call to 'g'}} \ + // expected-note@#cand2 {{candidate template ignored: constraints not satisfied [with T = int &]}} \ + // expected-note@#cand2 {{because 'int &' does not satisfy 'C'}} \ + // expected-note@#concept2 {{because 'std::is_trivially_relocatable_v<int &>' evaluated to false}} \ + // expected-note@#concept2 {{'int &' is not trivially relocatable}} \ + // expected-note@#concept2 {{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 new file mode 100644 index 0000000000000..d9cab20f4febd --- /dev/null +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -0,0 +1,146 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wno-vla-cxx-extension -Wno-c++26-extensions -std=c++20 %s + +struct S : A {}; // expected-error{{expected class name}} + +static_assert(__builtin_is_cpp_trivially_relocatable()); // expected-error {{expected a type}} +static_assert(__builtin_is_cpp_trivially_relocatable(0)); // expected-error {{expected a type}} +static_assert(__builtin_is_cpp_trivially_relocatable(S)); +static_assert(__builtin_is_cpp_trivially_relocatable(A)); // expected-error{{unknown type name 'A'}} + +static_assert(__builtin_is_cpp_trivially_relocatable(int, int)); // expected-error {{type trait requires 1 argument; have 2 arguments}} + +static_assert(__builtin_is_cpp_trivially_relocatable(int&)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(int &)'}} \ +// expected-note@-1 {{'int &' is not trivially relocatable}} \ +// expected-note@-1 {{because it is a reference type}} + + +static_assert(!__builtin_is_cpp_trivially_relocatable(int&)); +static_assert(!!__builtin_is_cpp_trivially_relocatable(int&)); +// expected-error@-1{{static assertion failed due to requirement '!!__builtin_is_cpp_trivially_relocatable(int &)'}} +static_assert(bool(__builtin_is_cpp_trivially_relocatable(int&))); +// expected-error@-1{{static assertion failed due to requirement 'bool(__builtin_is_cpp_trivially_relocatable(int &))'}} + +static_assert(__builtin_is_cpp_trivially_relocatable(int&) && __builtin_is_cpp_trivially_relocatable(int&)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(int &)'}} \ +// expected-note@-1 {{'int &' is not trivially relocatable}} \ +// expected-note@-1 {{because it is a reference type}} + +namespace concepts { +template <typename T> +requires __builtin_is_cpp_trivially_relocatable(T) void f(); // #cand1 + +template <typename T> +concept C = __builtin_is_cpp_trivially_relocatable(T); // #concept2 + +template <C T> void g(); // #cand2 + +void test() { + f<int&>(); + // expected-error@-1 {{no matching function for call to 'f'}} \ + // expected-note@#cand1 {{candidate template ignored: constraints not satisfied [with T = int &]}} \ + // expected-note@#cand1 {{because '__builtin_is_cpp_trivially_relocatable(int &)' evaluated to false}} \ + // expected-note@#cand1 {{'int &' is not trivially relocatable}} \ + // expected-note@#cand1 {{because it is a reference type}} + + g<int&>(); + // expected-error@-1 {{no matching function for call to 'g'}} \ + // expected-note@#cand2 {{candidate template ignored: constraints not satisfied [with T = int &]}} \ + // expected-note@#cand2 {{because 'int &' does not satisfy 'C'}} \ + // expected-note@#concept2 {{because '__builtin_is_cpp_trivially_relocatable(int &)' evaluated to false}} \ + // expected-note@#concept2 {{'int &' is not trivially relocatable}} \ + // expected-note@#concept2 {{because it is a reference type}} +} +} + +namespace trivially_relocatable { + +extern int vla_size; +static_assert(__builtin_is_cpp_trivially_relocatable(int[vla_size])); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(int[vla_size])'}} \ +// expected-note@-1 {{'int[vla_size]' is not trivially relocatable}} \ +// expected-note@-1 {{because it is a variably-modified type}} + +struct S; // expected-note {{forward declaration of 'trivially_relocatable::S'}} +static_assert(__builtin_is_cpp_trivially_relocatable(S)); +// expected-error@-1 {{incomplete type 'S' used in type trait expression}} + +struct B { + virtual ~B(); +}; +struct S : virtual B { // #tr-S + S(); + int & a; + const int ci; + B & b; + B c; + ~S(); +}; +static_assert(__builtin_is_cpp_trivially_relocatable(S)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::S)'}} \ +// expected-note@-1 {{'S' is not trivially relocatable}} \ +// expected-note@-1 {{because it has a virtual base 'B'}} \ +// expected-note@-1 {{because it has a non-trivially-relocatable base 'B'}} \ +// expected-note@-1 {{because it has a non-trivially-relocatable member 'c' of type 'B'}} \ +// expected-note@-1 {{because it has a user-provided destructor}} +// expected-note@#tr-S {{'S' defined here}} + +struct S2 { // #tr-S2 + S2(S2&&); + S2& operator=(const S2&); +}; +static_assert(__builtin_is_cpp_trivially_relocatable(S2)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::S2)'}} \ +// expected-note@-1 {{'S2' is not trivially relocatable}} \ +// 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@#tr-S2 {{'S2' defined here}} + + +struct S3 { // #tr-S3 + ~S3() = delete; +}; +static_assert(__builtin_is_cpp_trivially_relocatable(S3)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::S3)'}} \ +// expected-note@-1 {{'S3' is not trivially relocatable}} \ +// expected-note@-1 {{because it has a deleted destructor}} \ +// expected-note@#tr-S3 {{'S3' defined here}} + + +union U { // #tr-U + U(const U&); + U(U&&); + U& operator=(const U&); + U& operator=(U&&); +}; +static_assert(__builtin_is_cpp_trivially_relocatable(U)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::U)'}} \ +// expected-note@-1 {{'U' is not trivially relocatable}} \ +// 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@#tr-U {{'U' defined here}} +struct S4 trivially_relocatable_if_eligible { // #tr-S4 + ~S4(); + B b; +}; +static_assert(__builtin_is_cpp_trivially_relocatable(S4)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::S4)'}} \ +// expected-note@-1 {{'S4' is not trivially relocatable}} \ +// expected-note@-1 {{because it has a non-trivially-relocatable member 'b' of type 'B'}} \ +// expected-note@#tr-S4 {{'S4' defined here}} + +union U2 trivially_relocatable_if_eligible { // #tr-U2 + U2(const U2&); + U2(U2&&); + B b; +}; +static_assert(__builtin_is_cpp_trivially_relocatable(U2)); +// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::U2)'}} \ +// expected-note@-1 {{'U2' is not trivially relocatable}} \ +// 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}} + +} diff --git a/clang/test/SemaObjCXX/objc-weak-type-traits.mm b/clang/test/SemaObjCXX/objc-weak-type-traits.mm index e8f3e637ac272..91683c904d35d 100644 --- a/clang/test/SemaObjCXX/objc-weak-type-traits.mm +++ b/clang/test/SemaObjCXX/objc-weak-type-traits.mm @@ -1,5 +1,4 @@ // RUN: %clang_cc1 -fsyntax-only -fobjc-weak -fobjc-runtime-has-weak -verify -std=c++11 %s -Wno-deprecated-builtins -// expected-no-diagnostics // Check the results of the various type-trait query functions on // lifetime-qualified types in ObjC Weak. @@ -217,3 +216,9 @@ TRAIT_IS_TRUE(__is_trivially_relocatable, HasStrong); TRAIT_IS_FALSE(__is_trivially_relocatable, HasWeak); TRAIT_IS_TRUE(__is_trivially_relocatable, HasUnsafeUnretained); + + +static_assert(__builtin_is_cpp_trivially_relocatable(__weak id), ""); +//expected-error@-1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(__weak id)'}}\ +//expected-note@-1 {{'__weak id' is not trivially relocatable}}\ +//expected-note@-1 {{because it has an ARC lifetime qualifier}} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits