[clang] [libcxx] [Clang] Add __builtin_common_reference (PR #121199)

via cfe-commits cfe-commits at lists.llvm.org
Mon Mar 31 06:35:50 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

<details>
<summary>Changes</summary>



---

Patch is 40.04 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/121199.diff


9 Files Affected:

- (modified) clang/docs/LanguageExtensions.rst (+17) 
- (modified) clang/include/clang/Basic/BuiltinTemplates.td (+28-2) 
- (modified) clang/include/clang/Sema/Sema.h (+19) 
- (modified) clang/lib/Sema/SemaExprCXX.cpp (+5-87) 
- (modified) clang/lib/Sema/SemaTemplate.cpp (+335-18) 
- (modified) clang/lib/Sema/SemaType.cpp (+75) 
- (added) clang/test/SemaCXX/type-trait-common-reference.cpp (+141) 
- (modified) libcxx/include/__type_traits/common_reference.h (+26-11) 
- (modified) libcxx/include/module.modulemap (+1) 


``````````diff
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 3b8a9cac6587a..6fc86507ca423 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1677,6 +1677,23 @@ Builtin type aliases
 
 Clang provides a few builtin aliases to improve the throughput of certain metaprogramming facilities.
 
+__builtin_common_reference
+--------------------------
+
+.. code-block:: c++
+
+  template <template <class, class, template <class> class, template <class> class> class BasicCommonReferenceT,
+            template <class... Args> CommonTypeT,
+            template <class> HasTypeMember,
+            class HasNoTypeMember,
+            class... Ts>
+  using __builtin_common_reference = ...;
+
+This alias is used for implementing ``std::common_refernce``. If ``std::common_reference`` should contain a ``type``
+member, it is an alias to ``HasTypeMember<TheCommonReference>``. Otherwse it is an alias to ``HasNoTypeMember``. The
+``CommonTypeT`` is usually ``std::common_type_t``. ``BasicCommonReferenceT`` is usually an alias template to
+``basic_common_reference<T, U, TX, UX>::type``.
+
 __builtin_common_type
 ---------------------
 
diff --git a/clang/include/clang/Basic/BuiltinTemplates.td b/clang/include/clang/Basic/BuiltinTemplates.td
index d46ce063d2f7e..5c79e89800829 100644
--- a/clang/include/clang/Basic/BuiltinTemplates.td
+++ b/clang/include/clang/Basic/BuiltinTemplates.td
@@ -10,11 +10,11 @@ class TemplateArg<string name> {
   string Name = name;
 }
 
-class Template<list<TemplateArg> args, string name> : TemplateArg<name> {
+class Template<list<TemplateArg> args, string name = ""> : TemplateArg<name> {
   list<TemplateArg> Args = args;
 }
 
-class Class<string name, bit is_variadic = 0> : TemplateArg<name> {
+class Class<string name = "", bit is_variadic = 0> : TemplateArg<name> {
   bit IsVariadic = is_variadic;
 }
 
@@ -50,3 +50,29 @@ def __builtin_common_type : BuiltinTemplate<
    Template<[Class<"TypeMember">], "HasTypeMember">,
    Class<"HasNoTypeMember">,
    Class<"Ts", /*is_variadic=*/1>]>;
+
+// template <template <class,"
+//                     class,"
+//                     template <class> class,"
+//                     template <class> class> class BasicCommonReferenceT,"
+//           template <class... Args> class CommonTypeT,"
+//           template <class> class HasTypeMember,"
+//           class HasNoTypeMember,"
+//           class... Ts>"
+def __builtin_common_reference : BuiltinTemplate<
+            [Template<[Class<>,
+                       Class<>,
+                       Template<[Class<>]>,
+                       Template<[Class<>]>], "BasicCommonReferenceT">,
+             Template<[Class<"Args", /*is_variadic=*/1>], "CommonTypeT">,
+             Template<[Class<>], "HasTypeMember">,
+             Class<"HasNoTypeMember">,
+             Class<"Ts", /*is_variadic=*/1>]>;
+
+foreach Ref = ["", "lvalue", "rvalue"] in {
+  foreach Const = ["", "const"] in {
+    foreach Volatile = ["", "volatile"] in {
+      def __clang_internal_xref_#Ref#Const#Volatile : BuiltinTemplate<[Class<>]>;
+    }
+  }
+}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 066bce61c74c1..762cb851ee04f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15076,15 +15076,34 @@ class Sema final : public SemaBase {
   QualType BuiltinDecay(QualType BaseType, SourceLocation Loc);
   QualType BuiltinAddReference(QualType BaseType, UTTKind UKind,
                                SourceLocation Loc);
+
+  QualType BuiltinAddRValueReference(QualType BaseType, SourceLocation Loc) {
+    return BuiltinAddReference(BaseType, UnaryTransformType::AddRvalueReference,
+                               Loc);
+  }
+
+  QualType BuiltinAddLValueReference(QualType BaseType, SourceLocation Loc) {
+    return BuiltinAddReference(BaseType, UnaryTransformType::AddLvalueReference,
+                               Loc);
+  }
+
   QualType BuiltinRemoveExtent(QualType BaseType, UTTKind UKind,
                                SourceLocation Loc);
   QualType BuiltinRemoveReference(QualType BaseType, UTTKind UKind,
                                   SourceLocation Loc);
+
+  QualType BuiltinRemoveCVRef(QualType BaseType, SourceLocation Loc) {
+    return BuiltinRemoveReference(BaseType, UTTKind::RemoveCVRef, Loc);
+  }
+
   QualType BuiltinChangeCVRQualifiers(QualType BaseType, UTTKind UKind,
                                       SourceLocation Loc);
   QualType BuiltinChangeSignedness(QualType BaseType, UTTKind UKind,
                                    SourceLocation Loc);
 
+  bool BuiltinIsConvertible(QualType From, QualType To, SourceLocation Loc,
+                            bool CheckNothrow = false);
+
   /// Ensure that the type T is a literal type.
   ///
   /// This routine checks whether the type @p T is a literal type. If @p T is an
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 46895db4a0756..5ca5a8a57fa0f 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5739,76 +5739,6 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
 static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceInfo *Lhs,
                                     const TypeSourceInfo *Rhs, SourceLocation KeyLoc);
 
-static ExprResult CheckConvertibilityForTypeTraits(
-    Sema &Self, const TypeSourceInfo *Lhs, const TypeSourceInfo *Rhs,
-    SourceLocation KeyLoc, llvm::BumpPtrAllocator &OpaqueExprAllocator) {
-
-  QualType LhsT = Lhs->getType();
-  QualType RhsT = Rhs->getType();
-
-  // C++0x [meta.rel]p4:
-  //   Given the following function prototype:
-  //
-  //     template <class T>
-  //       typename add_rvalue_reference<T>::type create();
-  //
-  //   the predicate condition for a template specialization
-  //   is_convertible<From, To> shall be satisfied if and only if
-  //   the return expression in the following code would be
-  //   well-formed, including any implicit conversions to the return
-  //   type of the function:
-  //
-  //     To test() {
-  //       return create<From>();
-  //     }
-  //
-  //   Access checking is performed as if in a context unrelated to To and
-  //   From. Only the validity of the immediate context of the expression
-  //   of the return-statement (including conversions to the return type)
-  //   is considered.
-  //
-  // We model the initialization as a copy-initialization of a temporary
-  // of the appropriate type, which for this expression is identical to the
-  // return statement (since NRVO doesn't apply).
-
-  // Functions aren't allowed to return function or array types.
-  if (RhsT->isFunctionType() || RhsT->isArrayType())
-    return ExprError();
-
-  // A function definition requires a complete, non-abstract return type.
-  if (!Self.isCompleteType(Rhs->getTypeLoc().getBeginLoc(), RhsT) ||
-      Self.isAbstractType(Rhs->getTypeLoc().getBeginLoc(), RhsT))
-    return ExprError();
-
-  // Compute the result of add_rvalue_reference.
-  if (LhsT->isObjectType() || LhsT->isFunctionType())
-    LhsT = Self.Context.getRValueReferenceType(LhsT);
-
-  // Build a fake source and destination for initialization.
-  InitializedEntity To(InitializedEntity::InitializeTemporary(RhsT));
-  Expr *From = new (OpaqueExprAllocator.Allocate<OpaqueValueExpr>())
-      OpaqueValueExpr(KeyLoc, LhsT.getNonLValueExprType(Self.Context),
-                      Expr::getValueKindForType(LhsT));
-  InitializationKind Kind =
-      InitializationKind::CreateCopy(KeyLoc, SourceLocation());
-
-  // Perform the initialization in an unevaluated context within a SFINAE
-  // trap at translation unit scope.
-  EnterExpressionEvaluationContext Unevaluated(
-      Self, Sema::ExpressionEvaluationContext::Unevaluated);
-  Sema::SFINAETrap SFINAE(Self, /*AccessCheckingSFINAE=*/true);
-  Sema::ContextRAII TUContext(Self, Self.Context.getTranslationUnitDecl());
-  InitializationSequence Init(Self, To, Kind, From);
-  if (Init.Failed())
-    return ExprError();
-
-  ExprResult Result = Init.Perform(Self, To, Kind, From);
-  if (Result.isInvalid() || SFINAE.hasErrorOccurred())
-    return ExprError();
-
-  return Result;
-}
-
 static APValue EvaluateSizeTTypeTrait(Sema &S, TypeTrait Kind,
                                       SourceLocation KWLoc,
                                       ArrayRef<TypeSourceInfo *> Args,
@@ -5958,9 +5888,8 @@ static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind,
           S.Context.getPointerType(T.getNonReferenceType()));
       TypeSourceInfo *UPtr = S.Context.CreateTypeSourceInfo(
           S.Context.getPointerType(U.getNonReferenceType()));
-      return !CheckConvertibilityForTypeTraits(S, UPtr, TPtr, RParenLoc,
-                                               OpaqueExprAllocator)
-                  .isInvalid();
+      return S.BuiltinIsConvertible(UPtr->getType(), TPtr->getType(),
+                                    RParenLoc);
     }
 
     if (Kind == clang::TT_IsNothrowConstructible)
@@ -6200,20 +6129,9 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceI
   }
   case BTT_IsConvertible:
   case BTT_IsConvertibleTo:
-  case BTT_IsNothrowConvertible: {
-    if (RhsT->isVoidType())
-      return LhsT->isVoidType();
-    llvm::BumpPtrAllocator OpaqueExprAllocator;
-    ExprResult Result = CheckConvertibilityForTypeTraits(Self, Lhs, Rhs, KeyLoc,
-                                                         OpaqueExprAllocator);
-    if (Result.isInvalid())
-      return false;
-
-    if (BTT != BTT_IsNothrowConvertible)
-      return true;
-
-    return Self.canThrow(Result.get()) == CT_Cannot;
-  }
+  case BTT_IsNothrowConvertible:
+    return Self.BuiltinIsConvertible(LhsT, RhsT, KeyLoc,
+                                     BTT == BTT_IsNothrowConvertible);
 
   case BTT_IsAssignable:
   case BTT_IsNothrowAssignable:
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index be81b6a46b2c0..47601c1840b92 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -3097,6 +3097,33 @@ void Sema::NoteAllFoundTemplates(TemplateName Name) {
   }
 }
 
+static QualType InstantiateTemplate(Sema &S, TemplateName Template,
+                                    ArrayRef<TemplateArgument> Args,
+                                    SourceLocation Loc) {
+  TemplateArgumentListInfo ArgList;
+  for (auto Arg : Args) {
+    if (Arg.getKind() == TemplateArgument::Type) {
+      ArgList.addArgument(TemplateArgumentLoc(
+          Arg, S.Context.getTrivialTypeSourceInfo(Arg.getAsType())));
+    } else {
+      ArgList.addArgument(
+          S.getTrivialTemplateArgumentLoc(Arg, QualType(), Loc));
+    }
+  }
+
+  EnterExpressionEvaluationContext UnevaluatedContext(
+      S, Sema::ExpressionEvaluationContext::Unevaluated);
+  Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
+  Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());
+
+  QualType Instantiation = S.CheckTemplateIdType(Template, Loc, ArgList);
+
+  if (SFINAE.hasErrorOccurred())
+    return QualType();
+
+  return Instantiation;
+}
+
 static QualType builtinCommonTypeImpl(Sema &S, TemplateName BaseTemplate,
                                       SourceLocation TemplateLoc,
                                       ArrayRef<TemplateArgument> Ts) {
@@ -3107,24 +3134,7 @@ static QualType builtinCommonTypeImpl(Sema &S, TemplateName BaseTemplate,
     if (T1.getAsType()->isBuiltinType() && T2.getAsType()->isBuiltinType())
       return builtinCommonTypeImpl(S, BaseTemplate, TemplateLoc, {T1, T2});
 
-    TemplateArgumentListInfo Args;
-    Args.addArgument(TemplateArgumentLoc(
-        T1, S.Context.getTrivialTypeSourceInfo(T1.getAsType())));
-    Args.addArgument(TemplateArgumentLoc(
-        T2, S.Context.getTrivialTypeSourceInfo(T2.getAsType())));
-
-    EnterExpressionEvaluationContext UnevaluatedContext(
-        S, Sema::ExpressionEvaluationContext::Unevaluated);
-    Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
-    Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());
-
-    QualType BaseTemplateInst =
-        S.CheckTemplateIdType(BaseTemplate, TemplateLoc, Args);
-
-    if (SFINAE.hasErrorOccurred())
-      return QualType();
-
-    return BaseTemplateInst;
+    return InstantiateTemplate(S, BaseTemplate, {T1, T2}, TemplateLoc);
   };
 
   // Note A: For the common_type trait applied to a template parameter pack T of
@@ -3231,6 +3241,230 @@ static QualType builtinCommonTypeImpl(Sema &S, TemplateName BaseTemplate,
   }
 }
 
+static QualType CopyCV(QualType From, QualType To) {
+  if (From.isConstQualified())
+    To.addConst();
+  if (From.isVolatileQualified())
+    To.addVolatile();
+  return To;
+}
+
+// COND-RES(X, Y) be decltype(false ? declval<X(&)()>()() : declval<Y(&)()>()())
+static QualType CondRes(Sema &S, QualType X, QualType Y, SourceLocation Loc) {
+  EnterExpressionEvaluationContext UnevaluatedContext(
+      S, Sema::ExpressionEvaluationContext::Unevaluated);
+  Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
+  Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());
+
+  // false
+  OpaqueValueExpr CondExpr(SourceLocation(), S.Context.BoolTy, VK_PRValue);
+  ExprResult Cond = &CondExpr;
+
+  // declval<X(&)()>()()
+  OpaqueValueExpr LHSExpr(Loc, X.getNonLValueExprType(S.Context),
+                          Expr::getValueKindForType(X));
+  ExprResult LHS = &LHSExpr;
+
+  // declval<Y(&)()>()()
+  OpaqueValueExpr RHSExpr(Loc, Y.getNonLValueExprType(S.Context),
+                          Expr::getValueKindForType(Y));
+  ExprResult RHS = &RHSExpr;
+
+  ExprValueKind VK = VK_PRValue;
+  ExprObjectKind OK = OK_Ordinary;
+
+  // decltype(false ? declval<X(&)()>()() : declval<Y(&)()>()())
+  QualType Result = S.CheckConditionalOperands(Cond, LHS, RHS, VK, OK, Loc);
+
+  if (SFINAE.hasErrorOccurred())
+    return QualType();
+  if (VK == VK_LValue)
+    return S.BuiltinAddLValueReference(Result, Loc);
+  if (VK == VK_XValue)
+    return S.BuiltinAddRValueReference(Result, Loc);
+  return Result;
+}
+
+static QualType CommonRef(Sema &S, QualType A, QualType B, SourceLocation Loc) {
+  // Given types A and B, let X be remove_reference_t<A>, let Y be
+  // remove_reference_t<B>, and let COMMON-​REF(A, B) be:
+  assert(A->isReferenceType() && B->isReferenceType() &&
+         "A and B have to be ref qualified for a COMMON-REF");
+  auto X = A.getNonReferenceType();
+  auto Y = B.getNonReferenceType();
+
+  // If A and B are both lvalue reference types, COMMON-REF(A, B) is
+  // COND-RES(COPYCV(X, Y) &, COPYCV(​Y, X) &) if that type exists and is a
+  // reference type.
+  if (A->isLValueReferenceType() && B->isLValueReferenceType()) {
+    auto CR = CondRes(S, S.BuiltinAddLValueReference(CopyCV(X, Y), Loc),
+                      S.BuiltinAddLValueReference(CopyCV(Y, X), Loc), Loc);
+    if (CR.isNull() || !CR->isReferenceType())
+      return QualType();
+    return CR;
+  }
+
+  // Otherwise, let C be remove_reference_t<COMMON-REF(X&, Y&)>&&. If A and B
+  // are both rvalue reference types, C is well-formed, and
+  // is_convertible_v<A, C> && is_convertible_v<B, C> is true, then
+  // COMMON-REF(A, B) is C.
+  if (A->isRValueReferenceType() && B->isRValueReferenceType()) {
+    auto C = CommonRef(S, S.BuiltinAddLValueReference(X, Loc),
+                       S.BuiltinAddLValueReference(Y, Loc), Loc);
+    if (C.isNull())
+      return QualType();
+
+    C = C.getNonReferenceType();
+
+    if (S.BuiltinIsConvertible(A, C, Loc) && S.BuiltinIsConvertible(B, C, Loc))
+      return S.BuiltinAddRValueReference(C, Loc);
+    return QualType();
+  }
+
+  // Otherwise, if A is an lvalue reference and B is an rvalue reference, then
+  // COMMON-REF(A, B) is COMMON-REF(B, A).
+  if (A->isLValueReferenceType() && B->isRValueReferenceType())
+    std::swap(A, B);
+
+  // Otherwise, let D be COMMON-REF(const X&, Y&). If A is an rvalue reference
+  // and B is an lvalue reference and D is well-formed and
+  // is_convertible_v<A, D> is true, then COMMON-REF(A, B) is D.
+  if (A->isRValueReferenceType() && B->isLValueReferenceType()) {
+    auto X2 = X;
+    X2.addConst();
+    auto D = CommonRef(S, S.BuiltinAddLValueReference(X2, Loc),
+                       S.BuiltinAddLValueReference(Y, Loc), Loc);
+    if (!D.isNull() && S.BuiltinIsConvertible(A, D, Loc))
+      return D;
+    return QualType();
+  }
+
+  // Otherwise, COMMON-REF(A, B) is ill-formed.
+  // This is implemented by returning from the individual branches above.
+
+  llvm_unreachable("The above cases should be exhaustive");
+}
+
+static QualType builtinCommonReferenceImpl(Sema &S,
+                                           TemplateName CommonReference,
+                                           TemplateName CommonType,
+                                           SourceLocation TemplateLoc,
+                                           ArrayRef<TemplateArgument> Ts) {
+  switch (Ts.size()) {
+  // If sizeof...(T) is zero, there shall be no member type.
+  case 0:
+    return QualType();
+
+  // Otherwise, if sizeof...(T) is one, let T0 denote the sole type in the
+  // pack T. The member typedef type shall denote the same type as T0.
+  case 1:
+    return Ts[0].getAsType();
+
+  // Otherwise, if sizeof...(T) is two, let T1 and T2 denote the two types in
+  // the pack T. Then
+  case 2: {
+    auto T1 = Ts[0].getAsType();
+    auto T2 = Ts[1].getAsType();
+
+    // Let R be COMMON-REF(T1, T2). If T1 and T2 are reference types, R is
+    // well-formed, and is_convertible_v<add_pointer_t<T1>, add_pointer_t<R>> &&
+    // is_convertible_v<add_pointer_t<T2>, add_pointer_t<R>> is true, then the
+    // member typedef type denotes R.
+    if (T1->isReferenceType() && T2->isReferenceType()) {
+      QualType R = CommonRef(S, T1, T2, TemplateLoc);
+      if (!R.isNull()) {
+        if (S.BuiltinIsConvertible(S.BuiltinAddPointer(T1, TemplateLoc),
+                                   S.BuiltinAddPointer(R, TemplateLoc),
+                                   TemplateLoc) &&
+            S.BuiltinIsConvertible(S.BuiltinAddPointer(T2, TemplateLoc),
+                                   S.BuiltinAddPointer(R, TemplateLoc),
+                                   TemplateLoc)) {
+          return R;
+        }
+      }
+    }
+
+    // Otherwise, if basic_common_reference<remove_cvref_t<T1>,
+    // remove_cvref_t<T2>, ​XREF(​T1), XREF(T2)>​::​type is well-formed,
+    // then the member typedef type denotes that type.
+    {
+      auto getXRef = [&](QualType T) {
+        static BuiltinTemplateDecl *Quals[12] = {
+            S.Context.get__clang_internal_xref_Decl(),
+            S.Context.get__clang_internal_xref_constDecl(),
+            S.Context.get__clang_internal_xref_volatileDecl(),
+            S.Context.get__clang_internal_xref_constvolatileDecl(),
+            S.Context.get__clang_internal_xref_lvalueDecl(),
+            S.Context.get__clang_internal_xref_lvalueconstDecl(),
+            S.Context.get__clang_internal_xref_lvaluevolatileDecl(),
+            S.Context.get__clang_internal_xref_lvalueconstvolatileDecl(),
+            S.Context.get__clang_internal_xref_rvalueDecl(),
+            S.Context.get__clang_internal_xref_rvalueconstDecl(),
+            S.Context.get__clang_internal_xref_rvaluevolatileDecl(),
+            S.Context.get__clang_internal_xref_rvalueconstvolatileDecl(),
+        };
+        size_t Index = 0;
+        if (T->isLValueReferenceType()) {
+          T = T.getNonReferenceType();
+          Index += 4;
+        } else if (T->isRValueReferenceType()) {
+          T = T.getNonReferenceType();
+          Index += 8;
+        }
+        if (T.isConstQualified())
+          Index += 1;
+
+        if (T.isVolatileQualified())
+          Index += 2;
+
+        return Quals[Index];
+      };
+
+      auto BCR = InstantiateTemplate(S, CommonReference,
+                                     {S.BuiltinRemoveCVRef(T1, TemplateLoc),
+                                      S.BuiltinRemoveCVRef(T2, TemplateLoc),
+                                      TemplateName{getXRef(T1)},
+                                      TemplateName{getXRef(T2)}},
+                                     TemplateLoc);
+      if (!BCR.isNull())
+        return BCR;
+    }
+
+    // Otherwise, if COND-RES(T1...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/121199


More information about the cfe-commits mailing list