Author: Doug Gregor
Date: 2026-05-21T14:49:40-07:00
New Revision: 77e43ec11cd8fbe1de491118b54de9bba94510a8

URL: 
https://github.com/llvm/llvm-project/commit/77e43ec11cd8fbe1de491118b54de9bba94510a8
DIFF: 
https://github.com/llvm/llvm-project/commit/77e43ec11cd8fbe1de491118b54de9bba94510a8.diff

LOG: Associate documentation comments with macro definitions (#198452)

Added: 
    clang/include/clang/Lex/MacroBase.h
    clang/test/ExtractAPI/macro_doc_comments.c
    clang/test/Index/annotate-comments-macros.c

Modified: 
    clang-tools-extra/clang-doc/Mapper.cpp
    clang-tools-extra/clang-doc/Serialize.cpp
    clang-tools-extra/clang-move/Move.cpp
    clang-tools-extra/clangd/refactor/InsertionPoint.cpp
    clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
    clang/docs/ReleaseNotes.rst
    clang/include/clang/AST/ASTContext.h
    clang/include/clang/ExtractAPI/API.h
    clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
    clang/include/clang/Lex/MacroInfo.h
    clang/lib/AST/ASTContext.cpp
    clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
    clang/lib/Tooling/Transformer/SourceCode.cpp
    clang/test/Index/annotate-comments.cpp
    clang/tools/libclang/CIndex.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-doc/Mapper.cpp 
b/clang-tools-extra/clang-doc/Mapper.cpp
index 824c51e614eef..3c4c17e2b0279 100644
--- a/clang-tools-extra/clang-doc/Mapper.cpp
+++ b/clang-tools-extra/clang-doc/Mapper.cpp
@@ -150,7 +150,7 @@ bool MapASTVisitor::VisitVarDecl(const VarDecl *D) {
 
 comments::FullComment *
 MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const 
{
-  RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
+  RawComment *Comment = Context.getRawCommentNoCache(D);
   // FIXME: Move setAttached to the initial comment parsing.
   if (Comment) {
     Comment->setAttached();

diff  --git a/clang-tools-extra/clang-doc/Serialize.cpp 
b/clang-tools-extra/clang-doc/Serialize.cpp
index 818ecd478c21b..5ad7327076763 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -608,8 +608,7 @@ void Serializer::parseEnumerators(EnumInfo &I, const 
EnumDecl *D) {
     EnumValueInfo &Member = LocalMembers.emplace_back(
         E->getNameAsString(), ValueStr.str(), ValueExpr);
     ASTContext &Context = E->getASTContext();
-    if (RawComment *Comment =
-            E->getASTContext().getRawCommentForDeclNoCache(E)) {
+    if (RawComment *Comment = E->getASTContext().getRawCommentNoCache(E)) {
       Comment->setAttached();
       if (comments::FullComment *Fc = Comment->parse(Context, nullptr, E)) {
         auto *NewCI = allocateListNodeTransient<CommentInfo>();
@@ -925,7 +924,7 @@ void Serializer::populateMemberTypeInfo(T &I, const Decl 
*D) {
   ASTContext &Context = D->getASTContext();
   // TODO investigate whether we can use ASTContext::getCommentForDecl instead
   // of this logic. See also similar code in Mapper.cpp.
-  RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
+  RawComment *Comment = Context.getRawCommentNoCache(D);
   if (!Comment)
     return;
 
@@ -1210,7 +1209,7 @@ std::pair<Info *, Info *> Serializer::emitInfo(const 
CXXMethodDecl *D,
 void Serializer::extractCommentFromDecl(const Decl *D, TypedefInfo &Info) {
   assert(D && "Invalid Decl when extracting comment");
   ASTContext &Context = D->getASTContext();
-  RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
+  RawComment *Comment = Context.getRawCommentNoCache(D);
   if (!Comment)
     return;
 

diff  --git a/clang-tools-extra/clang-move/Move.cpp 
b/clang-tools-extra/clang-move/Move.cpp
index 519d359991cdb..1f966b3119620 100644
--- a/clang-tools-extra/clang-move/Move.cpp
+++ b/clang-tools-extra/clang-move/Move.cpp
@@ -314,7 +314,7 @@ CharSourceRange getFullRange(const Decl *D,
   const auto &SM = D->getASTContext().getSourceManager();
   SourceRange Full(SM.getExpansionLoc(D->getBeginLoc()), 
getLocForEndOfDecl(D));
   // Expand to comments that are associated with the Decl.
-  if (const auto *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) 
{
+  if (const auto *Comment = D->getASTContext().getRawCommentNoCache(D)) {
     if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getEndLoc()))
       Full.setEnd(Comment->getEndLoc());
     // FIXME: Don't delete a preceding comment, if there are no other entities

diff  --git a/clang-tools-extra/clangd/refactor/InsertionPoint.cpp 
b/clang-tools-extra/clangd/refactor/InsertionPoint.cpp
index e3b11ceb4f016..cf74044d41d39 100644
--- a/clang-tools-extra/clangd/refactor/InsertionPoint.cpp
+++ b/clang-tools-extra/clangd/refactor/InsertionPoint.cpp
@@ -65,7 +65,7 @@ std::optional<const Decl *> insertionDecl(const DeclContext 
&DC,
 
 SourceLocation beginLoc(const Decl &D) {
   auto Loc = D.getBeginLoc();
-  if (RawComment *Comment = D.getASTContext().getRawCommentForDeclNoCache(&D)) 
{
+  if (RawComment *Comment = D.getASTContext().getRawCommentNoCache(&D)) {
     auto CommentLoc = Comment->getBeginLoc();
     if (CommentLoc.isValid() && Loc.isValid() &&
         D.getASTContext().getSourceManager().isBeforeInTranslationUnit(

diff  --git a/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp 
b/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
index f4ee8ca312125..80428030fd4a8 100644
--- a/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
@@ -25,8 +25,7 @@ class ClangDocSerializeTestVisitor
   bool Public;
 
   comments::FullComment *getComment(const NamedDecl *D) const {
-    if (RawComment *Comment =
-            D->getASTContext().getRawCommentForDeclNoCache(D)) {
+    if (RawComment *Comment = D->getASTContext().getRawCommentNoCache(D)) {
       Comment->setAttached();
       return Comment->parse(D->getASTContext(), nullptr, D);
     }

diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a490dc5469a27..cf16e40d026c3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -836,6 +836,7 @@ libclang
 - Visit switch initializer statements 
(https://bugs.kde.org/show_bug.cgi?id=415537#c2)
 - Fix crash in clang_getBinaryOperatorKindSpelling and 
clang_getUnaryOperatorKindSpelling
 - The clang_Module_getASTFile API is deprecated and now always returns nullptr
+- The clang_Cursor_getCommentRange API will now return a comment range for 
macro definitions that have documentation comments.
 
 Code Completion
 ---------------

diff  --git a/clang/include/clang/AST/ASTContext.h 
b/clang/include/clang/AST/ASTContext.h
index b2fd522e6865c..9ef27cc1eb58e 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -30,6 +30,7 @@
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/PartialDiagnostic.h"
 #include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/MacroBase.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/DenseMapInfo.h"
 #include "llvm/ADT/DenseSet.h"
@@ -996,11 +997,15 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// True if comments are already loaded from ExternalASTSource.
   mutable bool CommentsLoaded = false;
 
-  /// Mapping from declaration to directly attached comment.
+  /// Key used to look up the raw comment attached to a declaration or macro.
+  using RawCommentLookupKey =
+      llvm::PointerUnion<const Decl *, const MacroInfo *>;
+
+  /// Mapping from declaration or macro to directly attached comment.
   ///
   /// Raw comments are owned by Comments list.  This mapping is populated
   /// lazily.
-  mutable llvm::DenseMap<const Decl *, const RawComment *> DeclRawComments;
+  mutable llvm::DenseMap<RawCommentLookupKey, const RawComment *> RawComments;
 
   /// Mapping from canonical declaration to the first redeclaration in chain
   /// that has a comment attached.
@@ -1022,37 +1027,40 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// redeclaration.
   mutable llvm::DenseMap<const Decl *, comments::FullComment *> ParsedComments;
 
-  /// Attaches \p Comment to \p OriginalD and to its redeclaration chain
-  /// and removes the redeclaration chain from the set of commentless chains.
+  /// Attaches \p Comment to \p Original (a declaration or macro), and to its
+  /// redeclaration chain when \p Original is a declaration. Removes the
+  /// redeclaration chain from the set of commentless chains.
   ///
-  /// Don't do anything if a comment has already been attached to \p OriginalD
+  /// Don't do anything if a comment has already been attached to \p Original
   /// or its redeclaration chain.
-  void cacheRawCommentForDecl(const Decl &OriginalD,
-                              const RawComment &Comment) const;
+  void cacheRawComment(RawCommentLookupKey Original,
+                       const RawComment &Comment) const;
 
-  /// \returns searches \p CommentsInFile for doc comment for \p D.
+  /// \returns searches \p CommentsInFile for doc comment for \p Key.
   ///
   /// \p RepresentativeLocForDecl is used as a location for searching doc
   /// comments. \p CommentsInFile is a mapping offset -> comment of files in 
the
   /// same file where \p RepresentativeLocForDecl is.
-  RawComment *getRawCommentForDeclNoCacheImpl(
-      const Decl *D, const SourceLocation RepresentativeLocForDecl,
+  RawComment *getRawCommentNoCacheImpl(
+      RawCommentLookupKey Key, const SourceLocation RepresentativeLoc,
       const std::map<unsigned, RawComment *> &CommentsInFile) const;
 
-  /// Return the documentation comment attached to a given declaration,
-  /// without looking into cache.
-  RawComment *getRawCommentForDeclNoCache(const Decl *D) const;
+  /// Return the documentation comment attached to a given declaration or
+  /// macro, without looking into cache.
+  RawComment *getRawCommentNoCache(RawCommentLookupKey Key) const;
 
 public:
   void addComment(const RawComment &RC);
 
-  /// Return the documentation comment attached to a given declaration.
-  /// Returns nullptr if no comment is attached.
+  /// Return the documentation comment attached to a given declaration or
+  /// macro.  Returns nullptr if no comment is attached.
   ///
   /// \param OriginalDecl if not nullptr, is set to declaration AST node that
   /// had the comment, if the comment we found comes from a redeclaration.
+  /// Macros have no redeclaration chain, so this is set to nullptr when
+  /// \p Key is a \c MacroInfo.
   const RawComment *
-  getRawCommentForAnyRedecl(const Decl *D,
+  getRawCommentForAnyRedecl(RawCommentLookupKey Key,
                             const Decl **OriginalDecl = nullptr) const;
 
   /// Searches existing comments for doc comments that should be attached to \p

diff  --git a/clang/include/clang/ExtractAPI/API.h 
b/clang/include/clang/ExtractAPI/API.h
index bea5416a626b9..62483bcf2cdc9 100644
--- a/clang/include/clang/ExtractAPI/API.h
+++ b/clang/include/clang/ExtractAPI/API.h
@@ -1380,11 +1380,12 @@ struct ObjCProtocolRecord : ObjCContainerRecord {
 /// This holds information associated with macro definitions.
 struct MacroDefinitionRecord : APIRecord {
   MacroDefinitionRecord(StringRef USR, StringRef Name, SymbolReference Parent,
-                        PresumedLoc Loc, DeclarationFragments Declaration,
+                        PresumedLoc Loc, const DocComment &Comment,
+                        DeclarationFragments Declaration,
                         DeclarationFragments SubHeading,
                         bool IsFromSystemHeader)
       : APIRecord(RK_MacroDefinition, USR, Name, Parent, Loc,
-                  AvailabilityInfo(), LinkageInfo(), {}, Declaration,
+                  AvailabilityInfo(), LinkageInfo(), Comment, Declaration,
                   SubHeading, IsFromSystemHeader) {}
 
   static bool classof(const APIRecord *Record) {

diff  --git a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h 
b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
index 67d54c9ebb811..38f57eeeddc3a 100644
--- a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
+++ b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
@@ -1538,14 +1538,14 @@ class ExtractAPIVisitor
 
   bool shouldDeclBeIncluded(const Decl *D) const { return true; }
   const RawComment *fetchRawCommentForDecl(const Decl *D) const {
-    if (const auto *Comment = this->Context.getRawCommentForDeclNoCache(D))
+    if (const auto *Comment = this->Context.getRawCommentNoCache(D))
       return Comment;
 
     if (const auto *Declarator = dyn_cast<DeclaratorDecl>(D)) {
       const auto *TagTypeDecl = Declarator->getType()->getAsTagDecl();
       if (TagTypeDecl && TagTypeDecl->isEmbeddedInDeclarator() &&
           TagTypeDecl->isCompleteDefinition())
-        return this->Context.getRawCommentForDeclNoCache(TagTypeDecl);
+        return this->Context.getRawCommentNoCache(TagTypeDecl);
     }
 
     return nullptr;

diff  --git a/clang/include/clang/Lex/MacroBase.h 
b/clang/include/clang/Lex/MacroBase.h
new file mode 100644
index 0000000000000..2ee719377ee02
--- /dev/null
+++ b/clang/include/clang/Lex/MacroBase.h
@@ -0,0 +1,43 @@
+//===--- MacroBase.h - Forward declarations for macros ----------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Forward-declares types that need PointerLikeTypeTraits.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LEX_MACROBASE_H
+#define LLVM_CLANG_LEX_MACROBASE_H
+
+#include "llvm/Support/PointerLikeTypeTraits.h"
+
+namespace clang {
+class MacroInfo;
+} // namespace clang
+
+namespace llvm {
+template <> struct PointerLikeTypeTraits<::clang::MacroInfo *> {
+  static inline void *getAsVoidPointer(::clang::MacroInfo *P) { return P; }
+  static inline ::clang::MacroInfo *getFromVoidPointer(void *P) {
+    return static_cast<::clang::MacroInfo *>(P);
+  }
+  static constexpr int NumLowBitsAvailable = 2;
+};
+
+template <> struct PointerLikeTypeTraits<const ::clang::MacroInfo *> {
+  static inline const void *getAsVoidPointer(const ::clang::MacroInfo *P) {
+    return P;
+  }
+  static inline const ::clang::MacroInfo *getFromVoidPointer(const void *P) {
+    return static_cast<const ::clang::MacroInfo *>(P);
+  }
+  static constexpr int NumLowBitsAvailable = 2;
+};
+} // namespace llvm
+
+#endif // LLVM_CLANG_LEX_MACROBASE_H

diff  --git a/clang/include/clang/Lex/MacroInfo.h 
b/clang/include/clang/Lex/MacroInfo.h
index cd47a7048f2c6..60048688a9a33 100644
--- a/clang/include/clang/Lex/MacroInfo.h
+++ b/clang/include/clang/Lex/MacroInfo.h
@@ -14,9 +14,10 @@
 #ifndef LLVM_CLANG_LEX_MACROINFO_H
 #define LLVM_CLANG_LEX_MACROINFO_H
 
-#include "clang/Lex/Token.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/MacroBase.h"
+#include "clang/Lex/Token.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/FoldingSet.h"
 #include "llvm/ADT/PointerIntPair.h"

diff  --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index bc4771aec77d1..a401a7471e6fc 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -66,6 +66,7 @@
 #include "clang/Basic/TargetCXXABI.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/Basic/XRayLists.h"
+#include "clang/Lex/MacroInfo.h"
 #include "llvm/ADT/APFixedPoint.h"
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/APSInt.h"
@@ -116,9 +117,36 @@ enum FloatingRank {
 };
 
 /// \returns The locations that are relevant when searching for Doc comments
-/// related to \p D.
+/// related to \p Key.
 static SmallVector<SourceLocation, 2>
-getDeclLocsForCommentSearch(const Decl *D, SourceManager &SourceMgr) {
+getLocsForCommentSearch(ASTContext::RawCommentLookupKey Key,
+                        SourceManager &SourceMgr) {
+  if (const auto *MI = dyn_cast<const MacroInfo *>(Key)) {
+    SourceLocation DefLoc = MI->getDefinitionLoc();
+    if (DefLoc.isInvalid() || !DefLoc.isFileID())
+      return {};
+
+    // The macro's definition location points at its name (e.g. FOO in
+    // `#define FOO 1`). The text between a preceding documentation comment
+    // and the name contains the `#define` directive itself, which would be
+    // rejected by the preprocessor-directive guard in
+    // getRawCommentNoCacheImpl. Walk back to the leading `#` so that
+    // the guard only fires when something *else* sits between the comment
+    // and our directive.
+    FileIDAndOffset Decomposed = SourceMgr.getDecomposedLoc(DefLoc);
+    bool Invalid = false;
+    StringRef Buffer = SourceMgr.getBufferData(Decomposed.first, &Invalid);
+    if (Invalid)
+      return {};
+    unsigned Offset = Decomposed.second;
+    if (size_t Found = Buffer.find_last_of("#\n", Offset);
+        Found != StringRef::npos)
+      Offset = Found;
+    return {SourceMgr.getLocForStartOfFile(Decomposed.first)
+                .getLocWithOffset(Offset)};
+  }
+
+  const auto *D = cast<const Decl *>(Key);
   assert(D);
 
   // User can not attach documentation to implicit declarations.
@@ -214,27 +242,29 @@ getDeclLocsForCommentSearch(const Decl *D, SourceManager 
&SourceMgr) {
   return Locations;
 }
 
-RawComment *ASTContext::getRawCommentForDeclNoCacheImpl(
-    const Decl *D, const SourceLocation RepresentativeLocForDecl,
+RawComment *ASTContext::getRawCommentNoCacheImpl(
+    RawCommentLookupKey Key, const SourceLocation RepresentativeLoc,
     const std::map<unsigned, RawComment *> &CommentsInTheFile) const {
   // If the declaration doesn't map directly to a location in a file, we
   // can't find the comment.
-  if (RepresentativeLocForDecl.isInvalid() ||
-      !RepresentativeLocForDecl.isFileID())
+  if (RepresentativeLoc.isInvalid() || !RepresentativeLoc.isFileID())
     return nullptr;
 
   // If there are no comments anywhere, we won't find anything.
   if (CommentsInTheFile.empty())
     return nullptr;
 
+  const auto *D = dyn_cast<const Decl *>(Key);
+  const bool IsMacro = isa<const MacroInfo *>(Key);
+
   // Decompose the location for the declaration and find the beginning of the
   // file buffer.
-  const FileIDAndOffset DeclLocDecomp =
-      SourceMgr.getDecomposedLoc(RepresentativeLocForDecl);
+  const FileIDAndOffset LocDecomp =
+      SourceMgr.getDecomposedLoc(RepresentativeLoc);
 
   // Slow path.
   auto OffsetCommentBehindDecl =
-      CommentsInTheFile.lower_bound(DeclLocDecomp.second);
+      CommentsInTheFile.lower_bound(LocDecomp.second);
 
   // First check whether we have a trailing comment.
   if (OffsetCommentBehindDecl != CommentsInTheFile.end()) {
@@ -242,13 +272,14 @@ RawComment *ASTContext::getRawCommentForDeclNoCacheImpl(
     if ((CommentBehindDecl->isDocumentation() ||
          LangOpts.CommentOpts.ParseAllComments) &&
         CommentBehindDecl->isTrailingComment() &&
-        (isa<FieldDecl>(D) || isa<EnumConstantDecl>(D) || isa<VarDecl>(D) ||
-         isa<ObjCMethodDecl>(D) || isa<ObjCPropertyDecl>(D))) {
+        (IsMacro || (D && (isa<FieldDecl>(D) || isa<EnumConstantDecl>(D) ||
+                           isa<VarDecl>(D) || isa<ObjCMethodDecl>(D) ||
+                           isa<ObjCPropertyDecl>(D))))) {
 
       // Check that Doxygen trailing comment comes after the declaration, 
starts
       // on the same line and in the same file as the declaration.
-      if (SourceMgr.getLineNumber(DeclLocDecomp.first, DeclLocDecomp.second) ==
-          Comments.getCommentBeginLine(CommentBehindDecl, DeclLocDecomp.first,
+      if (SourceMgr.getLineNumber(LocDecomp.first, LocDecomp.second) ==
+          Comments.getCommentBeginLine(CommentBehindDecl, LocDecomp.first,
                                        OffsetCommentBehindDecl->first)) {
         return CommentBehindDecl;
       }
@@ -275,14 +306,14 @@ RawComment *ASTContext::getRawCommentForDeclNoCacheImpl(
 
   // Get the corresponding buffer.
   bool Invalid = false;
-  const char *Buffer = SourceMgr.getBufferData(DeclLocDecomp.first,
-                                               &Invalid).data();
+  const char *Buffer =
+      SourceMgr.getBufferData(LocDecomp.first, &Invalid).data();
   if (Invalid)
     return nullptr;
 
   // Extract text between the comment and declaration.
   StringRef Text(Buffer + CommentEndOffset,
-                 DeclLocDecomp.second - CommentEndOffset);
+                 LocDecomp.second - CommentEndOffset);
 
   // There should be no other declarations or preprocessor directives between
   // comment and declaration.
@@ -292,13 +323,13 @@ RawComment *ASTContext::getRawCommentForDeclNoCacheImpl(
   return CommentBeforeDecl;
 }
 
-RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const {
-  const auto DeclLocs = getDeclLocsForCommentSearch(D, SourceMgr);
+RawComment *ASTContext::getRawCommentNoCache(RawCommentLookupKey Key) const {
+  const auto Locs = getLocsForCommentSearch(Key, SourceMgr);
 
-  for (const auto DeclLoc : DeclLocs) {
-    // If the declaration doesn't map directly to a location in a file, we
-    // can't find the comment.
-    if (DeclLoc.isInvalid() || !DeclLoc.isFileID())
+  for (const auto Loc : Locs) {
+    // If the declaration or macro doesn't map directly to a location in a 
file,
+    // we can't find the comment.
+    if (Loc.isInvalid() || !Loc.isFileID())
       continue;
 
     if (ExternalSource && !CommentsLoaded) {
@@ -309,7 +340,7 @@ RawComment *ASTContext::getRawCommentForDeclNoCache(const 
Decl *D) const {
     if (Comments.empty())
       continue;
 
-    const FileID File = SourceMgr.getDecomposedLoc(DeclLoc).first;
+    const FileID File = SourceMgr.getDecomposedLoc(Loc).first;
     if (!File.isValid())
       continue;
 
@@ -318,7 +349,7 @@ RawComment *ASTContext::getRawCommentForDeclNoCache(const 
Decl *D) const {
       continue;
 
     if (RawComment *Comment =
-            getRawCommentForDeclNoCacheImpl(D, DeclLoc, *CommentsInThisFile))
+            getRawCommentNoCacheImpl(Key, Loc, *CommentsInThisFile))
       return Comment;
   }
 
@@ -331,21 +362,37 @@ void ASTContext::addComment(const RawComment &RC) {
   Comments.addComment(RC, LangOpts.CommentOpts, BumpAlloc);
 }
 
-const RawComment *ASTContext::getRawCommentForAnyRedecl(
-                                                const Decl *D,
-                                                const Decl **OriginalDecl) 
const {
-  if (!D) {
+const RawComment *
+ASTContext::getRawCommentForAnyRedecl(RawCommentLookupKey Key,
+                                      const Decl **OriginalDecl) const {
+  if (Key.isNull()) {
     if (OriginalDecl)
-      OriginalDecl = nullptr;
+      *OriginalDecl = nullptr;
     return nullptr;
   }
 
+  // Macros have no redeclaration chain: look up directly, populate the cache,
+  // and return.
+  if (const auto *MI = dyn_cast<const MacroInfo *>(Key)) {
+    if (OriginalDecl)
+      *OriginalDecl = nullptr;
+    auto Existing = RawComments.find(Key);
+    if (Existing != RawComments.end())
+      return Existing->second;
+    if (const RawComment *RC = getRawCommentNoCache(Key)) {
+      cacheRawComment(MI, *RC);
+      return RC;
+    }
+    return nullptr;
+  }
+
+  const Decl *D = cast<const Decl *>(Key);
   D = &adjustDeclToTemplate(*D);
 
   // Any comment directly attached to D?
   {
-    auto DeclComment = DeclRawComments.find(D);
-    if (DeclComment != DeclRawComments.end()) {
+    auto DeclComment = RawComments.find(D);
+    if (DeclComment != RawComments.end()) {
       if (OriginalDecl)
         *OriginalDecl = D;
       return DeclComment->second;
@@ -362,8 +409,8 @@ const RawComment *ASTContext::getRawCommentForAnyRedecl(
     if (RedeclComment != RedeclChainComments.end()) {
       if (OriginalDecl)
         *OriginalDecl = RedeclComment->second;
-      auto CommentAtRedecl = DeclRawComments.find(RedeclComment->second);
-      assert(CommentAtRedecl != DeclRawComments.end() &&
+      auto CommentAtRedecl = RawComments.find(RedeclComment->second);
+      assert(CommentAtRedecl != RawComments.end() &&
              "This decl is supposed to have comment attached.");
       return CommentAtRedecl->second;
     }
@@ -398,9 +445,9 @@ const RawComment *ASTContext::getRawCommentForAnyRedecl(
       }
       continue;
     }
-    const RawComment *RedeclComment = getRawCommentForDeclNoCache(Redecl);
+    const RawComment *RedeclComment = getRawCommentNoCache(Redecl);
     if (RedeclComment) {
-      cacheRawCommentForDecl(*Redecl, *RedeclComment);
+      cacheRawComment(Redecl, *RedeclComment);
       if (OriginalDecl)
         *OriginalDecl = Redecl;
       return RedeclComment;
@@ -413,13 +460,15 @@ const RawComment *ASTContext::getRawCommentForAnyRedecl(
   return nullptr;
 }
 
-void ASTContext::cacheRawCommentForDecl(const Decl &OriginalD,
-                                        const RawComment &Comment) const {
+void ASTContext::cacheRawComment(RawCommentLookupKey Original,
+                                 const RawComment &Comment) const {
   assert(Comment.isDocumentation() || LangOpts.CommentOpts.ParseAllComments);
-  DeclRawComments.try_emplace(&OriginalD, &Comment);
-  const Decl *const CanonicalDecl = OriginalD.getCanonicalDecl();
-  RedeclChainComments.try_emplace(CanonicalDecl, &OriginalD);
-  CommentlessRedeclChains.erase(CanonicalDecl);
+  RawComments.try_emplace(Original, &Comment);
+  if (const auto *D = dyn_cast<const Decl *>(Original)) {
+    const Decl *const CanonicalDecl = D->getCanonicalDecl();
+    RedeclChainComments.try_emplace(CanonicalDecl, D);
+    CommentlessRedeclChains.erase(CanonicalDecl);
+  }
 }
 
 static void addRedeclaredMethods(const ObjCMethodDecl *ObjCMethod,
@@ -481,18 +530,18 @@ void 
ASTContext::attachCommentsToJustParsedDecls(ArrayRef<Decl *> Decls,
 
     D = &adjustDeclToTemplate(*D);
 
-    if (DeclRawComments.count(D) > 0)
+    if (RawComments.count(D) > 0)
       continue;
 
-    const auto DeclLocs = getDeclLocsForCommentSearch(D, SourceMgr);
+    const auto DeclLocs = getLocsForCommentSearch(D, SourceMgr);
 
     for (const auto DeclLoc : DeclLocs) {
       if (DeclLoc.isInvalid() || !DeclLoc.isFileID())
         continue;
 
-      if (RawComment *const DocComment = getRawCommentForDeclNoCacheImpl(
-              D, DeclLoc, *CommentsInThisFile)) {
-        cacheRawCommentForDecl(*D, *DocComment);
+      if (RawComment *const DocComment =
+              getRawCommentNoCacheImpl(D, DeclLoc, *CommentsInThisFile)) {
+        cacheRawComment(D, *DocComment);
         comments::FullComment *FC = DocComment->parse(*this, PP, D);
         ParsedComments[D->getCanonicalDecl()] = FC;
         break;
@@ -517,7 +566,7 @@ comments::FullComment 
*ASTContext::cloneFullComment(comments::FullComment *FC,
 }
 
 comments::FullComment *ASTContext::getLocalCommentForDeclUncached(const Decl 
*D) const {
-  const RawComment *RC = getRawCommentForDeclNoCache(D);
+  const RawComment *RC = getRawCommentNoCache(D);
   return RC ? RC->parse(*this, nullptr, D) : nullptr;
 }
 

diff  --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp 
b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
index 20389b5e4149d..85da480fb67a6 100644
--- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
+++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
@@ -282,8 +282,9 @@ class ExtractAPIConsumer : public ASTConsumer {
 
 class MacroCallback : public PPCallbacks {
 public:
-  MacroCallback(const SourceManager &SM, APISet &API, Preprocessor &PP)
-      : SM(SM), API(API), PP(PP) {}
+  MacroCallback(ASTContext &Ctx, const SourceManager &SM, APISet &API,
+                Preprocessor &PP)
+      : Ctx(Ctx), SM(SM), API(API), PP(PP) {}
 
   void EndOfMainFile() override {
     for (const auto &M : PP.macros()) {
@@ -321,8 +322,13 @@ class MacroCallback : public PPCallbacks {
       PresumedLoc Loc = SM.getPresumedLoc(DefLoc);
       SmallString<128> USR;
       index::generateUSRForMacro(Name, DefLoc, SM, USR);
+
+      DocComment Comment;
+      if (const auto *RC = Ctx.getRawCommentForAnyRedecl(MI))
+        Comment = RC->getFormattedLines(SM, Ctx.getDiagnostics());
+
       API.createRecord<extractapi::MacroDefinitionRecord>(
-          USR, Name, SymbolReference(), Loc,
+          USR, Name, SymbolReference(), Loc, Comment,
           DeclarationFragmentsBuilder::getFragmentsForMacro(Name, MI),
           DeclarationFragmentsBuilder::getSubHeadingForMacro(Name),
           SM.isInSystemHeader(DefLoc));
@@ -334,6 +340,7 @@ class MacroCallback : public PPCallbacks {
     return true;
   }
 
+  ASTContext &Ctx;
   const SourceManager &SM;
   APISet &API;
   Preprocessor &PP;
@@ -341,9 +348,9 @@ class MacroCallback : public PPCallbacks {
 
 class APIMacroCallback : public MacroCallback {
 public:
-  APIMacroCallback(const SourceManager &SM, APISet &API, Preprocessor &PP,
-                   LocationFileChecker &LCF)
-      : MacroCallback(SM, API, PP), LCF(LCF) {}
+  APIMacroCallback(ASTContext &Ctx, const SourceManager &SM, APISet &API,
+                   Preprocessor &PP, LocationFileChecker &LCF)
+      : MacroCallback(Ctx, SM, API, PP), LCF(LCF) {}
 
   bool shouldMacroBeIncluded(const SourceLocation &MacroLoc,
                              StringRef ModuleName) override {
@@ -415,7 +422,8 @@ ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, 
StringRef InFile) {
   auto LCF = std::make_unique<LocationFileChecker>(CI, KnownInputFiles);
 
   CI.getPreprocessor().addPPCallbacks(std::make_unique<APIMacroCallback>(
-      CI.getSourceManager(), *API, CI.getPreprocessor(), *LCF));
+      CI.getASTContext(), CI.getSourceManager(), *API, CI.getPreprocessor(),
+      *LCF));
 
   // Do not include location in anonymous decls.
   PrintingPolicy Policy = CI.getASTContext().getPrintingPolicy();
@@ -519,7 +527,7 @@ 
WrappingExtractAPIAction::CreateASTConsumer(CompilerInstance &CI,
       CI.getFrontendOpts().Inputs.back().getKind().getLanguage(), ProductName);
 
   CI.getPreprocessor().addPPCallbacks(std::make_unique<MacroCallback>(
-      CI.getSourceManager(), *API, CI.getPreprocessor()));
+      CI.getASTContext(), CI.getSourceManager(), *API, CI.getPreprocessor()));
 
   // Do not include location in anonymous decls.
   PrintingPolicy Policy = CI.getASTContext().getPrintingPolicy();

diff  --git a/clang/lib/Tooling/Transformer/SourceCode.cpp 
b/clang/lib/Tooling/Transformer/SourceCode.cpp
index 0ca5113035f7b..988f038980c87 100644
--- a/clang/lib/Tooling/Transformer/SourceCode.cpp
+++ b/clang/lib/Tooling/Transformer/SourceCode.cpp
@@ -454,7 +454,7 @@ CharSourceRange tooling::getAssociatedRange(const Decl 
&Decl,
   // that are not preceeding the decl, since we've already skipped trailing
   // comments with getEntityEndLoc.
   if (const RawComment *Comment =
-          Decl.getASTContext().getRawCommentForDeclNoCache(&Decl))
+          Decl.getASTContext().getRawCommentNoCache(&Decl))
     // Only include a preceding comment if:
     // * it is *not* separate from the declaration (not including any newline
     //   that immediately follows the comment),

diff  --git a/clang/test/ExtractAPI/macro_doc_comments.c 
b/clang/test/ExtractAPI/macro_doc_comments.c
new file mode 100644
index 0000000000000..d1d5e41574eb7
--- /dev/null
+++ b/clang/test/ExtractAPI/macro_doc_comments.c
@@ -0,0 +1,67 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -extract-api --pretty-sgf 
--emit-sgf-symbol-labels-for-testing --product-name=MacroDoc -triple 
arm64-apple-macosx \
+// RUN:   -fretain-comments-from-system-headers -isystem %S -x 
objective-c-header %s -o %t/output.symbols.json
+
+// Verify documentation comments attached to `#define` macros end up in the
+// symbol graph as a `docComment` block.
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix HELLO
+/// The greeting count.
+#define HELLO 1
+// HELLO-LABEL: "!testLabel": "c:@macro@HELLO"
+// HELLO:      "docComment": {
+// HELLO-NEXT:   "lines": [
+// HELLO:          "text": "The greeting count."
+// HELLO:        ]
+// HELLO-NEXT: },
+
+// A C-style block doc comment also attaches.
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix BLOCK
+/**
+ * \brief A version number.
+ */
+#define VERSION 42
+// BLOCK-LABEL: "!testLabel": "c:@macro@VERSION"
+// BLOCK:      "docComment": {
+// BLOCK-NEXT:   "lines": [
+// BLOCK:          "text": " \\brief A version number."
+// BLOCK:        ]
+// BLOCK-NEXT: },
+
+// Function-like macros pick up their preceding doc comment.
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix SUM
+/// Add two numbers.
+#define SUM(x, y) ((x) + (y))
+// SUM-LABEL: "!testLabel": "c:@macro@SUM"
+// SUM:      "docComment": {
+// SUM-NEXT:   "lines": [
+// SUM:          "text": "Add two numbers."
+// SUM:        ]
+// SUM-NEXT: },
+
+// Trailing `///<` doc comments attach to macros, just like fields/enumerators.
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix TRAIL
+#define ANSWER 42 ///< The answer.
+// TRAIL-LABEL: "!testLabel": "c:@macro@ANSWER"
+// TRAIL:      "docComment": {
+// TRAIL-NEXT:   "lines": [
+// TRAIL:          "text": "The answer."
+// TRAIL:        ]
+// TRAIL-NEXT: },
+
+// A macro with no documentation comment must NOT have a `docComment` key.
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix BARE
+#define BARE 7
+// BARE-LABEL: "!testLabel": "c:@macro@BARE"
+// BARE-NOT:   "docComment"
+// BARE:       "kind":
+
+// A non-doc `//` comment must not be attached as a docComment.
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix PLAIN
+// just an ordinary comment, not Doxygen
+#define PLAIN 8
+// PLAIN-LABEL: "!testLabel": "c:@macro@PLAIN"
+// PLAIN-NOT:   "docComment"
+// PLAIN:       "kind":
+
+// expected-no-diagnostics

diff  --git a/clang/test/Index/annotate-comments-macros.c 
b/clang/test/Index/annotate-comments-macros.c
new file mode 100644
index 0000000000000..ad81720fa0a7b
--- /dev/null
+++ b/clang/test/Index/annotate-comments-macros.c
@@ -0,0 +1,37 @@
+// Run lines are sensitive to line numbers and come below the code.
+
+/// Greeting count.
+#define HELLO 1
+
+#define BARE 2
+
+/**
+ * \brief Add two numbers.
+ */
+#define SUM(x, y) ((x) + (y))
+
+#define TRAILING 3 ///< Trailing macro doc.
+
+/// First definition.
+#define REDEF 1
+#undef REDEF
+/// Second definition.
+#define REDEF 2
+
+// RUN: c-index-test -test-load-source all %s | FileCheck %s
+
+// Documented object-like macro picks up the preceding `///` comment.
+// CHECK: annotate-comments-macros.c:4:9: macro definition=HELLO 
RawComment=[/// Greeting count.] RawCommentRange=[3:1 - 3:20] 
BriefComment=[Greeting count.]
+
+// A macro without any preceding doc comment must not have a RawComment.
+// CHECK: annotate-comments-macros.c:6:9: macro definition=BARE Extent=
+
+// Function-like macros are documented just like object-like ones.
+// CHECK: annotate-comments-macros.c:11:9: macro definition=SUM 
RawComment=[/**\n * \brief Add two numbers.\n */] RawCommentRange=[8:1 - 10:4] 
BriefComment=[Add two numbers.]
+
+// Trailing `///<` comments attach to macros, mirroring fields/enumerators.
+// CHECK: annotate-comments-macros.c:13:9: macro definition=TRAILING 
RawComment=[///< Trailing macro doc.] RawCommentRange=[13:20 - 13:44] 
BriefComment=[Trailing macro doc.]
+
+// Each redefinition of a macro carries its own preceding doc comment.
+// CHECK: annotate-comments-macros.c:16:9: macro definition=REDEF 
RawComment=[/// First definition.] RawCommentRange=[15:1 - 15:22] 
BriefComment=[First definition.]
+// CHECK: annotate-comments-macros.c:19:9: macro definition=REDEF 
RawComment=[/// Second definition.] RawCommentRange=[18:1 - 18:23] 
BriefComment=[Second definition.]

diff  --git a/clang/test/Index/annotate-comments.cpp 
b/clang/test/Index/annotate-comments.cpp
index 6f9f8f0bbbc9e..17e7beb1b7ca6 100644
--- a/clang/test/Index/annotate-comments.cpp
+++ b/clang/test/Index/annotate-comments.cpp
@@ -204,7 +204,7 @@ void isdoxy45(void);
 /// Ggg. IS_DOXYGEN_END
 void isdoxy46(void);
 
-/// IS_DOXYGEN_NOT_ATTACHED
+/// IS_MACRO_DOC
 #define FOO
 void notdoxy47(void);
 
@@ -295,6 +295,7 @@ void isdoxy54(int);
 // Adding a non-documentation comment with CHECK line between every two
 // documentation comments will only test a single code path.
 //
+// CHECK: annotate-comments.cpp:208:9: macro definition=FOO {{.*}} 
BriefComment=[IS_MACRO_DOC]
 // CHECK: annotate-comments.cpp:16:6: FunctionDecl=isdoxy4:{{.*}} isdoxy4 
IS_DOXYGEN_SINGLE
 // CHECK: annotate-comments.cpp:20:6: FunctionDecl=isdoxy5:{{.*}} isdoxy5 
IS_DOXYGEN_SINGLE
 // CHECK: annotate-comments.cpp:25:6: FunctionDecl=isdoxy6:{{.*}} isdoxy6 
IS_DOXYGEN_SINGLE

diff  --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 350cd2135657d..c07c4cd84bbce 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -9326,28 +9326,48 @@ CXString clang_Cursor_getBinaryOpcodeStr(enum 
CX_BinaryOperatorKind Op) {
       static_cast<CXBinaryOperatorKind>(Op));
 }
 
-CXSourceRange clang_Cursor_getCommentRange(CXCursor C) {
-  if (!clang_isDeclaration(C.kind))
-    return clang_getNullRange();
-
-  const Decl *D = getCursorDecl(C);
+static const RawComment *getCursorRawComment(CXCursor C) {
   ASTContext &Context = getCursorContext(C);
-  const RawComment *RC = Context.getRawCommentForAnyRedecl(D);
+  if (clang_isDeclaration(C.kind))
+    return Context.getRawCommentForAnyRedecl(getCursorDecl(C));
+  if (C.kind == CXCursor_MacroDefinition) {
+    const MacroDefinitionRecord *Def = getCursorMacroDefinition(C);
+    if (!Def)
+      return nullptr;
+    Preprocessor &PP = getCursorASTUnit(C)->getPreprocessor();
+    // Walk the macro directive history to find the specific MacroInfo for
+    // this cursor's definition. Looking up by name alone would always return
+    // the latest definition, which is wrong for redefined macros.
+    for (const MacroDirective *MD =
+             PP.getLocalMacroDirectiveHistory(Def->getName());
+         MD; MD = MD->getPrevious()) {
+      const auto *DMD = dyn_cast<DefMacroDirective>(MD);
+      if (!DMD)
+        continue;
+      const MacroInfo *MI = DMD->getInfo();
+      if (MI && MI->getDefinitionLoc() == Def->getLocation())
+        return Context.getRawCommentForAnyRedecl(MI);
+    }
+  }
+  return nullptr;
+}
+
+CXSourceRange clang_Cursor_getCommentRange(CXCursor C) {
+  const RawComment *RC = getCursorRawComment(C);
   if (!RC)
     return clang_getNullRange();
 
+  ASTContext &Context = getCursorContext(C);
   return cxloc::translateSourceRange(Context, RC->getSourceRange());
 }
 
 CXString clang_Cursor_getRawCommentText(CXCursor C) {
-  if (!clang_isDeclaration(C.kind))
+  const RawComment *RC = getCursorRawComment(C);
+  if (!RC)
     return cxstring::createNull();
 
-  const Decl *D = getCursorDecl(C);
   ASTContext &Context = getCursorContext(C);
-  const RawComment *RC = Context.getRawCommentForAnyRedecl(D);
-  StringRef RawText =
-      RC ? RC->getRawText(Context.getSourceManager()) : StringRef();
+  StringRef RawText = RC->getRawText(Context.getSourceManager());
 
   // Don't duplicate the string because RawText points directly into source
   // code.
@@ -9355,22 +9375,16 @@ CXString clang_Cursor_getRawCommentText(CXCursor C) {
 }
 
 CXString clang_Cursor_getBriefCommentText(CXCursor C) {
-  if (!clang_isDeclaration(C.kind))
+  const RawComment *RC = getCursorRawComment(C);
+  if (!RC)
     return cxstring::createNull();
 
-  const Decl *D = getCursorDecl(C);
   const ASTContext &Context = getCursorContext(C);
-  const RawComment *RC = Context.getRawCommentForAnyRedecl(D);
-
-  if (RC) {
-    StringRef BriefText = RC->getBriefText(Context);
-
-    // Don't duplicate the string because RawComment ensures that this memory
-    // will not go away.
-    return cxstring::createRef(BriefText);
-  }
+  StringRef BriefText = RC->getBriefText(Context);
 
-  return cxstring::createNull();
+  // Don't duplicate the string because RawComment ensures that this memory
+  // will not go away.
+  return cxstring::createRef(BriefText);
 }
 
 CXModule clang_Cursor_getModule(CXCursor C) {


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

Reply via email to