[clang] [Clang] Explain why a type trait evaluated to false. (PR #141238)
Erich Keane via cfe-commits
cfe-commits at lists.llvm.org
Fri May 23 08:15:56 PDT 2025
================
@@ -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);
----------------
erichkeane wrote:
This is one of those `auto` shouldn't be used here situations (I was quite confused by what we were looking at :D), though the `optional` around a `pair` is sorta self inflicted... I wonder if we should have a type-alias for that.
https://github.com/llvm/llvm-project/pull/141238
More information about the cfe-commits
mailing list