[clang] [lldb] [BoundsSafety] Support bounds-safety attributes in type positions (PR #179612)

Yeoul Na via cfe-commits cfe-commits at lists.llvm.org
Mon Jul 13 12:52:01 PDT 2026


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

>From c85d6252338e3dce74db9bef7805f2bd22d41d6a Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Wed, 22 Oct 2025 10:46:46 -0700
Subject: [PATCH 1/2] [BoundsSafety] Support bounds-safety attributes in type
 positions

This commit implements proper handling of bounds-safety attributes
(counted_by, counted_by_or_null, sized_by, sized_by_or_null) when they
appear in type attribute positions, correctly treating them as type
attributes rather than declaration attributes.

Previously, these attributes in type positions (e.g., `int * __counted_by(count) buf`)
were incorrectly treated as declaration attributes, leading to:
1. Inability to reference struct members declared later (in late-parse mode)
2. Incorrect acceptance of attributes on nested pointer types
3. Attributes being attached to the wrong type level

This commit ensures that these attributes are correctly added to type positions
even when it's not late parsed. The key changes:

- Introduce LateParsedAttrType placeholder type to defer attribute processing
  until the complete struct definition is available (for late-parse mode). The
  placeholder is then transformed to a concrete type (e.g., CountAttributedType)
  via TreeTransform
- Add LateParsedAttribute vector to DeclaratorChunk, Declarator, and DeclSpec to
  track late type attributes at each declarator level, similar to existing
  ParsedAttr handling
- Implement proper attribute distribution to nested type levels during type
  construction in GetTypeForDeclarator
- Add more semantic checks to reject attributes on nested pointers

Issue #166411
---
 clang/include/clang/AST/ASTContext.h          |   7 +
 clang/include/clang/AST/RecursiveASTVisitor.h |   6 +
 clang/include/clang/AST/TypeBase.h            |  41 ++
 clang/include/clang/AST/TypeLoc.h             |  31 ++
 clang/include/clang/AST/TypeProperties.td     |   6 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/include/clang/Basic/TypeNodes.td        |   1 +
 clang/include/clang/Parse/Parser.h            |  53 +-
 clang/include/clang/Sema/DeclSpec.h           |  87 ++--
 clang/include/clang/Sema/Sema.h               |  99 +++-
 clang/lib/AST/ASTContext.cpp                  |  17 +
 clang/lib/AST/ASTImporter.cpp                 |   7 +
 clang/lib/AST/ASTStructuralEquivalence.cpp    |   7 +
 clang/lib/AST/ItaniumMangle.cpp               |   1 +
 clang/lib/AST/TypePrinter.cpp                 |  15 +
 clang/lib/CodeGen/CGDebugInfo.cpp             |   1 +
 clang/lib/CodeGen/CodeGenFunction.cpp         |   1 +
 clang/lib/Parse/ParseDecl.cpp                 | 172 +++++--
 clang/lib/Parse/Parser.cpp                    |   6 +
 clang/lib/Sema/SemaBoundsSafety.cpp           | 124 +----
 clang/lib/Sema/SemaDecl.cpp                   | 336 ++++++++++++-
 clang/lib/Sema/SemaDeclAttr.cpp               |  44 --
 clang/lib/Sema/SemaExpr.cpp                   |   1 +
 clang/lib/Sema/SemaType.cpp                   | 277 ++++++++++-
 clang/lib/Sema/TreeTransform.h                |  20 +
 clang/lib/Serialization/ASTReader.cpp         |   4 +
 clang/lib/Serialization/ASTWriter.cpp         |   7 +
 .../AST/attr-counted-by-or-null-struct-ptrs.c |  23 -
 clang/test/AST/attr-counted-by-struct-ptrs.c  |  26 -
 .../AST/attr-sized-by-or-null-struct-ptrs.c   |  24 -
 clang/test/AST/attr-sized-by-struct-ptrs.c    |  24 -
 ...ounds-safety-attributed-type-late-parsed.c | 111 +++++
 ...ounds-safety-attributed-type-late-parsed.h |  58 +++
 ...ounds-safety-attributed-type-late-parsed.c |  69 +++
 .../attr-bounds-safety-function-ptr-param.c   | 173 +++++++
 .../attr-counted-by-late-parsed-struct-ptrs.c |  79 ++-
 .../Sema/attr-counted-by-or-null-last-field.c |   4 +-
 ...unted-by-or-null-late-parsed-struct-ptrs.c |  64 +--
 ...ruct-ptrs-completable-incomplete-pointee.c |  37 +-
 .../attr-counted-by-or-null-struct-ptrs.c     |  14 +-
 ...ruct-ptrs-completable-incomplete-pointee.c |  32 +-
 clang/test/Sema/attr-counted-by-struct-ptrs.c |  18 +-
 ...nted-by-weird-type-positions-late-parsed.c | 456 ++++++++++++++++++
 .../attr-counted-by-weird-type-positions.c    | 454 +++++++++++++++++
 .../attr-sized-by-late-parsed-struct-ptrs.c   |  54 +--
 ...sized-by-or-null-late-parsed-struct-ptrs.c |  57 +--
 .../Sema/attr-sized-by-or-null-struct-ptrs.c  |  14 +-
 clang/test/Sema/attr-sized-by-struct-ptrs.c   |  12 +-
 clang/tools/libclang/CIndex.cpp               |   4 +
 .../TypeSystem/Clang/TypeSystemClang.cpp      |  10 +-
 50 files changed, 2591 insertions(+), 600 deletions(-)
 create mode 100644 clang/test/Modules/bounds-safety-attributed-type-late-parsed.c
 create mode 100644 clang/test/PCH/Inputs/bounds-safety-attributed-type-late-parsed.h
 create mode 100644 clang/test/PCH/bounds-safety-attributed-type-late-parsed.c
 create mode 100644 clang/test/Sema/attr-bounds-safety-function-ptr-param.c
 create mode 100644 clang/test/Sema/attr-counted-by-weird-type-positions-late-parsed.c
 create mode 100644 clang/test/Sema/attr-counted-by-weird-type-positions.c

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index b2fd522e6865c..0f069cac1e095 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -109,6 +109,7 @@ enum class FloatModeKind;
 class GlobalDecl;
 class IdentifierTable;
 class LangOptions;
+struct LateParsedTypeAttribute;
 class MangleContext;
 class MangleNumberingContext;
 class MemberSpecializationInfo;
@@ -1620,6 +1621,12 @@ class ASTContext : public RefCountedBase<ASTContext> {
                          bool OrNull,
                          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.
+  QualType getLateParsedAttrType(QualType Wrapped,
+                                 LateParsedTypeAttribute *LateParsedAttr) const;
+
   /// Return the uniqued reference to a type adjusted from the original
   /// type to a new type.
   QualType getAdjustedType(QualType Orig, QualType New) const;
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index b5be0910194bd..ed765b1cb3147 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1164,6 +1164,9 @@ DEF_TRAVERSE_TYPE(CountAttributedType, {
   TRY_TO(TraverseType(T->desugar()));
 })
 
+DEF_TRAVERSE_TYPE(LateParsedAttrType,
+                  { TRY_TO(TraverseType(T->getWrappedType())); })
+
 DEF_TRAVERSE_TYPE(BTFTagAttributedType,
                   { TRY_TO(TraverseType(T->getWrappedType())); })
 
@@ -1522,6 +1525,9 @@ DEF_TRAVERSE_TYPELOC(AttributedType,
 DEF_TRAVERSE_TYPELOC(CountAttributedType,
                      { TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); })
 
+DEF_TRAVERSE_TYPELOC(LateParsedAttrType,
+                     { TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); })
+
 DEF_TRAVERSE_TYPELOC(BTFTagAttributedType,
                      { TRY_TO(TraverseTypeLoc(TL.getWrappedLoc())); })
 
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index c64eee11fd91e..be72754b2fc87 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -70,6 +70,7 @@ class TagDecl;
 class TemplateParameterList;
 class Type;
 class Attr;
+struct LateParsedTypeAttribute;
 
 enum {
   TypeAlignmentInBits = 4,
@@ -3545,6 +3546,46 @@ class CountAttributedType final
   StringRef getAttributeName(bool WithMacroPrefix) const;
 };
 
+/// Represents a placeholder type for late-parsed type attributes.
+/// This type wraps another type and holds an opaque pointer to a
+/// LateParsedAttribute that will be parsed later (e.g., in ActOnFields).
+/// Once parsed, this type is replaced with the appropriate attributed type
+/// (e.g., CountAttributedType for counted_by).
+class LateParsedAttrType : public Type, public llvm::FoldingSetNode {
+  friend class ASTContext; // ASTContext creates these.
+
+  QualType WrappedTy;
+  LateParsedTypeAttribute *LateParsedTypeAttr;
+
+  LateParsedAttrType(QualType Wrapped, QualType Canon,
+                     LateParsedTypeAttribute *Attr)
+      : Type(LateParsedAttr, Canon, Wrapped->getDependence()),
+        WrappedTy(Wrapped), LateParsedTypeAttr(Attr) {}
+
+public:
+  QualType getWrappedType() const { return WrappedTy; }
+  LateParsedTypeAttribute *getLateParsedAttribute() const {
+    return LateParsedTypeAttr;
+  }
+
+  bool isSugared() const { return true; }
+  QualType desugar() const { return WrappedTy; }
+
+  void Profile(llvm::FoldingSetNodeID &ID) {
+    Profile(ID, WrappedTy, LateParsedTypeAttr);
+  }
+
+  static void Profile(llvm::FoldingSetNodeID &ID, QualType Wrapped,
+                      LateParsedTypeAttribute *Attr) {
+    ID.AddPointer(Wrapped.getAsOpaquePtr());
+    ID.AddPointer(Attr);
+  }
+
+  static bool classof(const Type *T) {
+    return T->getTypeClass() == LateParsedAttr;
+  }
+};
+
 /// Represents a type which was implicitly adjusted by the semantic
 /// engine for arbitrary reasons.  For example, array and function types can
 /// decay, and function types can have their calling conventions adjusted.
diff --git a/clang/include/clang/AST/TypeLoc.h b/clang/include/clang/AST/TypeLoc.h
index 24df18dbaace4..2648806ac687a 100644
--- a/clang/include/clang/AST/TypeLoc.h
+++ b/clang/include/clang/AST/TypeLoc.h
@@ -1358,6 +1358,37 @@ class CountAttributedTypeLoc final
   SourceRange getLocalSourceRange() const;
 };
 
+struct LateParsedAttrLocInfo {
+  SourceLocation AttrNameLoc;
+};
+
+class LateParsedAttrTypeLoc
+    : public ConcreteTypeLoc<UnqualTypeLoc, LateParsedAttrTypeLoc,
+                             LateParsedAttrType, LateParsedAttrLocInfo> {
+public:
+  TypeLoc getInnerLoc() const { return getInnerTypeLoc(); }
+
+  SourceLocation getAttrNameLoc() const { return getLocalData()->AttrNameLoc; }
+
+  void setAttrNameLoc(SourceLocation Loc) { getLocalData()->AttrNameLoc = Loc; }
+
+  SourceRange getLocalSourceRange() const {
+    return SourceRange(getAttrNameLoc(), getAttrNameLoc());
+  }
+
+  void initializeLocal(ASTContext &Context, SourceLocation Loc) {
+    setAttrNameLoc(Loc);
+  }
+
+  unsigned getLocalDataSize() const { return sizeof(LateParsedAttrLocInfo); }
+
+  QualType getInnerType() const { return getTypePtr()->getWrappedType(); }
+
+  LateParsedTypeAttribute *getLateParsedAttribute() const {
+    return getTypePtr()->getLateParsedAttribute();
+  }
+};
+
 struct MacroQualifiedLocInfo {
   SourceLocation ExpansionLoc;
 };
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 0f3722b36774a..3da2d823c72cf 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -44,6 +44,12 @@ let Class = CountAttributedType in {
   def : Creator<[{ return ctx.getCountAttributedType(WrappedTy, CountExpr, CountInBytes, OrNull, CoupledDecls); }]>;
 }
 
+let Class = LateParsedAttrType in {
+  // Note: LateParsedAttrType is a transient placeholder type that should
+  // normally be replaced before serialization. So this won't be serialized.
+  def : Creator<[{ (void)ctx; llvm_unreachable("unreachable for serialization"); }]>;
+}
+
 let Class = AdjustedType in {
   def : Property<"originalType", QualType> {
     let Read = [{ node->getOriginalType() }];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c1f2dc21df15c..bac708b75fdfd 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7194,6 +7194,9 @@ def err_builtin_counted_by_ref_invalid_use : Error<
   "value returned by '__builtin_counted_by_ref' cannot be used in "
   "%select{an array subscript|a binary}0 expression">;
 
+def err_counted_by_on_nested_pointer : Error<
+  "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute on nested pointer type is not allowed">;
+
 let CategoryName = "ARC Semantic Issue" in {
 
 // ARC-mode diagnostics.
diff --git a/clang/include/clang/Basic/TypeNodes.td b/clang/include/clang/Basic/TypeNodes.td
index a9965a4a89aa1..700a73f669690 100644
--- a/clang/include/clang/Basic/TypeNodes.td
+++ b/clang/include/clang/Basic/TypeNodes.td
@@ -105,6 +105,7 @@ def ObjCInterfaceType : TypeNode<ObjCObjectType>, AlwaysCanonical;
 def ObjCObjectPointerType : TypeNode<Type>;
 def BoundsAttributedType : TypeNode<Type, 1>;
 def CountAttributedType : TypeNode<BoundsAttributedType>, NeverCanonical;
+def LateParsedAttrType : TypeNode<Type>, NeverCanonical;
 def PipeType : TypeNode<Type>;
 def AtomicType : TypeNode<Type>;
 def BitIntType : TypeNode<Type>;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index dc3dc8a4ae0e9..8176e75b07435 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -282,6 +282,8 @@ class Parser : public CodeCompletionHandler {
   friend class PoisonSEHIdentifiersRAIIObject;
   friend class ParenBraceBracketBalancer;
   friend class BalancedDelimiterTracker;
+  friend struct LateParsedAttribute;
+  friend struct LateParsedTypeAttribute;
 
   Parser(Preprocessor &PP, Sema &Actions, bool SkipFunctionBodies);
   ~Parser() override;
@@ -1945,10 +1947,12 @@ class Parser : public CodeCompletionHandler {
       DeclSpec &DS, AccessSpecifier AS, DeclSpecContext DSContext,
       LateParsedAttrList *LateAttrs = nullptr);
 
-  void ParseSpecifierQualifierList(
-      DeclSpec &DS, AccessSpecifier AS = AS_none,
-      DeclSpecContext DSC = DeclSpecContext::DSC_normal) {
-    ParseSpecifierQualifierList(DS, getImplicitTypenameContext(DSC), AS, DSC);
+  void
+  ParseSpecifierQualifierList(DeclSpec &DS, AccessSpecifier AS = AS_none,
+                              DeclSpecContext DSC = DeclSpecContext::DSC_normal,
+                              LateParsedAttrList *LateAttrs = nullptr) {
+    ParseSpecifierQualifierList(DS, getImplicitTypenameContext(DSC), AS, DSC,
+                                LateAttrs);
   }
 
   /// ParseSpecifierQualifierList
@@ -1959,10 +1963,12 @@ class Parser : public CodeCompletionHandler {
   /// [GNU]    attributes     specifier-qualifier-list[opt]
   /// \endverbatim
   ///
-  void ParseSpecifierQualifierList(
-      DeclSpec &DS, ImplicitTypenameContext AllowImplicitTypename,
-      AccessSpecifier AS = AS_none,
-      DeclSpecContext DSC = DeclSpecContext::DSC_normal);
+  void
+  ParseSpecifierQualifierList(DeclSpec &DS,
+                              ImplicitTypenameContext AllowImplicitTypename,
+                              AccessSpecifier AS = AS_none,
+                              DeclSpecContext DSC = DeclSpecContext::DSC_normal,
+                              LateParsedAttrList *LateAttrs = nullptr);
 
   /// ParseEnumSpecifier
   /// \verbatim
@@ -2234,6 +2240,8 @@ class Parser : public CodeCompletionHandler {
       ParsedAttributes Attrs(AttrFactory);
       ParseGNUAttributes(Attrs, LateAttrs, &D);
       D.takeAttributesAppending(Attrs);
+      if (LateAttrs)
+        Parser::TakeTypeAttrsAppendingFrom(D.getLateAttributes(), *LateAttrs);
     }
   }
 
@@ -2670,7 +2678,8 @@ class Parser : public CodeCompletionHandler {
   void ParseTypeQualifierListOpt(
       DeclSpec &DS, unsigned AttrReqs = AR_AllAttributesParsed,
       bool AtomicOrPtrauthAllowed = true, bool IdentifierRequired = false,
-      llvm::function_ref<void()> CodeCompletionHandler = {});
+      llvm::function_ref<void()> CodeCompletionHandler = {},
+      LateParsedAttrList *LateAttrs = nullptr);
 
   /// ParseDirectDeclarator
   /// \verbatim
@@ -8088,6 +8097,32 @@ class Parser : public CodeCompletionHandler {
 
   static void LateTemplateParserCallback(void *P, LateParsedTemplate &LPT);
 
+  /// Parse the cached tokens stored in \p LTA into \p OutAttrs.
+  ///
+  /// This callback takes ownership of \p LTA and deletes it. Ideally
+  /// \c LateParsedAttrType would own the object, but \c LateParsedTypeAttribute
+  /// is intentionally forward-declared in the AST layer to avoid a dependency
+  /// on Parser/Sema headers.
+  static void ParseLateParsedTypeAttributeCallback(LateParsedTypeAttribute *LTA,
+                                                   ParsedAttributes *OutAttrs);
+
+  /// Return the source location of the attribute name stored in \p LTA.
+  static SourceLocation
+  GetLateParsedAttributeLocationCallback(const LateParsedTypeAttribute *LTA);
+
+  /// Validate \p LA as a late-parsed type attribute and, if valid, wrap
+  /// \p type in a \c LateParsedAttrType placeholder in-place.
+  ///
+  /// \p LA is downcast to \c LateParsedTypeAttribute; if the cast fails the
+  /// attribute is not applicable here and the function returns \c true to skip.
+  /// \p pointerNestLevel is the number of pointer/array/function declarator
+  /// chunks that precede the current chunk (see \c getPointerNestLevel).
+  /// Returns \c true on success and \c false if the attribute is invalid for
+  /// \p type.
+  static bool ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA,
+                                                QualType &type,
+                                                unsigned pointerNestLevel);
+
   /// We've parsed something that could plausibly be intended to be a template
   /// name (\p LHS) followed by a '<' token, and the following code can't
   /// possibly be an expression. Determine if this is likely to be a template-id
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index b3c459821c79c..77b6b8705e755 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -213,6 +213,34 @@ namespace clang {
     unsigned location_size() const { return Builder.getBuffer().second; }
   };
 
+  /// A set of tokens that has been cached for later parsing.
+  typedef SmallVector<Token, 4> CachedTokens;
+
+  // A list of late-parsed attributes.  Used by ParseGNUAttributes.
+  class LateParsedAttrList : public SmallVector<LateParsedAttribute *, 2> {
+  public:
+    LateParsedAttrList(bool PSoon = false,
+                       bool LateAttrParseExperimentalExtOnly = false,
+                       bool LateAttrParseTypeAttrOnly = false)
+        : ParseSoon(PSoon),
+          LateAttrParseExperimentalExtOnly(LateAttrParseExperimentalExtOnly),
+          LateAttrParseTypeAttrOnly(LateAttrParseTypeAttrOnly) {}
+
+    bool parseSoon() const { return ParseSoon; }
+    /// returns true iff the attribute to be parsed should only be late parsed
+    /// if it is annotated with `LateAttrParseExperimentalExt`
+    bool lateAttrParseExperimentalExtOnly() const {
+      return LateAttrParseExperimentalExtOnly;
+    }
+
+    bool lateAttrParseTypeAttrOnly() const { return LateAttrParseTypeAttrOnly; }
+
+  private:
+    bool ParseSoon; // Are we planning to parse these shortly after creation?
+    bool LateAttrParseExperimentalExtOnly;
+    bool LateAttrParseTypeAttrOnly;
+  };
+
 /// Captures information about "declaration specifiers".
 ///
 /// "Declaration specifiers" encompasses storage-class-specifiers,
@@ -404,6 +432,9 @@ class DeclSpec {
   // attributes.
   ParsedAttributes Attrs;
 
+  // late attributes
+  LateParsedAttrList LateParsedAttrs;
+
   // Scope specifier for the type spec, if applicable.
   CXXScopeSpec TypeScope;
 
@@ -480,7 +511,9 @@ class DeclSpec {
         FS_virtual_specified(false), FS_noreturn_specified(false),
         FriendSpecifiedFirst(false), ConstexprSpecifier(static_cast<unsigned>(
                                          ConstexprSpecKind::Unspecified)),
-        Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
+        Attrs(attrFactory), LateParsedAttrs(true, true, true), writtenBS(),
+
+        ObjCQualifiers(nullptr) {}
 
   // storage-class-specifier
   SCS getStorageClassSpec() const { return (SCS)StorageClassSpec; }
@@ -880,6 +913,11 @@ class DeclSpec {
   ParsedAttributes &getAttributes() { return Attrs; }
   const ParsedAttributes &getAttributes() const { return Attrs; }
 
+  LateParsedAttrList &getLateAttributes() { return LateParsedAttrs; }
+  const LateParsedAttrList &getLateAttributes() const {
+    return LateParsedAttrs;
+  }
+
   void takeAttributesAppendingingFrom(ParsedAttributes &attrs) {
     Attrs.takeAllAppendingFrom(attrs);
   }
@@ -1248,40 +1286,12 @@ class UnqualifiedId {
   SourceLocation getEndLoc() const LLVM_READONLY { return EndLocation; }
 };
 
-/// A set of tokens that has been cached for later parsing.
-typedef SmallVector<Token, 4> CachedTokens;
-
-// A list of late-parsed attributes.  Used by ParseGNUAttributes.
-class LateParsedAttrList : public SmallVector<LateParsedAttribute *, 2> {
-public:
-  LateParsedAttrList(bool PSoon = false,
-                     bool LateAttrParseExperimentalExtOnly = false,
-                     bool LateAttrParseTypeAttrOnly = false)
-      : ParseSoon(PSoon),
-        LateAttrParseExperimentalExtOnly(LateAttrParseExperimentalExtOnly),
-        LateAttrParseTypeAttrOnly(LateAttrParseTypeAttrOnly) {}
-
-  bool parseSoon() const { return ParseSoon; }
-  /// returns true iff the attribute to be parsed should only be late parsed
-  /// if it is annotated with `LateAttrParseExperimentalExt`
-  bool lateAttrParseExperimentalExtOnly() const {
-    return LateAttrParseExperimentalExtOnly;
-  }
-
-  bool lateAttrParseTypeAttrOnly() const { return LateAttrParseTypeAttrOnly; }
-
-private:
-  bool ParseSoon; // Are we planning to parse these shortly after creation?
-  bool LateAttrParseExperimentalExtOnly;
-  bool LateAttrParseTypeAttrOnly;
-};
-
 /// One instance of this struct is used for each type in a
 /// declarator that is parsed.
 ///
 /// This is intended to be a small value object.
 struct DeclaratorChunk {
-  DeclaratorChunk() {};
+  DeclaratorChunk() : LateAttrList(true, true, true) {};
 
   enum {
     Pointer, Reference, Array, Function, BlockPointer, MemberPointer, Paren, Pipe
@@ -1299,6 +1309,7 @@ struct DeclaratorChunk {
   }
 
   ParsedAttributesView AttrList;
+  LateParsedAttrList LateAttrList;
 
   struct PointerTypeInfo {
     /// The type qualifiers: const/volatile/restrict/unaligned/atomic.
@@ -2011,6 +2022,8 @@ class Declarator {
   /// corresponding constructor parameter.
   const ParsedAttributesView &DeclarationAttrs;
 
+  LateParsedAttrList LateParsedAttrs;
+
   /// The asm label, if specified.
   Expr *AsmLabel;
 
@@ -2076,8 +2089,8 @@ class Declarator {
         Redeclaration(false), Extension(false), ObjCIvar(false),
         ObjCWeakProperty(false), InlineStorageUsed(false),
         HasInitializer(false), Attrs(DS.getAttributePool().getFactory()),
-        DeclarationAttrs(DeclarationAttrs), AsmLabel(nullptr),
-        TrailingRequiresClause(nullptr),
+        DeclarationAttrs(DeclarationAttrs), LateParsedAttrs(true, true, true),
+        AsmLabel(nullptr), TrailingRequiresClause(nullptr),
         InventedTemplateParameterList(nullptr) {
     assert(llvm::all_of(DeclarationAttrs,
                         [](const ParsedAttr &AL) {
@@ -2399,13 +2412,16 @@ class Declarator {
   /// This function takes attrs by R-Value reference because it takes ownership
   /// of those attributes from the parameter.
   void AddTypeInfo(const DeclaratorChunk &TI, ParsedAttributes &&attrs,
-                   SourceLocation EndLoc) {
+                   SourceLocation EndLoc,
+                   const LateParsedAttrList &LateAttrs = {}) {
     DeclTypeInfo.push_back(TI);
     DeclTypeInfo.back().getAttrs().prepend(attrs.begin(), attrs.end());
     getAttributePool().takeAllFrom(attrs.getPool());
 
     if (!EndLoc.isInvalid())
       SetRangeEnd(EndLoc);
+
+    DeclTypeInfo.back().LateAttrList.append(LateAttrs);
   }
 
   /// AddTypeInfo - Add a chunk to this declarator. Also extend the range to
@@ -2735,6 +2751,11 @@ class Declarator {
     return DeclarationAttrs;
   }
 
+  LateParsedAttrList &getLateAttributes() { return LateParsedAttrs; }
+  const LateParsedAttrList &getLateAttributes() const {
+    return LateParsedAttrs;
+  }
+
   /// hasAttributes - do we contain any attributes?
   bool hasAttributes() const {
     if (!getAttributes().empty() || !getDeclarationAttributes().empty() ||
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5202244cee2a7..7dc0550326cf2 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -143,6 +143,8 @@ class InitializationKind;
 class InitializationSequence;
 class InitializedEntity;
 enum class LangAS : unsigned int;
+struct LateParsedAttribute;
+struct LateParsedTypeAttribute;
 class LocalInstantiationScope;
 class LookupResult;
 class MangleNumberingContext;
@@ -1356,6 +1358,45 @@ class Sema final : public SemaBase {
     OpaqueParser = P;
   }
 
+  /// Callbacks to the parser to interact with late-parsed type attributes.
+  /// These allow Sema to call back into Parser without including Parser.h.
+
+  /// Callback type to parse and consume a LateParsedTypeAttribute. Used as an
+  /// argument to ProcessLateParsedTypeAttributes.
+  typedef void ParseLateParsedTypeAttributeCB(LateParsedTypeAttribute *LTA,
+                                              ParsedAttributes *OutAttrs);
+
+  /// Callback to get the attribute name location from a
+  /// LateParsedTypeAttribute.
+  typedef SourceLocation
+  GetLateParsedAttributeLocationCB(const LateParsedTypeAttribute *LTA);
+  GetLateParsedAttributeLocationCB *GetLateParsedAttributeLocationCallback =
+      nullptr;
+
+  /// Callback to process a single late-parsed type attribute: validates the
+  /// attribute kind/type and wraps \p type in a LateParsedAttrType node if
+  /// appropriate. Returns false if the attribute is invalid.
+  typedef bool ProcessLateParsedTypeAttrCB(LateParsedAttribute *LA,
+                                           QualType &type,
+                                           unsigned pointerNestLevel);
+  ProcessLateParsedTypeAttrCB *ProcessLateParsedTypeAttrCallback = nullptr;
+
+  void
+  SetLateParsedAttributeCallbacks(GetLateParsedAttributeLocationCB *GetLocCB,
+                                  ProcessLateParsedTypeAttrCB *ProcessCB) {
+    GetLateParsedAttributeLocationCallback = GetLocCB;
+    ProcessLateParsedTypeAttrCallback = ProcessCB;
+  }
+
+  /// Called from the Parser's ProcessLateParsedTypeAttrCallback to validate
+  /// a counted_by-family attribute type and, if valid, wrap \p type in a
+  /// LateParsedAttrType node. Returns false if the attribute should be
+  /// dropped.
+  bool ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind,
+                               SourceLocation AttrNameLoc, QualType &type,
+                               unsigned pointerNestLevel,
+                               LateParsedTypeAttribute *LTA);
+
   /// Callback to the parser to parse a type expressed as a string.
   std::function<TypeResult(StringRef, StringRef, SourceLocation)>
       ParseTypeFromStringCallback;
@@ -2491,26 +2532,37 @@ class Sema final : public SemaBase {
   /// Implementations are in SemaBoundsSafety.cpp
   ///@{
 public:
-  /// Check if applying the specified attribute variant from the "counted by"
-  /// family of attributes to FieldDecl \p FD is semantically valid. If
-  /// semantically invalid diagnostics will be emitted explaining the problems.
-  ///
-  /// \param FD The FieldDecl to apply the attribute to
-  /// \param E The count expression on the attribute
-  /// \param CountInBytes If true the attribute is from the "sized_by" family of
-  ///                     attributes. If the false the attribute is from
-  ///                     "counted_by" family of attributes.
-  /// \param OrNull If true the attribute is from the "_or_null" suffixed family
-  ///               of attributes. If false the attribute does not have the
-  ///               suffix.
-  ///
-  /// Together \p CountInBytes and \p OrNull decide the attribute variant. E.g.
-  /// \p CountInBytes and \p OrNull both being true indicates the
-  /// `counted_by_or_null` attribute.
-  ///
-  /// \returns false iff semantically valid.
-  bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
-                                 bool OrNull);
+  /// Perform semantic validation on a FieldDecl with a "counted_by" family
+  /// attribute. This is called after the attribute has been attached to the
+  /// field's type (as a CountAttributedType) to validate the attribute is
+  /// correctly applied.
+  ///
+  /// This performs declaration-level checks that require the FieldDecl to
+  /// exist, complementing the type-level checks performed in
+  /// HandleCountedByAttrOnType during type processing. Specifically, this
+  /// validates:
+  /// - Field is not in a union
+  /// - For array fields, the field is a flexible array member
+  /// - Count expression is an integer type (not bool)
+  /// - Count expression references a field in the same struct
+  /// - Count field is not in a union
+  ///
+  /// \param FD The FieldDecl with the attribute
+  /// \param E The count expression from the attribute
+  /// \param CountInBytes If true the attribute is from the "sized_by" family.
+  ///                     If false the attribute is from the "counted_by"
+  ///                     family.
+  /// \param OrNull If true the attribute has the "_or_null" suffix.
+  ///
+  /// Together \p CountInBytes and \p OrNull determine the attribute variant:
+  /// - (false, false) = counted_by
+  /// - (false, true)  = counted_by_or_null
+  /// - (true, false)  = sized_by
+  /// - (true, true)   = sized_by_or_null
+  ///
+  /// \returns true if invalid (diagnostics emitted), false if valid.
+  bool CheckCountedByAttrOnFieldDecl(FieldDecl *FD, Expr *E, bool CountInBytes,
+                                     bool OrNull);
 
   /// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
   /// `__counted_by_or_null` pointer type \param LHSTy.
@@ -4411,6 +4463,13 @@ class Sema final : public SemaBase {
                    ArrayRef<Decl *> Fields, SourceLocation LBrac,
                    SourceLocation RBrac, const ParsedAttributesView &AttrList);
 
+  /// Transform field types that contain late-parsed type attributes.
+  /// Called from two sites: once immediately after parsing a nested
+  /// non-anonymous record body, and once after ActOnFields for the outermost
+  /// record.
+  void ProcessLateParsedTypeAttributes(RecordDecl *EnclosingDecl,
+                                       ParseLateParsedTypeAttributeCB *ParseCB);
+
   /// ActOnTagStartDefinition - Invoked when we have entered the
   /// scope of a tag's definition (e.g., for an enumeration, class,
   /// struct, or union).
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index bc4771aec77d1..40a04d6e39a77 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -2531,6 +2531,10 @@ TypeInfo ASTContext::getTypeInfoImpl(const Type *T) const {
   case Type::CountAttributed:
     return getTypeInfo(cast<CountAttributedType>(T)->desugar().getTypePtr());
 
+  case Type::LateParsedAttr:
+    return getTypeInfo(
+        cast<LateParsedAttrType>(T)->getWrappedType().getTypePtr());
+
   case Type::BTFTagAttributed:
     return getTypeInfo(
         cast<BTFTagAttributedType>(T)->getWrappedType().getTypePtr());
@@ -3701,6 +3705,17 @@ QualType ASTContext::getCountAttributedType(
   return QualType(CATy, 0);
 }
 
+QualType ASTContext::getLateParsedAttrType(
+    QualType WrappedTy, LateParsedTypeAttribute *LateParsedAttr) const {
+  QualType CanonTy = getCanonicalType(WrappedTy);
+
+  auto *LPATy = new (*this, alignof(LateParsedAttrType))
+      LateParsedAttrType(WrappedTy, CanonTy, LateParsedAttr);
+
+  Types.push_back(LPATy);
+  return QualType(LPATy, 0);
+}
+
 QualType
 ASTContext::adjustType(QualType Orig,
                        llvm::function_ref<QualType(QualType)> Adjust) const {
@@ -14841,6 +14856,8 @@ static QualType getCommonSugarTypeNode(const ASTContext &Ctx, const Type *X,
                                       DX->isCountInBytes(), DX->isOrNull(),
                                       CDX);
   }
+  case Type::LateParsedAttr:
+    return QualType();
   case Type::PredefinedSugar:
     assert(cast<PredefinedSugarType>(X)->getKind() !=
            cast<PredefinedSugarType>(Y)->getKind());
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 7bab2d7dcddfa..d1415a007c712 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -1837,6 +1837,13 @@ ASTNodeImporter::VisitCountAttributedType(const CountAttributedType *T) {
       ArrayRef(CoupledDecls));
 }
 
+ExpectedType
+ASTNodeImporter::VisitLateParsedAttrType(const LateParsedAttrType *T) {
+  // LateParsedAttrType is a transient placeholder that should not normally
+  // appear during AST import. Import as the wrapped type.
+  return import(T->getWrappedType());
+}
+
 ExpectedType ASTNodeImporter::VisitTemplateTypeParmType(
     const TemplateTypeParmType *T) {
   Expected<TemplateTypeParmDecl *> ToDeclOrErr = import(T->getDecl());
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 9d970651a9e65..9f50af1201dad 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1197,6 +1197,13 @@ bool ASTStructuralEquivalence::isEquivalent(
       return false;
     break;
 
+  case Type::LateParsedAttr:
+    if (!IsStructurallyEquivalent(
+            Context, cast<LateParsedAttrType>(T1)->getWrappedType(),
+            cast<LateParsedAttrType>(T2)->getWrappedType()))
+      return false;
+    break;
+
   case Type::BTFTagAttributed:
     if (!IsStructurallyEquivalent(
             Context, cast<BTFTagAttributedType>(T1)->getWrappedType(),
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 1cb6fa05f22ac..0b0a21b37a27f 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -2472,6 +2472,7 @@ bool CXXNameMangler::mangleUnresolvedTypeOrSimpleId(QualType Ty,
   case Type::BitInt:
   case Type::DependentBitInt:
   case Type::CountAttributed:
+  case Type::LateParsedAttr:
     llvm_unreachable("type is illegal as a nested name specifier");
 
   case Type::SubstBuiltinTemplatePack:
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 80f5b90ba35c4..655cc24431cb1 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -288,6 +288,7 @@ bool TypePrinter::canPrefixQualifiers(const Type *T,
     case Type::MacroQualified:
     case Type::OverflowBehavior:
     case Type::CountAttributed:
+    case Type::LateParsedAttr:
       CanPrefixQualifiers = false;
       break;
 
@@ -1832,6 +1833,20 @@ void TypePrinter::printCountAttributedAfter(const CountAttributedType *T,
     printCountAttributedImpl(T, OS, Policy);
 }
 
+void TypePrinter::printLateParsedAttrBefore(const LateParsedAttrType *T,
+                                            raw_ostream &OS) {
+  // LateParsedAttrType is a transient placeholder that should not appear
+  // in user-facing output. Just print the wrapped type.
+  printBefore(T->getWrappedType(), OS);
+}
+
+void TypePrinter::printLateParsedAttrAfter(const LateParsedAttrType *T,
+                                           raw_ostream &OS) {
+  // LateParsedAttrType is a transient placeholder that should not appear
+  // in user-facing output. Just print the wrapped type.
+  printAfter(T->getWrappedType(), OS);
+}
+
 void TypePrinter::printAttributedBefore(const AttributedType *T,
                                         raw_ostream &OS) {
   // FIXME: Generate this with TableGen.
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index bfff3d9953471..3c4c9d718f803 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -4340,6 +4340,7 @@ llvm::DIType *CGDebugInfo::CreateTypeNode(QualType Ty, llvm::DIFile *Unit) {
   case Type::Decltype:
   case Type::PackIndexing:
   case Type::UnaryTransform:
+  case Type::LateParsedAttr:
     break;
   }
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index b920266b59808..51426183211ec 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -2662,6 +2662,7 @@ void CodeGenFunction::EmitVariablyModifiedType(QualType type) {
     case Type::SubstTemplateTypeParm:
     case Type::MacroQualified:
     case Type::CountAttributed:
+    case Type::LateParsedAttr:
       // Keep walking after single level desugaring.
       type = type.getSingleStepDesugaredType(getContext());
       break;
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 405dddf7991b4..3c0f0bcfef83a 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -117,6 +117,23 @@ static bool IsAttributeArgsParsedInFunctionScope(const IdentifierInfo &II) {
 #undef CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST
 }
 
+/// returns true iff attribute is annotated with `LateAttrParseExperimentalExt`
+/// in `Attr.td`.
+static bool IsAttributeTypeAttr(ParsedAttr::Kind Kind) {
+  switch (Kind) {
+#define ATTR(NAME)
+#define DECL_OR_TYPE_ATTR(NAME) case ParsedAttr::AT_##NAME:
+#define TYPE_ATTR(NAME) case ParsedAttr::AT_##NAME:
+#include "clang/Basic/AttrList.inc"
+    return true;
+  default:
+    return false;
+#undef DECL_OR_TYPE_ATTR
+#undef TYPE_ATTR
+#undef ATTR
+  }
+}
+
 /// Check if the a start and end source location expand to the same macro.
 static bool FindLocsWithCommonFileID(Preprocessor &PP, SourceLocation StartLoc,
                                      SourceLocation EndLoc) {
@@ -166,6 +183,9 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
     return false;
   }
 
+  ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind(
+      AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax());
+
   bool LateParse = false;
   if (!LateAttrs)
     LateParse = false;
@@ -174,7 +194,9 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
     // parsed for `LateAttrParseExperimentalExt` attributes. This will
     // only be late parsed if the experimental language option is enabled.
     LateParse = getLangOpts().ExperimentalLateParseAttributes &&
-                IsAttributeLateParsedExperimentalExt(*AttrName);
+                IsAttributeLateParsedExperimentalExt(*AttrName) &&
+                (IsAttributeTypeAttr(AttrKind) ||
+                 !LateAttrs->lateAttrParseTypeAttrOnly());
   } else {
     // The caller did not restrict late parsing to only
     // `LateAttrParseExperimentalExt` attributes so late parse
@@ -192,8 +214,13 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
   }
 
   // Handle attributes with arguments that require late parsing.
-  LateParsedAttribute *LA =
-      new LateParsedAttribute(this, *AttrName, AttrNameLoc);
+  // Late parsing for type attributes isn't properly supported in C++ yet.
+  LateParsedAttribute *LA = nullptr;
+  if (IsAttributeTypeAttr(AttrKind) && !getLangOpts().CPlusPlus)
+    LA = new LateParsedTypeAttribute(this, *AttrName, AttrNameLoc);
+  else
+    LA = new LateParsedAttribute(this, *AttrName, AttrNameLoc);
+
   LateAttrs->push_back(LA);
 
   // Attributes in a class are parsed at the end of the class, along
@@ -2750,12 +2777,20 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
 
 void Parser::ParseSpecifierQualifierList(
     DeclSpec &DS, ImplicitTypenameContext AllowImplicitTypename,
-    AccessSpecifier AS, DeclSpecContext DSC) {
+    AccessSpecifier AS, DeclSpecContext DSC, LateParsedAttrList *LateAttrs) {
   ParsedTemplateInfo TemplateInfo;
+
+  if (LateAttrs)
+    assert(!std::any_of(LateAttrs->begin(), LateAttrs->end(),
+                        [&](const LateParsedAttribute *LA) {
+                          return isa<LateParsedTypeAttribute>(LA);
+                        }) &&
+           "Late type attribute carried over");
+
   /// specifier-qualifier-list is a subset of declaration-specifiers.  Just
   /// parse declaration-specifiers and complain about extra stuff.
   /// TODO: diagnose attribute-specifiers and alignment-specifiers.
-  ParseDeclarationSpecifiers(DS, TemplateInfo, AS, DSC, nullptr,
+  ParseDeclarationSpecifiers(DS, TemplateInfo, AS, DSC, LateAttrs,
                              AllowImplicitTypename);
 
   // Validate declspec for type-name.
@@ -3166,10 +3201,11 @@ void Parser::DistributeCLateParsedAttrs(Decl *Dcl,
   if (!LateAttrs)
     return;
 
+  // Attach `Decl *` to each `LateParsedAttribute *`.
   if (Dcl) {
-    for (auto *LateAttr : *LateAttrs) {
-      if (LateAttr->Decls.empty())
-        LateAttr->addDecl(Dcl);
+    for (auto *LA : *LateAttrs) {
+      if (LA->Decls.empty())
+        LA->addDecl(Dcl);
     }
   }
 }
@@ -3242,12 +3278,6 @@ void Parser::ParseBoundsAttribute(IdentifierInfo &AttrName,
   ArgExprs.push_back(ArgExpr.get());
   Parens.consumeClose();
 
-  ASTContext &Ctx = Actions.getASTContext();
-
-  ArgExprs.push_back(IntegerLiteral::Create(
-      Ctx, llvm::APInt(Ctx.getTypeSize(Ctx.getSizeType()), 0),
-      Ctx.getSizeType(), SourceLocation()));
-
   Attrs.addNew(&AttrName, SourceRange(AttrNameLoc, Parens.getCloseLocation()),
                AttributeScopeInfo(), ArgExprs.data(), ArgExprs.size(), Form);
 }
@@ -3487,6 +3517,10 @@ void Parser::ParseDeclarationSpecifiers(
         DS.takeAttributesAppendingingFrom(attrs);
       }
 
+      if (LateAttrs) {
+        Parser::TakeTypeAttrsAppendingFrom(DS.getLateAttributes(), *LateAttrs);
+      }
+
       // If this is not a declaration specifier token, we're done reading decl
       // specifiers.  First verify that DeclSpec's are consistent.
       DS.Finish(Actions, Policy);
@@ -4019,7 +4053,6 @@ void Parser::ParseDeclarationSpecifiers(
     case tok::kw___declspec:
       ParseAttributes(PAKM_GNU | PAKM_Declspec, DS.getAttributes(), LateAttrs);
       continue;
-
     // Microsoft single token adornments.
     case tok::kw___forceinline: {
       isInvalid = DS.setFunctionSpecForceInline(Loc, PrevSpec, DiagID);
@@ -4774,7 +4807,8 @@ void Parser::ParseStructDeclaration(
   MaybeParseCXX11Attributes(Attrs);
 
   // Parse the common specifier-qualifiers-list piece.
-  ParseSpecifierQualifierList(DS);
+  ParseSpecifierQualifierList(DS, AS_none, DeclSpecContext::DSC_normal,
+                              LateFieldAttrs);
 
   // If there are no declarators, this is a free-standing declaration
   // specifier. Let the actions module cope with it.
@@ -4817,11 +4851,6 @@ void Parser::ParseStructDeclaration(
     } else
       DeclaratorInfo.D.SetIdentifier(nullptr, Tok.getLocation());
 
-    // Here, we now know that the unnamed struct is not an anonymous struct.
-    // Report an error if a counted_by attribute refers to a field in a
-    // different named struct.
-    DiagnoseCountAttributedTypeInUnnamedAnon(DS, *this);
-
     if (TryConsumeToken(tok::colon)) {
       ExprResult Res(ParseConstantExpression());
       if (Res.isInvalid())
@@ -4854,12 +4883,43 @@ void Parser::ParseLexedCAttributeList(LateParsedAttrList &LAs,
   assert(LAs.parseSoon() &&
          "Attribute list should be marked for immediate parsing.");
   for (auto *LA : LAs) {
+    assert(!isa<LateParsedTypeAttribute>(LA));
     ParseLexedCAttribute(*LA, OutAttrs);
     delete LA;
   }
   LAs.clear();
 }
 
+void Parser::ParseLateParsedTypeAttributeCallback(LateParsedTypeAttribute *LTA,
+                                                  ParsedAttributes *Attrs) {
+  // Parse the cached attribute tokens
+  LTA->ParseInto(*Attrs);
+  // LateParsedTypeAttribute is no longer needed so delete it. Ideally,
+  // LateParsedAttrType would own this object, but LateParsedTypeAttribute
+  // is intentionally forward declared to avoid making the AST depend on
+  // Sema/Parser components.
+  delete LTA;
+}
+
+SourceLocation Parser::GetLateParsedAttributeLocationCallback(
+    const LateParsedTypeAttribute *LTA) {
+  assert(LTA);
+  return LTA->AttrNameLoc;
+}
+
+bool Parser::ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA,
+                                               QualType &type,
+                                               unsigned pointerNestLevel) {
+  auto *LTA = dyn_cast_if_present<LateParsedTypeAttribute>(LA);
+  if (!LTA)
+    return true;
+
+  ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind(
+      &LTA->AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax());
+  return LTA->Self->Actions.ActOnLateParsedTypeAttr(
+      AttrKind, LTA->AttrNameLoc, type, pointerNestLevel, LTA);
+}
+
 ParsedAttributes Parser::ParseLexedCAttributeTokens(LateParsedAttribute &LA) {
   // Create a fake EOF so that attribute parsing won't go off the end of the
   // attribute.
@@ -5018,6 +5078,21 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
       // Parse all the comma separated declarators.
       ParsingDeclSpec DS(*this);
       ParseStructDeclaration(DS, CFieldCallback, &LateFieldAttrs);
+      if (DS.getTypeSpecType() == TST_struct) {
+
+        if (getLangOpts().ExperimentalLateParseAttributes) {
+          auto *RD = dyn_cast<RecordDecl>(DS.getRepAsDecl());
+          // The field contains a nested record definition. Trigger late
+          // parsing now for non-anonymous records; anonymous struct/union
+          // fields are handled as part of the enclosing record instead.
+          if (RD && !RD->isAnonymousStructOrUnion()) {
+            Actions.ProcessLateParsedTypeAttributes(
+                RD, ParseLateParsedTypeAttributeCallback);
+          }
+        } else {
+          DiagnoseCountAttributedTypeInUnnamedAnon(DS, *this);
+        }
+      }
     } else { // Handle @defs
       ConsumeToken();
       if (!Tok.isObjCAtKeyword(tok::objc_defs)) {
@@ -5058,8 +5133,7 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
 
   ParsedAttributes attrs(AttrFactory);
   // If attributes exist after struct contents, parse them.
-  MaybeParseGNUAttributes(attrs, &LateFieldAttrs);
-
+  MaybeParseGNUAttributes(attrs);
   SmallVector<Decl *, 32> FieldDecls(TagDecl->fields());
 
   Actions.ActOnFields(getCurScope(), RecordLoc, TagDecl, FieldDecls,
@@ -5067,6 +5141,16 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
 
   // Late parse field attributes if necessary.
   ParseLexedCAttributeList(LateFieldAttrs);
+
+  Scope *ParentScope = getCurScope()->getParent();
+  assert(ParentScope);
+  // Process late-parsed type attributes for the outermost record. Nested
+  // non-anonymous records are handled immediately after their declaration is
+  // parsed, which is when it is known whether the record is anonymous.
+  if (getLangOpts().ExperimentalLateParseAttributes &&
+      !ParentScope->isClassScope())
+    Actions.ProcessLateParsedTypeAttributes(
+        TagDecl, ParseLateParsedTypeAttributeCallback);
   StructScope.Exit();
   Actions.ActOnTagFinishDefinition(getCurScope(), TagDecl, T.getRange());
 }
@@ -6243,7 +6327,8 @@ bool Parser::isConstructorDeclarator(bool IsUnqualified, bool DeductionGuide,
 
 void Parser::ParseTypeQualifierListOpt(
     DeclSpec &DS, unsigned AttrReqs, bool AtomicOrPtrauthAllowed,
-    bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler) {
+    bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler,
+    LateParsedAttrList *LateAttrs) {
   if ((AttrReqs & AR_CXX11AttributesParsed) &&
       isAllowedCXX11AttributeSpecifier()) {
     ParsedAttributes Attrs(AttrFactory);
@@ -6405,7 +6490,9 @@ void Parser::ParseTypeQualifierListOpt(
       // recovery is graceful.
       if (AttrReqs & AR_GNUAttributesParsed ||
           AttrReqs & AR_GNUAttributesParsedAndRejected) {
-        ParseGNUAttributes(DS.getAttributes());
+
+        // FIXME: Late parse only when some flag is set.
+        ParseGNUAttributes(DS.getAttributes(), LateAttrs);
         continue; // do *not* consume the next token!
       }
       // otherwise, FALL THROUGH!
@@ -6561,6 +6648,8 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
     DeclSpec DS(AttrFactory);
     ParseTypeQualifierListOpt(DS);
 
+    assert(DS.getLateAttributes().empty());
+
     D.AddTypeInfo(
         DeclaratorChunk::getPipe(DS.getTypeQualifiers(), DS.getPipeLoc()),
         std::move(DS.getAttributes()), SourceLocation());
@@ -6588,26 +6677,41 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
                     ((D.getContext() != DeclaratorContext::CXXNew)
                          ? AR_GNUAttributesParsed
                          : AR_GNUAttributesParsedAndRejected);
+
+    // Late-parsed type attributes apply only to members and function
+    // parameters, not variables.
+    bool LateParsingContext = D.getContext() == DeclaratorContext::Member ||
+                              D.getContext() == DeclaratorContext::Prototype;
+
+    // No guard on ExperimentalLateParseAttributes is needed here;
+    // DS.getLateAttributes() already initializes with
+    // LateAttrParseExperimentalExtOnly.
+    LateParsedAttrList *LateAttrs =
+        LateParsingContext ? &DS.getLateAttributes() : nullptr;
+
     ParseTypeQualifierListOpt(DS, Reqs, /*AtomicOrPtrauthAllowed=*/true,
-                              !D.mayOmitIdentifier());
+                              !D.mayOmitIdentifier(), {}, LateAttrs);
     D.ExtendWithDeclSpec(DS);
 
     // Recursively parse the declarator.
     Actions.runWithSufficientStackSpace(
         D.getBeginLoc(), [&] { ParseDeclaratorInternal(D, DirectDeclParser); });
-    if (Kind == tok::star)
+    if (Kind == tok::star) {
       // Remember that we parsed a pointer type, and remember the type-quals.
       D.AddTypeInfo(DeclaratorChunk::getPointer(
                         DS.getTypeQualifiers(), Loc, DS.getConstSpecLoc(),
                         DS.getVolatileSpecLoc(), DS.getRestrictSpecLoc(),
                         DS.getAtomicSpecLoc(), DS.getUnalignedSpecLoc(),
                         DS.getOverflowBehaviorLoc(), DS.isWrapSpecified()),
-                    std::move(DS.getAttributes()), SourceLocation());
-    else
+                    std::move(DS.getAttributes()), SourceLocation(),
+                    std::move(DS.getLateAttributes()));
+    } else {
+      assert(DS.getLateAttributes().empty());
       // Remember that we parsed a Block type, and remember the type-quals.
       D.AddTypeInfo(
           DeclaratorChunk::getBlockPointer(DS.getTypeQualifiers(), Loc),
           std::move(DS.getAttributes()), SourceLocation());
+    }
   } else {
     // Is a reference
     DeclSpec DS(AttrFactory);
@@ -6660,6 +6764,8 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
       }
     }
 
+    assert(DS.getLateAttributes().empty());
+
     // Remember that we parsed a reference type.
     D.AddTypeInfo(DeclaratorChunk::getReference(DS.getTypeQualifiers(), Loc,
                                                 Kind == tok::amp),
@@ -7171,9 +7277,10 @@ void Parser::ParseParenDeclarator(Declarator &D) {
   // sort of paren this is.
   //
   ParsedAttributes attrs(AttrFactory);
+  LateParsedAttrList LateAttrs(true, true, true);
   bool RequiresArg = false;
   if (Tok.is(tok::kw___attribute)) {
-    ParseGNUAttributes(attrs);
+    ParseGNUAttributes(attrs, &LateAttrs);
 
     // We require that the argument list (if this is a non-grouping paren) be
     // present even if the attribute list was empty.
@@ -7228,7 +7335,7 @@ void Parser::ParseParenDeclarator(Declarator &D) {
     T.consumeClose();
     D.AddTypeInfo(
         DeclaratorChunk::getParen(T.getOpenLocation(), T.getCloseLocation()),
-        std::move(attrs), T.getCloseLocation());
+        std::move(attrs), T.getCloseLocation(), LateAttrs);
 
     D.setGroupingParens(hadGroupingParens);
 
@@ -7239,6 +7346,9 @@ void Parser::ParseParenDeclarator(Declarator &D) {
     return;
   }
 
+  assert(LateAttrs.empty() &&
+         "Late parsed type attribute on FirstParamAttr is dropped");
+
   // Okay, if this wasn't a grouping paren, it must be the start of a function
   // argument list.  Recognize that this declarator will never have an
   // identifier (and remember where it would have been), then call into
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 5d87453cf219e..0c26b8d3b341f 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -579,6 +579,12 @@ void Parser::Initialize() {
   }
 
   Actions.Initialize();
+  // Register callbacks so Sema can call back into the Parser to handle
+  // late-parsed type attributes (e.g. counted_by on struct fields), which
+  // may be processed at any point during parsing via ActOnFields.
+  Actions.SetLateParsedAttributeCallbacks(
+      GetLateParsedAttributeLocationCallback,
+      ProcessLateParsedTypeAttrCallback);
 
   // Prime the lexer look-ahead.
   ConsumeToken();
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index b01250ccd0ffc..a4a9515dc3dd4 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -28,11 +28,12 @@ getCountAttrKind(bool CountInBytes, bool OrNull) {
 
 static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) {
   const auto *RD = FD->getParent();
-  // An unnamed struct is treated as anonymous struct at this point.
-  // A struct may not be fully processed yet to determine
+  // An unnamed struct is anonymous struct only if it's not instantiated.
+  // However, the struct may not be fully processed yet to determine
   // whether it's anonymous or not. In that case, this function treats it as
   // an anonymous struct and tries to find a named parent.
-  while (RD && (RD->isAnonymousStructOrUnion() || RD->getName().empty())) {
+  while (RD && (RD->isAnonymousStructOrUnion() ||
+                (!RD->isCompleteDefinition() && RD->getName().empty()))) {
     const auto *Parent = dyn_cast<RecordDecl>(RD->getParent());
     if (!Parent)
       break;
@@ -49,10 +50,8 @@ enum class CountedByInvalidPointeeTypeKind {
   VALID,
 };
 
-bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
-                                     bool OrNull) {
-  // Check the context the attribute is used in
-
+bool Sema::CheckCountedByAttrOnFieldDecl(FieldDecl *FD, Expr *E,
+                                         bool CountInBytes, bool OrNull) {
   unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
 
   if (FD->getParent()->isUnion()) {
@@ -61,20 +60,7 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
     return true;
   }
 
-  const auto FieldTy = FD->getType();
-  if (FieldTy->isArrayType() && (CountInBytes || OrNull)) {
-    Diag(FD->getBeginLoc(),
-         diag::err_count_attr_not_on_ptr_or_flexible_array_member)
-        << Kind << FD->getLocation() << /* suggest counted_by */ 1;
-    return true;
-  }
-  if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) {
-    Diag(FD->getBeginLoc(),
-         diag::err_count_attr_not_on_ptr_or_flexible_array_member)
-        << Kind << FD->getLocation() << /* do not suggest counted_by */ 0;
-    return true;
-  }
-
+  const QualType FieldTy = FD->getType();
   LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
       LangOptions::StrictFlexArraysLevelKind::IncompleteOnly;
   if (FieldTy->isArrayType() &&
@@ -86,97 +72,7 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
     return true;
   }
 
-  CountedByInvalidPointeeTypeKind InvalidTypeKind =
-      CountedByInvalidPointeeTypeKind::VALID;
-  QualType PointeeTy;
-  int SelectPtrOrArr = 0;
-  if (FieldTy->isPointerType()) {
-    PointeeTy = FieldTy->getPointeeType();
-    SelectPtrOrArr = 0;
-  } else {
-    assert(FieldTy->isArrayType());
-    const ArrayType *AT = getASTContext().getAsArrayType(FieldTy);
-    PointeeTy = AT->getElementType();
-    SelectPtrOrArr = 1;
-  }
-  // Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means
-  // only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable
-  // when `FieldTy->isArrayType()`.
-  bool ShouldWarn = false;
-  if (PointeeTy->isAlwaysIncompleteType() && !CountInBytes) {
-    // In general using `counted_by` or `counted_by_or_null` on
-    // pointers where the pointee is an incomplete type are problematic. This is
-    // because it isn't possible to compute the pointer's bounds without knowing
-    // the pointee type size. At the same time it is common to forward declare
-    // types in header files.
-    //
-    // E.g.:
-    //
-    // struct Handle;
-    // struct Wrapper {
-    //   size_t count;
-    //   struct Handle* __counted_by(count) handles;
-    // }
-    //
-    // To allow the above code pattern but still prevent the pointee type from
-    // being incomplete in places where bounds checks are needed the following
-    // scheme is used:
-    //
-    // * When the pointee type might not always be an incomplete type (i.e.
-    // a type that is currently incomplete but might be completed later
-    // on in the translation unit) the attribute is allowed by this method
-    // but later uses of the FieldDecl are checked that the pointee type
-    // is complete see `BoundsSafetyCheckAssignmentToCountAttrPtr`,
-    // `BoundsSafetyCheckInitialization`, and
-    // `BoundsSafetyCheckUseOfCountAttrPtr`
-    //
-    // * When the pointee type is always an incomplete type (e.g.
-    // `void` in strict C mode) the attribute is disallowed by this method
-    // because we know the type can never be completed so there's no reason
-    // to allow it.
-    //
-    // Exception: void has an implicit size of 1 byte for pointer arithmetic
-    // (following GNU convention). Therefore, counted_by on void* is allowed
-    // and behaves equivalently to sized_by (treating the count as bytes).
-    bool IsVoidPtr = PointeeTy->isVoidType();
-    if (IsVoidPtr) {
-      // Emit a warning that this is a GNU extension.
-      Diag(FD->getBeginLoc(), diag::ext_gnu_counted_by_void_ptr) << Kind;
-      Diag(FD->getBeginLoc(), diag::note_gnu_counted_by_void_ptr_use_sized_by)
-          << Kind;
-      assert(InvalidTypeKind == CountedByInvalidPointeeTypeKind::VALID);
-    } else {
-      InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
-    }
-  } else if (PointeeTy->isSizelessType()) {
-    InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
-  } else if (PointeeTy->isFunctionType()) {
-    InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
-  } else if (PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
-    if (FieldTy->isArrayType() && !getLangOpts().BoundsSafety) {
-      // This is a workaround for the Linux kernel that has already adopted
-      // `counted_by` on a FAM where the pointee is a struct with a FAM. This
-      // should be an error because computing the bounds of the array cannot be
-      // done correctly without manually traversing every struct object in the
-      // array at runtime. To allow the code to be built this error is
-      // downgraded to a warning.
-      ShouldWarn = true;
-    }
-    InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER;
-  }
-
-  if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID) {
-    unsigned DiagID = ShouldWarn
-                          ? diag::warn_counted_by_attr_elt_type_unknown_size
-                          : diag::err_counted_by_attr_pointee_unknown_size;
-    Diag(FD->getBeginLoc(), DiagID)
-        << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
-        << (ShouldWarn ? 1 : 0) << Kind << FD->getSourceRange();
-    return true;
-  }
-
-  // Check the expression
-
+  // Validate the expression type
   if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
     Diag(E->getBeginLoc(), diag::err_count_attr_argument_not_integer)
         << Kind << E->getSourceRange();
@@ -191,6 +87,7 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
     return true;
   }
 
+  // Validate count field references
   auto *CountDecl = DRE->getDecl();
   FieldDecl *CountFD = dyn_cast<FieldDecl>(CountDecl);
   if (auto *IFD = dyn_cast<IndirectFieldDecl>(CountDecl)) {
@@ -212,9 +109,6 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
           << Kind << CountFD->getSourceRange();
       return true;
     }
-    // Whether CountRD is an anonymous struct is not determined at this
-    // point. Thus, an additional diagnostic in case it's not anonymous struct
-    // is done later in `Parser::ParseStructDeclaration`.
     auto *RD = GetEnclosingNamedOrTopAnonRecord(FD);
     auto *CountRD = GetEnclosingNamedOrTopAnonRecord(CountFD);
 
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 0059516649a86..8e7f72ed63467 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10,6 +10,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "TreeTransform.h"
 #include "TypeLocBuilder.h"
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
@@ -17172,7 +17173,7 @@ void Sema::ActOnFinishDelayedAttribute(Scope *S, Decl *D,
   // Always attach attributes to the underlying decl.
   if (TemplateDecl *TD = dyn_cast<TemplateDecl>(D))
     D = TD->getTemplatedDecl();
-  ProcessDeclAttributeList(S, D, Attrs);
+  ProcessDeclAttributeList(S, D, Attrs, ProcessDeclAttributeOptions());
   ProcessAPINotes(D);
 
   if (CXXMethodDecl *Method = dyn_cast_or_null<CXXMethodDecl>(D))
@@ -19891,6 +19892,328 @@ bool Sema::EntirelyFunctionPointers(const RecordDecl *Record) {
   return llvm::all_of(Record->decls(), IsFunctionPointerOrForwardDecl);
 }
 
+static QualType handleCountedByAttrField(Sema &S, QualType T, Decl *D,
+                                         const ParsedAttr &AL) {
+  if (!AL.diagnoseLangOpts(S))
+    return QualType();
+
+  assert(isa<FieldDecl>(D));
+
+  auto *CountExpr = AL.getArgAsExpr(0);
+  if (!CountExpr)
+    return QualType();
+
+  bool CountInBytes;
+  bool OrNull;
+  switch (AL.getKind()) {
+  case ParsedAttr::AT_CountedBy:
+    CountInBytes = false;
+    OrNull = false;
+    break;
+  case ParsedAttr::AT_CountedByOrNull:
+    CountInBytes = false;
+    OrNull = true;
+    break;
+  case ParsedAttr::AT_SizedBy:
+    CountInBytes = true;
+    OrNull = false;
+    break;
+  case ParsedAttr::AT_SizedByOrNull:
+    CountInBytes = true;
+    OrNull = true;
+    break;
+  default:
+    llvm_unreachable("unexpected counted_by family attribute");
+  }
+
+  return S.BuildCountAttributedArrayOrPointerType(T, CountExpr, CountInBytes,
+                                                  OrNull);
+}
+struct RebuildTypeWithLateParsedAttr
+    : TreeTransform<RebuildTypeWithLateParsedAttr> {
+  FieldDecl *FD;
+  Sema::ParseLateParsedTypeAttributeCB *ParseCallback;
+
+  RebuildTypeWithLateParsedAttr(Sema &SemaRef, FieldDecl *FD,
+                                Sema::ParseLateParsedTypeAttributeCB *ParseCB)
+      : TreeTransform(SemaRef), FD(FD), ParseCallback(ParseCB) {}
+
+  // Helper to check and diagnose if a type is CountAttributedType
+  bool diagnoseCountAttributedType(QualType Ty, SourceLocation Loc) {
+    if (const auto *CAT = Ty->getAs<CountAttributedType>()) {
+      SemaRef.Diag(Loc, diag::err_counted_by_on_nested_pointer)
+          << CAT->getKind();
+      FD->setInvalidDecl();
+      return true;
+    }
+    return false;
+  }
+
+  QualType TransformLateParsedAttrType(TypeLocBuilder &TLB,
+                                       LateParsedAttrTypeLoc TL) {
+    const LateParsedAttrType *LPA = TL.getTypePtr();
+    auto *LTA = LPA->getLateParsedAttribute();
+
+    assert(LTA && "LateParsedAttrType must have a LateParsedTypeAttribute");
+
+    AttributeFactory AF{};
+    ParsedAttributes Attrs(AF);
+
+    // Invoke the parser callback to parse and consume the cached tokens.
+    // ParseCallback is also responsible for deleting LTA.
+    assert(ParseCallback);
+    ParseCallback(LTA, &Attrs);
+
+    // Invalid argument
+    if (Attrs.empty())
+      return QualType();
+
+    assert(Attrs.size() == 1);
+    auto &AL = Attrs[0];
+
+    QualType InnerType = TransformType(TLB, TL.getInnerLoc());
+    if (InnerType.isNull()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    QualType T = handleCountedByAttrField(SemaRef, InnerType, FD, AL);
+    if (T.isNull()) {
+      AL.setInvalid();
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    AL.setUsedAsTypeAttr();
+
+    TLB.push<CountAttributedTypeLoc>(T);
+    return T;
+  }
+
+  QualType TransformPointerType(TypeLocBuilder &TLB, PointerTypeLoc TL) {
+    QualType PointeeType = getDerived().TransformType(TLB, TL.getPointeeLoc());
+    if (PointeeType.isNull()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    // Diagnose nested pointer with counted_by attribute
+    // e.g., int * __counted_by(n) *ptr;
+    if (diagnoseCountAttributedType(PointeeType, TL.getSigilLoc()))
+      return QualType();
+
+    QualType Result = TL.getType();
+    if (getDerived().AlwaysRebuild() ||
+        PointeeType != TL.getPointeeLoc().getType()) {
+      Result = getDerived().RebuildPointerType(PointeeType, TL.getSigilLoc());
+      if (Result.isNull()) {
+        FD->setInvalidDecl();
+        return QualType();
+      }
+    }
+
+    PointerTypeLoc NewTL = TLB.push<PointerTypeLoc>(Result);
+    NewTL.setSigilLoc(TL.getSigilLoc());
+    return Result;
+  }
+
+  QualType TransformConstantArrayType(TypeLocBuilder &TLB,
+                                      ConstantArrayTypeLoc TL) {
+    const ConstantArrayType *T = TL.getTypePtr();
+    QualType ElementType = getDerived().TransformType(TLB, TL.getElementLoc());
+    if (ElementType.isNull()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    // Diagnose array with element type having counted_by attribute
+    // e.g., int * __counted_by(n) arr[10];
+    if (diagnoseCountAttributedType(ElementType, TL.getLBracketLoc()))
+      return QualType();
+
+    // Continue with normal array transformation
+    Expr *OldSize = TL.getSizeExpr();
+    if (!OldSize)
+      OldSize = const_cast<Expr *>(T->getSizeExpr());
+    Expr *NewSize = nullptr;
+    if (OldSize) {
+      EnterExpressionEvaluationContext Unevaluated(
+          SemaRef, Sema::ExpressionEvaluationContext::ConstantEvaluated);
+      NewSize = getDerived().TransformExpr(OldSize).template getAs<Expr>();
+      NewSize = SemaRef.ActOnConstantExpression(NewSize).get();
+    }
+
+    QualType Result = TL.getType();
+    if (getDerived().AlwaysRebuild() || ElementType != T->getElementType() ||
+        (T->getSizeExpr() && NewSize != OldSize)) {
+      Result = getDerived().RebuildConstantArrayType(
+          ElementType, T->getSizeModifier(), T->getSize(), NewSize,
+          T->getIndexTypeCVRQualifiers(), TL.getBracketsRange());
+      if (Result.isNull()) {
+        FD->setInvalidDecl();
+        return QualType();
+      }
+    }
+
+    ArrayTypeLoc NewTL = TLB.push<ArrayTypeLoc>(Result);
+    NewTL.setLBracketLoc(TL.getLBracketLoc());
+    NewTL.setRBracketLoc(TL.getRBracketLoc());
+    NewTL.setSizeExpr(NewSize);
+
+    return Result;
+  }
+
+  QualType TransformIncompleteArrayType(TypeLocBuilder &TLB,
+                                        IncompleteArrayTypeLoc TL) {
+    const IncompleteArrayType *T = TL.getTypePtr();
+    QualType ElementType = getDerived().TransformType(TLB, TL.getElementLoc());
+    if (ElementType.isNull()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    // Diagnose flexible array member with element type having counted_by
+    // attribute e.g., int * __counted_by(n) arr[];
+    if (diagnoseCountAttributedType(ElementType, TL.getLBracketLoc()))
+      return QualType();
+
+    QualType Result = TL.getType();
+    if (getDerived().AlwaysRebuild() || ElementType != T->getElementType()) {
+      Result = getDerived().RebuildIncompleteArrayType(
+          ElementType, T->getSizeModifier(), T->getIndexTypeCVRQualifiers(),
+          TL.getBracketsRange());
+      if (Result.isNull()) {
+        FD->setInvalidDecl();
+        return QualType();
+      }
+    }
+
+    IncompleteArrayTypeLoc NewTL = TLB.push<IncompleteArrayTypeLoc>(Result);
+    NewTL.setLBracketLoc(TL.getLBracketLoc());
+    NewTL.setRBracketLoc(TL.getRBracketLoc());
+    NewTL.setSizeExpr(nullptr);
+
+    return Result;
+  }
+
+  QualType TransformVariableArrayType(TypeLocBuilder &TLB,
+                                      VariableArrayTypeLoc TL) {
+    const VariableArrayType *T = TL.getTypePtr();
+    QualType ElementType = getDerived().TransformType(TLB, TL.getElementLoc());
+    if (ElementType.isNull()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    // Diagnose VLA with element type having counted_by attribute
+    // e.g., int * __counted_by(n) arr[m];
+    if (diagnoseCountAttributedType(ElementType, TL.getLBracketLoc()))
+      return QualType();
+
+    // Transform the size expression
+    ExprResult SizeResult = getDerived().TransformExpr(T->getSizeExpr());
+    if (SizeResult.isInvalid()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+    Expr *Size = SizeResult.get();
+
+    QualType Result = TL.getType();
+    if (getDerived().AlwaysRebuild() || ElementType != T->getElementType() ||
+        Size != T->getSizeExpr()) {
+      Result = getDerived().RebuildVariableArrayType(
+          ElementType, T->getSizeModifier(), Size,
+          T->getIndexTypeCVRQualifiers(), TL.getBracketsRange());
+      if (Result.isNull()) {
+        FD->setInvalidDecl();
+        return QualType();
+      }
+    }
+
+    VariableArrayTypeLoc NewTL = TLB.push<VariableArrayTypeLoc>(Result);
+    NewTL.setLBracketLoc(TL.getLBracketLoc());
+    NewTL.setRBracketLoc(TL.getRBracketLoc());
+    NewTL.setSizeExpr(Size);
+
+    return Result;
+  }
+
+  QualType TransformDependentSizedArrayType(TypeLocBuilder &TLB,
+                                            DependentSizedArrayTypeLoc TL) {
+    const DependentSizedArrayType *T = TL.getTypePtr();
+    QualType ElementType = getDerived().TransformType(TLB, TL.getElementLoc());
+    if (ElementType.isNull()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    // Diagnose dependent-sized array with element type having counted_by
+    // attribute e.g., template<int N> struct S { int * __counted_by(n) arr[N];
+    // };
+    if (diagnoseCountAttributedType(ElementType, TL.getLBracketLoc()))
+      return QualType();
+
+    // Transform the size expression
+    EnterExpressionEvaluationContext Unevaluated(
+        SemaRef, Sema::ExpressionEvaluationContext::Unevaluated);
+    ExprResult SizeResult = getDerived().TransformExpr(T->getSizeExpr());
+    if (SizeResult.isInvalid()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+    Expr *Size = SizeResult.get();
+
+    QualType Result = TL.getType();
+    if (getDerived().AlwaysRebuild() || ElementType != T->getElementType() ||
+        Size != T->getSizeExpr()) {
+      Result = getDerived().RebuildDependentSizedArrayType(
+          ElementType, T->getSizeModifier(), Size,
+          T->getIndexTypeCVRQualifiers(), TL.getBracketsRange());
+      if (Result.isNull()) {
+        FD->setInvalidDecl();
+        return QualType();
+      }
+    }
+
+    DependentSizedArrayTypeLoc NewTL =
+        TLB.push<DependentSizedArrayTypeLoc>(Result);
+    NewTL.setLBracketLoc(TL.getLBracketLoc());
+    NewTL.setRBracketLoc(TL.getRBracketLoc());
+    NewTL.setSizeExpr(Size);
+
+    return Result;
+  }
+};
+
+void Sema::ProcessLateParsedTypeAttributes(
+    RecordDecl *EnclosingDecl, ParseLateParsedTypeAttributeCB *ParseCB) {
+  for (auto *I : EnclosingDecl->decls()) {
+    FieldDecl *FD = dyn_cast<FieldDecl>(I);
+    IndirectFieldDecl *IFD = dyn_cast<IndirectFieldDecl>(I);
+    if (!FD && IFD) {
+      FD = IFD->getAnonField();
+    }
+    if (!FD || FD->getType()->isRecordType())
+      continue;
+
+    RebuildTypeWithLateParsedAttr RebuildFieldType(*this, FD, ParseCB);
+    auto *OldTSI = FD->getTypeSourceInfo();
+    auto *TSI = RebuildFieldType.TransformType(FD->getTypeSourceInfo());
+    if (TSI && TSI != OldTSI) {
+      FD->setTypeSourceInfo(TSI);
+      FD->setType(TSI->getType());
+      if (IFD) {
+        IFD->setType(TSI->getType());
+      }
+    }
+
+    if (auto *CAT = FD->getType()->getAs<CountAttributedType>()) {
+      CheckCountedByAttrOnFieldDecl(FD, CAT->getCountExpr(),
+                                    CAT->isCountInBytes(), CAT->isOrNull());
+    }
+  }
+}
+
 void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
                        ArrayRef<Decl *> Fields, SourceLocation LBrac,
                        SourceLocation RBrac,
@@ -19928,6 +20251,17 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
     }
   }
 
+  if (!getLangOpts().ExperimentalLateParseAttributes) {
+    // Perform FieldDecl-dependent validation for counted_by family attributes
+    for (auto *D : Fields) {
+      FieldDecl *FD = cast<FieldDecl>(D);
+      if (auto *CAT = FD->getType()->getAs<CountAttributedType>()) {
+        CheckCountedByAttrOnFieldDecl(FD, CAT->getCountExpr(),
+                                      CAT->isCountInBytes(), CAT->isOrNull());
+      }
+    }
+  }
+
   // Verify that all the fields are okay.
   SmallVector<FieldDecl*, 32> RecFields;
   const FieldDecl *PreviousField = nullptr;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index c52b0f192454c..5a9a2c46e36e8 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6968,43 +6968,6 @@ static void handleNoPFPAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(NoFieldProtectionAttr::Create(S.Context, AL));
 }
 
-static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
-  auto *CountExpr = AL.getArgAsExpr(0);
-  if (!CountExpr)
-    return;
-
-  bool CountInBytes;
-  bool OrNull;
-  switch (AL.getKind()) {
-  case ParsedAttr::AT_CountedBy:
-    CountInBytes = false;
-    OrNull = false;
-    break;
-  case ParsedAttr::AT_CountedByOrNull:
-    CountInBytes = false;
-    OrNull = true;
-    break;
-  case ParsedAttr::AT_SizedBy:
-    CountInBytes = true;
-    OrNull = false;
-    break;
-  case ParsedAttr::AT_SizedByOrNull:
-    CountInBytes = true;
-    OrNull = true;
-    break;
-  default:
-    llvm_unreachable("unexpected counted_by family attribute");
-  }
-
-  FieldDecl *FD = cast<FieldDecl>(D);
-  if (S.CheckCountedByAttrOnField(FD, CountExpr, CountInBytes, OrNull))
-    return;
-
-  QualType CAT = S.BuildCountAttributedArrayOrPointerType(
-      FD->getType(), CountExpr, CountInBytes, OrNull);
-  FD->setType(CAT);
-}
-
 static void handleFunctionReturnThunksAttr(Sema &S, Decl *D,
                                            const ParsedAttr &AL) {
   StringRef KindStr;
@@ -8102,13 +8065,6 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
     handleAvailableOnlyInDefaultEvalMethod(S, D, AL);
     break;
 
-  case ParsedAttr::AT_CountedBy:
-  case ParsedAttr::AT_CountedByOrNull:
-  case ParsedAttr::AT_SizedBy:
-  case ParsedAttr::AT_SizedByOrNull:
-    handleCountedByAttrField(S, D, AL);
-    break;
-
   case ParsedAttr::AT_NoFieldProtection:
     handleNoPFPAttrField(S, D, AL);
     break;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 5b89236b11824..4e76b59531e36 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4653,6 +4653,7 @@ static void captureVariablyModifiedType(ASTContext &Context, QualType T,
     case Type::SubstTemplateTypeParm:
     case Type::MacroQualified:
     case Type::CountAttributed:
+    case Type::LateParsedAttr:
       // Keep walking after single level desugaring.
       T = T.getSingleStepDesugaredType(Context);
       break;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 44ac4f6630690..a33866b2f43f8 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -401,11 +401,22 @@ enum TypeAttrLocation {
   TAL_DeclName
 };
 
+static void fillAttrNameLocForLateParsedAttrTypeLoc(Sema &S,
+                                                    LateParsedAttrTypeLoc TL) {
+  if (auto *LateAttr = TL.getLateParsedAttribute())
+    if (S.GetLateParsedAttributeLocationCallback)
+      TL.setAttrNameLoc(S.GetLateParsedAttributeLocationCallback(LateAttr));
+}
+
 static void
 processTypeAttrs(TypeProcessingState &state, QualType &type,
                  TypeAttrLocation TAL, const ParsedAttributesView &attrs,
                  CUDAFunctionTarget CFT = CUDAFunctionTarget::HostDevice);
 
+static bool processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+                                 const LateParsedAttrList &LateAttrs,
+                                 unsigned chunkIndex = 0);
+
 static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
                                    QualType &type, CUDAFunctionTarget CFT);
 
@@ -1508,6 +1519,9 @@ static QualType ConvertDeclSpecToType(TypeProcessingState &state) {
     // are never distributed.
     processTypeAttrs(state, Result, TAL_DeclSpec, SlidingAttrs);
     processTypeAttrs(state, Result, TAL_DeclSpec, DS.getAttributes());
+
+    // Process the late attributes that appeared after the type name
+    processLateTypeAttrs(state, Result, DS.getLateAttributes());
   }
 
   // Apply const/volatile/restrict qualifiers to T.
@@ -5483,6 +5497,9 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
     processTypeAttrs(state, T, TAL_DeclChunk, DeclType.getAttrs(),
                      S.CUDA().IdentifyTarget(D.getAttributes()));
 
+    // TODO: Check the nested level here.
+    processLateTypeAttrs(state, T, DeclType.LateAttrList, chunkIndex);
+
     if (DeclType.Kind != DeclaratorChunk::Paren) {
       if (ExpectNoDerefChunk && !IsNoDerefableChunk(DeclType))
         S.Diag(DeclType.Loc, diag::warn_noderef_on_non_pointer_or_array);
@@ -5662,6 +5679,8 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
   processTypeAttrs(state, T, TAL_DeclName, NonSlidingAttrs);
   processTypeAttrs(state, T, TAL_DeclName, D.getAttributes());
 
+  processLateTypeAttrs(state, T, D.getLateAttributes());
+
   // Diagnose any ignored type attributes.
   state.diagnoseIgnoredTypeAttrs(T);
 
@@ -5965,6 +5984,10 @@ namespace {
       Visit(TL.getModifiedLoc());
       fillAttributedTypeLoc(TL, State);
     }
+    void VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+      Visit(TL.getInnerLoc());
+      fillAttrNameLocForLateParsedAttrTypeLoc(SemaRef, TL);
+    }
     void VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
       Visit(TL.getWrappedLoc());
     }
@@ -6253,6 +6276,9 @@ namespace {
     void VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
       // nothing
     }
+    void VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+      fillAttrNameLocForLateParsedAttrTypeLoc(State.getSema(), TL);
+    }
     void VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
       // nothing
     }
@@ -6420,6 +6446,14 @@ GetTypeSourceInfoForDeclarator(TypeProcessingState &State,
         break;
       }
 
+      case TypeLoc::LateParsedAttr: {
+        auto TL = CurrTL.castAs<LateParsedAttrTypeLoc>();
+        fillAttrNameLocForLateParsedAttrTypeLoc(S, TL);
+        CurrTL = TL.getNextTypeLoc().getUnqualifiedLoc();
+        break;
+      }
+
+      case TypeLoc::CountAttributed:
       case TypeLoc::Adjusted:
       case TypeLoc::BTFTagAttributed: {
         CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc();
@@ -9015,6 +9049,227 @@ static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
   }
 }
 
+static CountAttributedType::DynamicCountPointerKind
+getCountAttrKind(bool CountInBytes, bool OrNull) {
+  if (CountInBytes)
+    return OrNull ? CountAttributedType::SizedByOrNull
+                  : CountAttributedType::SizedBy;
+  return OrNull ? CountAttributedType::CountedByOrNull
+                : CountAttributedType::CountedBy;
+}
+
+enum class CountedByInvalidPointeeTypeKind {
+  INCOMPLETE,
+  SIZELESS,
+  FUNCTION,
+  FLEXIBLE_ARRAY_MEMBER,
+  VALID,
+};
+
+/// Calculate the pointer nesting level for counted_by attribute validation.
+/// Counts the number of pointer/array/function declarator chunks before the
+/// specified chunk index.
+///
+/// For example, given \c "int * __counted_by(n) *pp" the declarator chunks
+/// are (outermost first): [0]=Pointer(\c int **), [1]=Pointer(\c int *).
+/// When processing the inner pointer at \p chunkIndex=1, one Pointer chunk
+/// precedes it, so the function returns 1.
+///
+/// \param state The type processing state
+/// \param chunkIndex The index of the current declarator chunk
+/// \return The number of pointer/array/function chunks before chunkIndex
+static unsigned getPointerNestLevel(TypeProcessingState &state,
+                                    unsigned chunkIndex) {
+  unsigned pointerNestLevel = 0;
+  const auto &stateDeclarator = state.getDeclarator();
+  assert(chunkIndex <= stateDeclarator.getNumTypeObjects());
+  // DeclChunks are ordered identifier out. Index 0 is the outer most type
+  // object. Find outer pointer, array or function.
+  for (unsigned i = 0; i < chunkIndex; ++i) {
+    auto TypeObject = stateDeclarator.getTypeObject(i);
+    switch (TypeObject.Kind) {
+    case DeclaratorChunk::Function:
+    case DeclaratorChunk::Array:
+    case DeclaratorChunk::Pointer:
+      pointerNestLevel++;
+      break;
+    default:
+      break;
+    }
+  }
+  return pointerNestLevel;
+}
+
+static bool validateCountedByAttrType(Sema &S, QualType Ty,
+                                      ParsedAttr::Kind AttrKind,
+                                      SourceLocation AttrLoc,
+                                      unsigned pointerNestLevel,
+                                      bool &CountInBytes, bool &OrNull) {
+  switch (AttrKind) {
+  case ParsedAttr::AT_CountedBy:
+    CountInBytes = false;
+    OrNull = false;
+    break;
+  case ParsedAttr::AT_CountedByOrNull:
+    CountInBytes = false;
+    OrNull = true;
+    break;
+  case ParsedAttr::AT_SizedBy:
+    CountInBytes = true;
+    OrNull = false;
+    break;
+  case ParsedAttr::AT_SizedByOrNull:
+    CountInBytes = true;
+    OrNull = true;
+    break;
+  default:
+    llvm_unreachable("unexpected counted_by family attribute");
+  }
+
+  unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
+
+  if (Ty->isArrayType() && (CountInBytes || OrNull)) {
+    S.Diag(AttrLoc, diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+        << Kind << /* suggest counted_by */ 1;
+    return false;
+  }
+  if (!Ty->isArrayType() && !Ty->isPointerType()) {
+    S.Diag(AttrLoc, diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+        << Kind << /* do not suggest counted_by */ 0;
+    return false;
+  }
+
+  // Validate pointee type
+  CountedByInvalidPointeeTypeKind InvalidTypeKind =
+      CountedByInvalidPointeeTypeKind::VALID;
+  QualType PointeeTy;
+  int SelectPtrOrArr = 0;
+  if (Ty->isPointerType()) {
+    PointeeTy = Ty->getPointeeType();
+    SelectPtrOrArr = 0;
+  } else {
+    assert(Ty->isArrayType());
+    const ArrayType *AT = S.getASTContext().getAsArrayType(Ty);
+    PointeeTy = AT->getElementType();
+    SelectPtrOrArr = 1;
+  }
+
+  bool ShouldWarn = false;
+  if (PointeeTy->isAlwaysIncompleteType() && !CountInBytes) {
+    bool IsVoidPtr = PointeeTy->isVoidType();
+    if (IsVoidPtr) {
+      S.Diag(AttrLoc, diag::ext_gnu_counted_by_void_ptr) << Kind;
+      S.Diag(AttrLoc, diag::note_gnu_counted_by_void_ptr_use_sized_by) << Kind;
+      assert(InvalidTypeKind == CountedByInvalidPointeeTypeKind::VALID);
+    } else {
+      InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
+    }
+  } else if (PointeeTy->isSizelessType()) {
+    InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
+  } else if (PointeeTy->isFunctionType()) {
+    InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
+  } else if (PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
+    if (Ty->isArrayType() && !S.getLangOpts().BoundsSafety) {
+      ShouldWarn = true;
+    }
+    InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER;
+  }
+
+  if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID) {
+    unsigned DiagID = ShouldWarn
+                          ? diag::warn_counted_by_attr_elt_type_unknown_size
+                          : diag::err_counted_by_attr_pointee_unknown_size;
+    S.Diag(AttrLoc, DiagID)
+        << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
+        << (ShouldWarn ? 1 : 0) << Kind;
+    return false;
+  }
+
+  if (pointerNestLevel > 0) {
+    S.Diag(AttrLoc, diag::err_counted_by_on_nested_pointer) << Kind;
+    return false;
+  }
+
+  return true;
+}
+
+static void HandleCountedByAttrOnType(TypeProcessingState &State,
+                                      QualType &CurType, ParsedAttr &Attr) {
+  Sema &S = State.getSema();
+
+  // This attribute is only supported in C.
+  // FIXME: we should implement checkCommonAttributeFeatures() in SemaAttr.cpp
+  // such that it handles type attributes, and then call that from
+  // processTypeAttrs() instead of one-off checks like this.
+  if (!Attr.diagnoseLangOpts(S)) {
+    Attr.setInvalid();
+    return;
+  }
+
+  auto *CountExpr = Attr.getArgAsExpr(0);
+  if (!CountExpr)
+    return;
+
+  // This is a mechanism to prevent nested count pointer types in the contexts
+  // where late parsing isn't allowed: currently that is any context other than
+  // struct fields. In the context where late parsing is allowed, the level
+  // check will be done once the whole context is constructed.
+  unsigned chunkIndex = State.getCurrentChunkIndex();
+  unsigned pointerNestLevel = 0;
+
+  // Only calculate pointer nest level if we're processing a declarator chunk.
+  // For DeclSpec attributes, the declarator hasn't been constructed yet.
+  if (chunkIndex > 0) {
+    pointerNestLevel = getPointerNestLevel(State, chunkIndex);
+  }
+
+  bool CountInBytes, OrNull;
+  if (!validateCountedByAttrType(S, CurType, Attr.getKind(), Attr.getLoc(),
+                                 pointerNestLevel, CountInBytes, OrNull)) {
+    Attr.setInvalid();
+    return;
+  }
+
+  QualType NewType = S.BuildCountAttributedArrayOrPointerType(
+      CurType, CountExpr, CountInBytes, OrNull);
+  if (NewType.isNull()) {
+    Attr.setInvalid();
+    return;
+  }
+  CurType = NewType;
+}
+
+bool Sema::ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind,
+                                   SourceLocation AttrNameLoc, QualType &type,
+                                   unsigned pointerNestLevel,
+                                   LateParsedTypeAttribute *LTA) {
+  bool CountInBytes, OrNull;
+  if (!validateCountedByAttrType(*this, type, AttrKind, AttrNameLoc,
+                                 pointerNestLevel, CountInBytes, OrNull))
+    return false;
+  type = getASTContext().getLateParsedAttrType(type, LTA);
+  return true;
+}
+
+static bool processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+                                 const LateParsedAttrList &LateAttrs,
+                                 unsigned chunkIndex) {
+
+  if (LateAttrs.empty())
+    return true;
+
+  Sema &S = state.getSema();
+  unsigned pointerNestLevel = 0;
+
+  assert(S.ProcessLateParsedTypeAttrCallback);
+
+  for (auto *LA : LateAttrs)
+    if (!S.ProcessLateParsedTypeAttrCallback(LA, type, pointerNestLevel))
+      return false;
+
+  return true;
+}
+
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
                              TypeAttrLocation TAL,
                              const ParsedAttributesView &attrs,
@@ -9232,6 +9487,14 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
       break;
     }
 
+    case ParsedAttr::AT_CountedBy:
+    case ParsedAttr::AT_CountedByOrNull:
+    case ParsedAttr::AT_SizedBy:
+    case ParsedAttr::AT_SizedByOrNull:
+      HandleCountedByAttrOnType(state, type, attr);
+      attr.setUsedAsTypeAttr();
+      break;
+
     MS_TYPE_ATTRS_CASELIST:
       if (!handleMSPointerTypeQualifierAttr(state, attr, type))
         attr.setUsedAsTypeAttr();
@@ -9976,7 +10239,19 @@ QualType Sema::BuildCountAttributedArrayOrPointerType(QualType WrappedTy,
                                                       Expr *CountExpr,
                                                       bool CountInBytes,
                                                       bool OrNull) {
-  assert(WrappedTy->isIncompleteArrayType() || WrappedTy->isPointerType());
+  // Accept any array or pointer type here. For arrays, validation that it's
+  // a flexible array member is deferred until CheckCountedByAttrOnFieldDecl.
+  assert(WrappedTy->isArrayType() || WrappedTy->isPointerType());
+
+  // Reject non-DeclRefExpr early to avoid cast failure in
+  // BuildTypeCoupledDecls.
+  if (!isa<DeclRefExpr>(CountExpr)) {
+    unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
+    Diag(CountExpr->getBeginLoc(),
+         diag::err_count_attr_only_support_simple_decl_reference)
+        << Kind << CountExpr->getSourceRange();
+    return QualType();
+  }
 
   llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls;
   BuildTypeCoupledDecls(CountExpr, Decls);
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 9c0479fe8aa4f..ff8b99a4f4971 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -7760,6 +7760,26 @@ QualType TreeTransform<Derived>::TransformCountAttributedType(
   return Result;
 }
 
+template <typename Derived>
+QualType
+TreeTransform<Derived>::TransformLateParsedAttrType(TypeLocBuilder &TLB,
+                                                    LateParsedAttrTypeLoc TL) {
+  const LateParsedAttrType *OldTy = TL.getTypePtr();
+  QualType InnerTy = getDerived().TransformType(TLB, TL.getInnerLoc());
+  if (InnerTy.isNull())
+    return QualType();
+
+  QualType Result = TL.getType();
+  if (getDerived().AlwaysRebuild() || InnerTy != OldTy->getWrappedType()) {
+    Result = SemaRef.Context.getLateParsedAttrType(
+        InnerTy, OldTy->getLateParsedAttribute());
+  }
+
+  LateParsedAttrTypeLoc newTL = TLB.push<LateParsedAttrTypeLoc>(Result);
+  newTL.setAttrNameLoc(TL.getAttrNameLoc());
+  return Result;
+}
+
 template <typename Derived>
 QualType TreeTransform<Derived>::TransformBTFTagAttributedType(
     TypeLocBuilder &TLB, BTFTagAttributedTypeLoc TL) {
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 06bc8f8c8c13e..fc56d8888b5b0 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -7701,6 +7701,10 @@ void TypeLocReader::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
   // Nothing to do
 }
 
+void TypeLocReader::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+  // Nothing to do
+}
+
 void TypeLocReader::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
   // Nothing to do.
 }
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 074b0fccdb65d..fd947aa24c50d 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -156,6 +156,9 @@ static TypeCode getTypeCodeForTypeClass(Type::TypeClass id) {
 #define TYPE_BIT_CODE(CLASS_ID, CODE_ID, CODE_VALUE) \
   case Type::CLASS_ID: return TYPE_##CODE_ID;
 #include "clang/Serialization/TypeBitCodes.def"
+  case Type::LateParsedAttr:
+    llvm_unreachable(
+        "should be replaced with a concrete type before serialization");
   case Type::Builtin:
     llvm_unreachable("shouldn't be serializing a builtin type this way");
   }
@@ -600,6 +603,10 @@ void TypeLocWriter::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
   // Nothing to do
 }
 
+void TypeLocWriter::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+  // Nothing to do
+}
+
 void TypeLocWriter::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
   // Nothing to do.
 }
diff --git a/clang/test/AST/attr-counted-by-or-null-struct-ptrs.c b/clang/test/AST/attr-counted-by-or-null-struct-ptrs.c
index d42547003f0b3..ca603402f0a34 100644
--- a/clang/test/AST/attr-counted-by-or-null-struct-ptrs.c
+++ b/clang/test/AST/attr-counted-by-or-null-struct-ptrs.c
@@ -56,29 +56,6 @@ struct on_member_pointer_complete_ty_ty_pos {
   struct size_known *__counted_by_or_null(count) buf;
 };
 
-// TODO: This should be forbidden but isn't due to counted_by_or_null being treated as a
-// declaration attribute. The attribute ends up on the outer most pointer
-// (allowed by sema) even though syntactically its supposed to be on the inner
-// pointer (would not allowed by sema due to pointee being a function type).
-// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_fn_ptr_ty_ty_pos_inner definition
-// CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT:  `-FieldDecl {{.+}} fn_ptr 'void (** __counted_by_or_null(count))(void)':'void (**)(void)'
-struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
-  int count;
-  void (* __counted_by_or_null(count) * fn_ptr)(void);
-};
-
-// FIXME: The generated AST here is wrong. The attribute should be on the inner
-// pointer.
-// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_inner definition
-// CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT:  `-FieldDecl {{.+}} buf 'struct size_known ** __counted_by_or_null(count)':'struct size_known **'
-struct on_nested_pointer_inner {
-  int count;
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__counted_by_or_null` can only be nested when used in function parameters.
-  struct size_known *__counted_by_or_null(count) *buf;
-};
 
 // CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_outer definition
 // CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
diff --git a/clang/test/AST/attr-counted-by-struct-ptrs.c b/clang/test/AST/attr-counted-by-struct-ptrs.c
index afef9c8c3b95d..414a7007c7b49 100644
--- a/clang/test/AST/attr-counted-by-struct-ptrs.c
+++ b/clang/test/AST/attr-counted-by-struct-ptrs.c
@@ -45,8 +45,6 @@ struct on_pointer_anon_count {
 //==============================================================================
 // __counted_by on struct member pointer in type attribute position
 //==============================================================================
-// TODO: Correctly parse counted_by as a type attribute. Currently it is parsed
-// as a declaration attribute
 
 // CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_complete_ty_ty_pos definition
 // CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
@@ -56,30 +54,6 @@ struct on_member_pointer_complete_ty_ty_pos {
   struct size_known *__counted_by(count) buf;
 };
 
-// TODO: This should be forbidden but isn't due to counted_by being treated as a
-// declaration attribute. The attribute ends up on the outer most pointer
-// (allowed by sema) even though syntactically its supposed to be on the inner
-// pointer (would not allowed by sema due to pointee being a function type).
-// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_fn_ptr_ty_ty_pos_inner definition
-// CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT:  `-FieldDecl {{.+}} fn_ptr 'void (** __counted_by(count))(void)':'void (**)(void)'
-struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
-  int count;
-  void (* __counted_by(count) * fn_ptr)(void);
-};
-
-// FIXME: The generated AST here is wrong. The attribute should be on the inner
-// pointer.
-// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_inner definition
-// CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT:  `-FieldDecl {{.+}} buf 'struct size_known ** __counted_by(count)':'struct size_known **'
-struct on_nested_pointer_inner {
-  int count;
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__counted_by` can only be nested when used in function parameters.
-  struct size_known *__counted_by(count) *buf;
-};
-
 // CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_outer definition
 // CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
 // CHECK-NEXT:  `-FieldDecl {{.+}} buf 'struct size_known ** __counted_by(count)':'struct size_known **'
diff --git a/clang/test/AST/attr-sized-by-or-null-struct-ptrs.c b/clang/test/AST/attr-sized-by-or-null-struct-ptrs.c
index 7273280e4b60c..2592d0fd5cb4e 100644
--- a/clang/test/AST/attr-sized-by-or-null-struct-ptrs.c
+++ b/clang/test/AST/attr-sized-by-or-null-struct-ptrs.c
@@ -56,30 +56,6 @@ struct on_member_pointer_complete_ty_ty_pos {
   struct size_known *__sized_by_or_null(count) buf;
 };
 
-// TODO: This should be forbidden but isn't due to sized_by_or_null being treated as a
-// declaration attribute. The attribute ends up on the outer most pointer
-// (allowed by sema) even though syntactically its supposed to be on the inner
-// pointer (would not allowed by sema due to pointee being a function type).
-// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_fn_ptr_ty_ty_pos_inner definition
-// CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT:  `-FieldDecl {{.+}} fn_ptr 'void (** __sized_by_or_null(count))(void)':'void (**)(void)'
-struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
-  int count;
-  void (* __sized_by_or_null(count) * fn_ptr)(void);
-};
-
-// FIXME: The generated AST here is wrong. The attribute should be on the inner
-// pointer.
-// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_inner definition
-// CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT:  `-FieldDecl {{.+}} buf 'struct size_known ** __sized_by_or_null(count)':'struct size_known **'
-struct on_nested_pointer_inner {
-  int count;
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__sized_by_or_null` can only be nested when used in function parameters.
-  struct size_known *__sized_by_or_null(count) *buf;
-};
-
 // CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_outer definition
 // CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
 // CHECK-NEXT:  `-FieldDecl {{.+}} buf 'struct size_known ** __sized_by_or_null(count)':'struct size_known **'
diff --git a/clang/test/AST/attr-sized-by-struct-ptrs.c b/clang/test/AST/attr-sized-by-struct-ptrs.c
index 738eaf8cbf36b..4d7797fa82395 100644
--- a/clang/test/AST/attr-sized-by-struct-ptrs.c
+++ b/clang/test/AST/attr-sized-by-struct-ptrs.c
@@ -56,30 +56,6 @@ struct on_member_pointer_complete_ty_ty_pos {
   struct size_known *__sized_by(count) buf;
 };
 
-// TODO: This should be forbidden but isn't due to sized_by being treated as a
-// declaration attribute. The attribute ends up on the outer most pointer
-// (allowed by sema) even though syntactically its supposed to be on the inner
-// pointer (would not allowed by sema due to pointee being a function type).
-// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_fn_ptr_ty_ty_pos_inner definition
-// CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT:  `-FieldDecl {{.+}} fn_ptr 'void (** __sized_by(count))(void)':'void (**)(void)'
-struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
-  int count;
-  void (* __sized_by(count) * fn_ptr)(void);
-};
-
-// FIXME: The generated AST here is wrong. The attribute should be on the inner
-// pointer.
-// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_inner definition
-// CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT:  `-FieldDecl {{.+}} buf 'struct size_known ** __sized_by(count)':'struct size_known **'
-struct on_nested_pointer_inner {
-  int count;
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__sized_by` can only be nested when used in function parameters.
-  struct size_known *__sized_by(count) *buf;
-};
-
 // CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_outer definition
 // CHECK-NEXT:  |-FieldDecl {{.+}} referenced count 'int'
 // CHECK-NEXT:  `-FieldDecl {{.+}} buf 'struct size_known ** __sized_by(count)':'struct size_known **'
diff --git a/clang/test/Modules/bounds-safety-attributed-type-late-parsed.c b/clang/test/Modules/bounds-safety-attributed-type-late-parsed.c
new file mode 100644
index 0000000000000..58df0761f2022
--- /dev/null
+++ b/clang/test/Modules/bounds-safety-attributed-type-late-parsed.c
@@ -0,0 +1,111 @@
+// Test serialization of late-parsed bounds-safety attributes via Modules
+// This verifies that LateParsedAttrType is transformed to CountAttributedType
+// before serialization and remains as CountAttributedType after deserialization.
+
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fmodules -fmodules-cache-path=%t -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fmodules -fmodules-cache-path=%t -ast-dump-all %s | FileCheck %s
+// expected-no-diagnostics
+
+#pragma clang module build bounds_safety_late_parsed
+module bounds_safety_late_parsed {}
+#pragma clang module contents
+#pragma clang module begin bounds_safety_late_parsed
+
+// Test where counted_by references a field declared later
+struct LateRefPointer {
+  int *__attribute__((counted_by(count))) buf;
+  int count;
+};
+
+// Test with sized_by referencing later field
+struct LateRefSized {
+  int *__attribute__((sized_by(size))) data;
+  int size;
+};
+
+// Test with counted_by_or_null referencing later field
+struct LateRefCountedByOrNull {
+  int *__attribute__((counted_by_or_null(count))) buf;
+  int count;
+};
+
+// Test with sized_by_or_null referencing later field
+struct LateRefSizedByOrNull {
+  int *__attribute__((sized_by_or_null(size))) data;
+  int size;
+};
+
+// Test with nested struct
+struct LateRefNested {
+  struct Inner {
+    int value;
+  } *__attribute__((counted_by(n))) items;
+  int n;
+};
+
+// Test with multiple late-parsed attributes
+struct MultipleLateRefs {
+  int *__attribute__((counted_by(count1))) buf1;
+  int *__attribute__((sized_by(count2))) buf2;
+  int *__attribute__((counted_by_or_null(count3))) buf3;
+  int *__attribute__((sized_by_or_null(count4))) buf4;
+  int count1;
+  int count2;
+  int count3;
+  int count4;
+};
+
+#pragma clang module end
+#pragma clang module endbuild
+
+#pragma clang module import bounds_safety_late_parsed
+
+struct LateRefPointer *p1;
+struct LateRefSized *p2;
+struct LateRefCountedByOrNull *p3;
+struct LateRefSizedByOrNull *p4;
+struct LateRefNested *p5;
+struct MultipleLateRefs *p6;
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefPointer definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf 'int * __counted_by(count)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefSized definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed data 'int * __sized_by(size)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced size 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefCountedByOrNull definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf 'int * __counted_by_or_null(count)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefSizedByOrNull definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed data 'int * __sized_by_or_null(size)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced size 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefNested definition
+// CHECK: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed items 'struct Inner * __counted_by(n)':'struct Inner *'
+// CHECK: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced n 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct MultipleLateRefs definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf1 'int * __counted_by(count1)':'int *'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf2 'int * __sized_by(count2)':'int *'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf3 'int * __counted_by_or_null(count3)':'int *'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf4 'int * __sized_by_or_null(count4)':'int *'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count1 'int'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count2 'int'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count3 'int'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count4 'int'
+
+// Verify that LateParsedAttrType does not appear in the AST dump
+// CHECK-NOT: LateParsedAttr
+
+// Verify the import and variable declarations
+// CHECK: ImportDecl {{.*}} implicit bounds_safety_late_parsed
+// CHECK: VarDecl {{.*}} p1 'struct LateRefPointer *'
+// CHECK: VarDecl {{.*}} p2 'struct LateRefSized *'
+// CHECK: VarDecl {{.*}} p3 'struct LateRefCountedByOrNull *'
+// CHECK: VarDecl {{.*}} p4 'struct LateRefSizedByOrNull *'
+// CHECK: VarDecl {{.*}} p5 'struct LateRefNested *'
+// CHECK: VarDecl {{.*}} p6 'struct MultipleLateRefs *'
diff --git a/clang/test/PCH/Inputs/bounds-safety-attributed-type-late-parsed.h b/clang/test/PCH/Inputs/bounds-safety-attributed-type-late-parsed.h
new file mode 100644
index 0000000000000..c0751408045c7
--- /dev/null
+++ b/clang/test/PCH/Inputs/bounds-safety-attributed-type-late-parsed.h
@@ -0,0 +1,58 @@
+// Header for testing late-parsed bounds-safety attributes serialization
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+#define __sized_by(f)  __attribute__((sized_by(f)))
+#define __counted_by_or_null(f)  __attribute__((counted_by_or_null(f)))
+#define __sized_by_or_null(f)  __attribute__((sized_by_or_null(f)))
+
+// Test where counted_by references a field declared later
+struct LateRefPointer {
+  int *__counted_by(count) buf;
+  int count;
+};
+
+// Test with sized_by referencing later field
+struct LateRefSized {
+  int *__sized_by(size) data;
+  int size;
+};
+
+// Test with counted_by_or_null referencing later field
+struct LateRefCountedByOrNull {
+  int *__counted_by_or_null(count) buf;
+  int count;
+};
+
+// Test with sized_by_or_null referencing later field
+struct LateRefSizedByOrNull {
+  int *__sized_by_or_null(size) data;
+  int size;
+};
+
+// Test with nested struct
+struct LateRefNested {
+  struct Inner {
+    int value;
+  } *__counted_by(n) items;
+  int n;
+};
+
+// Test with multiple late-parsed attributes
+struct MultipleLateRefs {
+  int *__counted_by(count1) buf1;
+  int *__sized_by(count2) buf2;
+  int *__counted_by_or_null(count3) buf3;
+  int *__sized_by_or_null(count4) buf4;
+  int count1;
+  int count2;
+  int count3;
+  int count4;
+};
+
+// Test with anonymous struct/union
+struct LateRefAnon {
+  int *__counted_by(count) buf;
+  struct {
+    int count;
+  };
+};
diff --git a/clang/test/PCH/bounds-safety-attributed-type-late-parsed.c b/clang/test/PCH/bounds-safety-attributed-type-late-parsed.c
new file mode 100644
index 0000000000000..1d77bbe13927c
--- /dev/null
+++ b/clang/test/PCH/bounds-safety-attributed-type-late-parsed.c
@@ -0,0 +1,69 @@
+// Test serialization of late-parsed bounds-safety attributes via PCH
+// This verifies that LateParsedAttrType is transformed to CountAttributedType
+// before serialization and remains as CountAttributedType after deserialization.
+
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -include %S/Inputs/bounds-safety-attributed-type-late-parsed.h -fsyntax-only -verify %s
+
+// Test with pch.
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -emit-pch -o %t %S/Inputs/bounds-safety-attributed-type-late-parsed.h
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -include-pch %t -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -include-pch %t -ast-print %s | FileCheck %s --check-prefix PRINT
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -include-pch %t -ast-dump-all %s | FileCheck %s --check-prefix DUMP
+// expected-no-diagnostics
+
+// PRINT: struct LateRefPointer {
+// PRINT-NEXT:     int * __counted_by(count)buf;
+// PRINT-NEXT:     int count;
+// PRINT-NEXT: };
+
+// PRINT: struct LateRefSized {
+// PRINT-NEXT:     int * __sized_by(size)data;
+// PRINT-NEXT:     int size;
+// PRINT-NEXT: };
+
+// PRINT: struct LateRefCountedByOrNull {
+// PRINT-NEXT:     int * __counted_by_or_null(count)buf;
+// PRINT-NEXT:     int count;
+// PRINT-NEXT: };
+
+// PRINT: struct LateRefSizedByOrNull {
+// PRINT-NEXT:     int * __sized_by_or_null(size)data;
+// PRINT-NEXT:     int size;
+// PRINT-NEXT: };
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefPointer definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf 'int * __counted_by(count)':'int *'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced count 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefSized definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported data 'int * __sized_by(size)':'int *'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced size 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefCountedByOrNull definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf 'int * __counted_by_or_null(count)':'int *'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced count 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefSizedByOrNull definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported data 'int * __sized_by_or_null(size)':'int *'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced size 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefNested definition
+// DUMP: |-FieldDecl {{.*}} imported items 'struct Inner * __counted_by(n)':'struct Inner *'
+// DUMP: `-FieldDecl {{.*}} imported referenced n 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct MultipleLateRefs definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf1 'int * __counted_by(count1)':'int *'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf2 'int * __sized_by(count2)':'int *'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf3 'int * __counted_by_or_null(count3)':'int *'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf4 'int * __sized_by_or_null(count4)':'int *'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported referenced count1 'int'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported referenced count2 'int'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported referenced count3 'int'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced count4 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefAnon definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf 'int * __counted_by(count)':'int *'
+// DUMP: `-IndirectFieldDecl {{.*}} imported implicit referenced count 'int'
+
+// Verify that LateParsedAttrType does not appear in the AST dump
+// DUMP-NOT: LateParsedAttr
diff --git a/clang/test/Sema/attr-bounds-safety-function-ptr-param.c b/clang/test/Sema/attr-bounds-safety-function-ptr-param.c
new file mode 100644
index 0000000000000..091220e313958
--- /dev/null
+++ b/clang/test/Sema/attr-bounds-safety-function-ptr-param.c
@@ -0,0 +1,173 @@
+// XFAIL: *
+// FIXME: https://github.com/llvm/llvm-project/issues/166454
+
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+#define __counted_by(N) __attribute__((counted_by(N)))
+#define __counted_by_or_null(N) __attribute__((counted_by_or_null(N)))
+#define __sized_by(N) __attribute__((sized_by(N)))
+#define __sized_by_or_null(N) __attribute__((sized_by_or_null(N)))
+
+//==============================================================================
+// Test bounds safety attributes on function pointer parameters
+//==============================================================================
+
+struct counted_by_function_pointer_param {
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(int *__counted_by(len));
+  int len;
+};
+
+struct counted_by_or_null_function_pointer_param {
+  // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(int *__counted_by_or_null(len));
+  int len;
+};
+
+struct sized_by_function_pointer_param {
+  // expected-error at +1{{'sized_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(char *__sized_by(len));
+  int len;
+};
+
+struct sized_by_or_null_function_pointer_param {
+  // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(char *__sized_by_or_null(len));
+  int len;
+};
+
+//==============================================================================
+// Test multiple parameters with bounds safety attributes
+//==============================================================================
+
+struct multiple_params_with_bounds_safety {
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*multi_callback)(int *__counted_by(len1), char *data, int len1);
+  int len1;
+};
+
+struct mixed_bounds_safety_params {
+  // expected-error at +2{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*mixed_callback)(int *__counted_by(count), char *__sized_by_or_null(size), int count, int size);
+  int count;
+  int size;
+};
+
+//==============================================================================
+// Test cases that do not require late parsing (count field defined before use)
+//==============================================================================
+
+struct counted_by_no_late_parse {
+  int len;
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(int *__counted_by(len));
+};
+
+struct counted_by_or_null_no_late_parse {
+  int len;
+  // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(int *__counted_by_or_null(len));
+};
+
+struct sized_by_no_late_parse {
+  int len;
+  // expected-error at +1{{'sized_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(char *__sized_by(len));
+};
+
+struct sized_by_or_null_no_late_parse {
+  int len;
+  // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(char *__sized_by_or_null(len));
+};
+
+//==============================================================================
+// Test nested function pointer types
+//==============================================================================
+
+struct nested_function_pointer_with_bounds_safety {
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*outer_callback)(int (*inner)(int *__counted_by(len)), int len);
+  int len;
+};
+
+//==============================================================================
+// Test struct members with anonymous structs/unions (no late parsing needed)
+//==============================================================================
+
+struct with_anonymous_struct_no_late_parse {
+  int len;
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(int *__counted_by(len));
+};
+
+struct with_anonymous_union_no_late_parse {
+  union {
+    int len;
+    float f_len;
+  };
+  // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(int *__counted_by_or_null(len));
+};
+
+//==============================================================================
+// Test with different parameter positions
+//==============================================================================
+
+struct first_param_bounds_safety_no_late_parse {
+  int count;
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(int *__counted_by(count), void *data, int extra);
+};
+
+struct middle_param_bounds_safety_no_late_parse {
+  int size;
+  // expected-error at +1{{'sized_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(void *prefix, char *__sized_by(size), int suffix);
+};
+
+struct last_param_bounds_safety_no_late_parse {
+  int len;
+  // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(int a, float b, int *__counted_by_or_null(len));
+};
+
+//==============================================================================
+// Test with const and volatile qualifiers
+//==============================================================================
+
+struct const_param_bounds_safety_no_late_parse {
+  int count;
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(const int *__counted_by(count));
+};
+
+struct volatile_param_bounds_safety_no_late_parse {
+  int size;
+  // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(volatile char *__sized_by_or_null(size));
+};
+
+struct const_volatile_param_bounds_safety_no_late_parse {
+  int len;
+  // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback)(const volatile int *__counted_by_or_null(len));
+};
+
+//==============================================================================
+// Test with multiple function pointers in same struct
+//==============================================================================
+
+struct multiple_function_pointers_no_late_parse {
+  int len1, len2, size1, size2;
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback1)(int *__counted_by(len1));
+  // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  int (*callback2)(int *__counted_by_or_null(len2));
+  // expected-error at +1{{'sized_by' attribute cannot be applied to a parameter in a function pointer type}}
+  void (*callback3)(char *__sized_by(size1));
+  // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+  void (*callback4)(char *__sized_by_or_null(size2));
+};
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
index 443ccbbae66db..554bcfbd8d5c7 100644
--- a/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
@@ -29,9 +29,8 @@ struct on_member_pointer_const_incomplete_ty {
 };
 
 struct on_member_pointer_void_ty {
-  // expected-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
-  // expected-note at +1{{use '__sized_by' to suppress this warning}}
-  void* buf __counted_by(count);
+  // expected-warning at +1{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+  void* buf __counted_by(count); // expected-note{{use '__sized_by' to suppress this warning}}
   int count;
 };
 
@@ -88,9 +87,7 @@ struct on_member_pointer_struct_with_annotated_vla {
 };
 
 struct on_pointer_anon_buf {
-  // TODO: Support referring to parent scope
   struct {
-    // expected-error at +1{{use of undeclared identifier 'count'}}
     struct size_known *buf __counted_by(count);
   };
   int count;
@@ -106,131 +103,113 @@ struct on_pointer_anon_count {
 //==============================================================================
 // __counted_by on struct member pointer in type attribute position
 //==============================================================================
-// TODO: Correctly parse counted_by as a type attribute. Currently it is parsed
-// as a declaration attribute and is **not** late parsed resulting in the `count`
-// field being unavailable.
 
 struct on_member_pointer_complete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   struct size_known *__counted_by(count) buf;
   int count;
 };
 
 struct on_member_pointer_incomplete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   struct size_unknown * __counted_by(count) buf;
   int count;
 };
 
 struct on_member_pointer_const_incomplete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   const struct size_unknown * __counted_by(count) buf;
   int count;
 };
 
 struct on_member_pointer_void_ty_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being an incomplete type.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
-  void *__counted_by(count) buf;
+  // expected-warning at +1{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+  void *__counted_by(count) buf; // expected-note{{use '__sized_by' to suppress this warning}}
   int count;
 };
 
 // -
 
 struct on_member_pointer_fn_ptr_ty_pos {
-  // TODO: buffer of `count` function pointers should be allowed
-  // but fails because this isn't late parsed.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   void (** __counted_by(count) fn_ptr)(void);
   int count;
 };
 
 struct on_member_pointer_fn_ptr_ty_ptr_ty_pos {
-  // TODO: buffer of `count` function pointers should be allowed
-  // but fails because this isn't late parsed.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   fn_ptr_ty* __counted_by(count) fn_ptr;
   int count;
 };
 
 struct on_member_pointer_fn_ty_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a function type.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __counted_by(count) fn_ptr)(void);
   int count;
 };
 
 struct on_member_pointer_fn_ptr_ty_ty_pos {
-  // TODO: buffer of `count` function pointers should be allowed
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   void (** __counted_by(count) fn_ptr)(void);
   int count;
 };
 
 struct on_member_pointer_fn_ptr_ty_typedef_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a function type.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   fn_ptr_ty __counted_by(count) fn_ptr;
   int count;
 };
 
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a function type.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __counted_by(count) * fn_ptr)(void);
   int count;
 };
 
+struct on_member_ptr_ptr_fn_ptr_ty_ty_pos_inner {
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  void (**__counted_by(count) * fn_ptr)(void);
+  int count;
+};
+
 struct on_member_pointer_struct_with_vla_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a struct type with a VLA.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
   struct has_unannotated_vla *__counted_by(count) objects;
   int count;
 };
 
 struct on_member_pointer_struct_with_annotated_vla_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a struct type with a VLA.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct has_annotated_vla' is a struct type with a flexible array member}}
   struct has_annotated_vla* __counted_by(count) objects;
   int count;
 };
 
 struct on_nested_pointer_inner {
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__counted_by` can only be nested when used in function parameters.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
   struct size_known *__counted_by(count) *buf;
   int count;
 };
 
 struct on_nested_pointer_outer {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   struct size_known **__counted_by(count) buf;
   int count;
 };
 
+struct on_nested_pointer_array_inner {
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  struct size_known *__counted_by(count) arr[10];
+  int count;
+};
+
+struct on_nested_pointer_flexible_array_inner {
+  // expected-error at +2{{flexible array member 'arr' with type 'struct size_known *[]' is not at the end of struct}}
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  struct size_known *__counted_by(count) arr[];
+  int count; // expected-note{{next field declaration is here}}
+};
+
 struct on_pointer_anon_buf_ty_pos {
   struct {
-    // TODO: Support referring to parent scope
-    // expected-error at +1{{use of undeclared identifier 'count'}}
     struct size_known * __counted_by(count) buf;
   };
   int count;
 };
 
 struct on_pointer_anon_count_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   struct size_known *__counted_by(count) buf;
   struct {
     int count;
diff --git a/clang/test/Sema/attr-counted-by-or-null-last-field.c b/clang/test/Sema/attr-counted-by-or-null-last-field.c
index d0c50a733acef..9a1cae59f5282 100644
--- a/clang/test/Sema/attr-counted-by-or-null-last-field.c
+++ b/clang/test/Sema/attr-counted-by-or-null-last-field.c
@@ -128,9 +128,7 @@ struct on_member_ptr_incomplete_const_ty_ty_pos {
 
 struct on_member_ptr_void_ty_ty_pos {
   int count;
-  // expected-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
-  // expected-note at +1{{use '__sized_by_or_null' to suppress this warning}}
-  void * ptr __counted_by_or_null(count);
+  void * ptr __counted_by_or_null(count); // expected-warning{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} expected-note{{use '__sized_by_or_null' to suppress this warning}}
 };
 
 typedef void(fn_ty)(int);
diff --git a/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
index 233b729f87ccd..9f91f66b6e1c4 100644
--- a/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
@@ -30,9 +30,7 @@ struct on_member_pointer_const_incomplete_ty {
 };
 
 struct on_member_pointer_void_ty {
-  // expected-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
-  // expected-note at +1{{use '__sized_by_or_null' to suppress this warning}}
-  void* buf __counted_by_or_null(count);
+  void* buf __counted_by_or_null(count); // expected-warning{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} expected-note{{use '__sized_by_or_null' to suppress this warning}}
   int count;
 };
 
@@ -89,9 +87,7 @@ struct on_member_pointer_struct_with_annotated_vla {
 };
 
 struct on_pointer_anon_buf {
-  // TODO: Support referring to parent scope
   struct {
-    // expected-error at +1{{use of undeclared identifier 'count'}}
     struct size_known *buf __counted_by_or_null(count);
   };
   int count;
@@ -107,131 +103,99 @@ struct on_pointer_anon_count {
 //==============================================================================
 // __counted_by_or_null on struct member pointer in type attribute position
 //==============================================================================
-// TODO: Correctly parse counted_by_or_null as a type attribute. Currently it is parsed
-// as a declaration attribute and is **not** late parsed resulting in the `count`
-// field being unavailable.
 
 struct on_member_pointer_complete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   struct size_known *__counted_by_or_null(count) buf;
   int count;
 };
 
 struct on_member_pointer_incomplete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   struct size_unknown * __counted_by_or_null(count) buf;
   int count;
 };
 
 struct on_member_pointer_const_incomplete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   const struct size_unknown * __counted_by_or_null(count) buf;
   int count;
 };
 
 struct on_member_pointer_void_ty_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being an incomplete type.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
-  void *__counted_by_or_null(count) buf;
+  void *__counted_by_or_null(count) buf; // expected-warning{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} expected-note{{use '__sized_by_or_null' to suppress this warning}}
   int count;
 };
 
 // -
 
 struct on_member_pointer_fn_ptr_ty_pos {
-  // TODO: buffer of `count` function pointers should be allowed
-  // but fails because this isn't late parsed.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   void (** __counted_by_or_null(count) fn_ptr)(void);
   int count;
 };
 
 struct on_member_pointer_fn_ptr_ty_ptr_ty_pos {
-  // TODO: buffer of `count` function pointers should be allowed
-  // but fails because this isn't late parsed.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   fn_ptr_ty* __counted_by_or_null(count) fn_ptr;
   int count;
 };
 
 struct on_member_pointer_fn_ty_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a function type.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __counted_by_or_null(count) fn_ptr)(void);
   int count;
 };
 
 struct on_member_pointer_fn_ptr_ty_ty_pos {
-  // TODO: buffer of `count` function pointers should be allowed
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   void (** __counted_by_or_null(count) fn_ptr)(void);
   int count;
 };
 
 struct on_member_pointer_fn_ptr_ty_typedef_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a function type.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   fn_ptr_ty __counted_by_or_null(count) fn_ptr;
   int count;
 };
 
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a function type.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __counted_by_or_null(count) * fn_ptr)(void);
   int count;
 };
 
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+  // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
+  void (** __counted_by_or_null(count) * fn_ptr)(void);
+  int count;
+};
+
 struct on_member_pointer_struct_with_vla_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a struct type with a VLA.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
   struct has_unannotated_vla *__counted_by_or_null(count) objects;
   int count;
 };
 
 struct on_member_pointer_struct_with_annotated_vla_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a struct type with a VLA.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct has_annotated_vla' is a struct type with a flexible array member}}
   struct has_annotated_vla* __counted_by_or_null(count) objects;
   int count;
 };
 
 struct on_nested_pointer_inner {
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__counted_by_or_null` can only be nested when used in function parameters.
-  // expected-error at +1{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
   struct size_known *__counted_by_or_null(count) *buf;
   int count;
 };
 
 struct on_nested_pointer_outer {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   struct size_known **__counted_by_or_null(count) buf;
   int count;
 };
 
 struct on_pointer_anon_buf_ty_pos {
   struct {
-    // TODO: Support referring to parent scope
-    // expected-error at +1{{use of undeclared identifier 'count'}}
     struct size_known * __counted_by_or_null(count) buf;
   };
   int count;
 };
 
 struct on_pointer_anon_count_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'count'}}
   struct size_known *__counted_by_or_null(count) buf;
   struct {
     int count;
diff --git a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c
index cff5a14c70b99..4d4a6e81e3766 100644
--- a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c
+++ b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c
@@ -17,7 +17,7 @@
 // expected-note at +1 24{{forward declaration of 'struct IncompleteTy'}}
 struct IncompleteTy; // expected-note 27{{consider providing a complete definition for 'struct IncompleteTy'}}
 
-typedef struct IncompleteTy Incomplete_t; 
+typedef struct IncompleteTy Incomplete_t;
 
 struct CBBufDeclPos {
   int count;
@@ -75,7 +75,7 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
   void* tmp3 = implicit_full_init.buf;
   // expected-error at +1{{cannot use 'implicit_full_init.buf_typedef' with '__counted_by_or_null' attributed type 'Incomplete_t * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
   void* tmp4 = implicit_full_init.buf_typedef;
-  
+
   struct CBBufDeclPos explicit_non_desig_init = {
     0,
     // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
@@ -113,7 +113,7 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
   uninit.buf_typedef++; // // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
   ++uninit.buf_typedef; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
   uninit.buf_typedef -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
-  
+
   uninit.buf--; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
   --uninit.buf; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
   uninit.buf -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
@@ -139,16 +139,16 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
   // ## Use of fields in expressions
   // ===========================================================================
   // expected-error at +2{{cannot use 'uninit.buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
-  void* addr = 
+  void* addr =
     ((char*) uninit.buf ) + 1;
   // expected-error at +2{{cannot use 'uninit.buf_typedef' with '__counted_by_or_null' attributed type 'Incomplete_t * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
-  void* addr_typedef = 
+  void* addr_typedef =
     ((char*) uninit.buf_typedef ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
-  void* addr_ptr = 
+  void* addr_ptr =
     ((char*) ptr->buf ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by_or_null' attributed type 'Incomplete_t * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
-  void* addr_ptr_typedef = 
+  void* addr_ptr_typedef =
     ((char*) ptr->buf_typedef ) + 1;
 
 
@@ -289,7 +289,7 @@ void test_CBBufDeclPos_completed(struct CBBufDeclPos* ptr) {
   };
 
   struct CBBufDeclPos implicit_full_init = {0};
-  
+
   struct CBBufDeclPos explicit_non_desig_init = {
     0,
     0x0,
@@ -384,10 +384,10 @@ void use_CBBufTyPos(struct CBBufTyPos* ptr) {
 
   // Use
   // expected-error at +2{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy2 * __counted_by_or_null(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete}}
-  void* addr = 
+  void* addr =
     ((char*) ptr->buf ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by_or_null' attributed type 'Incomplete_ty2 * __counted_by_or_null(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete}}
-  void* addr_typedef = 
+  void* addr_typedef =
     ((char*) ptr->buf_typedef ) + 1;
 
   // expected-error at +1{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy2 * __counted_by_or_null(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete}}
@@ -458,10 +458,10 @@ void use_CBBufUnionTyPos(struct CBBufUnionTyPos* ptr) {
 
   // Use
   // expected-error at +2{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'union IncompleteUnionTy * __counted_by_or_null(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete}}
-  void* addr = 
+  void* addr =
     ((char*) ptr->buf ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by_or_null' attributed type 'IncompleteUnion_ty * __counted_by_or_null(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete}}
-  void* addr_typedef = 
+  void* addr_typedef =
     ((char*) ptr->buf_typedef ) + 1;
 
   // expected-error at +1{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'union IncompleteUnionTy * __counted_by_or_null(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete}}
@@ -532,10 +532,10 @@ void use_CBBufEnumTyPos(struct CBBufEnumTyPos* ptr) {
 
   // Use
   // expected-error at +2{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'enum IncompleteEnumTy * __counted_by_or_null(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete}}
-  void* addr = 
+  void* addr =
     ((char*) ptr->buf ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by_or_null' attributed type 'IncompleteEnum_ty * __counted_by_or_null(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete}}
-  void* addr_typedef = 
+  void* addr_typedef =
     ((char*) ptr->buf_typedef ) + 1;
 
   // expected-error at +1{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'enum IncompleteEnumTy * __counted_by_or_null(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete}}
@@ -616,16 +616,13 @@ struct IncompleteTy3;
 
 struct CBBufFAMofCountedByPtrs {
   int size;
-  // TODO: This is misleading. The attribute is written in the type position
-  // but clang currently doesn't treat it like that and it gets treated as
-  // an attribute on the array, rather than on the element type.
-  // expected-error at +1{{'counted_by_or_null' only applies to pointers; did you mean to use 'counted_by'?}}
+  // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
   struct IncompleteTy3* __counted_by_or_null(size) arr[];
 };
 
 void arr_of_counted_by_ptr(struct CBBufFAMofCountedByPtrs* ptr) {
-  // TODO: Should be disallowed once parsing attributes in the type position
-  // works.
+  // TODO: Diagnostic should appear here once `__counted_by_or_null` is allowed on
+  // nested pointers.
   ptr->arr[0] = 0x0;
   void* addr = ((char*) ptr->arr[0]) + 1;
 }
diff --git a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
index 0fd739ca7d4c3..e8ab29cd80181 100644
--- a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
@@ -105,8 +105,6 @@ struct on_pointer_anon_count {
 //==============================================================================
 // __counted_by_or_null on struct member pointer in type attribute position
 //==============================================================================
-// TODO: Correctly parse counted_by_or_null as a type attribute. Currently it is parsed
-// as a declaration attribute
 
 struct on_member_pointer_complete_ty_ty_pos {
   int count;
@@ -158,13 +156,18 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
   fn_ptr_ty __counted_by_or_null(count) fn_ptr;
 };
 
-// TODO: This should be forbidden but isn't due to counted_by_or_null being treated
-// as a declaration attribute.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int count;
+  // expected-error at +1{{cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __counted_by_or_null(count) * fn_ptr)(void);
 };
 
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+  int count;
+  // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
+  void (** __counted_by_or_null(count) * fn_ptr)(void);
+};
+
 struct on_member_pointer_struct_with_vla_ty_pos {
   int count;
   // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
@@ -181,9 +184,8 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
 };
 
 struct on_nested_pointer_inner {
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__counted_by_or_null` can only be nested when used in function parameters.
   int count;
+  // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
   struct size_known *__counted_by_or_null(count) *buf;
 };
 
diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c b/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
index d28a2086b51b8..f9afe558d0e13 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
@@ -17,7 +17,7 @@
 // expected-note at +1 24{{forward declaration of 'struct IncompleteTy'}}
 struct IncompleteTy; // expected-note 27{{consider providing a complete definition for 'struct IncompleteTy'}}
 
-typedef struct IncompleteTy Incomplete_t; 
+typedef struct IncompleteTy Incomplete_t;
 
 struct CBBufDeclPos {
   int count;
@@ -75,7 +75,7 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
   void* tmp3 = implicit_full_init.buf;
   // expected-error at +1{{cannot use 'implicit_full_init.buf_typedef' with '__counted_by' attributed type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
   void* tmp4 = implicit_full_init.buf_typedef;
-  
+
   struct CBBufDeclPos explicit_non_desig_init = {
     0,
     // expected-error at +1{{cannot initialize 'CBBufDeclPos::buf' with '__counted_by' attributed type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
@@ -113,7 +113,7 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
   uninit.buf_typedef++; // // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
   ++uninit.buf_typedef; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
   uninit.buf_typedef -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
-  
+
   uninit.buf--; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
   --uninit.buf; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
   uninit.buf -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
@@ -139,16 +139,16 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
   // ## Use of fields in expressions
   // ===========================================================================
   // expected-error at +2{{cannot use 'uninit.buf' with '__counted_by' attributed type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
-  void* addr = 
+  void* addr =
     ((char*) uninit.buf ) + 1;
   // expected-error at +2{{cannot use 'uninit.buf_typedef' with '__counted_by' attributed type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
-  void* addr_typedef = 
+  void* addr_typedef =
     ((char*) uninit.buf_typedef ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf' with '__counted_by' attributed type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
-  void* addr_ptr = 
+  void* addr_ptr =
     ((char*) ptr->buf ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by' attributed type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
-  void* addr_ptr_typedef = 
+  void* addr_ptr_typedef =
     ((char*) ptr->buf_typedef ) + 1;
 
 
@@ -289,7 +289,7 @@ void test_CBBufDeclPos_completed(struct CBBufDeclPos* ptr) {
   };
 
   struct CBBufDeclPos implicit_full_init = {0};
-  
+
   struct CBBufDeclPos explicit_non_desig_init = {
     0,
     0x0,
@@ -384,10 +384,10 @@ void use_CBBufTyPos(struct CBBufTyPos* ptr) {
 
   // Use
   // expected-error at +2{{cannot use 'ptr->buf' with '__counted_by' attributed type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete}}
-  void* addr = 
+  void* addr =
     ((char*) ptr->buf ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by' attributed type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete}}
-  void* addr_typedef = 
+  void* addr_typedef =
     ((char*) ptr->buf_typedef ) + 1;
 
   // expected-error at +1{{cannot use 'ptr->buf' with '__counted_by' attributed type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete}}
@@ -458,10 +458,10 @@ void use_CBBufUnionTyPos(struct CBBufUnionTyPos* ptr) {
 
   // Use
   // expected-error at +2{{cannot use 'ptr->buf' with '__counted_by' attributed type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete}}
-  void* addr = 
+  void* addr =
     ((char*) ptr->buf ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by' attributed type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete}}
-  void* addr_typedef = 
+  void* addr_typedef =
     ((char*) ptr->buf_typedef ) + 1;
 
   // expected-error at +1{{cannot use 'ptr->buf' with '__counted_by' attributed type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete}}
@@ -532,10 +532,10 @@ void use_CBBufEnumTyPos(struct CBBufEnumTyPos* ptr) {
 
   // Use
   // expected-error at +2{{cannot use 'ptr->buf' with '__counted_by' attributed type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete}}
-  void* addr = 
+  void* addr =
     ((char*) ptr->buf ) + 1;
   // expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by' attributed type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete}}
-  void* addr_typedef = 
+  void* addr_typedef =
     ((char*) ptr->buf_typedef ) + 1;
 
   // expected-error at +1{{cannot use 'ptr->buf' with '__counted_by' attributed type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete}}
@@ -616,9 +616,7 @@ struct IncompleteTy3;
 
 struct CBBufFAMofCountedByPtrs {
   int size;
-  // TODO: This is misleading. The attribute is written in the type position
-  // but clang currently doesn't treat it like that and it gets treated as
-  // an attribute on the array, rather than on the element type.
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
   struct IncompleteTy3* __counted_by(size) arr[];
 };
 
diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs.c b/clang/test/Sema/attr-counted-by-struct-ptrs.c
index a42f3895695a3..f8957ca8b4eba 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs.c
@@ -104,8 +104,6 @@ struct on_pointer_anon_count {
 //==============================================================================
 // __counted_by on struct member pointer in type attribute position
 //==============================================================================
-// TODO: Correctly parse counted_by as a type attribute. Currently it is parsed
-// as a declaration attribute
 
 struct on_member_pointer_complete_ty_ty_pos {
   int count;
@@ -157,11 +155,16 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
   fn_ptr_ty __counted_by(count) fn_ptr;
 };
 
-// TODO: This should be forbidden but isn't due to counted_by being treated
-// as a declaration attribute.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int count;
-  void (* __counted_by(count) * fn_ptr)(void);
+  // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
+  void (* __counted_by(count) * fn_ptr)(void);  // FIXME
+};
+
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+  int count;
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  void (** __counted_by(count) * fn_ptr)(void);
 };
 
 struct on_member_pointer_struct_with_vla_ty_pos {
@@ -180,10 +183,9 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
 };
 
 struct on_nested_pointer_inner {
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__counted_by` can only be nested when used in function parameters.
   int count;
-  struct size_known *__counted_by(count) *buf;
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  struct size_known *__counted_by(count) *buf;  // FIXME
 };
 
 struct on_nested_pointer_outer {
diff --git a/clang/test/Sema/attr-counted-by-weird-type-positions-late-parsed.c b/clang/test/Sema/attr-counted-by-weird-type-positions-late-parsed.c
new file mode 100644
index 0000000000000..0728c8623c202
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-weird-type-positions-late-parsed.c
@@ -0,0 +1,456 @@
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+
+// ============================================================================
+// SIMPLE POINTER: int *buf
+// ============================================================================
+
+// Position: after *, before identifier
+// Applies to `int *`.
+struct ptr_after_star {
+  int *__counted_by(count) buf;
+  int count;
+};
+
+// Position: before type specifier
+// Applies to the top-level type.
+struct ptr_before_type {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  __counted_by(count) int *buf;
+  int count;
+};
+
+// Position: after type, before *
+// Applies to `int`.
+struct ptr_after_type {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) *buf;
+  int count;
+};
+
+// Position: after identifier
+// Applies to the top-level type.
+struct ptr_after_ident {
+  int *buf __counted_by(count);
+  int count;
+};
+
+// ============================================================================
+// TYPEDEF POINTER: ptr_to_int_t buf
+// ============================================================================
+
+typedef int * ptr_to_int_t;
+
+// Position: after typedef name, before identifier
+// Applies to `ptr_to_int_t`.
+struct typedef_after_type {
+  ptr_to_int_t __counted_by(count) buf;
+  int count;
+};
+
+// Position: before typedef name
+// Applies to the top-level type.
+struct typedef_before_type {
+  __counted_by(count) ptr_to_int_t buf;
+  int count;
+};
+
+// Position: after identifier
+// Applies to the top-level type.
+struct typedef_after_ident {
+  ptr_to_int_t buf __counted_by(count);
+  int count;
+};
+
+// ============================================================================
+// POINTER TO ARRAY: int (*buf)[4]
+// ============================================================================
+
+// Position: after type, before (*...)
+// Applies to `int`.
+struct ptr_to_arr_after_type {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* buf)[4];
+  int count;
+};
+
+// Position: before type
+// Applies to the top-level type.
+struct ptr_to_arr_before_type {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  __counted_by(count) int (* buf)[4];
+  int count;
+};
+
+// Position: after *, before identifier (inside parens)
+// Applies to `int (*)[4]`.
+struct ptr_to_arr_after_star {
+  int (* __counted_by(count) buf)[4];
+  int count;
+};
+
+// Position: after identifier, before ) (inside parens)
+// Invalid position - causes parse error
+struct ptr_to_arr_after_ident {
+  int (*buf __counted_by(count))[4]; // Invalid position
+  // expected-error at -1{{expected ')'}}
+  // expected-note at -2{{to match this '('}}
+  int count;
+};
+
+// Position: after [4]
+// Applies to the top-level type.
+struct ptr_to_arr_after_brackets {
+  int (* buf)[4] __counted_by(count);
+  int count;
+};
+
+// Position: after (, before *
+struct ptr_to_arr_after_lparen {
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  int (__counted_by(count) *buf)[4];
+  int count;
+};
+
+// Position: inside [4]
+struct ptr_to_arr_inside_brackets {
+  int (* buf)[4 __counted_by(count)]; // Invalid syntax
+  // expected-error at -1{{expected ']'}}
+  // expected-note at -2{{to match this '['}}
+  int count;
+};
+
+// Position: before [4]
+struct ptr_to_arr_before_brackets {
+  // expected-error at +1{{expected ';' at end of declaration list}}
+  int (* buf) __counted_by(count) [4]; // Invalid syntax
+  int count;
+};
+
+// Position: double parens, after ((, before *
+struct ptr_to_arr_double_paren1 {
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  int ((__counted_by(count) * buf))[4];
+  int count;
+};
+
+// Position: double parens, after *, before identifier
+struct ptr_to_arr_double_paren2 {
+  int ((* __counted_by(count) buf))[4];
+  int count;
+};
+
+// ============================================================================
+// POINTER TO ARRAY WITH QUALIFIERS
+// ============================================================================
+
+// const pointer
+struct ptr_to_arr_const_ptr1 {
+  int (* const __counted_by(count) buf)[4];
+  int count;
+};
+
+struct ptr_to_arr_const_ptr2 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* const buf)[4];
+  int count;
+};
+
+// pointer to const
+struct ptr_to_arr_ptr_to_const {
+  const int (* __counted_by(count) buf)[4];
+  int count;
+};
+
+struct ptr_to_arr_ptr_to_const2 {
+  int const (* __counted_by(count) buf)[4];
+  int count;
+};
+
+// restrict pointer
+struct ptr_to_arr_restrict1 {
+  int (* __restrict __counted_by(count) buf)[4];
+  int count;
+};
+
+struct ptr_to_arr_restrict2 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* __restrict buf)[4];
+  int count;
+};
+
+// ============================================================================
+// POINTER TO MULTI-DIMENSIONAL ARRAY: int (*buf)[4][8]
+// ============================================================================
+
+struct ptr_to_multidim_arr_after_type {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* buf)[4][8];
+  int count;
+};
+
+struct ptr_to_multidim_arr_after_star {
+  int (* __counted_by(count) buf)[4][8];
+  int count;
+};
+
+struct ptr_to_multidim_arr_middle {
+  // expected-error at +1{{expected ';' at end of declaration list}}
+  int (* buf)[4] __counted_by(count) [8]; // Invalid position
+  int count;
+};
+
+struct ptr_to_multidim_arr_after_all {
+  int (* buf)[4][8] __counted_by(count);
+  // This doesn't trigger an error - the attribute applies to the pointer
+  int count;
+};
+
+// ============================================================================
+// ARRAY OF POINTERS TO ARRAY: int (*buf[10])[4]
+// ============================================================================
+
+struct arr_of_ptr_to_arr_after_type {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* buf[10])[4];
+  int count;
+};
+
+struct arr_of_ptr_to_arr_after_star {
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  int (* __counted_by(count) buf[10])[4];
+  int count;
+};
+
+struct arr_of_ptr_to_arr_middle {
+  // expected-error at +2{{'counted_by' on arrays only applies to C99 flexible array members}}
+  // expected-error at +1{{expected ';' at end of declaration list}}
+  int (* buf[10]) __counted_by(count) [4]; // Invalid position
+  int count;
+};
+
+struct arr_of_ptr_to_arr_inside_first_brackets {
+  int (* buf __counted_by(count) [10])[4];
+  // expected-error at -1{{expected ')'}}
+  // expected-note at -2{{to match this '('}}
+  int count;
+};
+
+// ============================================================================
+// TYPEDEF ARRAY: arr4_t *buf where arr4_t is int[4]
+// ============================================================================
+
+typedef int arr4_t[4];
+
+struct typedef_arr_before_type {
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  __counted_by(count) arr4_t * buf;
+  int count;
+};
+
+struct typedef_arr_after_type {
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  arr4_t __counted_by(count) * buf;
+  int count;
+};
+
+struct typedef_arr_after_star {
+  arr4_t * __counted_by(count) buf;
+  int count;
+};
+
+// ============================================================================
+// FUNCTION POINTER: int (*buf)(void)
+// ============================================================================
+
+// Position: after *, before identifier
+struct fptr_after_star {
+  // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'int (void)' is a function type}}
+  int (* __counted_by(count) buf)(void);
+  int count;
+};
+
+// Position: after (, before *
+struct fptr_after_lparen {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int (__counted_by(count) *buf)(void);
+  int count;
+};
+
+// ============================================================================
+// _ATOMIC POINTER VARIATIONS
+// ============================================================================
+
+// _Atomic(int *) - atomic pointer type
+struct atomic_ptr_type {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  _Atomic(int *) __counted_by(count) buf;
+  int count;
+};
+
+// Attribute inside _Atomic (likely invalid)
+struct atomic_ptr_attr_inside {
+  // expected-error at +1{{use of undeclared identifier 'count'}}
+  _Atomic(int *__counted_by(count)) buf;
+  int count;
+};
+
+struct atomic_ptr_attr_inside_no_forward_ref {
+  int count;
+  // FIXME: should not be allowed
+  _Atomic(int *__counted_by(count)) buf;
+};
+
+struct atomic_ptr_attr_after {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  __counted_by(count) _Atomic(int *) buf;
+  int count;
+};
+
+struct atomic_ptr_attr_after_no_forward_ref {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  __counted_by(count) _Atomic(int *) buf;
+};
+
+// _Atomic int * - could be atomic int or atomic pointer
+struct atomic_ambiguous {
+  _Atomic int * __counted_by(count) buf;
+  int count;
+};
+
+// int *_Atomic - atomic pointer (unambiguous)
+struct atomic_ptr_unambiguous1 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int *_Atomic __counted_by(count) buf;
+  int count;
+};
+
+// __counted_by before _Atomic
+struct atomic_ptr_attr_before_atomic1 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int *__counted_by(count) _Atomic buf;
+  int count;
+};
+
+// __counted_by before * _Atomic
+struct atomic_ptr_attr_before_atomic2 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) * _Atomic buf;
+  int count;
+};
+
+// _Atomic before type
+struct atomic_ptr_atomic_first1 {
+  _Atomic int *__counted_by(count) buf;
+  int count;
+};
+
+// _Atomic before type, attribute after *
+struct atomic_ptr_atomic_first2 {
+  _Atomic int * __counted_by(count) buf;
+  int count;
+};
+
+// __counted_by at the end
+struct atomic_ptr_attr_at_end1 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int *_Atomic buf __counted_by(count);
+  int count;
+};
+
+// __counted_by at the end with space
+struct atomic_ptr_attr_at_end2 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * _Atomic buf __counted_by(count);
+  int count;
+};
+
+// ============================================================================
+// _ATOMIC POINTER TO ARRAY
+// ============================================================================
+
+struct atomic_ptr_to_arr1 {
+  _Atomic int (* __counted_by(count) buf)[4];
+  int count;
+};
+
+struct atomic_ptr_to_arr2 {
+  // expected-error at +3{{expected a type}}
+  // expected-error at +2{{use of undeclared identifier 'count'}}
+  // expected-error at +1{{expected member name or ';' after declaration specifiers}}
+  int _Atomic (* __counted_by(count) buf)[4];
+  int count;
+};
+
+struct atomic_ptr_to_arr3 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int (* _Atomic __counted_by(count) buf)[4];
+  int count;
+};
+
+// ============================================================================
+// ATOMIC WITH CONST/VOLATILE/RESTRICT QUALIFIERS
+// ============================================================================
+
+// const _Atomic pointer
+struct atomic_const_ptr1 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * const _Atomic __counted_by(count) buf;
+  int count;
+};
+
+struct atomic_const_ptr2 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * _Atomic const __counted_by(count) buf;
+  int count;
+};
+
+struct atomic_const_ptr3 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  const int * _Atomic __counted_by(count) buf;
+  int count;
+};
+
+// volatile _Atomic pointer
+struct atomic_volatile_ptr1 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * volatile _Atomic __counted_by(count) buf;
+  int count;
+};
+
+struct atomic_volatile_ptr2 {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * _Atomic volatile __counted_by(count) buf;
+  int count;
+};
+
+// restrict _Atomic pointer
+struct atomic_restrict_ptr1 {
+  // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * __restrict _Atomic __counted_by(count) buf;
+  int count;
+};
+
+struct atomic_restrict_ptr2 {
+  // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * _Atomic __restrict __counted_by(count) buf;
+  int count;
+};
+
+// Combined qualifiers
+struct atomic_const_volatile_ptr {
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * const volatile _Atomic __counted_by(count) buf;
+  int count;
+};
+
+struct atomic_all_qualifiers {
+  // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * const volatile __restrict _Atomic __counted_by(count) buf;
+  int count;
+};
diff --git a/clang/test/Sema/attr-counted-by-weird-type-positions.c b/clang/test/Sema/attr-counted-by-weird-type-positions.c
new file mode 100644
index 0000000000000..2668ab1e18346
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-weird-type-positions.c
@@ -0,0 +1,454 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+
+// ============================================================================
+// SIMPLE POINTER: int *buf
+// ============================================================================
+
+// Position: after *, before identifier
+// Applies to `int *`.
+struct ptr_after_star {
+  int count;
+  int *__counted_by(count) buf;
+};
+
+// Position: before type specifier
+// Applies to the top-level type.
+struct ptr_before_type {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  __counted_by(count) int *buf;
+};
+
+// Position: after type, before *
+// Applies to `int`.
+struct ptr_after_type {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) *buf;
+};
+
+// Position: after identifier
+// Applies to the top-level type.
+struct ptr_after_ident {
+  int count;
+  int *buf __counted_by(count);
+};
+
+// ============================================================================
+// TYPEDEF POINTER: ptr_to_int_t buf
+// ============================================================================
+
+typedef int * ptr_to_int_t;
+
+// Position: after typedef name, before identifier
+// Applies to `ptr_to_int_t`.
+struct typedef_after_type {
+  int count;
+  ptr_to_int_t __counted_by(count) buf;
+};
+
+// Position: before typedef name
+// Applies to the top-level type.
+struct typedef_before_type {
+  int count;
+  __counted_by(count) ptr_to_int_t buf;
+};
+
+// Position: after identifier
+// Applies to the top-level type.
+struct typedef_after_ident {
+  int count;
+  ptr_to_int_t buf __counted_by(count);
+};
+
+// ============================================================================
+// POINTER TO ARRAY: int (*buf)[4]
+// ============================================================================
+
+// Position: after type, before (*...)
+// Applies to `int`.
+struct ptr_to_arr_after_type {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* buf)[4];
+};
+
+// Position: before type
+// Applies to the top-level type.
+struct ptr_to_arr_before_type {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  __counted_by(count) int (* buf)[4];
+};
+
+// Position: after *, before identifier (inside parens)
+// Applies to `int (*)[4]`.
+struct ptr_to_arr_after_star {
+  int count;
+  int (* __counted_by(count) buf)[4];
+};
+
+// Position: after identifier, before ) (inside parens)
+// Invalid position - causes parse error
+struct ptr_to_arr_after_ident {
+  int count;
+  int (*buf __counted_by(count))[4]; // Invalid position
+  // expected-error at -1{{expected ')'}}
+  // expected-note at -2{{to match this '('}}
+};
+
+// Position: after [4]
+// Applies to the top-level type.
+struct ptr_to_arr_after_brackets {
+  int count;
+  int (* buf)[4] __counted_by(count);
+};
+
+// Position: after (, before *
+struct ptr_to_arr_after_lparen {
+  int count;
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  int (__counted_by(count) *buf)[4];
+};
+
+// Position: inside [4]
+struct ptr_to_arr_inside_brackets {
+  int count;
+  int (* buf)[4 __counted_by(count)]; // Invalid syntax
+  // expected-error at -1{{expected ']'}}
+  // expected-note at -2{{to match this '['}}
+};
+
+// Position: before [4]
+struct ptr_to_arr_before_brackets {
+  int count;
+  // expected-error at +1{{expected ';' at end of declaration list}}
+  int (* buf) __counted_by(count) [4]; // Invalid syntax
+};
+
+// Position: double parens, after ((, before *
+struct ptr_to_arr_double_paren1 {
+  int count;
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  int ((__counted_by(count) * buf))[4];
+};
+
+// Position: double parens, after *, before identifier
+struct ptr_to_arr_double_paren2 {
+  int count;
+  int ((* __counted_by(count) buf))[4];
+};
+
+// ============================================================================
+// POINTER TO ARRAY WITH QUALIFIERS
+// ============================================================================
+
+// const pointer
+struct ptr_to_arr_const_ptr1 {
+  int count;
+  int (* const __counted_by(count) buf)[4];
+};
+
+struct ptr_to_arr_const_ptr2 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* const buf)[4];
+};
+
+// pointer to const
+struct ptr_to_arr_ptr_to_const {
+  int count;
+  const int (* __counted_by(count) buf)[4];
+};
+
+struct ptr_to_arr_ptr_to_const2 {
+  int count;
+  int const (* __counted_by(count) buf)[4];
+};
+
+// restrict pointer
+struct ptr_to_arr_restrict1 {
+  int count;
+  int (* __restrict __counted_by(count) buf)[4];
+};
+
+struct ptr_to_arr_restrict2 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* __restrict buf)[4];
+};
+
+// ============================================================================
+// POINTER TO MULTI-DIMENSIONAL ARRAY: int (*buf)[4][8]
+// ============================================================================
+
+struct ptr_to_multidim_arr_after_type {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* buf)[4][8];
+};
+
+struct ptr_to_multidim_arr_after_star {
+  int count;
+  int (* __counted_by(count) buf)[4][8];
+};
+
+struct ptr_to_multidim_arr_middle {
+  int count;
+  // expected-error at +1{{expected ';' at end of declaration list}}
+  int (* buf)[4] __counted_by(count) [8]; // Invalid position
+};
+
+struct ptr_to_multidim_arr_after_all {
+  int count;
+  int (* buf)[4][8] __counted_by(count);
+  // This doesn't trigger an error - the attribute applies to the pointer
+};
+
+// ============================================================================
+// ARRAY OF POINTERS TO ARRAY: int (*buf[10])[4]
+// ============================================================================
+
+struct arr_of_ptr_to_arr_after_type {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) (* buf[10])[4];
+};
+
+struct arr_of_ptr_to_arr_after_star {
+  int count;
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  int (* __counted_by(count) buf[10])[4];
+};
+
+struct arr_of_ptr_to_arr_middle {
+  int count;
+  // expected-error at +2{{'counted_by' on arrays only applies to C99 flexible array members}}
+  // expected-error at +1{{expected ';' at end of declaration list}}
+  int (* buf[10]) __counted_by(count) [4]; // Invalid position
+};
+
+struct arr_of_ptr_to_arr_inside_first_brackets {
+  int count;
+  int (* buf __counted_by(count) [10])[4];
+  // expected-error at -1{{expected ')'}}
+  // expected-note at -2{{to match this '('}}
+};
+
+// ============================================================================
+// TYPEDEF ARRAY: arr4_t *buf where arr4_t is int[4]
+// ============================================================================
+
+typedef int arr4_t[4];
+
+struct typedef_arr_before_type {
+  int count;
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  __counted_by(count) arr4_t * buf;
+};
+
+struct typedef_arr_after_type {
+  int count;
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  arr4_t __counted_by(count) * buf;
+};
+
+struct typedef_arr_after_star {
+  int count;
+  arr4_t * __counted_by(count) buf;
+};
+
+// ============================================================================
+// FUNCTION POINTER: int (*buf)(void)
+// ============================================================================
+
+// Position: after *, before identifier
+struct fptr_after_star {
+  int count;
+  // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'int (void)' is a function type}}
+  int (* __counted_by(count) buf)(void);
+};
+
+// Position: after (, before *
+struct fptr_after_lparen {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int (__counted_by(count) *buf)(void);
+};
+
+// ============================================================================
+// _ATOMIC POINTER VARIATIONS
+// ============================================================================
+
+// _Atomic(int *) - atomic pointer type
+struct atomic_ptr_type {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  _Atomic(int *) __counted_by(count) buf;
+};
+
+// Attribute inside _Atomic (likely invalid)
+struct atomic_ptr_attr_inside {
+  int count;
+  _Atomic(int *__counted_by(count)) buf;
+};
+
+struct atomic_ptr_attr_inside_no_forward_ref {
+  int count;
+  // FIXME: should not be allowed
+  _Atomic(int *__counted_by(count)) buf;
+};
+
+struct atomic_ptr_attr_after {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  __counted_by(count) _Atomic(int *) buf;
+};
+
+struct atomic_ptr_attr_after_no_forward_ref {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  __counted_by(count) _Atomic(int *) buf;
+};
+
+// _Atomic int * - could be atomic int or atomic pointer
+struct atomic_ambiguous {
+  int count;
+  _Atomic int * __counted_by(count) buf;
+};
+
+// int *_Atomic - atomic pointer (unambiguous)
+struct atomic_ptr_unambiguous1 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int *_Atomic __counted_by(count) buf;
+};
+
+// __counted_by before _Atomic
+struct atomic_ptr_attr_before_atomic1 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int *__counted_by(count) _Atomic buf;
+};
+
+// __counted_by before * _Atomic
+struct atomic_ptr_attr_before_atomic2 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(count) * _Atomic buf;
+};
+
+// _Atomic before type
+struct atomic_ptr_atomic_first1 {
+  int count;
+  _Atomic int *__counted_by(count) buf;
+};
+
+// _Atomic before type, attribute after *
+struct atomic_ptr_atomic_first2 {
+  int count;
+  _Atomic int * __counted_by(count) buf;
+};
+
+// __counted_by at the end
+struct atomic_ptr_attr_at_end1 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int *_Atomic buf __counted_by(count);
+};
+
+// __counted_by at the end with space
+struct atomic_ptr_attr_at_end2 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * _Atomic buf __counted_by(count);
+};
+
+// ============================================================================
+// _ATOMIC POINTER TO ARRAY
+// ============================================================================
+
+struct atomic_ptr_to_arr1 {
+  int count;
+  _Atomic int (* __counted_by(count) buf)[4];
+};
+
+struct atomic_ptr_to_arr2 {
+  int count;
+  // expected-error at +2{{expected a type}}
+  // expected-error at +1{{expected member name or ';' after declaration specifiers}}
+  int _Atomic (* __counted_by(count) buf)[4];
+};
+
+struct atomic_ptr_to_arr3 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int (* _Atomic __counted_by(count) buf)[4];
+};
+
+// ============================================================================
+// ATOMIC WITH CONST/VOLATILE/RESTRICT QUALIFIERS
+// ============================================================================
+
+// const _Atomic pointer
+struct atomic_const_ptr1 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * const _Atomic __counted_by(count) buf;
+};
+
+struct atomic_const_ptr2 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * _Atomic const __counted_by(count) buf;
+};
+
+struct atomic_const_ptr3 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  const int * _Atomic __counted_by(count) buf;
+};
+
+// volatile _Atomic pointer
+struct atomic_volatile_ptr1 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * volatile _Atomic __counted_by(count) buf;
+};
+
+struct atomic_volatile_ptr2 {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * _Atomic volatile __counted_by(count) buf;
+};
+
+// restrict _Atomic pointer
+struct atomic_restrict_ptr1 {
+  int count;
+  // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * __restrict _Atomic __counted_by(count) buf;
+};
+
+struct atomic_restrict_ptr2 {
+  int count;
+  // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * _Atomic __restrict __counted_by(count) buf;
+};
+
+// Combined qualifiers
+struct atomic_const_volatile_ptr {
+  int count;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * const volatile _Atomic __counted_by(count) buf;
+};
+
+struct atomic_all_qualifiers {
+  int count;
+  // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int * const volatile __restrict _Atomic __counted_by(count) buf;
+};
diff --git a/clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c
index 07f8801787d66..a985030c38b43 100644
--- a/clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c
@@ -84,9 +84,7 @@ struct on_member_pointer_struct_with_annotated_vla {
 };
 
 struct on_pointer_anon_buf {
-  // TODO: Support referring to parent scope
   struct {
-    // expected-error at +1{{use of undeclared identifier 'size'}}
     struct size_known *buf __sized_by(size);
   };
   int size;
@@ -102,35 +100,23 @@ struct on_pointer_anon_count {
 //==============================================================================
 // __sized_by on struct member pointer in type attribute position
 //==============================================================================
-// TODO: Correctly parse sized_by as a type attribute. Currently it is parsed
-// as a declaration attribute and is **not** late parsed resulting in the `size`
-// field being unavailable.
 
 struct on_member_pointer_complete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   struct size_known *__sized_by(size) buf;
   int size;
 };
 
 struct on_member_pointer_incomplete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   struct size_unknown * __sized_by(size) buf;
   int size;
 };
 
 struct on_member_pointer_const_incomplete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   const struct size_unknown * __sized_by(size) buf;
   int size;
 };
 
 struct on_member_pointer_void_ty_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being an incomplete type.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   void *__sized_by(size) buf;
   int size;
 };
@@ -138,91 +124,75 @@ struct on_member_pointer_void_ty_ty_pos {
 // -
 
 struct on_member_pointer_fn_ptr_ty_pos {
-  // TODO: buffer of `size` function pointers should be allowed
-  // but fails because this isn't late parsed.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   void (** __sized_by(size) fn_ptr)(void);
   int size;
 };
 
 struct on_member_pointer_fn_ptr_ty_ptr_ty_pos {
-  // TODO: buffer of `size` function pointers should be allowed
-  // but fails because this isn't late parsed.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   fn_ptr_ty* __sized_by(size) fn_ptr;
   int size;
 };
 
 struct on_member_pointer_fn_ty_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a function type.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __sized_by(size) fn_ptr)(void);
   int size;
 };
 
 struct on_member_pointer_fn_ptr_ty_ty_pos {
-  // TODO: buffer of `size` function pointers should be allowed
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   void (** __sized_by(size) fn_ptr)(void);
   int size;
 };
 
 struct on_member_pointer_fn_ptr_ty_typedef_ty_pos {
-  // TODO: This should be allowed with sized_by.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   fn_ptr_ty __sized_by(size) fn_ptr;
   int size;
 };
 
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
-  // TODO: This should be allowed with sized_by.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __sized_by(size) * fn_ptr)(void);
   int size;
 };
 
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+  // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
+  void (** __sized_by(size) * fn_ptr)(void);
+  int size;
+};
+
 struct on_member_pointer_struct_with_vla_ty_pos {
-  // TODO: This should be allowed with sized_by.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
   struct has_unannotated_vla *__sized_by(size) objects;
   int size;
 };
 
 struct on_member_pointer_struct_with_annotated_vla_ty_pos {
-  // TODO: This should be allowed with sized_by.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'struct has_annotated_vla' is a struct type with a flexible array member}}
   struct has_annotated_vla* __sized_by(size) objects;
   int size;
 };
 
 struct on_nested_pointer_inner {
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__sized_by` can only be nested when used in function parameters.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
   struct size_known *__sized_by(size) *buf;
   int size;
 };
 
 struct on_nested_pointer_outer {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   struct size_known **__sized_by(size) buf;
   int size;
 };
 
 struct on_pointer_anon_buf_ty_pos {
   struct {
-    // TODO: Support referring to parent scope
-    // expected-error at +1{{use of undeclared identifier 'size'}}
     struct size_known * __sized_by(size) buf;
   };
   int size;
 };
 
 struct on_pointer_anon_count_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   struct size_known *__sized_by(size) buf;
   struct {
     int size;
diff --git a/clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c
index afe5f0af28083..392db90adab92 100644
--- a/clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c
@@ -84,9 +84,7 @@ struct on_member_pointer_struct_with_annotated_vla {
 };
 
 struct on_pointer_anon_buf {
-  // TODO: Support referring to parent scope
   struct {
-    // expected-error at +1{{use of undeclared identifier 'size'}}
     struct size_known *buf __sized_by_or_null(size);
   };
   int size;
@@ -102,35 +100,23 @@ struct on_pointer_anon_count {
 //==============================================================================
 // __sized_by_or_null on struct member pointer in type attribute position
 //==============================================================================
-// TODO: Correctly parse sized_by_or_null as a type attribute. Currently it is parsed
-// as a declaration attribute and is **not** late parsed resulting in the `size`
-// field being unavailable.
 
 struct on_member_pointer_complete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   struct size_known *__sized_by_or_null(size) buf;
   int size;
 };
 
 struct on_member_pointer_incomplete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   struct size_unknown * __sized_by_or_null(size) buf;
   int size;
 };
 
 struct on_member_pointer_const_incomplete_ty_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   const struct size_unknown * __sized_by_or_null(size) buf;
   int size;
 };
 
 struct on_member_pointer_void_ty_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being an incomplete type.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   void *__sized_by_or_null(size) buf;
   int size;
 };
@@ -138,91 +124,78 @@ struct on_member_pointer_void_ty_ty_pos {
 // -
 
 struct on_member_pointer_fn_ptr_ty_pos {
-  // TODO: buffer of `size` function pointers should be allowed
-  // but fails because this isn't late parsed.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   void (** __sized_by_or_null(size) fn_ptr)(void);
   int size;
 };
 
 struct on_member_pointer_fn_ptr_ty_ptr_ty_pos {
-  // TODO: buffer of `size` function pointers should be allowed
-  // but fails because this isn't late parsed.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   fn_ptr_ty* __sized_by_or_null(size) fn_ptr;
   int size;
 };
 
 struct on_member_pointer_fn_ty_ty_pos {
-  // TODO: This should fail because the attribute is
-  // on a pointer with the pointee being a function type.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // TODO: Improve diagnostics (Issue #167368).
+  // expected-error at +1{{'sized_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __sized_by_or_null(size) fn_ptr)(void);
   int size;
 };
 
 struct on_member_pointer_fn_ptr_ty_ty_pos {
-  // TODO: buffer of `size` function pointers should be allowed
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   void (** __sized_by_or_null(size) fn_ptr)(void);
   int size;
 };
 
 struct on_member_pointer_fn_ptr_ty_typedef_ty_pos {
-  // TODO: This should be allowed with sized_by_or_null.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{'sized_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   fn_ptr_ty __sized_by_or_null(size) fn_ptr;
   int size;
 };
 
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
-  // TODO: This should be allowed with sized_by_or_null.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __sized_by_or_null(size) * fn_ptr)(void);
   int size;
 };
 
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+  // expected-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
+  void (** __sized_by_or_null(size) * fn_ptr)(void);
+  int size;
+};
+
 struct on_member_pointer_struct_with_vla_ty_pos {
-  // TODO: This should be allowed with sized_by_or_null.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // TODO: Allow this
+  // expected-error at +1{{'sized_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
   struct has_unannotated_vla *__sized_by_or_null(size) objects;
   int size;
 };
 
 struct on_member_pointer_struct_with_annotated_vla_ty_pos {
-  // TODO: This should be allowed with sized_by_or_null.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // TODO: Allow this
+  // expected-error at +1{{'sized_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct has_annotated_vla' is a struct type with a flexible array member}}
   struct has_annotated_vla* __sized_by_or_null(size) objects;
   int size;
 };
 
 struct on_nested_pointer_inner {
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__sized_by_or_null` can only be nested when used in function parameters.
-  // expected-error at +1{{use of undeclared identifier 'size'}}
+  // expected-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
   struct size_known *__sized_by_or_null(size) *buf;
   int size;
 };
 
 struct on_nested_pointer_outer {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   struct size_known **__sized_by_or_null(size) buf;
   int size;
 };
 
 struct on_pointer_anon_buf_ty_pos {
   struct {
-    // TODO: Support referring to parent scope
-    // expected-error at +1{{use of undeclared identifier 'size'}}
     struct size_known * __sized_by_or_null(size) buf;
   };
   int size;
 };
 
 struct on_pointer_anon_count_ty_pos {
-  // TODO: Allow this
-  // expected-error at +1{{use of undeclared identifier 'size'}}
   struct size_known *__sized_by_or_null(size) buf;
   struct {
     int size;
diff --git a/clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c b/clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c
index 4200c9275a180..fc4aee3730f89 100644
--- a/clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c
@@ -102,8 +102,6 @@ struct on_pointer_anon_size {
 //==============================================================================
 // __sized_by_or_null on struct member pointer in type attribute position
 //==============================================================================
-// TODO: Correctly parse sized_by_or_null as a type attribute. Currently it is parsed
-// as a declaration attribute
 
 struct on_member_pointer_complete_ty_ty_pos {
   int size;
@@ -151,13 +149,18 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
   fn_ptr_ty __sized_by_or_null(size) fn_ptr;
 };
 
-// TODO: This should be forbidden but isn't due to sized_by_or_null being treated
-// as a declaration attribute.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int size;
+  // expected-error at +1{{cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __sized_by_or_null(size) * fn_ptr)(void);
 };
 
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+  int size;
+  // expected-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
+  void (** __sized_by_or_null(size) * fn_ptr)(void);
+};
+
 struct on_member_pointer_struct_with_vla_ty_pos {
   int size;
   // expected-error at +1{{'sized_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
@@ -171,9 +174,8 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
 };
 
 struct on_nested_pointer_inner {
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__sized_by_or_null` can only be nested when used in function parameters.
   int size;
+  // expected-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
   struct size_known *__sized_by_or_null(size) *buf;
 };
 
diff --git a/clang/test/Sema/attr-sized-by-struct-ptrs.c b/clang/test/Sema/attr-sized-by-struct-ptrs.c
index 07373b247d0f7..49034685a9a59 100644
--- a/clang/test/Sema/attr-sized-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-struct-ptrs.c
@@ -151,13 +151,18 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
   fn_ptr_ty __sized_by(size) fn_ptr;
 };
 
-// TODO: This should be forbidden but isn't due to sized_by being treated
-// as a declaration attribute.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int size;
+  // expected-error at +1{{cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
   void (* __sized_by(size) * fn_ptr)(void);
 };
 
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+  int size;
+  // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
+  void (** __sized_by(size) * fn_ptr)(void);
+};
+
 struct on_member_pointer_struct_with_vla_ty_pos {
   int size;
   // expected-error at +1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
@@ -171,9 +176,8 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
 };
 
 struct on_nested_pointer_inner {
-  // TODO: This should be disallowed because in the `-fbounds-safety` model
-  // `__sized_by` can only be nested when used in function parameters.
   int size;
+  // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
   struct size_known *__sized_by(size) *buf;
 };
 
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 350cd2135657d..2e9a1ccb954eb 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -1738,6 +1738,10 @@ bool CursorVisitor::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
   return Visit(TL.getInnerLoc());
 }
 
+bool CursorVisitor::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+  return Visit(TL.getInnerLoc());
+}
+
 bool CursorVisitor::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
   return Visit(TL.getWrappedLoc());
 }
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index 82fd9844cf96a..7939f0192ec72 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -4099,6 +4099,9 @@ TypeSystemClang::GetTypeClass(lldb::opaque_compiler_type_t type) {
   case clang::Type::Using:
   case clang::Type::PredefinedSugar:
     llvm_unreachable("Handled in RemoveWrappingTypes!");
+  case clang::Type::LateParsedAttr:
+    llvm_unreachable("LateParsedAttrType is a transient parsing placeholder "
+                     "that is resolved before the AST is finalized.");
   case clang::Type::UnaryTransform:
     break;
   case clang::Type::FunctionNoProto:
@@ -4803,7 +4806,9 @@ lldb::Encoding TypeSystemClang::GetEncoding(lldb::opaque_compiler_type_t type) {
   case clang::Type::Using:
   case clang::Type::PredefinedSugar:
     llvm_unreachable("Handled in RemoveWrappingTypes!");
-
+  case clang::Type::LateParsedAttr:
+    llvm_unreachable("LateParsedAttrType is a transient parsing placeholder "
+                     "that is resolved before the AST is finalized.");
   case clang::Type::UnaryTransform:
     break;
 
@@ -5105,6 +5110,9 @@ lldb::Format TypeSystemClang::GetFormat(lldb::opaque_compiler_type_t type) {
   case clang::Type::Using:
   case clang::Type::PredefinedSugar:
     llvm_unreachable("Handled in RemoveWrappingTypes!");
+  case clang::Type::LateParsedAttr:
+    llvm_unreachable("LateParsedAttrType is a transient parsing placeholder "
+                     "that is resolved before the AST is finalized.");
   case clang::Type::UnaryTransform:
     break;
 

>From 72dab460e529ccd669a71f0e674b13320a00819d Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Mon, 13 Jul 2026 12:51:43 -0700
Subject: [PATCH 2/2] Propagate TypeDependence::LateParsedAttr and skip
 TreeTransform if it's not set

---
 clang/include/clang/AST/DependenceFlags.h | 10 ++++++++--
 clang/include/clang/AST/TypeBase.h        | 10 +++++++++-
 clang/lib/Sema/SemaDecl.cpp               |  2 +-
 3 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/AST/DependenceFlags.h b/clang/include/clang/AST/DependenceFlags.h
index c4395259f0758..9c3b6a315134e 100644
--- a/clang/include/clang/AST/DependenceFlags.h
+++ b/clang/include/clang/AST/DependenceFlags.h
@@ -69,12 +69,18 @@ struct TypeDependenceScope {
     /// yields an error type.
     Error = 16,
 
+    /// Whether this type contains a LateParsedAttrType placeholder — a
+    /// bounds-safety type attribute deferred until enclosing declarations
+    /// are visible. Cleared once RebuildTypeWithLateParsedAttr resolves
+    /// the placeholder into a concrete type (e.g. CountAttributedType).
+    LateParsedAttr = 32,
+
     None = 0,
-    All = 31,
+    All = 63,
 
     DependentInstantiation = Dependent | Instantiation,
 
-    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Error)
+    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/LateParsedAttr)
   };
 };
 using TypeDependence = TypeDependenceScope::TypeDependence;
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index be72754b2fc87..7d56157af2a17 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -2864,6 +2864,14 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
     return getDependence() & TypeDependence::VariablyModified;
   }
 
+  /// Whether this type contains a LateParsedAttrType placeholder that still
+  /// needs to be resolved by RebuildTypeWithLateParsedAttr. Meaningful only on
+  /// the sugared type as written; canonical types do not carry the
+  /// placeholder.
+  bool hasLateParsedAttr() const {
+    return getDependence() & TypeDependence::LateParsedAttr;
+  }
+
   /// Whether this type involves a variable-length array type
   /// with a definite size.
   bool hasSizedVLAType() const;
@@ -3559,7 +3567,7 @@ class LateParsedAttrType : public Type, public llvm::FoldingSetNode {
 
   LateParsedAttrType(QualType Wrapped, QualType Canon,
                      LateParsedTypeAttribute *Attr)
-      : Type(LateParsedAttr, Canon, Wrapped->getDependence()),
+      : Type(LateParsedAttr, Canon, TypeDependence::LateParsedAttr | Wrapped->getDependence()),
         WrappedTy(Wrapped), LateParsedTypeAttr(Attr) {}
 
 public:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8e7f72ed63467..4727c9673974e 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -20193,7 +20193,7 @@ void Sema::ProcessLateParsedTypeAttributes(
     if (!FD && IFD) {
       FD = IFD->getAnonField();
     }
-    if (!FD || FD->getType()->isRecordType())
+    if (!FD || !FD->getType()->hasLateParsedAttr() || FD->getType()->isRecordType())
       continue;
 
     RebuildTypeWithLateParsedAttr RebuildFieldType(*this, FD, ParseCB);



More information about the cfe-commits mailing list