https://github.com/rapidsna updated 
https://github.com/llvm/llvm-project/pull/223267

>From 1ba9ff3537c50ba94eddacc41ca56857f1802582 Mon Sep 17 00:00:00 2001
From: Yeoul Na <[email protected]>
Date: Wed, 9 Sep 2026 08:43:30 -0700
Subject: [PATCH 1/2] [BoundsSafety][NFC] Allow CountAttributedType's count to
 be filled in later

Prepare CountAttributedType so its count expression can be supplied
after the node is created, which the new late-parsed counted_by
mechanism needs: the type is built when the attribute is seen, but its
argument isn't parsed until the enclosing record is complete.

  - Drop the TrailingObjects coupled-decl storage in favour of an
    ASTContext-allocated array held by the ArrayRef the base class
    already has, so the decls can be attached after construction.
  - Add CountAttributedType::setCountExpr for in-place completion.
  - Add ASTContext::getIncompleteCountAttributedType (count-less, not
    uniqued) and completeCountAttributedType. Incomplete nodes are not
    registered in ASTContext.Types until completed, so a node abandoned
    with a null count is never reachable.

No functional change: getCountAttributedType still builds a
fully-formed, count-carrying type as before.
---
 clang/include/clang/AST/ASTContext.h | 16 ++++++++
 clang/include/clang/AST/TypeBase.h   | 39 ++++++++++++-------
 clang/lib/AST/ASTContext.cpp         | 57 +++++++++++++++++++++++++---
 clang/lib/AST/Type.cpp               |  8 ++--
 clang/lib/Sema/SemaBoundsSafety.cpp  |  4 +-
 5 files changed, 100 insertions(+), 24 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h 
b/clang/include/clang/AST/ASTContext.h
index f875ae365b892..4fef0d57fd1e4 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1663,6 +1663,22 @@ class ASTContext : public RefCountedBase<ASTContext> {
                          bool OrNull,
                          ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) 
const;
 
+  /// Return a `CountAttributedType` whose count expression has not been parsed
+  /// yet, for use by a late-parsed bounds attribute. The result is *not*
+  /// uniqued, and must be completed with `completeCountAttributedType` once 
the
+  /// argument becomes parseable. Returns the node rather than a `QualType` so
+  /// the caller can retain it for completion.
+  CountAttributedType *getIncompleteCountAttributedType(QualType WrappedTy,
+                                                        bool CountInBytes,
+                                                        bool OrNull) const;
+
+  /// Supply the count expression and coupled declarations for a type created 
by
+  /// `getIncompleteCountAttributedType`. Enclosing types keep pointing at the
+  /// same node, so nothing above it needs rebuilding.
+  void completeCountAttributedType(
+      CountAttributedType *CATy, Expr *CountExpr,
+      ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const;
+
   /// Return a placeholder type for a late-parsed type attribute.
   /// This type wraps another type and holds the LateParsedAttribute
   /// that will be parsed later.
diff --git a/clang/include/clang/AST/TypeBase.h 
b/clang/include/clang/AST/TypeBase.h
index cdbd20b62bac5..afcb0172623df 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -3474,6 +3474,13 @@ class BoundsAttributedType : public Type, public 
llvm::FoldingSetNode {
   BoundsAttributedType(TypeClass TC, QualType Wrapped, QualType Canon);
 
 public:
+  enum BoundsAttrKind {
+    CountedBy = 0,
+    SizedBy,
+    CountedByOrNull,
+    SizedByOrNull,
+  };
+
   bool isSugared() const { return true; }
   QualType desugar() const { return WrappedTy; }
 
@@ -3510,10 +3517,7 @@ class BoundsAttributedType : public Type, public 
llvm::FoldingSetNode {
 
 /// Represents a sugar type with `__counted_by` or `__sized_by` annotations,
 /// including their `_or_null` variants.
-class CountAttributedType final
-    : public BoundsAttributedType,
-      public llvm::TrailingObjects<CountAttributedType,
-                                   TypeCoupledDeclRefInfo> {
+class CountAttributedType final : public BoundsAttributedType {
   friend class ASTContext;
 
   Expr *CountExpr;
@@ -3523,27 +3527,34 @@ class CountAttributedType final
   /// __counted_by_or_null or __sized_by_or_null) \p CoupledDecls contains the
   /// list of declarations referenced by \p CountExpr, which the type depends 
on
   /// for the bounds information.
+  ///
+  /// \p CountExpr may be null, and \p CoupledDecls empty, for a type created 
by
+  /// a late-parsed attribute whose argument has not been parsed yet; such a
+  /// type is completed by \c setCountExpr once the enclosing scope is known.
+  /// See
+  /// \c Parser::CompleteLateParsedTypeAttributes.
   CountAttributedType(QualType Wrapped, QualType Canon, Expr *CountExpr,
                       bool CountInBytes, bool OrNull,
                       ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls);
 
-  unsigned numTrailingObjects(OverloadToken<TypeCoupledDeclRefInfo>) const {
-    return CountAttributedTypeBits.NumCoupledDecls;
+  /// Supply the count expression and its coupled declarations for a type that
+  /// was created without them by a late-parsed attribute. \p CoupledDecls must
+  /// already be allocated in the \c ASTContext, since it is retained by
+  /// reference.
+  void setCountExpr(Expr *E, ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) {
+    assert(!CountExpr && "count expression is already set");
+    assert(E && "completing with a null count expression");
+    CountExpr = E;
+    Decls = CoupledDecls;
+    CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size();
   }
 
 public:
-  enum DynamicCountPointerKind {
-    CountedBy = 0,
-    SizedBy,
-    CountedByOrNull,
-    SizedByOrNull,
-  };
-
   Expr *getCountExpr() const { return CountExpr; }
   bool isCountInBytes() const { return CountAttributedTypeBits.CountInBytes; }
   bool isOrNull() const { return CountAttributedTypeBits.OrNull; }
 
-  DynamicCountPointerKind getKind() const {
+  BoundsAttrKind getKind() const {
     if (isOrNull())
       return isCountInBytes() ? SizedByOrNull : CountedByOrNull;
     return isCountInBytes() ? SizedBy : CountedBy;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 84455fb6396bd..c58ad654e0050 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3754,11 +3754,29 @@ QualType ASTContext::removePtrSizeAddrSpace(QualType T) 
const {
   return T;
 }
 
+/// Allocate \p Decls in this context so a CountAttributedType can retain it by
+/// reference.
+static ArrayRef<TypeCoupledDeclRefInfo>
+allocateCoupledDecls(const ASTContext &Ctx,
+                     ArrayRef<TypeCoupledDeclRefInfo> Decls) {
+  if (Decls.empty())
+    return {};
+  auto *Slots = Ctx.Allocate<TypeCoupledDeclRefInfo>(Decls.size());
+  llvm::copy(Decls, Slots);
+  return ArrayRef(Slots, Decls.size());
+}
+
 QualType ASTContext::getCountAttributedType(
     QualType WrappedTy, Expr *CountExpr, bool CountInBytes, bool OrNull,
     ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const {
   assert(WrappedTy->isPointerType() || WrappedTy->isArrayType());
+  assert(CountExpr && "use getIncompleteCountAttributedType for a null count");
 
+  // Complete (non-late-parsed) path: the count expression is known up front.
+  // This deliberately preserves the pre-existing uniquing behavior -- the
+  // FoldingSet lookup/insert below is unchanged by late-parse support. Only
+  // getIncompleteCountAttributedType (count filled in later) opts out of
+  // uniquing.
   llvm::FoldingSetNodeID ID;
   CountAttributedType::Profile(ID, WrappedTy, CountExpr, CountInBytes, OrNull);
 
@@ -3768,17 +3786,46 @@ QualType ASTContext::getCountAttributedType(
     return QualType(CATy, 0);
 
   QualType CanonTy = getCanonicalType(WrappedTy);
-  size_t Size = CountAttributedType::totalSizeToAlloc<TypeCoupledDeclRefInfo>(
-      DependentDecls.size());
-  CATy = (CountAttributedType *)Allocate(Size, TypeAlignment);
-  new (CATy) CountAttributedType(WrappedTy, CanonTy, CountExpr, CountInBytes,
-                                 OrNull, DependentDecls);
+  ArrayRef<TypeCoupledDeclRefInfo> Decls =
+      allocateCoupledDecls(*this, DependentDecls);
+  CATy = new (*this, alignof(CountAttributedType)) CountAttributedType(
+      WrappedTy, CanonTy, CountExpr, CountInBytes, OrNull, Decls);
   Types.push_back(CATy);
   CountAttributedTypes.insert(CATy, Token);
 
   return QualType(CATy, 0);
 }
 
+CountAttributedType *ASTContext::getIncompleteCountAttributedType(
+    QualType WrappedTy, bool CountInBytes, bool OrNull) const {
+  assert(WrappedTy->isPointerType() || WrappedTy->isArrayType());
+
+  // Deliberately not uniqued. `CountAttributedType::Profile` keys on the
+  // `CountExpr` pointer, so every incomplete node would hash identically as
+  // `(WrappedTy, flags, nullptr)` and two fields with different counts would
+  // share a node. This is fine because expressions are not shared anyway.
+  // `getVariableArrayType` declines to unique for the same
+  // underlying reason: expressions themselves are not uniqued.
+  //
+  // Not added to `Types` yet: an incomplete node whose position turns out to 
be
+  // invalid (a nested counted_by, or a rejected argument) is abandoned without
+  // completion, and a node with a null count must never be reachable by
+  // anything that iterates `Types`. It is registered in
+  // `completeCountAttributedType` instead.
+  return new (*this, alignof(CountAttributedType)) CountAttributedType(
+      WrappedTy, getCanonicalType(WrappedTy), /*CountExpr=*/nullptr,
+      CountInBytes, OrNull, /*CoupledDecls=*/{});
+}
+
+void ASTContext::completeCountAttributedType(
+    CountAttributedType *CATy, Expr *CountExpr,
+    ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const {
+  ArrayRef<TypeCoupledDeclRefInfo> Decls =
+      allocateCoupledDecls(*this, DependentDecls);
+  CATy->setCountExpr(CountExpr, Decls);
+  Types.push_back(CATy);
+}
+
 QualType ASTContext::getLateParsedAttrType(
     QualType WrappedTy, LateParsedTypeAttribute *LateParsedAttr) const {
   QualType CanonTy = getCanonicalType(WrappedTy);
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 4b539b7c2b1f6..7254835e47390 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4158,9 +4158,11 @@ CountAttributedType::CountAttributedType(
   CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size();
   CountAttributedTypeBits.CountInBytes = CountInBytes;
   CountAttributedTypeBits.OrNull = OrNull;
-  auto *DeclSlot = getTrailingObjects();
-  llvm::copy(CoupledDecls, DeclSlot);
-  Decls = llvm::ArrayRef(DeclSlot, CoupledDecls.size());
+  // `CoupledDecls` is allocated in the ASTContext by the caller, so it can be
+  // retained by reference. This lets a type created by a late-parsed attribute
+  // start out with no decls and gain them later via `setCountExpr`, which a
+  // trailing-object array could not accommodate.
+  Decls = CoupledDecls;
 }
 
 StringRef CountAttributedType::getAttributeName(bool WithMacroPrefix) const {
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp 
b/clang/lib/Sema/SemaBoundsSafety.cpp
index 066dab2f0bef2..75041c801b6ff 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -17,8 +17,8 @@
 
 namespace clang {
 
-static CountAttributedType::DynamicCountPointerKind
-getCountAttrKind(bool CountInBytes, bool OrNull) {
+static CountAttributedType::BoundsAttrKind getCountAttrKind(bool CountInBytes,
+                                                            bool OrNull) {
   if (CountInBytes)
     return OrNull ? CountAttributedType::SizedByOrNull
                   : CountAttributedType::SizedBy;

>From 7c8520f175d6a16dc58a796acced4468a93c5006 Mon Sep 17 00:00:00 2001
From: Yeoul Na <[email protected]>
Date: Wed, 16 Sep 2026 17:05:17 -0700
Subject: [PATCH 2/2] Address Erich's feedback about allocations should happen
 in ::create

---
 clang/include/clang/AST/TypeBase.h | 28 +++++++-------
 clang/lib/AST/ASTContext.cpp       | 59 ++++++++++++++----------------
 clang/lib/AST/Type.cpp             | 42 +++++++++++++++++++--
 3 files changed, 80 insertions(+), 49 deletions(-)

diff --git a/clang/include/clang/AST/TypeBase.h 
b/clang/include/clang/AST/TypeBase.h
index afcb0172623df..eb34e775f7da6 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -3530,24 +3530,26 @@ class CountAttributedType final : public 
BoundsAttributedType {
   ///
   /// \p CountExpr may be null, and \p CoupledDecls empty, for a type created 
by
   /// a late-parsed attribute whose argument has not been parsed yet; such a
-  /// type is completed by \c setCountExpr once the enclosing scope is known.
-  /// See
+  /// type is completed by \c complete once the enclosing scope is known. See
   /// \c Parser::CompleteLateParsedTypeAttributes.
   CountAttributedType(QualType Wrapped, QualType Canon, Expr *CountExpr,
                       bool CountInBytes, bool OrNull,
                       ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls);
 
-  /// Supply the count expression and its coupled declarations for a type that
-  /// was created without them by a late-parsed attribute. \p CoupledDecls must
-  /// already be allocated in the \c ASTContext, since it is retained by
-  /// reference.
-  void setCountExpr(Expr *E, ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) {
-    assert(!CountExpr && "count expression is already set");
-    assert(E && "completing with a null count expression");
-    CountExpr = E;
-    Decls = CoupledDecls;
-    CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size();
-  }
+  /// Allocate and construct a \c CountAttributedType in \p Ctx, including its
+  /// coupled-declaration array. \p CountExpr may be null (with \p CoupledDecls
+  /// empty) for a late-parsed attribute whose argument is not yet parsed;
+  /// complete such a node later with \c complete.
+  static CountAttributedType *
+  Create(const ASTContext &Ctx, QualType Wrapped, QualType Canon,
+         Expr *CountExpr, bool CountInBytes, bool OrNull,
+         ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls);
+
+  /// Supply the count expression and coupled declarations for a node created 
by
+  /// \c Create with a null count -- a late-parsed attribute whose argument has
+  /// now been parsed. Allocates the decl array in \p Ctx, so the node owns it.
+  void complete(const ASTContext &Ctx, Expr *E,
+                ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls);
 
 public:
   Expr *getCountExpr() const { return CountExpr; }
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index c58ad654e0050..ee9ac03bd1682 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3754,18 +3754,6 @@ QualType ASTContext::removePtrSizeAddrSpace(QualType T) 
const {
   return T;
 }
 
-/// Allocate \p Decls in this context so a CountAttributedType can retain it by
-/// reference.
-static ArrayRef<TypeCoupledDeclRefInfo>
-allocateCoupledDecls(const ASTContext &Ctx,
-                     ArrayRef<TypeCoupledDeclRefInfo> Decls) {
-  if (Decls.empty())
-    return {};
-  auto *Slots = Ctx.Allocate<TypeCoupledDeclRefInfo>(Decls.size());
-  llvm::copy(Decls, Slots);
-  return ArrayRef(Slots, Decls.size());
-}
-
 QualType ASTContext::getCountAttributedType(
     QualType WrappedTy, Expr *CountExpr, bool CountInBytes, bool OrNull,
     ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const {
@@ -3786,10 +3774,8 @@ QualType ASTContext::getCountAttributedType(
     return QualType(CATy, 0);
 
   QualType CanonTy = getCanonicalType(WrappedTy);
-  ArrayRef<TypeCoupledDeclRefInfo> Decls =
-      allocateCoupledDecls(*this, DependentDecls);
-  CATy = new (*this, alignof(CountAttributedType)) CountAttributedType(
-      WrappedTy, CanonTy, CountExpr, CountInBytes, OrNull, Decls);
+  CATy = CountAttributedType::Create(*this, WrappedTy, CanonTy, CountExpr,
+                                     CountInBytes, OrNull, DependentDecls);
   Types.push_back(CATy);
   CountAttributedTypes.insert(CATy, Token);
 
@@ -3800,29 +3786,38 @@ CountAttributedType 
*ASTContext::getIncompleteCountAttributedType(
     QualType WrappedTy, bool CountInBytes, bool OrNull) const {
   assert(WrappedTy->isPointerType() || WrappedTy->isArrayType());
 
-  // Deliberately not uniqued. `CountAttributedType::Profile` keys on the
-  // `CountExpr` pointer, so every incomplete node would hash identically as
+  // Deliberately opts out of the uniquing that `getCountAttributedType` does:
+  // `CountAttributedType::Profile` keys on the `CountExpr` pointer, which is
+  // null here, so every incomplete node would profile identically as
   // `(WrappedTy, flags, nullptr)` and two fields with different counts would
-  // share a node. This is fine because expressions are not shared anyway.
-  // `getVariableArrayType` declines to unique for the same
-  // underlying reason: expressions themselves are not uniqued.
+  // collide. The node stays un-uniqued even after completion; see
+  // `completeCountAttributedType`.
   //
-  // Not added to `Types` yet: an incomplete node whose position turns out to 
be
-  // invalid (a nested counted_by, or a rejected argument) is abandoned without
-  // completion, and a node with a null count must never be reachable by
-  // anything that iterates `Types`. It is registered in
-  // `completeCountAttributedType` instead.
-  return new (*this, alignof(CountAttributedType)) CountAttributedType(
-      WrappedTy, getCanonicalType(WrappedTy), /*CountExpr=*/nullptr,
-      CountInBytes, OrNull, /*CoupledDecls=*/{});
+  // Also deliberately not in `Types` yet. An incomplete node can be abandoned
+  // without ever being completed (a nested counted_by, or an argument that
+  // fails to parse), and a null-count node must not be reachable by anything
+  // that scans `Types`. `completeCountAttributedType` registers it once the
+  // count is in place.
+  return CountAttributedType::Create(
+      *this, WrappedTy, getCanonicalType(WrappedTy),
+      /*CountExpr=*/nullptr, CountInBytes, OrNull,
+      /*CoupledDecls=*/{});
 }
 
 void ASTContext::completeCountAttributedType(
     CountAttributedType *CATy, Expr *CountExpr,
     ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const {
-  ArrayRef<TypeCoupledDeclRefInfo> Decls =
-      allocateCoupledDecls(*this, DependentDecls);
-  CATy->setCountExpr(CountExpr, Decls);
+  CATy->complete(*this, CountExpr, DependentDecls);
+  // Safe for `Types` scanners now that the count is in place; see
+  // `getIncompleteCountAttributedType` for why it was held back.
+  //
+  // It stays out of the `CountAttributedTypes` FoldingSet permanently, unlike
+  // an eagerly built node: this pointer is already embedded in the enclosing
+  // types and handed out, so an equal node that happens to exist cannot be
+  // merged into. The only cost is that a completed node is never
+  // pointer-shared with an equal eager one, which does not affect semantic
+  // type equality -- `hasSameType` compares canonical types, and this sugar's
+  // canonical type is the wrapped type's.
   Types.push_back(CATy);
 }
 
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 7254835e47390..87053d6048dd9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4158,13 +4158,47 @@ CountAttributedType::CountAttributedType(
   CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size();
   CountAttributedTypeBits.CountInBytes = CountInBytes;
   CountAttributedTypeBits.OrNull = OrNull;
-  // `CoupledDecls` is allocated in the ASTContext by the caller, so it can be
-  // retained by reference. This lets a type created by a late-parsed attribute
-  // start out with no decls and gain them later via `setCountExpr`, which a
-  // trailing-object array could not accommodate.
+  // `CoupledDecls` is already allocated by the caller (Create), so it
+  // can be retained by reference. This lets a type created by a late-parsed
+  // attribute start out with no decls and gain them later via `complete`,
+  // which a trailing-object array could not accommodate.
   Decls = CoupledDecls;
 }
 
+/// Copy \p Decls into \p Ctx so a \c CountAttributedType can retain it by
+/// reference. The node owns this allocation rather than its callers, so both
+/// \c Create and \c complete route through here.
+static ArrayRef<TypeCoupledDeclRefInfo>
+allocateCoupledDecls(const ASTContext &Ctx,
+                     ArrayRef<TypeCoupledDeclRefInfo> Decls) {
+  if (Decls.empty())
+    return {};
+  auto *Slots = Ctx.Allocate<TypeCoupledDeclRefInfo>(Decls.size());
+  llvm::copy(Decls, Slots);
+  return ArrayRef(Slots, Decls.size());
+}
+
+CountAttributedType *
+CountAttributedType::Create(const ASTContext &Ctx, QualType Wrapped,
+                            QualType Canon, Expr *CountExpr, bool CountInBytes,
+                            bool OrNull,
+                            ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) {
+  ArrayRef<TypeCoupledDeclRefInfo> Decls =
+      allocateCoupledDecls(Ctx, CoupledDecls);
+  return new (Ctx, alignof(CountAttributedType)) CountAttributedType(
+      Wrapped, Canon, CountExpr, CountInBytes, OrNull, Decls);
+}
+
+void CountAttributedType::complete(
+    const ASTContext &Ctx, Expr *E,
+    ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) {
+  assert(!CountExpr && "count expression is already set");
+  assert(E && "completing with a null count expression");
+  CountExpr = E;
+  Decls = allocateCoupledDecls(Ctx, CoupledDecls);
+  CountAttributedTypeBits.NumCoupledDecls = Decls.size();
+}
+
 StringRef CountAttributedType::getAttributeName(bool WithMacroPrefix) const {
 // TODO: This method isn't really ideal because it doesn't return the spelling
 // of the attribute that was used in the user's code. This method is used for

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to