[clang] [Clang] Explain why a type trait evaluated to false. (PR #141238)

via cfe-commits cfe-commits at lists.llvm.org
Fri May 23 08:06:58 PDT 2025


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 <corentinjabot at 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 at -1 {{static assertion failed due to requirement 'std::{{.*}}is_trivially_relocatable<int &>::value'}} \
+// expected-note at -1 {{'int &' is not trivially relocatable}} \
+// expected-note at -1 {{because it is a reference type}}
+static_assert(std::is_trivially_relocatable_v<int&>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_trivially_relocatable_v<int &>'}} \
+// expected-note at -1 {{'int &' is not trivially relocatable}} \
+// expected-note at -1 {{because it is a reference type}}
+
+namespace test_namespace {
+    using namespace std;
+    static_assert(is_trivially_relocatable<int&>::value);
+    // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_trivially_relocatable<int &>::value'}} \
+    // expected-note at -1 {{'int &' is not trivially relocatable}} \
+    // expected-note at -1 {{because it is a reference type}}
+    static_assert(is_trivially_relocatable_v<int&>);
+    // expected-error at -1 {{static assertion failed due to requirement 'is_trivially_relocatable_v<int &>'}} \
+    // expected-note at -1 {{'int &' is not trivially relocatable}} \
+    // expected-note at -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 at -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 at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(int &)'}} \
+// expected-note at -1 {{'int &' is not trivially relocatable}} \
+// expected-note at -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 at -1{{static assertion failed due to requirement '!!__builtin_is_cpp_trivially_relocatable(int &)'}}
+static_assert(bool(__builtin_is_cpp_trivially_relocatable(int&)));
+// expected-error at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(int &)'}} \
+// expected-note at -1 {{'int &' is not trivially relocatable}} \
+// expected-note at -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 at -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 at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(int[vla_size])'}} \
+// expected-note at -1 {{'int[vla_size]' is not trivially relocatable}} \
+// expected-note at -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 at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::S)'}} \
+// expected-note at -1 {{'S' is not trivially relocatable}} \
+// expected-note at -1 {{because it has a virtual base 'B'}} \
+// expected-note at -1 {{because it has a non-trivially-relocatable base 'B'}} \
+// expected-note at -1 {{because it has a non-trivially-relocatable member 'c' of type 'B'}} \
+// expected-note at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::S2)'}} \
+// expected-note at -1 {{'S2' is not trivially relocatable}} \
+// expected-note at -1 {{because it has a user provided move constructor}} \
+// expected-note at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::S3)'}} \
+// expected-note at -1 {{'S3' is not trivially relocatable}} \
+// expected-note at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::U)'}} \
+// expected-note at -1 {{'U' is not trivially relocatable}} \
+// expected-note at -1 {{because it is a union with a user-declared copy constructor}} \
+// expected-note at -1 {{because it is a union with a user-declared copy assignment operator}} \
+// expected-note at -1 {{because it is a union with a user-declared move constructor}} \
+// expected-note at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::S4)'}} \
+// expected-note at -1 {{'S4' is not trivially relocatable}} \
+// expected-note at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(trivially_relocatable::U2)'}} \
+// expected-note at -1 {{'U2' is not trivially relocatable}} \
+// expected-note at -1 {{because it has a deleted destructor}} \
+// expected-note at -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 at -1 {{static assertion failed due to requirement '__builtin_is_cpp_trivially_relocatable(__weak id)'}}\
+//expected-note at -1 {{'__weak id' is not trivially relocatable}}\
+//expected-note at -1 {{because it has an ARC lifetime qualifier}}



More information about the cfe-commits mailing list