llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Doug Gregor (DougGregor) <details> <summary>Changes</summary> Generalize the infrastructure that finds comments attached to declarations to also find comments attached to macros. Surface these comments through libclang and the symbol graph. Fixes issue #<!-- -->83819. --- Patch is 23.57 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/198452.diff 8 Files Affected: - (modified) clang/include/clang/AST/ASTContext.h (+23-14) - (modified) clang/include/clang/ExtractAPI/API.h (+3-2) - (modified) clang/lib/AST/ASTContext.cpp (+72-18) - (modified) clang/lib/ExtractAPI/ExtractAPIConsumer.cpp (+17-8) - (added) clang/test/ExtractAPI/macro_doc_comments.c (+67) - (added) clang/test/Index/annotate-comments-macros.c (+27) - (modified) clang/test/Index/annotate-comments.cpp (+2-1) - (modified) clang/tools/libclang/CIndex.cpp (+27-23) ``````````diff diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index b2fd522e6865c..5e95c1971f33d 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/MacroInfo.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/DenseSet.h" @@ -996,11 +997,16 @@ 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 *> + DeclRawComments; /// Mapping from canonical declaration to the first redeclaration in chain /// that has a comment attached. @@ -1022,37 +1028,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, + void cacheRawCommentForDecl(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, + RawCommentLookupKey Key, const SourceLocation RepresentativeLocForDecl, 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 *getRawCommentForDeclNoCache(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/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index bc4771aec77d1..6621789a7c1a8 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,38 @@ 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) { +getDeclLocsForCommentSearch(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 + // getRawCommentForDeclNoCacheImpl. 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; + while (Offset > 0 && Buffer[Offset - 1] != '#' && + Buffer[Offset - 1] != '\n') + --Offset; + if (Offset > 0 && Buffer[Offset - 1] == '#') + --Offset; + return {SourceMgr.getLocForStartOfFile(Decomposed.first) + .getLocWithOffset(Offset)}; + } + + const auto *D = cast<const Decl *>(Key); assert(D); // User can not attach documentation to implicit declarations. @@ -215,7 +245,7 @@ getDeclLocsForCommentSearch(const Decl *D, SourceManager &SourceMgr) { } RawComment *ASTContext::getRawCommentForDeclNoCacheImpl( - const Decl *D, const SourceLocation RepresentativeLocForDecl, + RawCommentLookupKey Key, const SourceLocation RepresentativeLocForDecl, 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. @@ -227,6 +257,9 @@ RawComment *ASTContext::getRawCommentForDeclNoCacheImpl( 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 = @@ -242,8 +275,10 @@ 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. @@ -292,8 +327,9 @@ RawComment *ASTContext::getRawCommentForDeclNoCacheImpl( return CommentBeforeDecl; } -RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const { - const auto DeclLocs = getDeclLocsForCommentSearch(D, SourceMgr); +RawComment * +ASTContext::getRawCommentForDeclNoCache(RawCommentLookupKey Key) const { + const auto DeclLocs = getDeclLocsForCommentSearch(Key, SourceMgr); for (const auto DeclLoc : DeclLocs) { // If the declaration doesn't map directly to a location in a file, we @@ -318,7 +354,7 @@ RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const { continue; if (RawComment *Comment = - getRawCommentForDeclNoCacheImpl(D, DeclLoc, *CommentsInThisFile)) + getRawCommentForDeclNoCacheImpl(Key, DeclLoc, *CommentsInThisFile)) return Comment; } @@ -332,14 +368,30 @@ void ASTContext::addComment(const RawComment &RC) { } const RawComment *ASTContext::getRawCommentForAnyRedecl( - const Decl *D, + RawCommentLookupKey Key, const Decl **OriginalDecl) const { - if (!D) { + 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 = DeclRawComments.find(Key); + if (Existing != DeclRawComments.end()) + return Existing->second; + if (const RawComment *RC = getRawCommentForDeclNoCache(Key)) { + cacheRawCommentForDecl(MI, *RC); + return RC; + } + return nullptr; + } + + const Decl *D = cast<const Decl *>(Key); D = &adjustDeclToTemplate(*D); // Any comment directly attached to D? @@ -400,7 +452,7 @@ const RawComment *ASTContext::getRawCommentForAnyRedecl( } const RawComment *RedeclComment = getRawCommentForDeclNoCache(Redecl); if (RedeclComment) { - cacheRawCommentForDecl(*Redecl, *RedeclComment); + cacheRawCommentForDecl(Redecl, *RedeclComment); if (OriginalDecl) *OriginalDecl = Redecl; return RedeclComment; @@ -413,13 +465,15 @@ const RawComment *ASTContext::getRawCommentForAnyRedecl( return nullptr; } -void ASTContext::cacheRawCommentForDecl(const Decl &OriginalD, +void ASTContext::cacheRawCommentForDecl(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); + DeclRawComments.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, @@ -492,7 +546,7 @@ void ASTContext::attachCommentsToJustParsedDecls(ArrayRef<Decl *> Decls, if (RawComment *const DocComment = getRawCommentForDeclNoCacheImpl( D, DeclLoc, *CommentsInThisFile)) { - cacheRawCommentForDecl(*D, *DocComment); + cacheRawCommentForDecl(D, *DocComment); comments::FullComment *FC = DocComment->parse(*this, PP, D); ParsedComments[D->getCanonicalDecl()] = FC; break; diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp index 20389b5e4149d..92c722d9f06bb 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,8 @@ 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/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..0dfa52906a04a --- /dev/null +++ b/clang/test/Index/annotate-comments-macros.c @@ -0,0 +1,27 @@ +// 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. + +// 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.] 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_E... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/198452 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
