Index: cfe/trunk/include/clang/AST/ASTContext.h =================================================================== --- cfe/trunk/include/clang/AST/ASTContext.h +++ cfe/trunk/include/clang/AST/ASTContext.h @@ -729,71 +729,49 @@ /// True if comments are already loaded from ExternalASTSource. mutable bool CommentsLoaded = false; - class RawCommentAndCacheFlags { - public: - enum Kind { - /// We searched for a comment attached to the particular declaration, but - /// didn't find any. - /// - /// getRaw() == 0. - NoCommentInDecl = 0, - - /// We have found a comment attached to this particular declaration. - /// - /// getRaw() != 0. - FromDecl, - - /// This declaration does not have an attached comment, and we have - /// searched the redeclaration chain. - /// - /// If getRaw() == 0, the whole redeclaration chain does not have any - /// comments. - /// - /// If getRaw() != 0, it is a comment propagated from other - /// redeclaration. - FromRedecl - }; - - Kind getKind() const LLVM_READONLY { - return Data.getInt(); - } - - void setKind(Kind K) { - Data.setInt(K); - } - - const RawComment *getRaw() const LLVM_READONLY { - return Data.getPointer(); - } - - void setRaw(const RawComment *RC) { - Data.setPointer(RC); - } - - const Decl *getOriginalDecl() const LLVM_READONLY { - return OriginalDecl; - } - - void setOriginalDecl(const Decl *Orig) { - OriginalDecl = Orig; - } - - private: - llvm::PointerIntPair Data; - const Decl *OriginalDecl; - }; + /// Mapping from declaration to directly attached comment. + /// + /// Raw comments are owned by Comments list. This mapping is populated + /// lazily. + mutable llvm::DenseMap DeclRawComments; - /// Mapping from declarations to comments attached to any - /// redeclaration. + /// Mapping from canonical declaration to the first redeclaration in chain + /// that has a comment attached. /// /// Raw comments are owned by Comments list. This mapping is populated /// lazily. - mutable llvm::DenseMap RedeclComments; + mutable llvm::DenseMap RedeclChainComments; + + /// Keeps track of redeclaration chains that don't have any comment attached. + /// Mapping from canonical declaration to redeclaration chain that has no + /// comments attached to any redeclaration. Specifically it's mapping to + /// the last redeclaration we've checked. + /// + /// Shall not contain declarations that have comments attached to any + /// redeclaration in their chain. + mutable llvm::DenseMap CommentlessRedeclChains; /// Mapping from declarations to parsed comments attached to any /// redeclaration. mutable llvm::DenseMap ParsedComments; + /// Attaches \p Comment to \p OriginalD and to its redeclaration chain + /// and removes the redeclaration chain from the set of commentless chains. + /// + /// Don't do anything if a comment has already been attached to \p OriginalD + /// or its redeclaration chain. + void cacheRawCommentForDecl(const Decl &OriginalD, + const RawComment &Comment) const; + + /// \returns searches \p CommentsInFile for doc comment for \p D. + /// + /// \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, + const std::map &CommentsInFile) const; + /// Return the documentation comment attached to a given declaration, /// without looking into cache. RawComment *getRawCommentForDeclNoCache(const Decl *D) const; @@ -818,6 +796,16 @@ getRawCommentForAnyRedecl(const Decl *D, const Decl **OriginalDecl = nullptr) const; + /// Searches existing comments for doc comments that should be attached to \p + /// Decls. If any doc comment is found, it is parsed. + /// + /// Requirement: All \p Decls are in the same file. + /// + /// If the last comment in the file is already attached we assume + /// there are not comments left to be attached to \p Decls. + void attachCommentsToJustParsedDecls(ArrayRef Decls, + const Preprocessor *PP); + /// Return parsed documentation comment attached to a given declaration. /// Returns nullptr if no comment is attached. /// Index: cfe/trunk/include/clang/AST/RawCommentList.h =================================================================== --- cfe/trunk/include/clang/AST/RawCommentList.h +++ cfe/trunk/include/clang/AST/RawCommentList.h @@ -10,8 +10,11 @@ #define LLVM_CLANG_AST_RAWCOMMENTLIST_H #include "clang/Basic/CommentOptions.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include namespace clang { @@ -196,17 +199,24 @@ void addComment(const RawComment &RC, const CommentOptions &CommentOpts, llvm::BumpPtrAllocator &Allocator); - ArrayRef getComments() const { - return Comments; - } + /// \returns nullptr in case there are no comments in in \p File. + const std::map *getCommentsInFile(FileID File) const; + + bool empty() const; + + unsigned getCommentBeginLine(RawComment *C, FileID File, + unsigned Offset) const; + unsigned getCommentEndOffset(RawComment *C) const; private: SourceManager &SourceMgr; - std::vector Comments; - - void addDeserializedComments(ArrayRef DeserializedComments); + // mapping: FileId -> comment begin offset -> comment + llvm::DenseMap> OrderedComments; + mutable llvm::DenseMap CommentBeginLine; + mutable llvm::DenseMap CommentEndOffset; friend class ASTReader; + friend class ASTWriter; }; } // end namespace clang Index: cfe/trunk/lib/AST/ASTContext.cpp =================================================================== --- cfe/trunk/lib/AST/ASTContext.cpp +++ cfe/trunk/lib/AST/ASTContext.cpp @@ -98,62 +98,60 @@ Float16Rank, HalfRank, FloatRank, DoubleRank, LongDoubleRank, Float128Rank }; -RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const { +/// \returns location that is relevant when searching for Doc comments related +/// to \p D. +static SourceLocation getDeclLocForCommentSearch(const Decl *D, + SourceManager &SourceMgr) { assert(D); - // If we already tried to load comments but there are none, - // we won't find anything. - if (CommentsLoaded && Comments.getComments().empty()) - return nullptr; - // User can not attach documentation to implicit declarations. if (D->isImplicit()) - return nullptr; + return {}; // User can not attach documentation to implicit instantiations. if (const auto *FD = dyn_cast(D)) { if (FD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) - return nullptr; + return {}; } if (const auto *VD = dyn_cast(D)) { if (VD->isStaticDataMember() && VD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) - return nullptr; + return {}; } if (const auto *CRD = dyn_cast(D)) { if (CRD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) - return nullptr; + return {}; } if (const auto *CTSD = dyn_cast(D)) { TemplateSpecializationKind TSK = CTSD->getSpecializationKind(); if (TSK == TSK_ImplicitInstantiation || TSK == TSK_Undeclared) - return nullptr; + return {}; } if (const auto *ED = dyn_cast(D)) { if (ED->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) - return nullptr; + return {}; } if (const auto *TD = dyn_cast(D)) { // When tag declaration (but not definition!) is part of the // decl-specifier-seq of some other declaration, it doesn't get comment if (TD->isEmbeddedInDeclarator() && !TD->isCompleteDefinition()) - return nullptr; + return {}; } // TODO: handle comments for function parameters properly. if (isa(D)) - return nullptr; + return {}; // TODO: we could look up template parameter documentation in the template // documentation. if (isa(D) || isa(D) || isa(D)) - return nullptr; + return {}; // Find declaration location. // For Objective-C declarations we generally don't expect to have multiple @@ -161,20 +159,19 @@ // location". // For all other declarations multiple declarators are used quite frequently, // so we use the location of the identifier as the "declaration location". - SourceLocation DeclLoc; if (isa(D) || isa(D) || isa(D) || isa(D) || isa(D)) - DeclLoc = D->getBeginLoc(); + return D->getBeginLoc(); else { - DeclLoc = D->getLocation(); + const SourceLocation DeclLoc = D->getLocation(); if (DeclLoc.isMacroID()) { if (isa(D)) { // If location of the typedef name is in a macro, it is because being // declared via a macro. Try using declaration's starting location as // the "declaration location". - DeclLoc = D->getBeginLoc(); + return D->getBeginLoc(); } else if (const auto *TD = dyn_cast(D)) { // If location of the tag decl is inside a macro, but the spelling of // the tag name comes from a macro argument, it looks like a special @@ -183,102 +180,73 @@ // attach the comment to the tag decl. if (SourceMgr.isMacroArgExpansion(DeclLoc) && TD->isCompleteDefinition()) - DeclLoc = SourceMgr.getExpansionLoc(DeclLoc); + return SourceMgr.getExpansionLoc(DeclLoc); } } + return DeclLoc; } + return {}; +} + +RawComment *ASTContext::getRawCommentForDeclNoCacheImpl( + const Decl *D, const SourceLocation RepresentativeLocForDecl, + const std::map &CommentsInTheFile) const { // If the declaration doesn't map directly to a location in a file, we // can't find the comment. - if (DeclLoc.isInvalid() || !DeclLoc.isFileID()) + if (RepresentativeLocForDecl.isInvalid() || + !RepresentativeLocForDecl.isFileID()) return nullptr; - if (!CommentsLoaded && ExternalSource) { - ExternalSource->ReadComments(); - -#ifndef NDEBUG - ArrayRef RawComments = Comments.getComments(); - assert(std::is_sorted(RawComments.begin(), RawComments.end(), - BeforeThanCompare(SourceMgr))); -#endif - - CommentsLoaded = true; - } - - ArrayRef RawComments = Comments.getComments(); // If there are no comments anywhere, we won't find anything. - if (RawComments.empty()) + if (CommentsInTheFile.empty()) return nullptr; - // Find the comment that occurs just after this declaration. - ArrayRef::iterator Comment; - { - // When searching for comments during parsing, the comment we are looking - // for is usually among the last two comments we parsed -- check them - // first. - RawComment CommentAtDeclLoc( - SourceMgr, SourceRange(DeclLoc), LangOpts.CommentOpts, false); - BeforeThanCompare Compare(SourceMgr); - ArrayRef::iterator MaybeBeforeDecl = RawComments.end() - 1; - bool Found = Compare(*MaybeBeforeDecl, &CommentAtDeclLoc); - if (!Found && RawComments.size() >= 2) { - MaybeBeforeDecl--; - Found = Compare(*MaybeBeforeDecl, &CommentAtDeclLoc); - } - - if (Found) { - Comment = MaybeBeforeDecl + 1; - assert(Comment == - llvm::lower_bound(RawComments, &CommentAtDeclLoc, Compare)); - } else { - // Slow path. - Comment = llvm::lower_bound(RawComments, &CommentAtDeclLoc, Compare); - } - } - // Decompose the location for the declaration and find the beginning of the // file buffer. - std::pair DeclLocDecomp = SourceMgr.getDecomposedLoc(DeclLoc); + const std::pair DeclLocDecomp = + SourceMgr.getDecomposedLoc(RepresentativeLocForDecl); + + // Slow path. + auto OffsetCommentBehindDecl = + CommentsInTheFile.lower_bound(DeclLocDecomp.second); // First check whether we have a trailing comment. - if (Comment != RawComments.end() && - ((*Comment)->isDocumentation() || LangOpts.CommentOpts.ParseAllComments) - && (*Comment)->isTrailingComment() && - (isa(D) || isa(D) || isa(D) || - isa(D) || isa(D))) { - std::pair CommentBeginDecomp - = SourceMgr.getDecomposedLoc((*Comment)->getSourceRange().getBegin()); - // 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) - == SourceMgr.getLineNumber(CommentBeginDecomp.first, - CommentBeginDecomp.second)) { - (**Comment).setAttached(); - return *Comment; + if (OffsetCommentBehindDecl != CommentsInTheFile.end()) { + RawComment *CommentBehindDecl = OffsetCommentBehindDecl->second; + if ((CommentBehindDecl->isDocumentation() || + LangOpts.CommentOpts.ParseAllComments) && + CommentBehindDecl->isTrailingComment() && + (isa(D) || isa(D) || isa(D) || + isa(D) || isa(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, + OffsetCommentBehindDecl->first)) { + return CommentBehindDecl; + } } } // The comment just after the declaration was not a trailing comment. // Let's look at the previous comment. - if (Comment == RawComments.begin()) + if (OffsetCommentBehindDecl == CommentsInTheFile.begin()) return nullptr; - --Comment; + + auto OffsetCommentBeforeDecl = --OffsetCommentBehindDecl; + RawComment *CommentBeforeDecl = OffsetCommentBeforeDecl->second; // Check that we actually have a non-member Doxygen comment. - if (!((*Comment)->isDocumentation() || + if (!(CommentBeforeDecl->isDocumentation() || LangOpts.CommentOpts.ParseAllComments) || - (*Comment)->isTrailingComment()) + CommentBeforeDecl->isTrailingComment()) return nullptr; // Decompose the end of the comment. - std::pair CommentEndDecomp - = SourceMgr.getDecomposedLoc((*Comment)->getSourceRange().getEnd()); - - // If the comment and the declaration aren't in the same file, then they - // aren't related. - if (DeclLocDecomp.first != CommentEndDecomp.first) - return nullptr; + const unsigned CommentEndOffset = + Comments.getCommentEndOffset(CommentBeforeDecl); // Get the corresponding buffer. bool Invalid = false; @@ -288,26 +256,49 @@ return nullptr; // Extract text between the comment and declaration. - StringRef Text(Buffer + CommentEndDecomp.second, - DeclLocDecomp.second - CommentEndDecomp.second); + StringRef Text(Buffer + CommentEndOffset, + DeclLocDecomp.second - CommentEndOffset); // There should be no other declarations or preprocessor directives between // comment and declaration. if (Text.find_first_of(";{}#@") != StringRef::npos) return nullptr; - (**Comment).setAttached(); - return *Comment; + return CommentBeforeDecl; +} + +RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const { + const SourceLocation DeclLoc = getDeclLocForCommentSearch(D, SourceMgr); + + // If the declaration doesn't map directly to a location in a file, we + // can't find the comment. + if (DeclLoc.isInvalid() || !DeclLoc.isFileID()) + return nullptr; + + if (ExternalSource && !CommentsLoaded) { + ExternalSource->ReadComments(); + CommentsLoaded = true; + } + + if (Comments.empty()) + return nullptr; + + const FileID File = SourceMgr.getDecomposedLoc(DeclLoc).first; + const auto CommentsInThisFile = Comments.getCommentsInFile(File); + if (!CommentsInThisFile || CommentsInThisFile->empty()) + return nullptr; + + return getRawCommentForDeclNoCacheImpl(D, DeclLoc, *CommentsInThisFile); } /// If we have a 'templated' declaration for a template, adjust 'D' to /// refer to the actual template. /// If we have an implicit instantiation, adjust 'D' to refer to template. -static const Decl *adjustDeclToTemplate(const Decl *D) { - if (const auto *FD = dyn_cast(D)) { +static const Decl &adjustDeclToTemplate(const Decl &D) { + if (const auto *FD = dyn_cast(&D)) { // Is this function declaration part of a function template? if (const FunctionTemplateDecl *FTD = FD->getDescribedFunctionTemplate()) - return FTD; + return *FTD; // Nothing to do if function is not an implicit instantiation. if (FD->getTemplateSpecializationKind() != TSK_ImplicitInstantiation) @@ -315,28 +306,28 @@ // Function is an implicit instantiation of a function template? if (const FunctionTemplateDecl *FTD = FD->getPrimaryTemplate()) - return FTD; + return *FTD; // Function is instantiated from a member definition of a class template? if (const FunctionDecl *MemberDecl = FD->getInstantiatedFromMemberFunction()) - return MemberDecl; + return *MemberDecl; return D; } - if (const auto *VD = dyn_cast(D)) { + if (const auto *VD = dyn_cast(&D)) { // Static data member is instantiated from a member definition of a class // template? if (VD->isStaticDataMember()) if (const VarDecl *MemberDecl = VD->getInstantiatedFromStaticDataMember()) - return MemberDecl; + return *MemberDecl; return D; } - if (const auto *CRD = dyn_cast(D)) { + if (const auto *CRD = dyn_cast(&D)) { // Is this class declaration part of a class template? if (const ClassTemplateDecl *CTD = CRD->getDescribedClassTemplate()) - return CTD; + return *CTD; // Class is an implicit instantiation of a class template or partial // specialization? @@ -346,23 +337,23 @@ llvm::PointerUnion PU = CTSD->getSpecializedTemplateOrPartial(); - return PU.is() ? - static_cast(PU.get()) : - static_cast( - PU.get()); + return PU.is() + ? *static_cast(PU.get()) + : *static_cast( + PU.get()); } // Class is instantiated from a member definition of a class template? if (const MemberSpecializationInfo *Info = - CRD->getMemberSpecializationInfo()) - return Info->getInstantiatedFrom(); + CRD->getMemberSpecializationInfo()) + return *Info->getInstantiatedFrom(); return D; } - if (const auto *ED = dyn_cast(D)) { + if (const auto *ED = dyn_cast(&D)) { // Enum is instantiated from a member definition of a class template? if (const EnumDecl *MemberDecl = ED->getInstantiatedFromMemberEnum()) - return MemberDecl; + return *MemberDecl; return D; } @@ -373,72 +364,81 @@ const RawComment *ASTContext::getRawCommentForAnyRedecl( const Decl *D, const Decl **OriginalDecl) const { - D = adjustDeclToTemplate(D); + if (!D) { + if (OriginalDecl) + OriginalDecl = nullptr; + return nullptr; + } - // Check whether we have cached a comment for this declaration already. + D = &adjustDeclToTemplate(*D); + + // Any comment directly attached to D? { - llvm::DenseMap::iterator Pos = - RedeclComments.find(D); - if (Pos != RedeclComments.end()) { - const RawCommentAndCacheFlags &Raw = Pos->second; - if (Raw.getKind() != RawCommentAndCacheFlags::NoCommentInDecl) { - if (OriginalDecl) - *OriginalDecl = Raw.getOriginalDecl(); - return Raw.getRaw(); - } + auto DeclComment = DeclRawComments.find(D); + if (DeclComment != DeclRawComments.end()) { + if (OriginalDecl) + *OriginalDecl = D; + return DeclComment->second; } } - // Search for comments attached to declarations in the redeclaration chain. - const RawComment *RC = nullptr; - const Decl *OriginalDeclForRC = nullptr; - for (auto I : D->redecls()) { - llvm::DenseMap::iterator Pos = - RedeclComments.find(I); - if (Pos != RedeclComments.end()) { - const RawCommentAndCacheFlags &Raw = Pos->second; - if (Raw.getKind() != RawCommentAndCacheFlags::NoCommentInDecl) { - RC = Raw.getRaw(); - OriginalDeclForRC = Raw.getOriginalDecl(); - break; - } - } else { - RC = getRawCommentForDeclNoCache(I); - OriginalDeclForRC = I; - RawCommentAndCacheFlags Raw; - if (RC) { - // Call order swapped to work around ICE in VS2015 RTM (Release Win32) - // https://connect.microsoft.com/VisualStudio/feedback/details/1741530 - Raw.setKind(RawCommentAndCacheFlags::FromDecl); - Raw.setRaw(RC); - } else - Raw.setKind(RawCommentAndCacheFlags::NoCommentInDecl); - Raw.setOriginalDecl(I); - RedeclComments[I] = Raw; - if (RC) - break; + // Any comment attached to any redeclaration of D? + const Decl *CanonicalD = D->getCanonicalDecl(); + if (!CanonicalD) + return nullptr; + + { + auto RedeclComment = RedeclChainComments.find(CanonicalD); + if (RedeclComment != RedeclChainComments.end()) { + if (OriginalDecl) + *OriginalDecl = RedeclComment->second; + auto CommentAtRedecl = DeclRawComments.find(RedeclComment->second); + assert(CommentAtRedecl != DeclRawComments.end() && + "This decl is supposed to have comment attached."); + return CommentAtRedecl->second; } } - // If we found a comment, it should be a documentation comment. - assert(!RC || RC->isDocumentation() || LangOpts.CommentOpts.ParseAllComments); - - if (OriginalDecl) - *OriginalDecl = OriginalDeclForRC; - - // Update cache for every declaration in the redeclaration chain. - RawCommentAndCacheFlags Raw; - Raw.setRaw(RC); - Raw.setKind(RawCommentAndCacheFlags::FromRedecl); - Raw.setOriginalDecl(OriginalDeclForRC); + // Any redeclarations of D that we haven't checked for comments yet? + // We can't use DenseMap::iterator directly since it'd get invalid. + auto LastCheckedRedecl = [this, CanonicalD]() -> const Decl * { + auto LookupRes = CommentlessRedeclChains.find(CanonicalD); + if (LookupRes != CommentlessRedeclChains.end()) + return LookupRes->second; + return nullptr; + }(); - for (auto I : D->redecls()) { - RawCommentAndCacheFlags &R = RedeclComments[I]; - if (R.getKind() == RawCommentAndCacheFlags::NoCommentInDecl) - R = Raw; + for (const auto Redecl : D->redecls()) { + assert(Redecl); + // Skip all redeclarations that have been checked previously. + if (LastCheckedRedecl) { + if (LastCheckedRedecl == Redecl) { + LastCheckedRedecl = nullptr; + } + continue; + } + const RawComment *RedeclComment = getRawCommentForDeclNoCache(Redecl); + if (RedeclComment) { + cacheRawCommentForDecl(*Redecl, *RedeclComment); + if (OriginalDecl) + *OriginalDecl = Redecl; + return RedeclComment; + } + CommentlessRedeclChains[CanonicalD] = Redecl; } - return RC; + if (OriginalDecl) + *OriginalDecl = nullptr; + return nullptr; +} + +void ASTContext::cacheRawCommentForDecl(const Decl &OriginalD, + 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); } static void addRedeclaredMethods(const ObjCMethodDecl *ObjCMethod, @@ -458,6 +458,52 @@ } } +void ASTContext::attachCommentsToJustParsedDecls(ArrayRef Decls, + const Preprocessor *PP) { + if (Comments.empty() || Decls.empty()) + return; + + // See if there are any new comments that are not attached to a decl. + // The location doesn't have to be precise - we care only about the file. + const FileID File = + SourceMgr.getDecomposedLoc((*Decls.begin())->getLocation()).first; + auto CommentsInThisFile = Comments.getCommentsInFile(File); + if (!CommentsInThisFile || CommentsInThisFile->empty() || + CommentsInThisFile->rbegin()->second->isAttached()) + return; + + // There is at least one comment not attached to a decl. + // Maybe it should be attached to one of 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 (const Decl *D : Decls) { + assert(D); + if (D->isInvalidDecl()) + continue; + + D = &adjustDeclToTemplate(*D); + + const SourceLocation DeclLoc = getDeclLocForCommentSearch(D, SourceMgr); + + if (DeclLoc.isInvalid() || !DeclLoc.isFileID()) + continue; + + if (DeclRawComments.count(D) > 0) + continue; + + if (RawComment *const DocComment = + getRawCommentForDeclNoCacheImpl(D, DeclLoc, *CommentsInThisFile)) { + cacheRawCommentForDecl(*D, *DocComment); + comments::FullComment *FC = DocComment->parse(*this, PP, D); + ParsedComments[D->getCanonicalDecl()] = FC; + } + } +} + comments::FullComment *ASTContext::cloneFullComment(comments::FullComment *FC, const Decl *D) const { auto *ThisDeclInfo = new (*this) comments::DeclInfo; @@ -481,9 +527,9 @@ comments::FullComment *ASTContext::getCommentForDecl( const Decl *D, const Preprocessor *PP) const { - if (D->isInvalidDecl()) + if (!D || D->isInvalidDecl()) return nullptr; - D = adjustDeclToTemplate(D); + D = &adjustDeclToTemplate(*D); const Decl *Canonical = D->getCanonicalDecl(); llvm::DenseMap::iterator Pos = @@ -498,7 +544,7 @@ return Pos->second; } - const Decl *OriginalDecl; + const Decl *OriginalDecl = nullptr; const RawComment *RC = getRawCommentForAnyRedecl(D, &OriginalDecl); if (!RC) { @@ -577,7 +623,7 @@ // should parse the comment in context of that other Decl. This is important // because comments can contain references to parameter names which can be // different across redeclarations. - if (D != OriginalDecl) + if (D != OriginalDecl && OriginalDecl) return getCommentForDecl(OriginalDecl, PP); comments::FullComment *FC = RC->parse(*this, PP, D); Index: cfe/trunk/lib/AST/RawCommentList.cpp =================================================================== --- cfe/trunk/lib/AST/RawCommentList.cpp +++ cfe/trunk/lib/AST/RawCommentList.cpp @@ -275,27 +275,25 @@ if (RC.isInvalid()) return; - // Check if the comments are not in source order. - while (!Comments.empty() && - !SourceMgr.isBeforeInTranslationUnit(Comments.back()->getBeginLoc(), - RC.getBeginLoc())) { - // If they are, just pop a few last comments that don't fit. - // This happens if an \#include directive contains comments. - Comments.pop_back(); - } - // Ordinary comments are not interesting for us. if (RC.isOrdinary() && !CommentOpts.ParseAllComments) return; + std::pair Loc = + SourceMgr.getDecomposedLoc(RC.getBeginLoc()); + + const FileID CommentFile = Loc.first; + const unsigned CommentOffset = Loc.second; + // If this is the first Doxygen comment, save it (because there isn't // anything to merge it with). - if (Comments.empty()) { - Comments.push_back(new (Allocator) RawComment(RC)); + if (OrderedComments[CommentFile].empty()) { + OrderedComments[CommentFile][CommentOffset] = + new (Allocator) RawComment(RC); return; } - const RawComment &C1 = *Comments.back(); + const RawComment &C1 = *OrderedComments[CommentFile].rbegin()->second; const RawComment &C2 = RC; // Merge comments only if there is only whitespace between them. @@ -318,21 +316,43 @@ onlyWhitespaceBetween(SourceMgr, C1.getEndLoc(), C2.getBeginLoc(), /*MaxNewlinesAllowed=*/1)) { SourceRange MergedRange(C1.getBeginLoc(), C2.getEndLoc()); - *Comments.back() = RawComment(SourceMgr, MergedRange, CommentOpts, true); + *OrderedComments[CommentFile].rbegin()->second = + RawComment(SourceMgr, MergedRange, CommentOpts, true); } else { - Comments.push_back(new (Allocator) RawComment(RC)); + OrderedComments[CommentFile][CommentOffset] = + new (Allocator) RawComment(RC); } } -void RawCommentList::addDeserializedComments(ArrayRef DeserializedComments) { - std::vector MergedComments; - MergedComments.reserve(Comments.size() + DeserializedComments.size()); - - std::merge(Comments.begin(), Comments.end(), - DeserializedComments.begin(), DeserializedComments.end(), - std::back_inserter(MergedComments), - BeforeThanCompare(SourceMgr)); - std::swap(Comments, MergedComments); +const std::map * +RawCommentList::getCommentsInFile(FileID File) const { + auto CommentsInFile = OrderedComments.find(File); + if (CommentsInFile == OrderedComments.end()) + return nullptr; + + return &CommentsInFile->second; +} + +bool RawCommentList::empty() const { return OrderedComments.empty(); } + +unsigned RawCommentList::getCommentBeginLine(RawComment *C, FileID File, + unsigned Offset) const { + auto Cached = CommentBeginLine.find(C); + if (Cached != CommentBeginLine.end()) + return Cached->second; + const unsigned Line = SourceMgr.getLineNumber(File, Offset); + CommentBeginLine[C] = Line; + return Line; +} + +unsigned RawCommentList::getCommentEndOffset(RawComment *C) const { + auto Cached = CommentEndOffset.find(C); + if (Cached != CommentEndOffset.end()) + return Cached->second; + const unsigned Offset = + SourceMgr.getDecomposedLoc(C->getSourceRange().getEnd()).second; + CommentEndOffset[C] = Offset; + return Offset; } std::string RawComment::getFormattedText(const SourceManager &SourceMgr, Index: cfe/trunk/lib/Sema/SemaDecl.cpp =================================================================== --- cfe/trunk/lib/Sema/SemaDecl.cpp +++ cfe/trunk/lib/Sema/SemaDecl.cpp @@ -12465,20 +12465,10 @@ } } - // 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); - } + // FIMXE: We assume every Decl in the group is in the same file. + // This is false when preprocessor constructs the group from decls in + // different files (e. g. macros or #include). + Context.attachCommentsToJustParsedDecls(Group, &getPreprocessor()); } /// Common checks for a parameter-declaration that should apply to both function Index: cfe/trunk/lib/Serialization/ASTReader.cpp =================================================================== --- cfe/trunk/lib/Serialization/ASTReader.cpp +++ cfe/trunk/lib/Serialization/ASTReader.cpp @@ -9731,10 +9731,17 @@ } } NextCursor: - // De-serialized SourceLocations get negative FileIDs for other modules, - // potentially invalidating the original order. Sort it again. - llvm::sort(Comments, BeforeThanCompare(SourceMgr)); - Context.Comments.addDeserializedComments(Comments); + llvm::DenseMap> + FileToOffsetToComment; + for (RawComment *C : Comments) { + SourceLocation CommentLoc = C->getBeginLoc(); + if (CommentLoc.isValid()) { + std::pair Loc = + SourceMgr.getDecomposedLoc(CommentLoc); + if (Loc.first.isValid()) + Context.Comments.OrderedComments[Loc.first].emplace(Loc.second, C); + } + } } } Index: cfe/trunk/lib/Serialization/ASTWriter.cpp =================================================================== --- cfe/trunk/lib/Serialization/ASTWriter.cpp +++ cfe/trunk/lib/Serialization/ASTWriter.cpp @@ -3266,15 +3266,17 @@ auto _ = llvm::make_scope_exit([this] { Stream.ExitBlock(); }); if (!PP->getPreprocessorOpts().WriteCommentListToPCH) return; - ArrayRef RawComments = Context->Comments.getComments(); RecordData Record; - for (const auto *I : RawComments) { - Record.clear(); - AddSourceRange(I->getSourceRange(), Record); - Record.push_back(I->getKind()); - Record.push_back(I->isTrailingComment()); - Record.push_back(I->isAlmostTrailingComment()); - Stream.EmitRecord(COMMENTS_RAW_COMMENT, Record); + for (const auto &FO : Context->Comments.OrderedComments) { + for (const auto &OC : FO.second) { + const RawComment *I = OC.second; + Record.clear(); + AddSourceRange(I->getSourceRange(), Record); + Record.push_back(I->getKind()); + Record.push_back(I->isTrailingComment()); + Record.push_back(I->isAlmostTrailingComment()); + Stream.EmitRecord(COMMENTS_RAW_COMMENT, Record); + } } } Index: cfe/trunk/test/Index/comment-redeclarations.cpp =================================================================== --- cfe/trunk/test/Index/comment-redeclarations.cpp +++ cfe/trunk/test/Index/comment-redeclarations.cpp @@ -0,0 +1,14 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: c-index-test -test-load-source all -comments-xml-schema=%S/../../bindings/xml/comment-xml-schema.rng -target x86_64-apple-darwin10 %s > %t/out +// RUN: FileCheck %s < %t/out + +class Foo; +// CHECK: CXComment_Text Text=[ Foo is the best!])))] + +/// Foo is the best! +class Foo; +// CHECK: CXComment_Text Text=[ Foo is the best!])))] + +class Foo {}; +// CHECK: CXComment_Text Text=[ Foo is the best!])))]