================
@@ -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

Reply via email to