Index: clang/include/clang/AST/ASTContext.h =================================================================== --- clang/include/clang/AST/ASTContext.h +++ clang/include/clang/AST/ASTContext.h @@ -814,6 +814,14 @@ getRawCommentForAnyRedecl(const Decl *D, const Decl **OriginalDecl = nullptr) const; + /// For every comment not attached to any decl check if it should be attached + /// to any of \param Decls. + /// + /// \param PP the Preprocessor used with this TU. Could be nullptr if + /// preprocessor is not available. + void tryToAttachCommentsToDecls(ArrayRef Decls, + const Preprocessor *PP); + /// Return parsed documentation comment attached to a given declaration. /// Returns nullptr if no comment is attached. /// Index: clang/lib/AST/ASTContext.cpp =================================================================== --- clang/lib/AST/ASTContext.cpp +++ clang/lib/AST/ASTContext.cpp @@ -489,6 +489,140 @@ return RC; } +void ASTContext::tryToAttachCommentsToDecls(ArrayRef Decls, + const Preprocessor *PP) { + // Explicitly not calling ExternalSource->ReadComments() as we're interested + // only in comments and decls that were parsed just now. + ArrayRef RawComments = Comments.getComments(); + if (RawComments.empty()) + return; + + auto CacheCommentForDecl = [this, PP](const Decl *D, const RawComment *C) { + RawCommentAndCacheFlags CacheEntry; + CacheEntry.setKind(RawCommentAndCacheFlags::FromDecl); + CacheEntry.setRaw(C); + CacheEntry.setOriginalDecl(D); + RedeclComments[D] = CacheEntry; + + // Always try to parse in order to eventually produce diagnostics. + comments::FullComment *FC = C->parse(*this, PP, D); + // But cache only if we don't have a comment yet + const Decl *Canonical = D->getCanonicalDecl(); + auto ParsedComment = ParsedComments.find(Canonical); + if (ParsedComment != ParsedComments.end()) + ParsedComment->second = FC; + }; + + // explicit comment location caching + std::unordered_map> + DecomposedCommentBegin; + std::unordered_map> + DecomposedCommentEnd; + std::unordered_map> + CommentBeginLine; + + // Don't store the result for long - might go dangling. + auto GetCachedCommentBegin = + [&DecomposedCommentBegin, + this](RawComment *RC) -> const std::pair & { + assert(RC); + auto BeginIt = DecomposedCommentBegin.find(RC); + if (BeginIt != DecomposedCommentBegin.end()) { + return BeginIt->second; + } + DecomposedCommentBegin[RC] = + SourceMgr.getDecomposedLoc(RC->getSourceRange().getBegin()); + return DecomposedCommentBegin[RC]; + }; + // Don't store the result for long - might go dangling. + auto GetCachedCommentEnd = + [&DecomposedCommentEnd, + this](RawComment *RC) -> const std::pair & { + assert(RC); + auto EndIt = DecomposedCommentEnd.find(RC); + if (EndIt != DecomposedCommentEnd.end()) { + return EndIt->second; + } + DecomposedCommentEnd[RC] = + SourceMgr.getDecomposedLoc(RC->getSourceRange().getEnd()); + return DecomposedCommentEnd[RC]; + }; + auto GetCachedCommentBeginLine = + [&CommentBeginLine, + this](const std::pair &CommentBeginLoc) -> unsigned { + auto BeginFileIt = + CommentBeginLine.find(CommentBeginLoc.first.getHashValue()); + if (BeginFileIt != CommentBeginLine.end()) { + auto BeginLineIt = BeginFileIt->second.find(CommentBeginLoc.second); + if (BeginLineIt != BeginFileIt->second.end()) { + return BeginLineIt->second; + } + } + CommentBeginLine[CommentBeginLoc.first.getHashValue()] + [CommentBeginLoc.second] = SourceMgr.getLineNumber( + CommentBeginLoc.first, CommentBeginLoc.second); + return CommentBeginLine[CommentBeginLoc.first.getHashValue()] + [CommentBeginLoc.second]; + }; + + for (const Decl *D : Decls) { + D = adjustDeclToTemplate(D); + if (!CanDeclHaveDocComment(D)) + continue; + + { + auto CIt = RedeclComments.find(D); + if (CIt != RedeclComments.end() && CIt->second.getOriginalDecl() == D) { + continue; + } + } + + llvm::Optional OptCandidateCommentLoc = + getCandidateCommentLocation(SourceMgr, D); + if (!OptCandidateCommentLoc) + continue; + + const std::pair DeclLocDecomp = + SourceMgr.getDecomposedLoc(OptCandidateCommentLoc.getValue()); + + // FIXME: We might optimize by keeping count of unattached comments and + // terminating early. + for (auto CIt = RawComments.begin(); CIt != RawComments.end(); ++CIt) { + RawComment *C = *CIt; + if (!C->isDocumentation() && !LangOpts.CommentOpts.ParseAllComments) + continue; + + if (C->isAttached()) + continue; + + if (C->isTrailingComment()) { + if (isa(D) || isa(D) || isa(D) || + isa(D) || isa(D)) { + const std::pair &CommentBeginDecomp = + GetCachedCommentBegin(C); + // Check that Doxygen trailing comment comes after the declaration, + // starts on the same line and in the same file as the declaration. + if (DeclLocDecomp.first == CommentBeginDecomp.first && + SourceMgr.getLineNumber(DeclLocDecomp.first, + DeclLocDecomp.second) == + GetCachedCommentBeginLine(CommentBeginDecomp)) { + C->setAttached(); + CacheCommentForDecl(D, C); + break; + } + } + } else { + if (IsCommentBeforeDecl(SourceMgr, GetCachedCommentEnd(C), + DeclLocDecomp)) { + C->setAttached(); + CacheCommentForDecl(D, C); + break; + } + } + } + } +} + static void addRedeclaredMethods(const ObjCMethodDecl *ObjCMethod, SmallVectorImpl &Redeclared) { const DeclContext *DC = ObjCMethod->getDeclContext(); Index: clang/lib/Sema/SemaDecl.cpp =================================================================== --- clang/lib/Sema/SemaDecl.cpp +++ clang/lib/Sema/SemaDecl.cpp @@ -12380,19 +12380,7 @@ } // See if there are any new comments that are not attached to a decl. - ArrayRef Comments = Context.getRawCommentList().getComments(); - if (!Comments.empty() && - !Comments.back()->isAttached()) { - // There is at least one comment that not attached to a decl. - // Maybe it should be attached to one of these decls? - // - // Note that this way we pick up not only comments that precede the - // declaration, but also comments that *follow* the declaration -- thanks to - // the lookahead in the lexer: we've consumed the semicolon and looked - // ahead through comments. - for (unsigned i = 0, e = Group.size(); i != e; ++i) - Context.getCommentForDecl(Group[i], &PP); - } + Context.tryToAttachCommentsToDecls(Group, &PP); } /// ActOnParamDeclarator - Called from Parser::ParseFunctionDeclarator() Index: clang/test/Sema/warn-documentation.cpp =================================================================== --- clang/test/Sema/warn-documentation.cpp +++ clang/test/Sema/warn-documentation.cpp @@ -760,16 +760,6 @@ /// \endcode int test_verbatim_2(); -// FIXME: we give a bad diagnostic here because we throw away non-documentation -// comments early. -// -// expected-warning@+3 {{'\endcode' command does not terminate a verbatim text block}} -/// \code -// foo -/// \endcode -int test_verbatim_3(); - - // expected-warning@+1 {{empty paragraph passed to '\brief' command}} int test1; ///< \brief\author Aaa