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/lib/AST/CommentSema.cpp b/lib/AST/CommentSema.cpp
index bb0d03e..7b42c86 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 @@
   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<unsigned, 2> Position;
+  if (resolveTParamReference(Arg, TemplateParameters, &Position)) {
+    Command->setPosition(copyArray(llvm::makeArrayRef(Position)));
+    llvm::StringMap<TParamCommandComment *>::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 @@
   return IsFunctionDecl;
 }
 
+bool Sema::isTemplateDecl() {
+  if (!IsThisDeclInspected)
+    inspectThisDecl();
+  return IsTemplateDecl;
+}
+
 ArrayRef<const ParmVarDecl *> Sema::getParamVars() {
   if (!IsThisDeclInspected)
     inspectThisDecl();
@@ -397,18 +488,56 @@
   assert(!IsThisDeclInspected);
   if (!ThisDecl) {
     IsFunctionDecl = false;
+    IsTemplateDecl = false;
     ParamVars = ArrayRef<const ParmVarDecl *>();
+    TemplateParameters = NULL;
   } else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(ThisDecl)) {
     IsFunctionDecl = true;
+    IsTemplateDecl = false;
     ParamVars = ArrayRef<const ParmVarDecl *>(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<ObjCMethodDecl>(ThisDecl)) {
     IsFunctionDecl = true;
+    IsTemplateDecl = false;
     ParamVars = ArrayRef<const ParmVarDecl *>(MD->param_begin(),
                                               MD->param_size());
+    TemplateParameters = NULL;
+  } else if (const FunctionTemplateDecl *FTD =
+                 dyn_cast<FunctionTemplateDecl>(ThisDecl)) {
+    IsFunctionDecl = true;
+    IsTemplateDecl = true;
+    const FunctionDecl *FD = FTD->getTemplatedDecl();
+    ParamVars = ArrayRef<const ParmVarDecl *>(FD->param_begin(),
+                                              FD->getNumParams());
+    TemplateParameters = FTD->getTemplateParameters();
+  } else if (const ClassTemplateDecl *CTD =
+                 dyn_cast<ClassTemplateDecl>(ThisDecl)) {
+    IsFunctionDecl = false;
+    IsTemplateDecl = true;
+    ParamVars = ArrayRef<const ParmVarDecl *>();
+    TemplateParameters = CTD->getTemplateParameters();
+  } else if (const ClassTemplatePartialSpecializationDecl *CTPSD =
+                 dyn_cast<ClassTemplatePartialSpecializationDecl>(ThisDecl)) {
+    IsFunctionDecl = false;
+    IsTemplateDecl = true;
+    ParamVars = ArrayRef<const ParmVarDecl *>();
+    TemplateParameters = CTPSD->getTemplateParameters();
+  } else if (isa<ClassTemplateSpecializationDecl>(ThisDecl)) {
+    IsFunctionDecl = false;
+    IsTemplateDecl = true;
+    ParamVars = ArrayRef<const ParmVarDecl *>();
+    TemplateParameters = NULL;
   } else {
     IsFunctionDecl = false;
+    IsTemplateDecl = false;
     ParamVars = ArrayRef<const ParmVarDecl *>();
+    TemplateParameters = NULL;
   }
   ParamVarDocs.resize(ParamVars.size(), NULL);
   IsThisDeclInspected = true;
@@ -424,34 +553,136 @@
   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<const ParmVarDecl *> 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;
+  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;;
+}
 
-      unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance);
-      if (EditDistance < BestEditDistance) {
-        BestEditDistance = EditDistance;
-        BestPVDIndex = i;
-      }
+namespace {
+bool ResolveTParamReferenceHelper(
+                            StringRef Name,
+                            const TemplateParameterList *TemplateParameters,
+                            SmallVectorImpl<unsigned> *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<TemplateTemplateParmDecl>(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<unsigned> *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<TemplateTemplateParmDecl>(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 @@
       .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 @@
       .Default(false);
 }
 
+bool Sema::isTParamCommand(StringRef Name) {
+  return Name == "tparam";
+}
+
 unsigned Sema::getBlockCommandNumArgs(StringRef Name) {
   return llvm::StringSwitch<unsigned>(Name)
       .Cases("brief", "short", 0)