Skip to content

Conversation

tcottin
Copy link
Contributor

@tcottin tcottin commented Aug 10, 2025

This is the final patch to solve clangd/clangd#529.

This uses the patches from llvm/llvm-project#140498 and llvm/llvm-project#150790 to improve the hover content for doxygen documented code.

The patch uses the information from the parsed doxygen comment to extend and rearrange the hover documentation:

  • own section for brief, notes and warning documentation
  • for functions add parameter/template parameter documentation
  • for functions add return documentation

Note: this requires to configure

Documentation:
  CommentFormat: Doxygen

in your .clangd configuration.

@llvmbot
Copy link
Member

llvmbot commented Aug 10, 2025

@llvm/pr-subscribers-clang
@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clangd

Author: None (tcottin)

Changes

This is the final patch to solve clangd/clangd#529.

This uses the patches from llvm/llvm-project#140498 and llvm/llvm-project#150790 to improve the hover content for doxygen documented code.

The patch uses the information from the parsed doxygen comment to extend and rearrange the hover documentation:

  • own section for brief, notes and warning documentation
  • for functions add parameter/template parameter documentation
  • for functions add return documentation

Note: this requires to configure

Documentation:
  CommentFormat: Doxygen

in your .clangd configuration.


Patch is 34.76 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/152918.diff

7 Files Affected:

  • (modified) clang-tools-extra/clangd/CodeCompletionStrings.cpp (+6-2)
  • (modified) clang-tools-extra/clangd/Hover.cpp (+78-22)
  • (modified) clang-tools-extra/clangd/Hover.h (+3)
  • (modified) clang-tools-extra/clangd/SymbolDocumentation.cpp (+96-7)
  • (modified) clang-tools-extra/clangd/SymbolDocumentation.h (+66-10)
  • (modified) clang-tools-extra/clangd/unittests/HoverTests.cpp (+340-37)
  • (modified) clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp (+94-40)
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
index d6579640cb0fb..02c3ea8abd8af 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -112,7 +112,7 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
   std::string Doc;
 
   if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen &&
-      isa<ParmVarDecl>(Decl)) {
+      (isa<ParmVarDecl>(Decl) || isa<TemplateTypeParmDecl>(Decl))) {
     // Parameters are documented in their declaration context (function or
     // template function).
     const NamedDecl *ND = dyn_cast<NamedDecl>(Decl.getDeclContext());
@@ -135,7 +135,11 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
     std::string RawDoc;
     llvm::raw_string_ostream OS(RawDoc);
 
-    V.parameterDocToString(dyn_cast<ParmVarDecl>(&Decl)->getName(), OS);
+    if (isa<ParmVarDecl>(Decl))
+      V.parameterDocToString(dyn_cast<ParmVarDecl>(&Decl)->getName(), OS);
+    else
+      V.templateTypeParmDocToString(
+          dyn_cast<TemplateTypeParmDecl>(&Decl)->getName(), OS);
 
     Doc = StringRef(RawDoc).trim().str();
   } else {
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 0afa90285db52..45e47233c9e1f 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -1276,6 +1276,7 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
     HI.Definition =
         URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str();
     HI.DefinitionLanguage = "";
+    HI.IsIncludeDirective = true;
     maybeAddUsedSymbols(AST, HI, Inc);
     return HI;
   }
@@ -1504,7 +1505,26 @@ markup::Document HoverInfo::presentDoxygen() const {
     Header.appendText(index::getSymbolKindString(Kind)).appendSpace();
   assert(!Name.empty() && "hover triggered on a nameless symbol");
 
-  Header.appendCode(Name);
+  if (IsIncludeDirective) {
+    Header.appendCode(Name);
+
+    if (!Definition.empty())
+      Output.addParagraph().appendCode(Definition);
+
+    if (!UsedSymbolNames.empty()) {
+      Output.addRuler();
+      usedSymbolNamesToMarkup(Output);
+    }
+
+    return Output;
+  }
+
+  if (!Definition.empty()) {
+    Output.addRuler();
+    definitionScopeToMarkup(Output);
+  } else {
+    Header.appendCode(Name);
+  }
 
   if (!Provider.empty()) {
     providerToMarkupParagraph(Output);
@@ -1512,33 +1532,76 @@ markup::Document HoverInfo::presentDoxygen() const {
 
   // Put a linebreak after header to increase readability.
   Output.addRuler();
-  // Print Types on their own lines to reduce chances of getting line-wrapped by
-  // editor, as they might be long.
-  if (ReturnType) {
-    // For functions we display signature in a list form, e.g.:
-    // → `x`
-    // Parameters:
-    // - `bool param1`
-    // - `int param2 = 5`
-    Output.addParagraph().appendText("→ ").appendCode(
-        llvm::to_string(*ReturnType));
-  }
 
   SymbolDocCommentVisitor SymbolDoc(Documentation, CommentOpts);
 
+  if (SymbolDoc.hasBriefCommand()) {
+    SymbolDoc.briefToMarkup(Output.addParagraph());
+    Output.addRuler();
+  }
+
+  // For functions we display signature in a list form, e.g.:
+  // Template Parameters:
+  // - `typename T` - description
+  // Parameters:
+  // - `bool param1` - description
+  // - `int param2 = 5` - description
+  // Returns
+  // `type` - description
+  if (TemplateParameters && !TemplateParameters->empty()) {
+    Output.addParagraph().appendBoldText("Template Parameters:");
+    markup::BulletList &L = Output.addBulletList();
+    for (const auto &Param : *TemplateParameters) {
+      markup::Paragraph &P = L.addItem().addParagraph();
+      P.appendCode(llvm::to_string(Param));
+      if (SymbolDoc.isTemplateTypeParmDocumented(llvm::to_string(Param.Name))) {
+        P.appendText(" - ");
+        SymbolDoc.templateTypeParmDocToMarkup(llvm::to_string(Param.Name), P);
+      }
+    }
+    Output.addRuler();
+  }
+
   if (Parameters && !Parameters->empty()) {
-    Output.addParagraph().appendText("Parameters:");
+    Output.addParagraph().appendBoldText("Parameters:");
     markup::BulletList &L = Output.addBulletList();
     for (const auto &Param : *Parameters) {
       markup::Paragraph &P = L.addItem().addParagraph();
       P.appendCode(llvm::to_string(Param));
 
       if (SymbolDoc.isParameterDocumented(llvm::to_string(Param.Name))) {
-        P.appendText(" -");
+        P.appendText(" - ");
         SymbolDoc.parameterDocToMarkup(llvm::to_string(Param.Name), P);
       }
     }
+    Output.addRuler();
+  }
+
+  // Print Types on their own lines to reduce chances of getting line-wrapped by
+  // editor, as they might be long.
+  if (ReturnType &&
+      ((ReturnType->Type != "void" && !ReturnType->AKA.has_value()) ||
+       (ReturnType->AKA.has_value() && ReturnType->AKA != "void"))) {
+    Output.addParagraph().appendBoldText("Returns:");
+    markup::Paragraph &P = Output.addParagraph();
+    P.appendCode(llvm::to_string(*ReturnType));
+
+    if (SymbolDoc.hasReturnCommand()) {
+      P.appendText(" - ");
+      SymbolDoc.returnToMarkup(P);
+    }
+    Output.addRuler();
   }
+
+  // add specially handled doxygen commands.
+  SymbolDoc.warningsToMarkup(Output);
+  SymbolDoc.notesToMarkup(Output);
+
+  // add any other documentation.
+  SymbolDoc.docToMarkup(Output);
+
+  Output.addRuler();
+
   // Don't print Type after Parameters or ReturnType as this will just duplicate
   // the information
   if (Type && !ReturnType && !Parameters)
@@ -1559,13 +1622,6 @@ markup::Document HoverInfo::presentDoxygen() const {
     calleeArgInfoToMarkupParagraph(Output.addParagraph());
   }
 
-  SymbolDoc.docToMarkup(Output);
-
-  if (!Definition.empty()) {
-    Output.addRuler();
-    definitionScopeToMarkup(Output);
-  }
-
   if (!UsedSymbolNames.empty()) {
     Output.addRuler();
     usedSymbolNamesToMarkup(Output);
@@ -1613,7 +1669,7 @@ markup::Document HoverInfo::presentDefault() const {
   }
 
   if (Parameters && !Parameters->empty()) {
-    Output.addParagraph().appendText("Parameters: ");
+    Output.addParagraph().appendText("Parameters:");
     markup::BulletList &L = Output.addBulletList();
     for (const auto &Param : *Parameters)
       L.addItem().addParagraph().appendCode(llvm::to_string(Param));
diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h
index 614180a7b9846..2eb3dd99ab3fc 100644
--- a/clang-tools-extra/clangd/Hover.h
+++ b/clang-tools-extra/clangd/Hover.h
@@ -120,6 +120,9 @@ struct HoverInfo {
   // alphabetical order.
   std::vector<std::string> UsedSymbolNames;
 
+  /// True if this symbol is a include directive.
+  bool IsIncludeDirective = false;
+
   /// Produce a user-readable information based on the specified markup kind.
   std::string present(MarkupKind Kind) const;
 
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp
index dea637b9100da..9152355cd0f79 100644
--- a/clang-tools-extra/clangd/SymbolDocumentation.cpp
+++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp
@@ -33,8 +33,8 @@ void commandToMarkup(markup::Paragraph &Out, StringRef Command,
                      comments::CommandMarkerKind CommandMarker,
                      StringRef Args) {
   Out.appendBoldText(commandMarkerAsString(CommandMarker) + Command.str());
+  Out.appendSpace();
   if (!Args.empty()) {
-    Out.appendSpace();
     Out.appendEmphasizedText(Args.str());
   }
 }
@@ -132,7 +132,8 @@ class ParagraphToMarkupDocument
   const comments::CommandTraits &Traits;
 
   /// If true, the next leading space after a new line is trimmed.
-  bool LastChunkEndsWithNewline = false;
+  /// Initially set it to true, to always trim the first text line.
+  bool LastChunkEndsWithNewline = true;
 };
 
 class ParagraphToString
@@ -263,8 +264,76 @@ class BlockCommentToMarkupDocument
   StringRef CommentEscapeMarker;
 };
 
-void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName,
-                                                   markup::Paragraph &Out) {
+void SymbolDocCommentVisitor::visitBlockCommandComment(
+    const comments::BlockCommandComment *B) {
+  switch (B->getCommandID()) {
+  case comments::CommandTraits::KCI_brief: {
+    if (!BriefParagraph) {
+      BriefParagraph = B->getParagraph();
+      return;
+    }
+    break;
+  }
+  case comments::CommandTraits::KCI_return:
+  case comments::CommandTraits::KCI_returns:
+    if (!ReturnParagraph) {
+      ReturnParagraph = B->getParagraph();
+      return;
+    }
+    break;
+  case comments::CommandTraits::KCI_retval:
+    RetvalParagraphs.push_back(B->getParagraph());
+    return;
+  case comments::CommandTraits::KCI_warning:
+    WarningParagraphs.push_back(B->getParagraph());
+    return;
+  case comments::CommandTraits::KCI_note:
+    NoteParagraphs.push_back(B->getParagraph());
+    return;
+  default:
+    break;
+  }
+
+  // For all other commands, we store them in the UnhandledCommands map.
+  // This allows us to keep the order of the comments.
+  UnhandledCommands[CommentPartIndex] = B;
+  CommentPartIndex++;
+}
+
+void SymbolDocCommentVisitor::paragraphsToMarkup(
+    markup::Document &Out,
+    const llvm::SmallVector<const comments::ParagraphComment *> &Paragraphs)
+    const {
+  if (Paragraphs.empty())
+    return;
+
+  for (const auto *P : Paragraphs) {
+    ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P);
+  }
+}
+
+void SymbolDocCommentVisitor::briefToMarkup(markup::Paragraph &Out) const {
+  if (!BriefParagraph)
+    return;
+  ParagraphToMarkupDocument(Out, Traits).visit(BriefParagraph);
+}
+
+void SymbolDocCommentVisitor::returnToMarkup(markup::Paragraph &Out) const {
+  if (!ReturnParagraph)
+    return;
+  ParagraphToMarkupDocument(Out, Traits).visit(ReturnParagraph);
+}
+
+void SymbolDocCommentVisitor::notesToMarkup(markup::Document &Out) const {
+  paragraphsToMarkup(Out, NoteParagraphs);
+}
+
+void SymbolDocCommentVisitor::warningsToMarkup(markup::Document &Out) const {
+  paragraphsToMarkup(Out, WarningParagraphs);
+}
+
+void SymbolDocCommentVisitor::parameterDocToMarkup(
+    StringRef ParamName, markup::Paragraph &Out) const {
   if (ParamName.empty())
     return;
 
@@ -274,7 +343,7 @@ void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName,
 }
 
 void SymbolDocCommentVisitor::parameterDocToString(
-    StringRef ParamName, llvm::raw_string_ostream &Out) {
+    StringRef ParamName, llvm::raw_string_ostream &Out) const {
   if (ParamName.empty())
     return;
 
@@ -283,9 +352,9 @@ void SymbolDocCommentVisitor::parameterDocToString(
   }
 }
 
-void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) {
+void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) const {
   for (unsigned I = 0; I < CommentPartIndex; ++I) {
-    if (const auto *BC = BlockCommands.lookup(I)) {
+    if (const auto *BC = UnhandledCommands.lookup(I)) {
       BlockCommentToMarkupDocument(Out, Traits).visit(BC);
     } else if (const auto *P = FreeParagraphs.lookup(I)) {
       ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P);
@@ -293,5 +362,25 @@ void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) {
   }
 }
 
+void SymbolDocCommentVisitor::templateTypeParmDocToMarkup(
+    StringRef TemplateParamName, markup::Paragraph &Out) const {
+  if (TemplateParamName.empty())
+    return;
+
+  if (const auto *TP = TemplateParameters.lookup(TemplateParamName)) {
+    ParagraphToMarkupDocument(Out, Traits).visit(TP->getParagraph());
+  }
+}
+
+void SymbolDocCommentVisitor::templateTypeParmDocToString(
+    StringRef TemplateParamName, llvm::raw_string_ostream &Out) const {
+  if (TemplateParamName.empty())
+    return;
+
+  if (const auto *P = TemplateParameters.lookup(TemplateParamName)) {
+    ParagraphToString(Out, Traits).visit(P->getParagraph());
+  }
+}
+
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h
index b5120ba04e8f1..9d173745965e2 100644
--- a/clang-tools-extra/clangd/SymbolDocumentation.h
+++ b/clang-tools-extra/clangd/SymbolDocumentation.h
@@ -106,16 +106,43 @@ class SymbolDocCommentVisitor
     return Parameters.contains(ParamName);
   }
 
-  void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out);
+  bool isTemplateTypeParmDocumented(StringRef ParamName) const {
+    return TemplateParameters.contains(ParamName);
+  }
 
-  void parameterDocToString(StringRef ParamName, llvm::raw_string_ostream &Out);
+  bool hasBriefCommand() const { return BriefParagraph; }
 
-  void docToMarkup(markup::Document &Out);
+  bool hasReturnCommand() const { return ReturnParagraph; }
 
-  void visitBlockCommandComment(const comments::BlockCommandComment *B) {
-    BlockCommands[CommentPartIndex] = B;
-    CommentPartIndex++;
-  }
+  bool hasRetvalCommands() const { return !RetvalParagraphs.empty(); }
+
+  bool hasNoteCommands() const { return !NoteParagraphs.empty(); }
+
+  bool hasWarningCommands() const { return !WarningParagraphs.empty(); }
+
+  /// Converts all unhandled comment commands to a markup document.
+  void docToMarkup(markup::Document &Out) const;
+  /// Converts the "brief" command(s) to a markup document.
+  void briefToMarkup(markup::Paragraph &Out) const;
+  /// Converts the "return" command(s) to a markup document.
+  void returnToMarkup(markup::Paragraph &Out) const;
+  /// Converts the "note" command(s) to a markup document.
+  void notesToMarkup(markup::Document &Out) const;
+  /// Converts the "warning" command(s) to a markup document.
+  void warningsToMarkup(markup::Document &Out) const;
+
+  void visitBlockCommandComment(const comments::BlockCommandComment *B);
+
+  void templateTypeParmDocToMarkup(StringRef TemplateParamName,
+                                   markup::Paragraph &Out) const;
+
+  void templateTypeParmDocToString(StringRef TemplateParamName,
+                                   llvm::raw_string_ostream &Out) const;
+
+  void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out) const;
+
+  void parameterDocToString(StringRef ParamName,
+                            llvm::raw_string_ostream &Out) const;
 
   void visitParagraphComment(const comments::ParagraphComment *P) {
     FreeParagraphs[CommentPartIndex] = P;
@@ -126,6 +153,10 @@ class SymbolDocCommentVisitor
     Parameters[P->getParamNameAsWritten()] = P;
   }
 
+  void visitTParamCommandComment(const comments::TParamCommandComment *TP) {
+    TemplateParameters[TP->getParamNameAsWritten()] = std::move(TP);
+  }
+
 private:
   comments::CommandTraits Traits;
   llvm::BumpPtrAllocator Allocator;
@@ -136,17 +167,42 @@ class SymbolDocCommentVisitor
   /// This index allows us to keep the order of the other comment parts.
   unsigned CommentPartIndex = 0;
 
+  /// Paragraph of the "brief" command.
+  const comments::ParagraphComment *BriefParagraph = nullptr;
+
+  /// Paragraph of the "return" command.
+  const comments::ParagraphComment *ReturnParagraph = nullptr;
+
+  /// Paragraph(s) of the "note" command(s)
+  llvm::SmallVector<const comments::ParagraphComment *> RetvalParagraphs;
+
+  /// Paragraph(s) of the "note" command(s)
+  llvm::SmallVector<const comments::ParagraphComment *> NoteParagraphs;
+
+  /// Paragraph(s) of the "warning" command(s)
+  llvm::SmallVector<const comments::ParagraphComment *> WarningParagraphs;
+
+  /// All the paragraphs we don't have any special handling for,
+  /// e.g. "details".
+  llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *>
+      UnhandledCommands;
+
   /// Parsed paragaph(s) of the "param" comamnd(s)
   llvm::SmallDenseMap<StringRef, const comments::ParamCommandComment *>
       Parameters;
 
-  /// All the block commands.
-  llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *>
-      BlockCommands;
+  /// Parsed paragaph(s) of the "tparam" comamnd(s)
+  llvm::SmallDenseMap<StringRef, const comments::TParamCommandComment *>
+      TemplateParameters;
 
   /// All "free" text paragraphs.
   llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *>
       FreeParagraphs;
+
+  void
+  paragraphsToMarkup(markup::Document &Out,
+                     const llvm::SmallVector<const comments::ParagraphComment *>
+                         &Paragraphs) const;
 };
 
 } // namespace clangd
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 3331164ab0024..f7e88c66973f3 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -3409,7 +3409,8 @@ TEST(Hover, DocsFromMostSpecial) {
 TEST(Hover, Present) {
   struct {
     const std::function<void(HoverInfo &)> Builder;
-    llvm::StringRef ExpectedRender;
+    llvm::StringRef ExpectedMarkdownRender;
+    llvm::StringRef ExpectedDoxygenRender;
   } Cases[] = {
       {
           [](HoverInfo &HI) {
@@ -3417,6 +3418,7 @@ TEST(Hover, Present) {
             HI.Name = "X";
           },
           R"(X)",
+          R"(### `X`)",
       },
       {
           [](HoverInfo &HI) {
@@ -3424,6 +3426,7 @@ TEST(Hover, Present) {
             HI.Name = "foo";
           },
           R"(namespace-alias foo)",
+          R"(### namespace-alias `foo`)",
       },
       {
           [](HoverInfo &HI) {
@@ -3446,6 +3449,24 @@ Size: 10 bytes
 documentation
 
 template <typename T, typename C = bool> class Foo {})",
+          R"(### class
+
+---
+```cpp
+template <typename T, typename C = bool> class Foo {}
+```
+
+---
+**Template Parameters:**
+
+- `typename T`
+- `typename C = bool`
+
+---
+documentation
+
+---
+Size: 10 bytes)",
       },
       {
           [](HoverInfo &HI) {
@@ -3476,6 +3497,26 @@ template <typename T, typename C = bool> class Foo {})",
           "\n"
           "// In namespace ns\n"
           "ret_type foo(params) {}",
+          R"(### function
+
+---
+```cpp
+// In namespace ns
+ret_type foo(params) {}
+```
+
+---
+**Parameters:**
+
+- 
+- `type (aka can_type)`
+- `type foo (aka can_type)`
+- `type foo = default (aka can_type)`
+
+---
+**Returns:**
+
+`ret_type (aka can_ret_type)`)",
       },
       {
           [](HoverInfo &HI) {
@@ -3502,6 +3543,22 @@ Size: 4 bytes (+4 bytes padding), alignment 4 bytes
 
 // In test::Bar
 def)",
+          R"(### field
+
+---
+```cpp
+// In test::Bar
+def
+```
+
+---
+Type: `type (aka can_type)`
+
+Value = `value`
+
+Offset: 12 bytes
+
+Size: 4 bytes (+4 bytes padding), alignment 4 bytes)",
       },
       {
           [](HoverInfo &HI) {
@@ -3528,6 +3585,22 @@ Size: 25 bits (+4 bits padding), alignment 8 bytes
 
 // In test::Bar
 def)",
+          R"(### field
+
+---
+```cpp
+// In test::Bar
+def
+```
+
+---
+Type: `type (aka can_type)`
+
+Value = `value`
+
+Offset: 4 bytes and 3 bits
+
+Size: 25 bits (+4 bits padding), alignment 8 bytes)",
       },
       {
           [](HoverInfo &HI) {
@@ -3541,6 +3614,13 @@ def)",
 
 // In test::Bar
 public: def)",
+          R"(### field
+
+---
+```cpp
+// In test::Bar
+public: def
+```)",
       },
       {
           [](HoverInfo &HI) {
@@ -3560,6 +3640,18 @@ public: def)",
 
 // In cls<int>
 protected: size_t method())",
+          R"(### instance-method
+
+---
+```cpp
+// In cls<int>
+protected: size_t method()
+```
+
+---
+**Returns:**
+
+`size_t (aka unsigned long)`)",
       },
       {
           [](HoverInfo &HI) {
@@ -3587,6 +3679,19 @@ protected: size_t method())",
 
 // In cls
 public: cls(int a, int b = 5))",
+          R"(### constructor
+
+---
+```cpp
+// In cls
+public: cls(int a, int b = 5)
+```
+
+---
+**Parameters:**
+
+- `int a`
+- `int b = 5`)",
       },
       {
           [](HoverInfo &HI) {
@@ -3600,6 +3705,13 @@ public: cls(int a, int b = 5))",
 
 // In namespace ns1
 private: union foo {})",
+          R"(### union
+
+---
+```cpp
+// In namespace ns1
+private: union foo {}
+```)",
       },
       {
           [](HoverInfo &HI) {
@@ -3625,6 +3737,20 @@ Passed as arg_a
 
 // In test::Bar
 int foo = 3)",
+          R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed as arg_a)",
       },
       {
           [](HoverInfo &HI) {
@@ -3636,6 +3762,10 @@ int foo = 3)",
           },
           R"(variable foo...
[truncated]

@@ -1504,41 +1505,103 @@ markup::Document HoverInfo::presentDoxygen() const {
Header.appendText(index::getSymbolKindString(Kind)).appendSpace();
assert(!Name.empty() && "hover triggered on a nameless symbol");

Header.appendCode(Name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW the NOTE: comment above should be removed now I think?

@llvmbot llvmbot added the clang Clang issues not falling into any other category label Aug 12, 2025
@tcottin
Copy link
Contributor Author

tcottin commented Aug 23, 2025

ping @emaxx-google

@emaxx-google
Copy link
Contributor

There seems to be a test failure, but otherwise the change looks good to me, thanks.

@tcottin
Copy link
Contributor Author

tcottin commented Aug 25, 2025

Thanks for the review @emaxx-google!
The failing test seems not related to my change.

I think the root cause is #154610

After this was merged the lldb test fails:
https://lab.llvm.org/buildbot/#/builders/195/builds/13641

@emaxx-google emaxx-google enabled auto-merge (squash) August 25, 2025 11:27
auto-merge was automatically disabled August 25, 2025 17:04

Head branch was pushed to by a user without write access

@tcottin
Copy link
Contributor Author

tcottin commented Aug 25, 2025

@emaxx-google I think the error is fixed now, but I saw a -Wswitch warning I missed for the new IncludeDirective enumerator.
I pushed another change to fix this.
This disabled the auto merge. Could you look at it and approve again?

@emaxx-google emaxx-google enabled auto-merge (squash) August 25, 2025 17:32
@emaxx-google emaxx-google merged commit a3e2b64 into llvm:main Aug 25, 2025
9 checks passed
@itislu
Copy link

itislu commented Aug 25, 2025

I think there were quite a few silent bystanders for this series of PRs, excitingly following the progress.
Big thank you @tcottin for your work, it's a great feature and I'm sure I'm just one of many who is looking forward to this getting released. Also thank you to all the reviewers!

@llvm-ci
Copy link
Collaborator

llvm-ci commented Aug 25, 2025

LLVM Buildbot has detected a new failure on builder ppc64le-lld-multistage-test running on ppc64le-lld-multistage-test while building clang-tools-extra,clang at step 12 "build-stage2-unified-tree".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/168/builds/15570

Here is the relevant piece of the build log for the reference
Step 12 (build-stage2-unified-tree) failure: build (failure)
...
413.140 [140/369/6153] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/GCNVOPDUtils.cpp.o
413.232 [140/368/6154] Building CXX object unittests/Target/AMDGPU/CMakeFiles/AMDGPUTests.dir/PALMetadata.cpp.o
413.244 [140/367/6155] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/SIPostRABundler.cpp.o
413.245 [140/366/6156] Building CXX object tools/lld/ELF/CMakeFiles/lldELF.dir/OutputSections.cpp.o
413.247 [140/365/6157] Building CXX object unittests/DebugInfo/DWARF/CMakeFiles/DebugInfoDWARFTests.dir/DWARFFormValueTest.cpp.o
413.278 [140/364/6158] Building CXX object tools/lld/ELF/CMakeFiles/lldELF.dir/Arch/ARM.cpp.o
413.282 [140/363/6159] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/SIFixSGPRCopies.cpp.o
413.294 [140/362/6160] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/R600ControlFlowFinalizer.cpp.o
413.295 [140/361/6161] Building CXX object tools/sancov/CMakeFiles/sancov.dir/sancov.cpp.o
413.337 [140/360/6162] Building CXX object tools/clang/tools/libclang/CMakeFiles/libclang.dir/CXIndexDataConsumer.cpp.o
FAILED: tools/clang/tools/libclang/CMakeFiles/libclang.dir/CXIndexDataConsumer.cpp.o 
ccache /home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/install/stage1/bin/clang++ -DGTEST_HAS_RTTI=0 -D_CINDEX_LIB_ -D_DEBUG -D_GLIBCXX_ASSERTIONS -D_GLIBCXX_USE_CXX11_ABI=1 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/build/stage2/tools/clang/tools/libclang -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/clang/tools/libclang -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/clang/include -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/build/stage2/tools/clang/include -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/build/stage2/include -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/llvm/include -fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror -Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -pedantic -Wno-long-long -Wc++98-compat-extra-semi -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion -Wmisleading-indentation -Wctad-maybe-unsupported -fdiagnostics-color -ffunction-sections -fdata-sections -fno-common -Woverloaded-virtual -Wno-nested-anon-types -O3 -DNDEBUG -std=c++17 -fPIC  -fno-exceptions -funwind-tables -fno-rtti -UNDEBUG -MD -MT tools/clang/tools/libclang/CMakeFiles/libclang.dir/CXIndexDataConsumer.cpp.o -MF tools/clang/tools/libclang/CMakeFiles/libclang.dir/CXIndexDataConsumer.cpp.o.d -o tools/clang/tools/libclang/CMakeFiles/libclang.dir/CXIndexDataConsumer.cpp.o -c /home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/clang/tools/libclang/CXIndexDataConsumer.cpp
/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/clang/tools/libclang/CXIndexDataConsumer.cpp:1235:11: error: enumeration value 'IncludeDirective' not handled in switch [-Werror,-Wswitch]
 1235 |   switch (K) {
      |           ^
1 error generated.
413.341 [140/359/6163] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPULowerKernelArguments.cpp.o
413.395 [140/358/6164] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/SIModeRegister.cpp.o
413.413 [140/357/6165] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/SIPreEmitPeephole.cpp.o
413.426 [140/356/6166] Building CXX object tools/dsymutil/CMakeFiles/dsymutil.dir/MachOUtils.cpp.o
413.564 [140/355/6167] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPUCombinerHelper.cpp.o
413.591 [140/354/6168] Building CXX object unittests/CodeGen/GlobalISel/CMakeFiles/GlobalISelTests.dir/GISelMITest.cpp.o
413.659 [140/353/6169] Building CXX object tools/clang/tools/driver/CMakeFiles/clang.dir/driver.cpp.o
413.729 [140/352/6170] Building CXX object tools/lld/ELF/CMakeFiles/lldELF.dir/Arch/RISCV.cpp.o
413.734 [140/351/6171] Building CXX object unittests/DebugInfo/LogicalView/CMakeFiles/DebugInfoLogicalViewTests.dir/DWARFReaderTest.cpp.o
413.740 [140/350/6172] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/SIOptimizeExecMasking.cpp.o
413.754 [140/349/6173] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPULibCalls.cpp.o
413.798 [140/348/6174] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPUGlobalISelDivergenceLowering.cpp.o
413.827 [140/347/6175] Building CXX object unittests/MIR/CMakeFiles/MIRTests.dir/MachineMetadata.cpp.o
413.990 [140/346/6176] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPUSetWavePriority.cpp.o
413.999 [140/345/6177] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/R600OptimizeVectorRegisters.cpp.o
414.057 [140/344/6178] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/GCNDPPCombine.cpp.o
414.115 [140/343/6179] Building CXX object tools/llvm-mca/CMakeFiles/llvm-mca.dir/llvm-mca.cpp.o
414.183 [140/342/6180] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPUAlwaysInlinePass.cpp.o
414.206 [140/341/6181] Building CXX object tools/clang/unittests/CMakeFiles/AllClangUnitTests.dir/StaticAnalyzer/AnalyzerOptionsTest.cpp.o
414.218 [140/340/6182] Building CXX object tools/lld/wasm/CMakeFiles/lldWasm.dir/Symbols.cpp.o
414.405 [140/339/6183] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPUWaitSGPRHazards.cpp.o
414.492 [140/338/6184] Building CXX object tools/lld/wasm/CMakeFiles/lldWasm.dir/OutputSections.cpp.o
414.662 [140/337/6185] Building CXX object tools/clang/tools/libclang/CMakeFiles/libclang.dir/CIndexInclusionStack.cpp.o
414.704 [140/336/6186] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPUPostLegalizerCombiner.cpp.o
414.802 [140/335/6187] Building CXX object tools/clang/unittests/CMakeFiles/AllClangUnitTests.dir/Tooling/CastExprTest.cpp.o
414.848 [140/334/6188] Building CXX object unittests/tools/llvm-cfi-verify/CMakeFiles/CFIVerifyTests.dir/GraphBuilder.cpp.o
414.945 [140/333/6189] Building CXX object unittests/CodeGen/CMakeFiles/CodeGenTests.dir/SelectionDAGPatternMatchTest.cpp.o
415.111 [140/332/6190] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/R600MachineCFGStructurizer.cpp.o
415.169 [140/331/6191] Building CXX object tools/lld/wasm/CMakeFiles/lldWasm.dir/OutputSegment.cpp.o
415.230 [140/330/6192] Building CXX object tools/lld/MachO/CMakeFiles/lldMachO.dir/BPSectionOrderer.cpp.o
415.233 [140/329/6193] Building CXX object tools/lld/wasm/CMakeFiles/lldWasm.dir/Relocations.cpp.o
415.306 [140/328/6194] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/R600MCInstLower.cpp.o
415.404 [140/327/6195] Building CXX object lib/Target/AMDGPU/CMakeFiles/LLVMAMDGPUCodeGen.dir/AMDGPUInsertDelayAlu.cpp.o

@kazutakahirata
Copy link
Contributor

@tcottin @emaxx-google I've landed 83910de to fix a warning from this PR. I see that you are putting SymbolKind::IncludeDirective right next to SymbolKind::Unknown in clang-tools-extra/clangd/CodeComplete.cpp, so I'm doing the same in clang/tools/libclang/CXIndexDataConsumer.cpp. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category clang-tools-extra clangd
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants