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
