================ @@ -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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits