From: Dmitri Gribenko Date: Tue, 31 Jul 2012 22:37:06 +0000 (+0000) Subject: Comment parsing: add support for \tparam command on all levels. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=96b098674908eaa59a9128f3305cda6fbbdad563;p=clang Comment parsing: add support for \tparam command on all levels. The only caveat is renumbering CXCommentKind enum for aesthetic reasons -- this breaks libclang binary compatibility, but should not be a problem since API is so new. This also fixes PR13372 as a side-effect. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@161087 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang-c/Index.h b/include/clang-c/Index.h index c21dafd2dc..ab6c3afdb8 100644 --- a/include/clang-c/Index.h +++ b/include/clang-c/Index.h @@ -3276,6 +3276,14 @@ enum CXCommentKind { */ CXComment_ParamCommand = 7, + /** + * \brief A \\tparam command that describes a template parameter (name and + * description). + * + * \brief For example: \\tparam T description. + */ + CXComment_TParamCommand = 8, + /** * \brief A verbatim block command (e. g., preformatted code). Verbatim * block has an opening and a closing command and contains multiple lines of @@ -3286,25 +3294,25 @@ enum CXCommentKind { * aaa * \\endverbatim */ - CXComment_VerbatimBlockCommand = 8, + CXComment_VerbatimBlockCommand = 9, /** * \brief A line of text that is contained within a * CXComment_VerbatimBlockCommand node. */ - CXComment_VerbatimBlockLine = 9, + CXComment_VerbatimBlockLine = 10, /** * \brief A verbatim line command. Verbatim line has an opening command, * a single line of text (up to the newline after the opening command) and * has no closing command. */ - CXComment_VerbatimLine = 10, + CXComment_VerbatimLine = 11, /** * \brief A full comment attached to a declaration, contains block content. */ - CXComment_FullComment = 11 + CXComment_FullComment = 12 }; /** @@ -3563,6 +3571,63 @@ CINDEX_LINKAGE enum CXCommentParamPassDirection clang_ParamCommandComment_getDirection( CXComment Comment); +/** + * \param Comment a \c CXComment_TParamCommand AST node. + * + * \returns template parameter name. + */ +CINDEX_LINKAGE +CXString clang_TParamCommandComment_getParamName(CXComment Comment); + +/** + * \param Comment a \c CXComment_TParamCommand AST node. + * + * \returns non-zero if the parameter that this AST node represents was found + * in the template parameter list and + * \c clang_TParamCommandComment_getDepth and + * \c clang_TParamCommandComment_getIndex functions will return a meaningful + * value. + */ +CINDEX_LINKAGE +unsigned clang_TParamCommandComment_isParamPositionValid(CXComment Comment); + +/** + * \param Comment a \c CXComment_TParamCommand AST node. + * + * \returns zero-based nesting depth of this parameter in the template parameter list. + * + * For example, + * \verbatim + * template class TT> + * void test(TT aaa); + * \endverbatim + * for C and TT nesting depth is 0, + * for T nesting depth is 1. + */ +CINDEX_LINKAGE +unsigned clang_TParamCommandComment_getDepth(CXComment Comment); + +/** + * \param Comment a \c CXComment_TParamCommand AST node. + * + * \returns zero-based parameter index in the template parameter list at a + * given nesting depth. + * + * For example, + * \verbatim + * template class TT> + * void test(TT aaa); + * \endverbatim + * for C and TT nesting depth is 0, so we can ask for index at depth 0: + * at depth 0 C's index is 0, TT's index is 1. + * + * For T nesting depth is 1, so we can ask for index at depth 0 and 1: + * at depth 0 T's index is 1 (same as TT's), + * at depth 1 T's index is 0. + */ +CINDEX_LINKAGE +unsigned clang_TParamCommandComment_getIndex(CXComment Comment, unsigned Depth); + /** * \param Comment a \c CXComment_VerbatimBlockLine AST node. * @@ -3606,6 +3671,15 @@ CINDEX_LINKAGE CXString clang_HTMLTagComment_getAsString(CXComment Comment); * \li "param-name-index-invalid" and "param-descr-index-invalid" are used if * parameter index is invalid. * + * Template parameter documentation is rendered as a \ list with + * parameters sorted in template parameter list order. CSS classes used: + * \li "tparam-name-index-NUMBER" for parameter name (\); + * \li "tparam-descr-index-NUMBER" for parameter description (\); + * \li "taram-name-index-other" and "tparam-descr-index-other" are used for + * names inside template template parameters; + * \li "tparam-name-index-invalid" and "tparam-descr-index-invalid" are used if + * parameter position is invalid. + * * \param Comment a \c CXComment_FullComment AST node. * * \returns string containing an HTML fragment. diff --git a/include/clang/AST/Comment.h b/include/clang/AST/Comment.h index 4c20618ca1..089e7cd8e3 100644 --- a/include/clang/AST/Comment.h +++ b/include/clang/AST/Comment.h @@ -723,6 +723,68 @@ public: } }; +/// Doxygen \\tparam command, describes a template parameter. +class TParamCommandComment : public BlockCommandComment { +private: + /// If this template parameter name was resolved (found in template parameter + /// list), then this stores a list of position indexes in all template + /// parameter lists. + /// + /// For example: + /// \verbatim + /// template class TT> + /// void test(TT aaa); + /// \endverbatim + /// For C: Position = { 0 } + /// For TT: Position = { 1 } + /// For T: Position = { 1, 0 } + llvm::ArrayRef Position; + +public: + TParamCommandComment(SourceLocation LocBegin, + SourceLocation LocEnd, + StringRef Name) : + BlockCommandComment(TParamCommandCommentKind, LocBegin, LocEnd, Name) + { } + + static bool classof(const Comment *C) { + return C->getCommentKind() == TParamCommandCommentKind; + } + + static bool classof(const TParamCommandComment *) { return true; } + + bool hasParamName() const { + return getNumArgs() > 0; + } + + StringRef getParamName() const { + return Args[0].Text; + } + + SourceRange getParamNameRange() const { + return Args[0].Range; + } + + bool isPositionValid() const LLVM_READONLY { + return !Position.empty(); + } + + unsigned getDepth() const { + assert(isPositionValid()); + return Position.size(); + } + + unsigned getIndex(unsigned Depth) const { + assert(isPositionValid()); + return Position[Depth]; + } + + void setPosition(ArrayRef NewPosition) { + Position = NewPosition; + assert(isPositionValid()); + } +}; + /// A line of text contained in a verbatim block. class VerbatimBlockLineComment : public Comment { StringRef Text; diff --git a/include/clang/AST/CommentParser.h b/include/clang/AST/CommentParser.h index 43c669adba..53ea2fcdec 100644 --- a/include/clang/AST/CommentParser.h +++ b/include/clang/AST/CommentParser.h @@ -42,17 +42,6 @@ class Parser { /// Source manager for the comment being parsed. const SourceManager &SourceMgr; - template - ArrayRef copyArray(ArrayRef Source) { - size_t Size = Source.size(); - if (Size != 0) { - T *Mem = Allocator.Allocate(Size); - std::uninitialized_copy(Source.begin(), Source.end(), Mem); - return llvm::makeArrayRef(Mem, Size); - } else - return llvm::makeArrayRef(static_cast(NULL), 0); - } - DiagnosticsEngine &Diags; DiagnosticBuilder Diag(SourceLocation Loc, unsigned DiagID) { @@ -105,6 +94,11 @@ public: ParamCommandComment *PC, TextTokenRetokenizer &Retokenizer); + /// Parse arguments for \\tparam command. + TParamCommandComment *parseTParamCommandArgs( + TParamCommandComment *TPC, + TextTokenRetokenizer &Retokenizer); + BlockCommandComment *parseBlockCommandArgs( BlockCommandComment *BC, TextTokenRetokenizer &Retokenizer, diff --git a/include/clang/AST/CommentSema.h b/include/clang/AST/CommentSema.h index 17d9ab7761..c51439b96f 100644 --- a/include/clang/AST/CommentSema.h +++ b/include/clang/AST/CommentSema.h @@ -19,12 +19,14 @@ #include "clang/AST/Comment.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/Allocator.h" namespace clang { class Decl; class FunctionDecl; class ParmVarDecl; +class TemplateParameterList; class SourceMgr; namespace comments { @@ -56,6 +58,18 @@ class Sema { /// true. llvm::SmallVector ParamVarDocs; + /// Template parameters that can be referenced by \\tparam if \c ThisDecl is + /// a template. + /// + /// Contains a valid value if \c IsThisDeclInspected is true. + const TemplateParameterList *TemplateParameters; + + /// Comment AST nodes that correspond to parameter names in + /// \c TemplateParameters. + /// + /// Contains a valid value if \c IsThisDeclInspected is true. + llvm::StringMap TemplateParameterDocs; + /// True if we extracted all important information from \c ThisDecl into /// \c Sema members. unsigned IsThisDeclInspected : 1; @@ -64,6 +78,10 @@ class Sema { /// Contains a valid value if \c IsThisDeclInspected is true. unsigned IsFunctionDecl : 1; + /// Is \c ThisDecl a template declaration. + /// Contains a valid value if \c IsThisDeclInspected is true. + unsigned IsTemplateDecl : 1; + DiagnosticBuilder Diag(SourceLocation Loc, unsigned DiagID) { return Diags.Report(Loc, DiagID); } @@ -78,6 +96,18 @@ public: void setDecl(const Decl *D); + /// Returns a copy of array, owned by Sema's allocator. + template + ArrayRef copyArray(ArrayRef Source) { + size_t Size = Source.size(); + if (Size != 0) { + T *Mem = Allocator.Allocate(Size); + std::uninitialized_copy(Source.begin(), Source.end(), Mem); + return llvm::makeArrayRef(Mem, Size); + } else + return llvm::makeArrayRef(static_cast(NULL), 0); + } + ParagraphComment *actOnParagraphComment( ArrayRef Content); @@ -111,6 +141,19 @@ public: ParamCommandComment *actOnParamCommandFinish(ParamCommandComment *Command, ParagraphComment *Paragraph); + TParamCommandComment *actOnTParamCommandStart(SourceLocation LocBegin, + SourceLocation LocEnd, + StringRef Name); + + TParamCommandComment *actOnTParamCommandParamNameArg( + TParamCommandComment *Command, + SourceLocation ArgLocBegin, + SourceLocation ArgLocEnd, + StringRef Arg); + + TParamCommandComment *actOnTParamCommandFinish(TParamCommandComment *Command, + ParagraphComment *Paragraph); + InlineCommandComment *actOnInlineCommand(SourceLocation CommandLocBegin, SourceLocation CommandLocEnd, StringRef CommandName); @@ -165,6 +208,8 @@ public: void checkBlockCommandEmptyParagraph(BlockCommandComment *Command); bool isFunctionDecl(); + bool isTemplateDecl(); + ArrayRef getParamVars(); /// Extract all important semantic information from \c ThisDecl into @@ -180,8 +225,17 @@ public: unsigned correctTypoInParmVarReference(StringRef Typo, ArrayRef ParamVars); + bool resolveTParamReference(StringRef Name, + const TemplateParameterList *TemplateParameters, + SmallVectorImpl *Position); + + StringRef correctTypoInTParamReference( + StringRef Typo, + const TemplateParameterList *TemplateParameters); + bool isBlockCommand(StringRef Name); bool isParamCommand(StringRef Name); + bool isTParamCommand(StringRef Name); unsigned getBlockCommandNumArgs(StringRef Name); bool isInlineCommand(StringRef Name) const; diff --git a/include/clang/Basic/CommentNodes.td b/include/clang/Basic/CommentNodes.td index 0e3f284521..7bf32b78b5 100644 --- a/include/clang/Basic/CommentNodes.td +++ b/include/clang/Basic/CommentNodes.td @@ -17,6 +17,7 @@ def BlockContentComment : Comment<1>; def ParagraphComment : DComment; def BlockCommandComment : DComment; def ParamCommandComment : DComment; + def TParamCommandComment : DComment; def VerbatimBlockComment : DComment; def VerbatimLineComment : DComment; diff --git a/include/clang/Basic/DiagnosticCommentKinds.td b/include/clang/Basic/DiagnosticCommentKinds.td index 91aa5f19e6..4aa812ebc1 100644 --- a/include/clang/Basic/DiagnosticCommentKinds.td +++ b/include/clang/Basic/DiagnosticCommentKinds.td @@ -77,5 +77,26 @@ def warn_doc_param_not_found : Warning< def note_doc_param_name_suggestion : Note< "did you mean '%0'?">; +// \tparam command + +def warn_doc_tparam_not_attached_to_a_template_decl : Warning< + "'\\tparam' command used in a comment that is not attached to " + "a template declaration">, + InGroup, DefaultIgnore; + +def warn_doc_tparam_duplicate : Warning< + "template parameter '%0' is already documented">, + InGroup, DefaultIgnore; + +def note_doc_tparam_previous : Note< + "previous documentation">; + +def warn_doc_tparam_not_found : Warning< + "template parameter '%0' not found in the template declaration">, + InGroup, DefaultIgnore; + +def note_doc_tparam_name_suggestion : Note< + "did you mean '%0'?">; + } // end of documentation issue category } // end of AST component diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 46a4d87f9a..edcfe8ea7a 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -72,6 +72,13 @@ RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const { if (isa(D)) return NULL; + // TODO: we could look up template parameter documentation in the template + // documentation. + if (isa(D) || + isa(D) || + isa(D)) + return NULL; + ArrayRef RawComments = Comments.getComments(); // If there are no comments anywhere, we won't find anything. @@ -86,7 +93,9 @@ RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const { // 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) || + isa(D) || isa(D)) DeclLoc = D->getLocStart(); else DeclLoc = D->getLocation(); diff --git a/lib/AST/CommentBriefParser.cpp b/lib/AST/CommentBriefParser.cpp index 47d52e19a2..22209c097f 100644 --- a/lib/AST/CommentBriefParser.cpp +++ b/lib/AST/CommentBriefParser.cpp @@ -48,6 +48,7 @@ bool isBlockCommand(StringRef Name) { .Case("pre", true) .Case("post", true) .Cases("param", "arg", true) + .Case("tparam", true) .Default(false); } } // unnamed namespace diff --git a/lib/AST/CommentDumper.cpp b/lib/AST/CommentDumper.cpp index f02ea334cd..dffc8233a0 100644 --- a/lib/AST/CommentDumper.cpp +++ b/lib/AST/CommentDumper.cpp @@ -50,6 +50,7 @@ public: void visitParagraphComment(const ParagraphComment *C); void visitBlockCommandComment(const BlockCommandComment *C); void visitParamCommandComment(const ParamCommandComment *C); + void visitTParamCommandComment(const TParamCommandComment *C); void visitVerbatimBlockComment(const VerbatimBlockComment *C); void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); void visitVerbatimLineComment(const VerbatimLineComment *C); @@ -176,6 +177,24 @@ void CommentDumper::visitParamCommandComment(const ParamCommandComment *C) { OS << " ParamIndex=" << C->getParamIndex(); } +void CommentDumper::visitTParamCommandComment(const TParamCommandComment *C) { + dumpComment(C); + + if (C->hasParamName()) { + OS << " Param=\"" << C->getParamName() << "\""; + } + + if (C->isPositionValid()) { + OS << " Position=<"; + for (unsigned i = 0, e = C->getDepth(); i != e; ++i) { + OS << C->getIndex(i); + if (i != e - 1) + OS << ", "; + } + OS << ">"; + } +} + void CommentDumper::visitVerbatimBlockComment(const VerbatimBlockComment *C) { dumpComment(C); diff --git a/lib/AST/CommentParser.cpp b/lib/AST/CommentParser.cpp index 6d535673f8..63560e197d 100644 --- a/lib/AST/CommentParser.cpp +++ b/lib/AST/CommentParser.cpp @@ -276,6 +276,19 @@ ParamCommandComment *Parser::parseParamCommandArgs( return PC; } +TParamCommandComment *Parser::parseTParamCommandArgs( + TParamCommandComment *TPC, + TextTokenRetokenizer &Retokenizer) { + Token Arg; + if (Retokenizer.lexWord(Arg)) + TPC = S.actOnTParamCommandParamNameArg(TPC, + Arg.getLocation(), + Arg.getEndLocation(), + Arg.getText()); + + return TPC; +} + BlockCommandComment *Parser::parseBlockCommandArgs( BlockCommandComment *BC, TextTokenRetokenizer &Retokenizer, @@ -299,14 +312,21 @@ BlockCommandComment *Parser::parseBlockCommand() { assert(Tok.is(tok::command)); ParamCommandComment *PC; + TParamCommandComment *TPC; BlockCommandComment *BC; bool IsParam = false; + bool IsTParam = false; unsigned NumArgs = 0; if (S.isParamCommand(Tok.getCommandName())) { IsParam = true; PC = S.actOnParamCommandStart(Tok.getLocation(), Tok.getEndLocation(), Tok.getCommandName()); + } if (S.isTParamCommand(Tok.getCommandName())) { + IsTParam = true; + TPC = S.actOnTParamCommandStart(Tok.getLocation(), + Tok.getEndLocation(), + Tok.getCommandName()); } else { NumArgs = S.getBlockCommandNumArgs(Tok.getCommandName()); BC = S.actOnBlockCommandStart(Tok.getLocation(), @@ -323,13 +343,15 @@ BlockCommandComment *Parser::parseBlockCommand() { return S.actOnBlockCommandFinish(IsParam ? PC : BC, Paragraph); } - if (IsParam || NumArgs > 0) { + if (IsParam || IsTParam || NumArgs > 0) { // In order to parse command arguments we need to retokenize a few // following text tokens. TextTokenRetokenizer Retokenizer(Allocator, *this); if (IsParam) PC = parseParamCommandArgs(PC, Retokenizer); + else if (IsTParam) + TPC = parseTParamCommandArgs(TPC, Retokenizer); else BC = parseBlockCommandArgs(BC, Retokenizer, NumArgs); @@ -341,6 +363,8 @@ BlockCommandComment *Parser::parseBlockCommand() { // paragraph. if (IsParam) return S.actOnParamCommandFinish(PC, cast(Block)); + else if (IsTParam) + return S.actOnTParamCommandFinish(TPC, cast(Block)); else return S.actOnBlockCommandFinish(BC, cast(Block)); } @@ -419,7 +443,7 @@ HTMLStartTagComment *Parser::parseHTMLStartTag() { case tok::html_greater: HST = S.actOnHTMLStartTagFinish(HST, - copyArray(llvm::makeArrayRef(Attrs)), + S.copyArray(llvm::makeArrayRef(Attrs)), Tok.getLocation(), /* IsSelfClosing = */ false); consumeToken(); @@ -427,7 +451,7 @@ HTMLStartTagComment *Parser::parseHTMLStartTag() { case tok::html_slash_greater: HST = S.actOnHTMLStartTagFinish(HST, - copyArray(llvm::makeArrayRef(Attrs)), + S.copyArray(llvm::makeArrayRef(Attrs)), Tok.getLocation(), /* IsSelfClosing = */ true); consumeToken(); @@ -446,14 +470,14 @@ HTMLStartTagComment *Parser::parseHTMLStartTag() { continue; return S.actOnHTMLStartTagFinish(HST, - copyArray(llvm::makeArrayRef(Attrs)), + S.copyArray(llvm::makeArrayRef(Attrs)), SourceLocation(), /* IsSelfClosing = */ false); default: // Not a token from an HTML start tag. Thus HTML tag prematurely ended. HST = S.actOnHTMLStartTagFinish(HST, - copyArray(llvm::makeArrayRef(Attrs)), + S.copyArray(llvm::makeArrayRef(Attrs)), SourceLocation(), /* IsSelfClosing = */ false); bool StartLineInvalid; @@ -563,7 +587,7 @@ BlockContentComment *Parser::parseParagraphOrBlockCommand() { break; } - return S.actOnParagraphComment(copyArray(llvm::makeArrayRef(Content))); + return S.actOnParagraphComment(S.copyArray(llvm::makeArrayRef(Content))); } VerbatimBlockComment *Parser::parseVerbatimBlock() { @@ -601,12 +625,12 @@ VerbatimBlockComment *Parser::parseVerbatimBlock() { if (Tok.is(tok::verbatim_block_end)) { VB = S.actOnVerbatimBlockFinish(VB, Tok.getLocation(), Tok.getVerbatimBlockName(), - copyArray(llvm::makeArrayRef(Lines))); + S.copyArray(llvm::makeArrayRef(Lines))); consumeToken(); } else { // Unterminated \\verbatim block VB = S.actOnVerbatimBlockFinish(VB, SourceLocation(), "", - copyArray(llvm::makeArrayRef(Lines))); + S.copyArray(llvm::makeArrayRef(Lines))); } return VB; @@ -680,7 +704,7 @@ FullComment *Parser::parseFullComment() { while (Tok.is(tok::newline)) consumeToken(); } - return S.actOnFullComment(copyArray(llvm::makeArrayRef(Blocks))); + return S.actOnFullComment(S.copyArray(llvm::makeArrayRef(Blocks))); } } // end namespace comments diff --git a/lib/AST/CommentSema.cpp b/lib/AST/CommentSema.cpp index bb0d03ebd2..7b42c861d3 100644 --- a/lib/AST/CommentSema.cpp +++ b/lib/AST/CommentSema.cpp @@ -11,6 +11,7 @@ #include "clang/AST/CommentDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/DeclTemplate.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringSwitch.h" @@ -200,6 +201,90 @@ ParamCommandComment *Sema::actOnParamCommandFinish(ParamCommandComment *Command, return Command; } +TParamCommandComment *Sema::actOnTParamCommandStart(SourceLocation LocBegin, + SourceLocation LocEnd, + StringRef Name) { + TParamCommandComment *Command = + new (Allocator) TParamCommandComment(LocBegin, LocEnd, Name); + + if (!isTemplateDecl()) + Diag(Command->getLocation(), + diag::warn_doc_tparam_not_attached_to_a_template_decl) + << Command->getCommandNameRange(); + + return Command; +} + +TParamCommandComment *Sema::actOnTParamCommandParamNameArg( + TParamCommandComment *Command, + SourceLocation ArgLocBegin, + SourceLocation ArgLocEnd, + StringRef Arg) { + // Parser will not feed us more arguments than needed. + assert(Command->getNumArgs() == 0); + + typedef BlockCommandComment::Argument Argument; + Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin, + ArgLocEnd), + Arg); + Command->setArgs(llvm::makeArrayRef(A, 1)); + + if (!isTemplateDecl()) { + // We already warned that this \\tparam is not attached to a template decl. + return Command; + } + + SmallVector Position; + if (resolveTParamReference(Arg, TemplateParameters, &Position)) { + Command->setPosition(copyArray(llvm::makeArrayRef(Position))); + llvm::StringMap::iterator PrevCommandIt = + TemplateParameterDocs.find(Arg); + if (PrevCommandIt != TemplateParameterDocs.end()) { + SourceRange ArgRange(ArgLocBegin, ArgLocEnd); + Diag(ArgLocBegin, diag::warn_doc_tparam_duplicate) + << Arg << ArgRange; + TParamCommandComment *PrevCommand = PrevCommandIt->second; + Diag(PrevCommand->getLocation(), diag::note_doc_tparam_previous) + << PrevCommand->getParamNameRange(); + } + TemplateParameterDocs[Arg] = Command; + return Command; + } + + SourceRange ArgRange(ArgLocBegin, ArgLocEnd); + Diag(ArgLocBegin, diag::warn_doc_tparam_not_found) + << Arg << ArgRange; + + if (!TemplateParameters || TemplateParameters->size() == 0) + return Command; + + StringRef CorrectedName; + if (TemplateParameters->size() == 1) { + const NamedDecl *Param = TemplateParameters->getParam(0); + const IdentifierInfo *II = Param->getIdentifier(); + if (II) + CorrectedName = II->getName(); + } else { + CorrectedName = correctTypoInTParamReference(Arg, TemplateParameters); + } + + if (!CorrectedName.empty()) { + Diag(ArgLocBegin, diag::note_doc_tparam_name_suggestion) + << CorrectedName + << FixItHint::CreateReplacement(ArgRange, CorrectedName); + } + + return Command; +} + +TParamCommandComment *Sema::actOnTParamCommandFinish( + TParamCommandComment *Command, + ParagraphComment *Paragraph) { + Command->setParagraph(Paragraph); + checkBlockCommandEmptyParagraph(Command); + return Command; +} + InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin, SourceLocation CommandLocEnd, StringRef CommandName) { @@ -387,6 +472,12 @@ bool Sema::isFunctionDecl() { return IsFunctionDecl; } +bool Sema::isTemplateDecl() { + if (!IsThisDeclInspected) + inspectThisDecl(); + return IsTemplateDecl; +} + ArrayRef Sema::getParamVars() { if (!IsThisDeclInspected) inspectThisDecl(); @@ -397,18 +488,56 @@ void Sema::inspectThisDecl() { assert(!IsThisDeclInspected); if (!ThisDecl) { IsFunctionDecl = false; + IsTemplateDecl = false; ParamVars = ArrayRef(); + TemplateParameters = NULL; } else if (const FunctionDecl *FD = dyn_cast(ThisDecl)) { IsFunctionDecl = true; + IsTemplateDecl = false; ParamVars = ArrayRef(FD->param_begin(), FD->getNumParams()); + TemplateParameters = NULL; + unsigned NumLists = FD->getNumTemplateParameterLists(); + if (NumLists != 0) { + IsTemplateDecl = true; + TemplateParameters = FD->getTemplateParameterList(NumLists - 1); + } } else if (const ObjCMethodDecl *MD = dyn_cast(ThisDecl)) { IsFunctionDecl = true; + IsTemplateDecl = false; ParamVars = ArrayRef(MD->param_begin(), MD->param_size()); + TemplateParameters = NULL; + } else if (const FunctionTemplateDecl *FTD = + dyn_cast(ThisDecl)) { + IsFunctionDecl = true; + IsTemplateDecl = true; + const FunctionDecl *FD = FTD->getTemplatedDecl(); + ParamVars = ArrayRef(FD->param_begin(), + FD->getNumParams()); + TemplateParameters = FTD->getTemplateParameters(); + } else if (const ClassTemplateDecl *CTD = + dyn_cast(ThisDecl)) { + IsFunctionDecl = false; + IsTemplateDecl = true; + ParamVars = ArrayRef(); + TemplateParameters = CTD->getTemplateParameters(); + } else if (const ClassTemplatePartialSpecializationDecl *CTPSD = + dyn_cast(ThisDecl)) { + IsFunctionDecl = false; + IsTemplateDecl = true; + ParamVars = ArrayRef(); + TemplateParameters = CTPSD->getTemplateParameters(); + } else if (isa(ThisDecl)) { + IsFunctionDecl = false; + IsTemplateDecl = true; + ParamVars = ArrayRef(); + TemplateParameters = NULL; } else { IsFunctionDecl = false; + IsTemplateDecl = false; ParamVars = ArrayRef(); + TemplateParameters = NULL; } ParamVarDocs.resize(ParamVars.size(), NULL); IsThisDeclInspected = true; @@ -424,34 +553,136 @@ unsigned Sema::resolveParmVarReference(StringRef Name, return ParamCommandComment::InvalidParamIndex; } +namespace { +class SimpleTypoCorrector { + StringRef Typo; + const unsigned MaxEditDistance; + + const NamedDecl *BestDecl; + unsigned BestEditDistance; + unsigned BestIndex; + unsigned NextIndex; + +public: + SimpleTypoCorrector(StringRef Typo) : + Typo(Typo), MaxEditDistance((Typo.size() + 2) / 3), + BestDecl(NULL), BestEditDistance(MaxEditDistance + 1), + BestIndex(0), NextIndex(0) + { } + + void addDecl(const NamedDecl *ND); + + const NamedDecl *getBestDecl() const { + if (BestEditDistance > MaxEditDistance) + return NULL; + + return BestDecl; + } + + unsigned getBestDeclIndex() const { + assert(getBestDecl()); + return BestIndex; + } +}; + +void SimpleTypoCorrector::addDecl(const NamedDecl *ND) { + unsigned CurrIndex = NextIndex++; + + const IdentifierInfo *II = ND->getIdentifier(); + if (!II) + return; + + StringRef Name = II->getName(); + unsigned MinPossibleEditDistance = abs((int)Name.size() - (int)Typo.size()); + if (MinPossibleEditDistance > 0 && + Typo.size() / MinPossibleEditDistance < 3) + return; + + unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance); + if (EditDistance < BestEditDistance) { + BestEditDistance = EditDistance; + BestDecl = ND; + BestIndex = CurrIndex; + } +} +} // unnamed namespace + unsigned Sema::correctTypoInParmVarReference( StringRef Typo, ArrayRef ParamVars) { - const unsigned MaxEditDistance = (Typo.size() + 2) / 3; - unsigned BestPVDIndex = 0; - unsigned BestEditDistance = MaxEditDistance + 1; - for (unsigned i = 0, e = ParamVars.size(); i != e; ++i) { - const IdentifierInfo *II = ParamVars[i]->getIdentifier(); - if (II) { - StringRef Name = II->getName(); - unsigned MinPossibleEditDistance = - abs((int)Name.size() - (int)Typo.size()); - if (MinPossibleEditDistance > 0 && - Typo.size() / MinPossibleEditDistance < 3) - continue; - - unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance); - if (EditDistance < BestEditDistance) { - BestEditDistance = EditDistance; - BestPVDIndex = i; - } + SimpleTypoCorrector Corrector(Typo); + for (unsigned i = 0, e = ParamVars.size(); i != e; ++i) + Corrector.addDecl(ParamVars[i]); + if (Corrector.getBestDecl()) + return Corrector.getBestDeclIndex(); + else + return ParamCommandComment::InvalidParamIndex;; +} + +namespace { +bool ResolveTParamReferenceHelper( + StringRef Name, + const TemplateParameterList *TemplateParameters, + SmallVectorImpl *Position) { + for (unsigned i = 0, e = TemplateParameters->size(); i != e; ++i) { + const NamedDecl *Param = TemplateParameters->getParam(i); + const IdentifierInfo *II = Param->getIdentifier(); + if (II && II->getName() == Name) { + Position->push_back(i); + return true; + } + + if (const TemplateTemplateParmDecl *TTP = + dyn_cast(Param)) { + Position->push_back(i); + if (ResolveTParamReferenceHelper(Name, TTP->getTemplateParameters(), + Position)) + return true; + Position->pop_back(); } } + return false; +} +} // unnamed namespace - if (BestEditDistance <= MaxEditDistance) - return BestPVDIndex; - else - return ParamCommandComment::InvalidParamIndex; +bool Sema::resolveTParamReference( + StringRef Name, + const TemplateParameterList *TemplateParameters, + SmallVectorImpl *Position) { + Position->clear(); + if (!TemplateParameters) + return false; + + return ResolveTParamReferenceHelper(Name, TemplateParameters, Position); +} + +namespace { +void CorrectTypoInTParamReferenceHelper( + const TemplateParameterList *TemplateParameters, + SimpleTypoCorrector &Corrector) { + for (unsigned i = 0, e = TemplateParameters->size(); i != e; ++i) { + const NamedDecl *Param = TemplateParameters->getParam(i); + Corrector.addDecl(Param); + + if (const TemplateTemplateParmDecl *TTP = + dyn_cast(Param)) + CorrectTypoInTParamReferenceHelper(TTP->getTemplateParameters(), + Corrector); + } +} +} // unnamed namespace + +StringRef Sema::correctTypoInTParamReference( + StringRef Typo, + const TemplateParameterList *TemplateParameters) { + SimpleTypoCorrector Corrector(Typo); + CorrectTypoInTParamReferenceHelper(TemplateParameters, Corrector); + if (const NamedDecl *ND = Corrector.getBestDecl()) { + const IdentifierInfo *II = ND->getIdentifier(); + assert(II && "SimpleTypoCorrector should not return this decl"); + return II->getName(); + } + return StringRef(); } // TODO: tablegen @@ -465,7 +696,7 @@ bool Sema::isBlockCommand(StringRef Name) { .Case("authors", true) .Case("pre", true) .Case("post", true) - .Default(false) || isParamCommand(Name); + .Default(false) || isParamCommand(Name) || isTParamCommand(Name); } bool Sema::isParamCommand(StringRef Name) { @@ -475,6 +706,10 @@ bool Sema::isParamCommand(StringRef Name) { .Default(false); } +bool Sema::isTParamCommand(StringRef Name) { + return Name == "tparam"; +} + unsigned Sema::getBlockCommandNumArgs(StringRef Name) { return llvm::StringSwitch(Name) .Cases("brief", "short", 0) diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index e511157366..0063a37a08 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -7618,6 +7618,7 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D) { << FD->getName() << "dllimport"; } } + ActOnDocumentableDecl(FD); return FD; } diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index 1c3feb51eb..cbfa1ba42e 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -1139,6 +1139,8 @@ Sema::CheckClassTemplate(Scope *S, unsigned TagSpec, TagUseKind TUK, if (PrevClassTemplate) mergeDeclAttributes(NewClass, PrevClassTemplate->getTemplatedDecl()); + ActOnDocumentableDecl(NewTemplate); + return NewTemplate; } @@ -5568,7 +5570,9 @@ Sema::ActOnClassTemplateSpecialization(Scope *S, unsigned TagSpec, Decl *Sema::ActOnTemplateDeclarator(Scope *S, MultiTemplateParamsArg TemplateParameterLists, Declarator &D) { - return HandleDeclarator(S, D, move(TemplateParameterLists)); + Decl *NewDecl = HandleDeclarator(S, D, move(TemplateParameterLists)); + ActOnDocumentableDecl(NewDecl); + return NewDecl; } Decl *Sema::ActOnStartOfFunctionTemplateDef(Scope *FnBodyScope, diff --git a/test/Index/annotate-comments.cpp b/test/Index/annotate-comments.cpp index 5aebb6dee0..b1b72ff573 100644 --- a/test/Index/annotate-comments.cpp +++ b/test/Index/annotate-comments.cpp @@ -290,6 +290,30 @@ void comment_to_html_conversion_15(int x1, int x2); /// \param x1 Aaa. void comment_to_html_conversion_16(int x1, int x2); +/// \tparam +/// \param aaa Blah blah +template +void comment_to_html_conversion_17(T aaa); + +/// \tparam T2 Bbb +/// \tparam T1 Aaa +template +void comment_to_html_conversion_18(T1 aaa, T2 bbb); + +/// \tparam T2 Bbb +/// \tparam U Zzz +/// \tparam V Ccc +/// \tparam T1 Aaa +template +void comment_to_html_conversion_19(T1 aaa, T2 bbb); + +/// \tparam TTT Ddd +/// \tparam C Ccc +/// \tparam T Aaa +/// \tparam TT Bbb +template class TT, class C> class TTT> +void comment_to_html_conversion_20(); + /// \brief Aaa. /// /// Bbb. @@ -297,34 +321,34 @@ void comment_to_html_conversion_16(int x1, int x2); /// \param x2 Ddd. /// \param x1 Ccc. /// \returns Eee. -void comment_to_html_conversion_17(int x1, int x2); +void comment_to_html_conversion_21(int x1, int x2); ///
Aaa -void comment_to_html_conversion_18(); +void comment_to_html_conversion_22(); /// \verbatim /// Aaa /// Aaa /// \endverbatim -void comment_to_html_conversion_19(); +void comment_to_html_conversion_23(); /// \b Aaa -void comment_to_html_conversion_20(); +void comment_to_html_conversion_24(); /// \c Aaa \p Bbb -void comment_to_html_conversion_21(); +void comment_to_html_conversion_25(); /// \a Aaa \e Bbb \em Ccc -void comment_to_html_conversion_22(); +void comment_to_html_conversion_26(); /// \\ \@ \& \$ \# \< \> \% \" \. \:: -void comment_to_html_conversion_23(); +void comment_to_html_conversion_27(); /// & < > " -void comment_to_html_conversion_24(); +void comment_to_html_conversion_28(); /// 0<i -void comment_to_html_conversion_25(); +void comment_to_html_conversion_29(); #endif @@ -555,7 +579,70 @@ void comment_to_html_conversion_25(); // CHECK-NEXT: (CXComment_ParamCommand in implicitly ParamName=[x1] ParamIndex=0 // CHECK-NEXT: (CXComment_Paragraph // CHECK-NEXT: (CXComment_Text Text=[ Aaa.]))))] -// CHECK: annotate-comments.cpp:300:6: FunctionDecl=comment_to_html_conversion_17:{{.*}} FullCommentAsHTML=[

Aaa.

Bbb.

x1
Ccc.
x2
Ddd.

Returns Eee.

] +// CHECK: annotate-comments.cpp:296:6: FunctionTemplate=comment_to_html_conversion_17:{{.*}} FullCommentAsHTML=[
aaa
Blah blah
] +// CHECK-NEXT: CommentAST=[ +// CHECK-NEXT: (CXComment_FullComment +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[] ParamPosition=Invalid +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) +// CHECK-NEXT: (CXComment_ParamCommand in implicitly ParamName=[aaa] ParamIndex=0 +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Blah blah]))))] +// CHECK: annotate-comments.cpp:301:6: FunctionTemplate=comment_to_html_conversion_18:{{.*}} FullCommentAsHTML=[
T1
Aaa
T2
Bbb
] +// CHECK-NEXT: CommentAST=[ +// CHECK-NEXT: (CXComment_FullComment +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[T2] ParamPosition={1} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Bbb] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[T1] ParamPosition={0} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Aaa]))))] +// CHECK: annotate-comments.cpp:308:6: FunctionTemplate=comment_to_html_conversion_19:{{.*}} FullCommentAsHTML=[
T1
Aaa
T2
Bbb
V
Ccc
U
Zzz
] +// CHECK-NEXT: CommentAST=[ +// CHECK-NEXT: (CXComment_FullComment +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[T2] ParamPosition={1} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Bbb] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[U] ParamPosition=Invalid +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Zzz] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[V] ParamPosition={2} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Ccc] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[T1] ParamPosition={0} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Aaa]))))] +// CHECK: annotate-comments.cpp:315:6: FunctionTemplate=comment_to_html_conversion_20:{{.*}} FullCommentAsHTML=[
TTT
Ddd
C
Ccc
T
Aaa
TT
Bbb
] +// CHECK-NEXT: CommentAST=[ +// CHECK-NEXT: (CXComment_FullComment +// CHECK-NEXT: (CXComment_Paragraph IsWhitespace +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace)) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[TTT] ParamPosition={0} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Ddd] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[C] ParamPosition={0, 1} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Ccc] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[T] ParamPosition={0, 0, 0} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Aaa] HasTrailingNewline) +// CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace))) +// CHECK-NEXT: (CXComment_TParamCommand ParamName=[TT] ParamPosition={0, 0} +// CHECK-NEXT: (CXComment_Paragraph +// CHECK-NEXT: (CXComment_Text Text=[ Bbb]))))] +// CHECK: annotate-comments.cpp:324:6: FunctionDecl=comment_to_html_conversion_21:{{.*}} FullCommentAsHTML=[

Aaa.

Bbb.

x1
Ccc.
x2
Ddd.

Returns Eee.

] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph IsWhitespace @@ -578,7 +665,7 @@ void comment_to_html_conversion_25(); // CHECK-NEXT: (CXComment_BlockCommand CommandName=[returns] // CHECK-NEXT: (CXComment_Paragraph // CHECK-NEXT: (CXComment_Text Text=[ Eee.]))))] -// CHECK: annotate-comments.cpp:303:6: FunctionDecl=comment_to_html_conversion_18:{{.*}} FullCommentAsHTML=[


Aaa

] +// CHECK: annotate-comments.cpp:327:6: FunctionDecl=comment_to_html_conversion_22:{{.*}} FullCommentAsHTML=[


Aaa

] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph @@ -587,7 +674,7 @@ void comment_to_html_conversion_25(); // CHECK-NEXT: (CXComment_HTMLStartTag Name=[a] Attrs: href=http://example.com/) // CHECK-NEXT: (CXComment_Text Text=[Aaa]) // CHECK-NEXT: (CXComment_HTMLEndTag Name=[a])))] -// CHECK: annotate-comments.cpp:309:6: FunctionDecl=comment_to_html_conversion_19:{{.*}} FullCommentAsHTML=[
 <a href="http://example.com/">Aaa</a>\n <a href='http://example.com/'>Aaa</a>
] +// CHECK: annotate-comments.cpp:333:6: FunctionDecl=comment_to_html_conversion_23:{{.*}} FullCommentAsHTML=[
 <a href="http://example.com/">Aaa</a>\n <a href='http://example.com/'>Aaa</a>
] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph IsWhitespace @@ -595,13 +682,13 @@ void comment_to_html_conversion_25(); // CHECK-NEXT: (CXComment_VerbatimBlockCommand CommandName=[verbatim] // CHECK-NEXT: (CXComment_VerbatimBlockLine Text=[ Aaa]) // CHECK-NEXT: (CXComment_VerbatimBlockLine Text=[ Aaa])))] -// CHECK: annotate-comments.cpp:312:6: FunctionDecl=comment_to_html_conversion_20:{{.*}} FullCommentAsHTML=[

Aaa

] +// CHECK: annotate-comments.cpp:336:6: FunctionDecl=comment_to_html_conversion_24:{{.*}} FullCommentAsHTML=[

Aaa

] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace) // CHECK-NEXT: (CXComment_InlineCommand CommandName=[b] RenderBold Arg[0]=Aaa)))] -// CHECK: annotate-comments.cpp:315:6: FunctionDecl=comment_to_html_conversion_21:{{.*}} FullCommentAsHTML=[

Aaa Bbb

] +// CHECK: annotate-comments.cpp:339:6: FunctionDecl=comment_to_html_conversion_25:{{.*}} FullCommentAsHTML=[

Aaa Bbb

] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph @@ -609,7 +696,7 @@ void comment_to_html_conversion_25(); // CHECK-NEXT: (CXComment_InlineCommand CommandName=[c] RenderMonospaced Arg[0]=Aaa) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace) // CHECK-NEXT: (CXComment_InlineCommand CommandName=[p] RenderMonospaced Arg[0]=Bbb)))] -// CHECK: annotate-comments.cpp:318:6: FunctionDecl=comment_to_html_conversion_22:{{.*}} FullCommentAsHTML=[

Aaa Bbb Ccc

] +// CHECK: annotate-comments.cpp:342:6: FunctionDecl=comment_to_html_conversion_26:{{.*}} FullCommentAsHTML=[

Aaa Bbb Ccc

] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph @@ -619,7 +706,7 @@ void comment_to_html_conversion_25(); // CHECK-NEXT: (CXComment_InlineCommand CommandName=[e] RenderEmphasized Arg[0]=Bbb) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace) // CHECK-NEXT: (CXComment_InlineCommand CommandName=[em] RenderEmphasized Arg[0]=Ccc)))] -// CHECK: annotate-comments.cpp:321:6: FunctionDecl=comment_to_html_conversion_23:{{.*}} FullCommentAsHTML=[

\ @ & $ # < > % " . ::

] +// CHECK: annotate-comments.cpp:345:6: FunctionDecl=comment_to_html_conversion_27:{{.*}} FullCommentAsHTML=[

\ @ & $ # < > % " . ::

] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph @@ -645,7 +732,7 @@ void comment_to_html_conversion_25(); // CHECK-NEXT: (CXComment_Text Text=[.]) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace) // CHECK-NEXT: (CXComment_Text Text=[::])))] -// CHECK: annotate-comments.cpp:324:6: FunctionDecl=comment_to_html_conversion_24:{{.*}} FullCommentAsHTML=[

& < > "

] +// CHECK: annotate-comments.cpp:348:6: FunctionDecl=comment_to_html_conversion_28:{{.*}} FullCommentAsHTML=[

& < > "

] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph @@ -657,7 +744,7 @@ void comment_to_html_conversion_25(); // CHECK-NEXT: (CXComment_Text Text=[>]) // CHECK-NEXT: (CXComment_Text Text=[ ] IsWhitespace) // CHECK-NEXT: (CXComment_Text Text=["])))] -// CHECK: annotate-comments.cpp:327:6: FunctionDecl=comment_to_html_conversion_25:{{.*}} FullCommentAsHTML=[

0<i

] +// CHECK: annotate-comments.cpp:351:6: FunctionDecl=comment_to_html_conversion_29:{{.*}} FullCommentAsHTML=[

0<i

] // CHECK-NEXT: CommentAST=[ // CHECK-NEXT: (CXComment_FullComment // CHECK-NEXT: (CXComment_Paragraph diff --git a/test/Sema/warn-documentation.cpp b/test/Sema/warn-documentation.cpp index 87d8840c7a..f0813682fc 100644 --- a/test/Sema/warn-documentation.cpp +++ b/test/Sema/warn-documentation.cpp @@ -170,18 +170,86 @@ class C { int test_param15(int bbb, int ccc); }; +// expected-warning@+1 {{parameter 'aab' not found in the function declaration}} +/// \param aab Blah blah. +template +void test_param16(int bbb, int ccc); + // expected-warning@+3 {{parameter 'a' is already documented}} // expected-note@+1 {{previous documentation}} /// \param a Aaa. /// \param a Aaa. -int test_param16(int a); +int test_param17(int a); // expected-warning@+4 {{parameter 'x2' is already documented}} // expected-note@+2 {{previous documentation}} /// \param x1 Aaa. /// \param x2 Bbb. /// \param x2 Ccc. -int test_param17(int x1, int x2, int x3); +int test_param18(int x1, int x2, int x3); + + +// expected-warning@+1 {{'\tparam' command used in a comment that is not attached to a template declaration}} +/// \tparam T Aaa +int test_tparam1; + +// expected-warning@+1 {{'\tparam' command used in a comment that is not attached to a template declaration}} +/// \tparam T Aaa +void test_tparam2(int aaa); + +// expected-warning@+1 {{empty paragraph passed to '\tparam' command}} +/// \tparam +/// \param aaa Blah blah +template +void test_tparam3(T aaa); + +// expected-warning@+1 {{template parameter 'T' not found in the template declaration}} expected-note@+1 {{did you mean 'TT'?}} +/// \tparam T Aaa +template +void test_tparam4(TT aaa); + +// expected-warning@+1 {{template parameter 'T' not found in the template declaration}} expected-note@+1 {{did you mean 'TT'?}} +/// \tparam T Aaa +template +class test_tparam5 { + // expected-warning@+1 {{template parameter 'T' not found in the template declaration}} expected-note@+1 {{did you mean 'TTT'?}} + /// \tparam T Aaa + template + void test_tparam6(TTT aaa); +}; + +/// \tparam T1 Aaa +/// \tparam T2 Bbb +template +void test_tparam7(T1 aaa, T2 bbb); + +// expected-warning@+1 {{template parameter 'SomTy' not found in the template declaration}} expected-note@+1 {{did you mean 'SomeTy'?}} +/// \tparam SomTy Aaa +/// \tparam OtherTy Bbb +template +void test_tparam8(SomeTy aaa, OtherTy bbb); + +// expected-warning@+2 {{template parameter 'T1' is already documented}} expected-note@+1 {{previous documentation}} +/// \tparam T1 Aaa +/// \tparam T1 Bbb +template +void test_tparam9(T1 aaa, T2 bbb); + +/// \tparam T Aaa +/// \tparam TT Bbb +template class TT> +void test_tparam10(TT aaa); + +/// \tparam T Aaa +/// \tparam TT Bbb +/// \tparam TTT Ccc +template class TT, class C> class TTT> +void test_tparam11(); + +/// \tparam I Aaa +template +void test_tparam12(); + // expected-warning@+1 {{empty paragraph passed to '\brief' command}} int test1; ///< \brief\brief Aaa @@ -292,6 +360,125 @@ namespace test_attach24 { } } +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +void test_attach26(T aaa); + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +void test_attach27(T aaa, U bbb); + +// expected-warning@+2 {{empty paragraph passed to '\brief' command}} +// expected-warning@+2 {{template parameter 'T' not found in the template declaration}} +/// \brief\brief Aaa +/// \tparam T Aaa +template<> +void test_attach27(int aaa, int bbb); + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +class test_attach28 { + T aaa; +}; + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +class test_attach29 { }; + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +class test_attach29 { }; + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +template<> +class test_attach29 { }; + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +class test_attach30 { }; + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +class test_attach30 { }; + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +class test_attach30 { }; + +// expected-warning@+2 {{empty paragraph passed to '\brief' command}} +// expected-warning@+2 {{template parameter 'T' not found in the template declaration}} +/// \brief\brief Aaa +/// \tparam T Aaa +template<> +class test_attach30 { }; + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +class test_attach31 { + // expected-warning@+1 {{empty paragraph passed to '\brief' command}} + /// \brief\brief Aaa + /// \tparam T Aaa + template + void test_attach32(T aaa, U bbb); +}; + +template +class test_attach33 { + // expected-warning@+2 {{empty paragraph passed to '\brief' command}} + // expected-warning@+2 {{template parameter 'T' not found in the template declaration}} + /// \brief\brief Aaa + /// \tparam T Aaa + template + void test_attach34(TT aaa, UU bbb); +}; + +// expected-warning@+2 {{empty paragraph passed to '\brief' command}} +// expected-warning@+2 {{template parameter 'T' not found in the template declaration}} +/// \brief\brief Aaa +/// \tparam T Aaa +template<> template<> +void test_attach33::test_attach34(int aaa, int bbb) {} + +template +class test_attach35 { + // expected-warning@+2 {{empty paragraph passed to '\brief' command}} + // expected-warning@+2 {{'\tparam' command used in a comment that is not attached to a template declaration}} + /// \brief\brief Aaa + /// \tparam T Aaa + void test_attach36(int aaa, int bbb); +}; + +// expected-warning@+1 {{empty paragraph passed to '\brief' command}} +/// \brief\brief Aaa +/// \tparam T Aaa +template +void test_attach35::test_attach36(int aaa, int bbb) {} + +// expected-warning@+2 {{empty paragraph passed to '\brief' command}} +// expected-warning@+2 {{template parameter 'T' not found in the template declaration}} +/// \brief\brief Aaa +/// \tparam T Aaa +template<> +void test_attach35::test_attach36(int aaa, int bbb) {} + + // PR13411, reduced. We used to crash on this. /** * @code Aaa. diff --git a/tools/c-index-test/c-index-test.c b/tools/c-index-test/c-index-test.c index 9f270f0151..4af2548895 100644 --- a/tools/c-index-test/c-index-test.c +++ b/tools/c-index-test/c-index-test.c @@ -378,6 +378,23 @@ static void DumpCXCommentInternal(struct CommentASTDumpingContext *Ctx, else printf(" ParamIndex=Invalid"); break; + case CXComment_TParamCommand: + printf("CXComment_TParamCommand"); + PrintCXStringWithPrefixAndDispose( + "ParamName", + clang_TParamCommandComment_getParamName(Comment)); + if (clang_TParamCommandComment_isParamPositionValid(Comment)) { + printf(" ParamPosition={"); + for (i = 0, e = clang_TParamCommandComment_getDepth(Comment); + i != e; ++i) { + printf("%u", clang_TParamCommandComment_getIndex(Comment, i)); + if (i != e - 1) + printf(", "); + } + printf("}"); + } else + printf(" ParamPosition=Invalid"); + break; case CXComment_VerbatimBlockCommand: printf("CXComment_VerbatimBlockCommand"); PrintCXStringWithPrefixAndDispose( diff --git a/tools/libclang/CXComment.cpp b/tools/libclang/CXComment.cpp index f8e89f2877..9bdab61942 100644 --- a/tools/libclang/CXComment.cpp +++ b/tools/libclang/CXComment.cpp @@ -59,6 +59,9 @@ enum CXCommentKind clang_Comment_getKind(CXComment CXC) { case Comment::ParamCommandCommentKind: return CXComment_ParamCommand; + case Comment::TParamCommandCommentKind: + return CXComment_TParamCommand; + case Comment::VerbatimBlockCommentKind: return CXComment_VerbatimBlockCommand; @@ -291,6 +294,38 @@ enum CXCommentParamPassDirection clang_ParamCommandComment_getDirection( llvm_unreachable("unknown ParamCommandComment::PassDirection"); } +CXString clang_TParamCommandComment_getParamName(CXComment CXC) { + const TParamCommandComment *TPCC = getASTNodeAs(CXC); + if (!TPCC || !TPCC->hasParamName()) + return createCXString((const char *) 0); + + return createCXString(TPCC->getParamName(), /*DupString=*/ false); +} + +unsigned clang_TParamCommandComment_isParamPositionValid(CXComment CXC) { + const TParamCommandComment *TPCC = getASTNodeAs(CXC); + if (!TPCC) + return false; + + return TPCC->isPositionValid(); +} + +unsigned clang_TParamCommandComment_getDepth(CXComment CXC) { + const TParamCommandComment *TPCC = getASTNodeAs(CXC); + if (!TPCC || !TPCC->isPositionValid()) + return 0; + + return TPCC->getDepth(); +} + +unsigned clang_TParamCommandComment_getIndex(CXComment CXC, unsigned Depth) { + const TParamCommandComment *TPCC = getASTNodeAs(CXC); + if (!TPCC || !TPCC->isPositionValid() || Depth >= TPCC->getDepth()) + return 0; + + return TPCC->getIndex(Depth); +} + CXString clang_VerbatimBlockLineComment_getText(CXComment CXC) { const VerbatimBlockLineComment *VBL = getASTNodeAs(CXC); @@ -333,6 +368,34 @@ public: } }; +/// This comparison will sort template parameters in the following order: +/// \li real template parameters (depth = 1) in index order; +/// \li all other names (depth > 1); +/// \li unresolved names. +class TParamCommandCommentComparePosition { +public: + bool operator()(const TParamCommandComment *LHS, + const TParamCommandComment *RHS) const { + // Sort unresolved names last. + if (!LHS->isPositionValid()) + return false; + if (!RHS->isPositionValid()) + return true; + + if (LHS->getDepth() > 1) + return false; + if (RHS->getDepth() > 1) + return true; + + // Sort template parameters in index order. + if (LHS->getDepth() == 1 && RHS->getDepth() == 1) + return LHS->getIndex(0) < RHS->getIndex(0); + + // Leave all other names in source order. + return true; + } +}; + class CommentASTToHTMLConverter : public ConstCommentVisitor { public: @@ -349,6 +412,7 @@ public: void visitParagraphComment(const ParagraphComment *C); void visitBlockCommandComment(const BlockCommandComment *C); void visitParamCommandComment(const ParamCommandComment *C); + void visitTParamCommandComment(const TParamCommandComment *C); void visitVerbatimBlockComment(const VerbatimBlockComment *C); void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); void visitVerbatimLineComment(const VerbatimLineComment *C); @@ -486,6 +550,34 @@ void CommentASTToHTMLConverter::visitParamCommandComment( Result << ""; } +void CommentASTToHTMLConverter::visitTParamCommandComment( + const TParamCommandComment *C) { + if (C->isPositionValid()) { + if (C->getDepth() == 1) + Result << "
getIndex(0) + << "\">"; + else + Result << "
"; + } else + Result << "
"; + + Result << C->getParamName() << "
"; + + if (C->isPositionValid()) { + if (C->getDepth() == 1) + Result << "
getIndex(0) + << "\">"; + else + Result << "
"; + } else + Result << "
"; + + visitNonStandaloneParagraphComment(C->getParagraph()); + Result << "
"; +} + void CommentASTToHTMLConverter::visitVerbatimBlockComment( const VerbatimBlockComment *C) { unsigned NumLines = C->getNumLines(); @@ -518,6 +610,7 @@ void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { const ParagraphComment *FirstParagraph = NULL; const BlockCommandComment *Returns = NULL; SmallVector Params; + SmallVector TParams; SmallVector MiscBlocks; // Extract various blocks into separate variables and vectors above. @@ -568,6 +661,15 @@ void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { break; } + case Comment::TParamCommandCommentKind: { + const TParamCommandComment *TPCC = cast(Child); + if (!TPCC->hasParamName()) + break; + + TParams.push_back(TPCC); + break; + } + case Comment::VerbatimBlockCommentKind: case Comment::VerbatimLineCommentKind: MiscBlocks.push_back(cast(Child)); @@ -590,6 +692,9 @@ void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { std::stable_sort(Params.begin(), Params.end(), ParamCommandCommentCompareIndex()); + std::stable_sort(TParams.begin(), TParams.end(), + TParamCommandCommentComparePosition()); + bool FirstParagraphIsBrief = false; if (Brief) visit(Brief); @@ -607,6 +712,13 @@ void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { visit(C); } + if (TParams.size() != 0) { + Result << "
"; + for (unsigned i = 0, e = TParams.size(); i != e; ++i) + visit(TParams[i]); + Result << "
"; + } + if (Params.size() != 0) { Result << "
"; for (unsigned i = 0, e = Params.size(); i != e; ++i) diff --git a/tools/libclang/libclang.exports b/tools/libclang/libclang.exports index bc8c113fad..d796b154ad 100644 --- a/tools/libclang/libclang.exports +++ b/tools/libclang/libclang.exports @@ -42,6 +42,10 @@ clang_ParamCommandComment_isParamIndexValid clang_ParamCommandComment_getParamIndex clang_ParamCommandComment_isDirectionExplicit clang_ParamCommandComment_getDirection +clang_TParamCommandComment_getParamName +clang_TParamCommandComment_isParamPositionValid +clang_TParamCommandComment_getDepth +clang_TParamCommandComment_getIndex clang_VerbatimBlockLineComment_getText clang_VerbatimLineComment_getText clang_HTMLTagComment_getAsString diff --git a/unittests/AST/CommentParser.cpp b/unittests/AST/CommentParser.cpp index faf11b28fd..c6d809ff40 100644 --- a/unittests/AST/CommentParser.cpp +++ b/unittests/AST/CommentParser.cpp @@ -221,6 +221,39 @@ template return ::testing::AssertionSuccess(); } +::testing::AssertionResult HasTParamCommandAt( + const Comment *C, + size_t Idx, + TParamCommandComment *&TPCC, + StringRef CommandName, + StringRef ParamName, + ParagraphComment *&Paragraph) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, TPCC); + if (!AR) + return AR; + + StringRef ActualCommandName = TPCC->getCommandName(); + if (ActualCommandName != CommandName) + return ::testing::AssertionFailure() + << "TParamCommandComment has name \"" << ActualCommandName.str() << "\", " + "expected \"" << CommandName.str() << "\""; + + if (!TPCC->hasParamName()) + return ::testing::AssertionFailure() + << "TParamCommandComment has no parameter name"; + + StringRef ActualParamName = TPCC->getParamName(); + if (ActualParamName != ParamName) + return ::testing::AssertionFailure() + << "TParamCommandComment has parameter name \"" << ActualParamName.str() + << "\", " + "expected \"" << ParamName.str() << "\""; + + Paragraph = TPCC->getParagraph(); + + return ::testing::AssertionSuccess(); +} + ::testing::AssertionResult HasInlineCommandAt(const Comment *C, size_t Idx, InlineCommandComment *&ICC, @@ -838,6 +871,33 @@ TEST_F(CommentParserTest, ParamCommand6) { } } +TEST_F(CommentParserTest, TParamCommand1) { + const char *Sources[] = { + "// \\tparam aaa Bbb\n", + "// \\tparam\n" + "// aaa Bbb\n", + "// \\tparam \n" + "// aaa Bbb\n", + "// \\tparam aaa\n" + "// Bbb\n" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + TParamCommandComment *TPCC; + ParagraphComment *PC; + ASSERT_TRUE(HasTParamCommandAt(FC, 1, TPCC, "tparam", + "aaa", PC)); + ASSERT_TRUE(HasChildCount(TPCC, 1)); + ASSERT_TRUE(HasParagraphCommentAt(TPCC, 0, " Bbb")); + } + } +} + TEST_F(CommentParserTest, InlineCommand1) { const char *Source = "// \\c";