[clang-tools-extra] [clangd] Support go-to-definition on type hints. The core part (PR #86629)

Younan Zhang via cfe-commits cfe-commits at lists.llvm.org
Tue Jul 16 08:10:15 PDT 2024


https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/86629

>From b8a69cbd9e0ee0aa35b38b7e3a78048cbe61447e Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sat, 16 Mar 2024 23:30:10 +0800
Subject: [PATCH 01/36] [clangd] Support go-to-definition on type hints. The
 core part

---
 clang-tools-extra/clangd/AST.cpp              |   9 +
 clang-tools-extra/clangd/AST.h                |   2 +
 clang-tools-extra/clangd/InlayHints.cpp       | 251 +++++++++++++++++-
 .../clangd/index/IndexAction.cpp              |   9 +-
 .../clangd/unittests/InlayHintTests.cpp       |  22 ++
 5 files changed, 279 insertions(+), 14 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 1b86ea19cf28d..ef87f1bcb8443 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -1019,5 +1019,14 @@ bool isExpandedFromParameterPack(const ParmVarDecl *D) {
   return getUnderlyingPackType(D) != nullptr;
 }
 
+std::optional<URI> toURI(OptionalFileEntryRef File) {
+  if (!File)
+    return std::nullopt;
+  auto AbsolutePath = File->getFileEntry().tryGetRealPathName();
+  if (AbsolutePath.empty())
+    return std::nullopt;
+  return URI::create(AbsolutePath);
+}
+
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index fb0722d697cd0..3ae624b1ab741 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -250,6 +250,8 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth = 10);
 /// reference to one (e.g. `Args&...` or `Args&&...`).
 bool isExpandedFromParameterPack(const ParmVarDecl *D);
 
+std::optional<URI> toURI(OptionalFileEntryRef File);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index cd4f1931b3ce1..f9e0a51ddcc9f 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -21,6 +21,7 @@
 #include "clang/AST/Stmt.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/AST/Type.h"
+#include "clang/AST/TypeVisitor.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/OperatorKinds.h"
 #include "clang/Basic/SourceManager.h"
@@ -372,6 +373,197 @@ maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
   return Params;
 }
 
+std::optional<Location> toLocation(SourceManager &SM, SourceRange Range) {
+  if (Range.isInvalid())
+    return std::nullopt;
+  if (auto URI =
+          toURI(SM.getFileEntryRefForID(SM.getFileID(Range.getBegin())))) {
+    Location L;
+    L.range.start = sourceLocToPosition(SM, Range.getBegin());
+    L.range.end = sourceLocToPosition(SM, Range.getEnd());
+    if (auto File = URIForFile::fromURI(*URI, ""))
+      L.uri = File.get();
+    return L;
+  }
+  return std::nullopt;
+}
+
+class TypeInlayHintLabelPartBuilder
+    : public TypeVisitor<TypeInlayHintLabelPartBuilder> {
+  QualType Current;
+  ASTContext &Context;
+  const PrintingPolicy &PP;
+  std::vector<InlayHintLabelPart> &LabelChunks;
+
+  bool ShouldAddLinksToTagTypes = false;
+
+  struct CurrentTypeRAII {
+    TypeInlayHintLabelPartBuilder &Builder;
+    QualType PreviousType;
+    bool PreviousShouldAddLinksToTagTypes;
+    CurrentTypeRAII(TypeInlayHintLabelPartBuilder &Builder, QualType New,
+                    bool ShouldAddLinksToTagTypes)
+        : Builder(Builder), PreviousType(Builder.Current) {
+      Builder.Current = New;
+      Builder.ShouldAddLinksToTagTypes = ShouldAddLinksToTagTypes;
+    }
+    ~CurrentTypeRAII() {
+      Builder.Current = PreviousType;
+      Builder.ShouldAddLinksToTagTypes = PreviousShouldAddLinksToTagTypes;
+    }
+  };
+
+  void addLabel(llvm::function_ref<void(llvm::raw_ostream &)> NamePrinter,
+                llvm::function_ref<SourceRange()> SourceRangeGetter) {
+    auto &Name = LabelChunks.emplace_back();
+    llvm::raw_string_ostream OS(Name.value);
+    NamePrinter(OS);
+    Name.location = toLocation(Context.getSourceManager(), SourceRangeGetter());
+  }
+
+  void printTemplateArgumentList(llvm::ArrayRef<TemplateArgument> Args) {
+    unsigned Size = Args.size();
+    for (unsigned I = 0; I < Size; ++I) {
+      auto &TA = Args[I];
+      if (PP.SuppressDefaultTemplateArgs && TA.getIsDefaulted())
+        continue;
+      if (I)
+        LabelChunks.emplace_back(", ");
+      printTemplateArgument(TA);
+    }
+  }
+
+  void printTemplateArgument(const TemplateArgument &TA) {
+    if (TA.getKind() == TemplateArgument::Pack)
+      return printTemplateArgumentList(TA.pack_elements());
+    if (TA.getKind() == TemplateArgument::Type) {
+      CurrentTypeRAII Guard(*this, TA.getAsType(),
+                            /*ShouldAddLinksToTagTypes=*/true);
+      return Visit(TA.getAsType().getTypePtr());
+    }
+    llvm::raw_string_ostream OS(LabelChunks.emplace_back().value);
+    TA.print(PP, OS, /*IncludeType=*/true);
+  }
+
+  void
+  processTemplateSpecialization(TemplateName TN,
+                                llvm::ArrayRef<TemplateArgument> Args,
+                                SourceRange TemplateNameRange = SourceRange()) {
+    SourceRange Range;
+    TemplateDecl *TD = nullptr;
+    switch (TN.getKind()) {
+    case TemplateName::Template:
+      TD = TN.getAsTemplateDecl();
+      Range = TD->getSourceRange();
+      LLVM_FALLTHROUGH;
+    default:
+      break;
+    }
+
+    addLabel([&](llvm::raw_ostream &OS) { TN.print(OS, PP); },
+             [&] {
+               if (TemplateNameRange.isValid())
+                 return TemplateNameRange;
+               return Range;
+             });
+
+    LabelChunks.emplace_back("<");
+    printTemplateArgumentList(Args);
+    LabelChunks.emplace_back(">");
+  }
+
+public:
+
+#ifndef NDEBUG
+  ~TypeInlayHintLabelPartBuilder() {
+    llvm::errs() << "TypeInlayHintLabelPartBuilder:\n";
+    Current->dump();
+    for (auto &L : LabelChunks)
+      llvm::errs() << L << ", ";
+    llvm::errs() << "\n";
+  }
+#endif
+
+  TypeInlayHintLabelPartBuilder(QualType Current, ASTContext &Context,
+                                const PrintingPolicy &PP,
+                                bool ShouldAddLinksToTagTypes,
+                                std::vector<InlayHintLabelPart> &LabelChunks)
+      : Current(Current), Context(Context), PP(PP), LabelChunks(LabelChunks) {}
+
+  void VisitType(const Type *) {
+    LabelChunks.emplace_back(Current.getAsString(PP));
+  }
+
+  void VisitTagType(const TagType *TT) {
+    if (!ShouldAddLinksToTagTypes)
+      return VisitType(TT);
+    auto *D = TT->getDecl();
+    if (auto *Specialization = dyn_cast<ClassTemplateSpecializationDecl>(D))
+      return processTemplateSpecialization(
+          TemplateName(Specialization->getSpecializedTemplate()),
+          Specialization->getTemplateArgs().asArray());
+    if (auto *RD = dyn_cast<CXXRecordDecl>(D);
+        RD && !RD->getTemplateInstantiationPattern())
+      return addLabel(
+          [&](llvm::raw_ostream &OS) { return RD->printName(OS, PP); },
+          [&] { return RD->getSourceRange(); });
+    return VisitType(TT);
+  }
+
+  void VisitAutoType(const AutoType *AT) {
+    if (!AT->isDeduced() || AT->getDeducedType()->isDecltypeType())
+      return;
+    CurrentTypeRAII Guard(*this, AT->getDeducedType(),
+                          ShouldAddLinksToTagTypes);
+    return Visit(AT->getDeducedType().getTypePtr());
+  }
+
+  void VisitElaboratedType(const ElaboratedType *ET) {
+    if (auto *NNS = ET->getQualifier()) {
+      switch (NNS->getKind()) {
+      case NestedNameSpecifier::Identifier:
+      case NestedNameSpecifier::Namespace:
+      case NestedNameSpecifier::NamespaceAlias:
+      case NestedNameSpecifier::Global:
+      case NestedNameSpecifier::Super: {
+        auto &Name = LabelChunks.emplace_back();
+        llvm::raw_string_ostream OS(Name.value);
+        NNS->print(OS, PP);
+      } break;
+      case NestedNameSpecifier::TypeSpec:
+      case NestedNameSpecifier::TypeSpecWithTemplate:
+        Visit(NNS->getAsType());
+        break;
+      }
+    }
+    CurrentTypeRAII Guard(*this, ET->getNamedType(), ShouldAddLinksToTagTypes);
+    return Visit(ET->getNamedType().getTypePtr());
+  }
+
+  void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
+    SourceRange Range;
+    if (auto *Specialization =
+            dyn_cast_if_present<ClassTemplateSpecializationDecl>(
+                TST->desugar().getCanonicalType()->getAsCXXRecordDecl()))
+      Range = Specialization->getSourceRange();
+    return processTemplateSpecialization(TST->getTemplateName(),
+                                         TST->template_arguments(), Range);
+  }
+
+  void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
+    CurrentTypeRAII Guard(*this, ST->getReplacementType(),
+                          /*ShouldAddLinksToTagTypes=*/true);
+    return Visit(ST->getReplacementType().getTypePtr());
+  }
+};
+
+unsigned lengthOfInlayHintLabelPart(llvm::ArrayRef<InlayHintLabelPart> Labels) {
+  unsigned Size = 0;
+  for (auto &P : Labels)
+    Size += P.value.size();
+  return Size;
+}
+
 struct Callee {
   // Only one of Decl or Loc is set.
   // Loc is for calls through function pointers.
@@ -949,9 +1141,29 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
     addInlayHint(*LSPRange, Side, Kind, Prefix, Label, Suffix);
   }
 
+  void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind,
+                    llvm::StringRef Prefix,
+                    llvm::ArrayRef<InlayHintLabelPart> Labels,
+                    llvm::StringRef Suffix) {
+    auto LSPRange = getHintRange(R);
+    if (!LSPRange)
+      return;
+
+    addInlayHint(*LSPRange, Side, Kind, Prefix, Labels, Suffix);
+  }
+
   void addInlayHint(Range LSPRange, HintSide Side, InlayHintKind Kind,
                     llvm::StringRef Prefix, llvm::StringRef Label,
                     llvm::StringRef Suffix) {
+    return addInlayHint(LSPRange, Side, Kind, Prefix,
+                        /*Labels=*/std::vector<InlayHintLabelPart>{Label.str()},
+                        Suffix);
+  }
+
+  void addInlayHint(Range LSPRange, HintSide Side, InlayHintKind Kind,
+                    llvm::StringRef Prefix,
+                    llvm::ArrayRef<InlayHintLabelPart> Labels,
+                    llvm::StringRef Suffix) {
     // We shouldn't get as far as adding a hint if the category is disabled.
     // We'd like to disable as much of the analysis as possible above instead.
     // Assert in debug mode but add a dynamic check in production.
@@ -977,8 +1189,21 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
       return;
     bool PadLeft = Prefix.consume_front(" ");
     bool PadRight = Suffix.consume_back(" ");
+    if (Prefix.empty() && Suffix.empty()) {
+      Results.push_back(InlayHint{LSPPos,
+                                  /*label=*/Labels, Kind, PadLeft, PadRight,
+                                  LSPRange});
+      return;
+    }
+    std::vector<InlayHintLabelPart> JoinedLabels;
+    JoinedLabels.reserve(Labels.size() + !Prefix.empty() + !Suffix.empty());
+    if (!Prefix.empty())
+      JoinedLabels.push_back(InlayHintLabelPart(Prefix.str()));
+    llvm::copy(Labels, std::back_inserter(JoinedLabels));
+    if (!Suffix.empty())
+      JoinedLabels.push_back(InlayHintLabelPart(Suffix.str()));
     Results.push_back(InlayHint{LSPPos,
-                                /*label=*/{(Prefix + Label + Suffix).str()},
+                                /*label=*/std::move(JoinedLabels),
                                 Kind, PadLeft, PadRight, LSPRange});
   }
 
@@ -1005,14 +1230,22 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
     // The sugared type is more useful in some cases, and the canonical
     // type in other cases.
     auto Desugared = maybeDesugar(AST, T);
-    std::string TypeName = Desugared.getAsString(TypeHintPolicy);
-    if (T != Desugared && !shouldPrintTypeHint(TypeName)) {
+    std::vector<InlayHintLabelPart> Chunks;
+    TypeInlayHintLabelPartBuilder Builder(
+        Desugared, AST, TypeHintPolicy,
+        /*ShouldAddLinksToTagTypes=*/T != Desugared, Chunks);
+    Builder.Visit(Desugared.getTypePtr());
+    if (T != Desugared && shouldPrintTypeHint(Chunks)) {
       // If the desugared type is too long to display, fallback to the sugared
       // type.
-      TypeName = T.getAsString(TypeHintPolicy);
+      Chunks.clear();
+      TypeInlayHintLabelPartBuilder Builder(T, AST, TypeHintPolicy,
+                                            /*ShouldAddLinksToTagTypes=*/false,
+                                            Chunks);
+      Builder.Visit(T.getTypePtr());
     }
-    if (shouldPrintTypeHint(TypeName))
-      addInlayHint(R, HintSide::Right, InlayHintKind::Type, Prefix, TypeName,
+    if (shouldPrintTypeHint(Chunks))
+      addInlayHint(R, HintSide::Right, InlayHintKind::Type, Prefix, Chunks,
                    /*Suffix=*/"");
   }
 
@@ -1021,9 +1254,11 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
                  /*Prefix=*/"", Text, /*Suffix=*/"=");
   }
 
-  bool shouldPrintTypeHint(llvm::StringRef TypeName) const noexcept {
+  bool shouldPrintTypeHint(
+      llvm::ArrayRef<InlayHintLabelPart> TypeLabels) const noexcept {
     return Cfg.InlayHints.TypeNameLimit == 0 ||
-           TypeName.size() < Cfg.InlayHints.TypeNameLimit;
+           lengthOfInlayHintLabelPart(TypeLabels) <
+               Cfg.InlayHints.TypeNameLimit;
   }
 
   void addBlockEndHint(SourceRange BraceRange, StringRef DeclPrefix,
diff --git a/clang-tools-extra/clangd/index/IndexAction.cpp b/clang-tools-extra/clangd/index/IndexAction.cpp
index ed56c2a9d2e81..f3fbb9fc307e2 100644
--- a/clang-tools-extra/clangd/index/IndexAction.cpp
+++ b/clang-tools-extra/clangd/index/IndexAction.cpp
@@ -32,12 +32,9 @@ namespace clangd {
 namespace {
 
 std::optional<std::string> toURI(OptionalFileEntryRef File) {
-  if (!File)
-    return std::nullopt;
-  auto AbsolutePath = File->getFileEntry().tryGetRealPathName();
-  if (AbsolutePath.empty())
-    return std::nullopt;
-  return URI::create(AbsolutePath).toString();
+  if (auto URI = clang::clangd::toURI(File))
+    return URI->toString();
+  return std::nullopt;
 }
 
 // Collects the nodes and edges of include graph during indexing action.
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 5b1531eb2fa60..7f99a3522e490 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1637,6 +1637,28 @@ TEST(TypeHints, SubstTemplateParameterAliases) {
                         ExpectedHint{": static_vector<int>", "vector_name"});
 }
 
+TEST(TypeHints, Links) {
+  TestTU TU = TestTU::withCode(R"cpp(
+struct S {};
+template <class T, unsigned V, class... U>
+struct W {
+};
+enum Kind {
+  K = 1,
+};
+namespace std {
+template <class E> struct vector {};
+template <> struct vector<bool> {};
+} // namespace std
+int main() {
+  auto v = std::vector<bool>();
+})cpp");
+  TU.ExtraArgs.push_back("-std=c++2c");
+  auto AST = TU.build();
+
+  hintsOfKind(AST, InlayHintKind::Type);
+}
+
 TEST(DesignatorHints, Basic) {
   assertDesignatorHints(R"cpp(
     struct S { int x, y, z; };

>From fb3bbd0a53d3d4f85a3df4fefda4ae932a44beff Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Tue, 26 Mar 2024 14:41:38 +0800
Subject: [PATCH 02/36] Small fixes

---
 clang-tools-extra/clangd/InlayHints.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index f9e0a51ddcc9f..ed84bd2a643b2 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -474,7 +474,7 @@ class TypeInlayHintLabelPartBuilder
 
 public:
 
-#ifndef NDEBUG
+#if 0
   ~TypeInlayHintLabelPartBuilder() {
     llvm::errs() << "TypeInlayHintLabelPartBuilder:\n";
     Current->dump();
@@ -532,7 +532,13 @@ class TypeInlayHintLabelPartBuilder
       } break;
       case NestedNameSpecifier::TypeSpec:
       case NestedNameSpecifier::TypeSpecWithTemplate:
+        CurrentTypeRAII Guard(
+            *this,
+            QualType(NNS->getAsType(),
+                     /*Quals=*/0), // Do we need cv-qualifiers on type specifiers?
+            ShouldAddLinksToTagTypes);
         Visit(NNS->getAsType());
+        LabelChunks.emplace_back("::");
         break;
       }
     }
@@ -1235,7 +1241,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
         Desugared, AST, TypeHintPolicy,
         /*ShouldAddLinksToTagTypes=*/T != Desugared, Chunks);
     Builder.Visit(Desugared.getTypePtr());
-    if (T != Desugared && shouldPrintTypeHint(Chunks)) {
+    if (T != Desugared && !shouldPrintTypeHint(Chunks)) {
       // If the desugared type is too long to display, fallback to the sugared
       // type.
       Chunks.clear();

>From 36199ce6b52acba62743adfc4ef80cb2640889f7 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 31 Mar 2024 22:33:49 +0800
Subject: [PATCH 03/36] Add unittests

---
 clang-tools-extra/clangd/InlayHints.cpp       | 129 +++++++++------
 .../clangd/unittests/InlayHintTests.cpp       | 156 ++++++++++++++++--
 2 files changed, 221 insertions(+), 64 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index ed84bd2a643b2..7304775a3d951 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -11,6 +11,7 @@
 #include "Config.h"
 #include "HeuristicResolver.h"
 #include "ParsedAST.h"
+#include "Protocol.h"
 #include "SourceCode.h"
 #include "clang/AST/ASTDiagnostic.h"
 #include "clang/AST/Decl.h"
@@ -415,12 +416,30 @@ class TypeInlayHintLabelPartBuilder
 
   void addLabel(llvm::function_ref<void(llvm::raw_ostream &)> NamePrinter,
                 llvm::function_ref<SourceRange()> SourceRangeGetter) {
-    auto &Name = LabelChunks.emplace_back();
-    llvm::raw_string_ostream OS(Name.value);
+    std::string Label;
+    llvm::raw_string_ostream OS(Label);
     NamePrinter(OS);
+    if (!ShouldAddLinksToTagTypes)
+      return addLabel(std::move(Label));
+    auto &Name = LabelChunks.emplace_back();
+    Name.value = std::move(Label);
     Name.location = toLocation(Context.getSourceManager(), SourceRangeGetter());
   }
 
+  void addLabel(std::string Label) {
+    if (LabelChunks.empty()) {
+      LabelChunks.emplace_back(std::move(Label));
+      return;
+    }
+    auto &Back = LabelChunks.back();
+    if (Back.location) {
+      LabelChunks.emplace_back(std::move(Label));
+      return;
+    }
+    // Let's combine the "unclickable" pieces together.
+    Back.value += std::move(Label);
+  }
+
   void printTemplateArgumentList(llvm::ArrayRef<TemplateArgument> Args) {
     unsigned Size = Args.size();
     for (unsigned I = 0; I < Size; ++I) {
@@ -428,27 +447,41 @@ class TypeInlayHintLabelPartBuilder
       if (PP.SuppressDefaultTemplateArgs && TA.getIsDefaulted())
         continue;
       if (I)
-        LabelChunks.emplace_back(", ");
+        addLabel(", ");
       printTemplateArgument(TA);
     }
   }
 
   void printTemplateArgument(const TemplateArgument &TA) {
-    if (TA.getKind() == TemplateArgument::Pack)
+    switch (TA.getKind()) {
+    case TemplateArgument::Pack:
       return printTemplateArgumentList(TA.pack_elements());
-    if (TA.getKind() == TemplateArgument::Type) {
+    case TemplateArgument::Type: {
       CurrentTypeRAII Guard(*this, TA.getAsType(),
                             /*ShouldAddLinksToTagTypes=*/true);
       return Visit(TA.getAsType().getTypePtr());
     }
-    llvm::raw_string_ostream OS(LabelChunks.emplace_back().value);
+    // TODO: Add support for NTTP arguments.
+    case TemplateArgument::Expression:
+    case TemplateArgument::StructuralValue:
+    case TemplateArgument::Null:
+    case TemplateArgument::Declaration:
+    case TemplateArgument::NullPtr:
+    case TemplateArgument::Integral:
+    case TemplateArgument::Template:
+    case TemplateArgument::TemplateExpansion:
+      break;
+    }
+    std::string Label;
+    llvm::raw_string_ostream OS(Label);
     TA.print(PP, OS, /*IncludeType=*/true);
+    addLabel(std::move(Label));
   }
 
   void
-  processTemplateSpecialization(TemplateName TN,
-                                llvm::ArrayRef<TemplateArgument> Args,
-                                SourceRange TemplateNameRange = SourceRange()) {
+  handleTemplateSpecialization(TemplateName TN,
+                               llvm::ArrayRef<TemplateArgument> Args,
+                               SourceRange TemplateNameRange = SourceRange()) {
     SourceRange Range;
     TemplateDecl *TD = nullptr;
     switch (TN.getKind()) {
@@ -467,39 +500,31 @@ class TypeInlayHintLabelPartBuilder
                return Range;
              });
 
-    LabelChunks.emplace_back("<");
+    addLabel("<");
     printTemplateArgumentList(Args);
-    LabelChunks.emplace_back(">");
+    addLabel(">");
   }
 
 public:
-
-#if 0
-  ~TypeInlayHintLabelPartBuilder() {
-    llvm::errs() << "TypeInlayHintLabelPartBuilder:\n";
-    Current->dump();
-    for (auto &L : LabelChunks)
-      llvm::errs() << L << ", ";
-    llvm::errs() << "\n";
-  }
-#endif
-
   TypeInlayHintLabelPartBuilder(QualType Current, ASTContext &Context,
                                 const PrintingPolicy &PP,
                                 bool ShouldAddLinksToTagTypes,
+                                llvm::StringRef Prefix,
                                 std::vector<InlayHintLabelPart> &LabelChunks)
-      : Current(Current), Context(Context), PP(PP), LabelChunks(LabelChunks) {}
-
-  void VisitType(const Type *) {
-    LabelChunks.emplace_back(Current.getAsString(PP));
+      : Current(Current), Context(Context), PP(PP), LabelChunks(LabelChunks) {
+    LabelChunks.reserve(16);
+    if (!Prefix.empty())
+      addLabel(Prefix.str());
   }
 
+  void VisitType(const Type *) { addLabel(Current.getAsString(PP)); }
+
   void VisitTagType(const TagType *TT) {
     if (!ShouldAddLinksToTagTypes)
       return VisitType(TT);
     auto *D = TT->getDecl();
     if (auto *Specialization = dyn_cast<ClassTemplateSpecializationDecl>(D))
-      return processTemplateSpecialization(
+      return handleTemplateSpecialization(
           TemplateName(Specialization->getSpecializedTemplate()),
           Specialization->getTemplateArgs().asArray());
     if (auto *RD = dyn_cast<CXXRecordDecl>(D);
@@ -526,9 +551,9 @@ class TypeInlayHintLabelPartBuilder
       case NestedNameSpecifier::NamespaceAlias:
       case NestedNameSpecifier::Global:
       case NestedNameSpecifier::Super: {
-        auto &Name = LabelChunks.emplace_back();
-        llvm::raw_string_ostream OS(Name.value);
-        NNS->print(OS, PP);
+        std::string Label;
+        llvm::raw_string_ostream OS(Label);
+        addLabel(std::move(Label));
       } break;
       case NestedNameSpecifier::TypeSpec:
       case NestedNameSpecifier::TypeSpecWithTemplate:
@@ -538,7 +563,7 @@ class TypeInlayHintLabelPartBuilder
                      /*Quals=*/0), // Do we need cv-qualifiers on type specifiers?
             ShouldAddLinksToTagTypes);
         Visit(NNS->getAsType());
-        LabelChunks.emplace_back("::");
+        addLabel("::");
         break;
       }
     }
@@ -552,8 +577,8 @@ class TypeInlayHintLabelPartBuilder
             dyn_cast_if_present<ClassTemplateSpecializationDecl>(
                 TST->desugar().getCanonicalType()->getAsCXXRecordDecl()))
       Range = Specialization->getSourceRange();
-    return processTemplateSpecialization(TST->getTemplateName(),
-                                         TST->template_arguments(), Range);
+    return handleTemplateSpecialization(TST->getTemplateName(),
+                                        TST->template_arguments(), Range);
   }
 
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
@@ -1149,13 +1174,13 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
 
   void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind,
                     llvm::StringRef Prefix,
-                    llvm::ArrayRef<InlayHintLabelPart> Labels,
+                    std::vector<InlayHintLabelPart> Labels,
                     llvm::StringRef Suffix) {
     auto LSPRange = getHintRange(R);
     if (!LSPRange)
       return;
 
-    addInlayHint(*LSPRange, Side, Kind, Prefix, Labels, Suffix);
+    addInlayHint(*LSPRange, Side, Kind, Prefix, std::move(Labels), Suffix);
   }
 
   void addInlayHint(Range LSPRange, HintSide Side, InlayHintKind Kind,
@@ -1168,8 +1193,9 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
 
   void addInlayHint(Range LSPRange, HintSide Side, InlayHintKind Kind,
                     llvm::StringRef Prefix,
-                    llvm::ArrayRef<InlayHintLabelPart> Labels,
+                    std::vector<InlayHintLabelPart> Labels,
                     llvm::StringRef Suffix) {
+    assert(!Labels.empty() && "Expected non-empty labels");
     // We shouldn't get as far as adding a hint if the category is disabled.
     // We'd like to disable as much of the analysis as possible above instead.
     // Assert in debug mode but add a dynamic check in production.
@@ -1201,16 +1227,21 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
                                   LSPRange});
       return;
     }
-    std::vector<InlayHintLabelPart> JoinedLabels;
-    JoinedLabels.reserve(Labels.size() + !Prefix.empty() + !Suffix.empty());
-    if (!Prefix.empty())
-      JoinedLabels.push_back(InlayHintLabelPart(Prefix.str()));
-    llvm::copy(Labels, std::back_inserter(JoinedLabels));
-    if (!Suffix.empty())
-      JoinedLabels.push_back(InlayHintLabelPart(Suffix.str()));
+    if (!Prefix.empty()) {
+      if (auto &Label = Labels.front(); !Label.location)
+        Label.value = Prefix.str() + Label.value;
+      else
+        Labels.insert(Labels.begin(), InlayHintLabelPart(Prefix.str()));
+    }
+    if (!Suffix.empty()) {
+      if (auto &Label = Labels.back(); !Label.location)
+        Label.value += Suffix.str();
+      else
+        Labels.push_back(InlayHintLabelPart(Suffix.str()));
+    }
     Results.push_back(InlayHint{LSPPos,
-                                /*label=*/std::move(JoinedLabels),
-                                Kind, PadLeft, PadRight, LSPRange});
+                                /*label=*/std::move(Labels), Kind, PadLeft,
+                                PadRight, LSPRange});
   }
 
   // Get the range of the main file that *exactly* corresponds to R.
@@ -1239,7 +1270,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
     std::vector<InlayHintLabelPart> Chunks;
     TypeInlayHintLabelPartBuilder Builder(
         Desugared, AST, TypeHintPolicy,
-        /*ShouldAddLinksToTagTypes=*/T != Desugared, Chunks);
+        /*ShouldAddLinksToTagTypes=*/T != Desugared, Prefix, Chunks);
     Builder.Visit(Desugared.getTypePtr());
     if (T != Desugared && !shouldPrintTypeHint(Chunks)) {
       // If the desugared type is too long to display, fallback to the sugared
@@ -1247,11 +1278,13 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
       Chunks.clear();
       TypeInlayHintLabelPartBuilder Builder(T, AST, TypeHintPolicy,
                                             /*ShouldAddLinksToTagTypes=*/false,
-                                            Chunks);
+                                            Prefix, Chunks);
       Builder.Visit(T.getTypePtr());
     }
     if (shouldPrintTypeHint(Chunks))
-      addInlayHint(R, HintSide::Right, InlayHintKind::Type, Prefix, Chunks,
+      addInlayHint(R, HintSide::Right, InlayHintKind::Type,
+                   /*Prefix=*/"", // We have handled prefixes in the builder.
+                   std::move(Chunks),
                    /*Suffix=*/"");
   }
 
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 7f99a3522e490..20f32bc7b7f6b 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -55,6 +55,19 @@ struct ExpectedHint {
   }
 };
 
+struct ExpectedHintLabelPiece {
+  std::string Label;
+  std::optional<std::string> TargetRangeName = std::nullopt;
+
+  friend llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
+                                       const ExpectedHintLabelPiece &Hint) {
+    Stream << Hint.Label;
+    if (!Hint.TargetRangeName)
+      Stream << " that points to $" << Hint.TargetRangeName;
+    return Stream;
+  }
+};
+
 MATCHER_P2(HintMatcher, Expected, Code, llvm::to_string(Expected)) {
   llvm::StringRef ExpectedView(Expected.Label);
   std::string ResultLabel = arg.joinLabels();
@@ -73,6 +86,34 @@ MATCHER_P2(HintMatcher, Expected, Code, llvm::to_string(Expected)) {
   return true;
 }
 
+MATCHER_P2(HintLabelPieceMatcher, Expected, Code, llvm::to_string(Expected)) {
+  llvm::StringRef ExpectedView(Expected.Label);
+  std::string ResultLabel = arg.value;
+  if (ResultLabel != ExpectedView.trim(" ")) {
+    *result_listener << "label is '" << ResultLabel << "'";
+    return false;
+  }
+  if (!Expected.TargetRangeName && !arg.location)
+    return true;
+  if (Expected.TargetRangeName && !arg.location) {
+    *result_listener << " range " << *Expected.TargetRangeName
+                     << " is expected, but we have nothing.";
+    return false;
+  }
+  if (!Expected.TargetRangeName && arg.location) {
+    *result_listener << " the link points to " << llvm::to_string(arg.location)
+                     << ", but we expect nothing.";
+    return false;
+  }
+  if (arg.location->range != Code.range(*Expected.TargetRangeName)) {
+    *result_listener << "range is " << llvm::to_string(arg.location->range)
+                     << " but $" << *Expected.TargetRangeName << " points to "
+                     << llvm::to_string(Code.range(*Expected.TargetRangeName));
+    return false;
+  }
+  return true;
+}
+
 MATCHER_P(labelIs, Label, "") { return arg.joinLabels() == Label; }
 
 Config noHintsConfig() {
@@ -1638,25 +1679,108 @@ TEST(TypeHints, SubstTemplateParameterAliases) {
 }
 
 TEST(TypeHints, Links) {
-  TestTU TU = TestTU::withCode(R"cpp(
-struct S {};
-template <class T, unsigned V, class... U>
-struct W {
-};
-enum Kind {
-  K = 1,
-};
-namespace std {
-template <class E> struct vector {};
-template <> struct vector<bool> {};
-} // namespace std
-int main() {
-  auto v = std::vector<bool>();
-})cpp");
+  Annotations Source(R"cpp(
+    $Package[[template <class T, class U>
+    struct Package {]]};
+
+    $SpecializationOfPackage[[template <>
+    struct Package<float, const int> {]]};
+
+    $Container[[template <class... T>
+    struct Container {]]};
+
+    $NttpContainer[[template <auto... T>
+    struct NttpContainer {]]};
+
+    enum struct ScopedEnum {
+      X = 1,
+    };
+
+    enum Enum {
+      E = 2,
+    };
+
+    namespace ns {
+      template <class T>
+      struct Nested {
+        template <class U>
+        struct Class {
+        };
+      };
+    }
+
+    void foo() {
+      auto $1[[C]] = Container<Package<char, int>>();
+      auto $2[[D]] = Container<Package<float, const int>>();
+      auto $3[[E]] = Container<Container<int, int>, long>();
+      auto $4[[F]] = NttpContainer<D, E, ScopedEnum::X, Enum::E>();
+      auto $5[[G]] = ns::Nested<Container<int>>::Class<Package<char, int>>();
+    }
+  )cpp");
+  TestTU TU = TestTU::withCode(Source.code());
   TU.ExtraArgs.push_back("-std=c++2c");
   auto AST = TU.build();
 
-  hintsOfKind(AST, InlayHintKind::Type);
+  auto HintAt = [&](llvm::ArrayRef<InlayHint> InlayHints,
+                    llvm::StringRef Range) {
+    auto *Hint = llvm::find_if(InlayHints, [&](const InlayHint &InlayHint) {
+      return InlayHint.range == Source.range(Range);
+    });
+    assert(Hint && "No range was found");
+    return llvm::ArrayRef(Hint->label);
+  };
+
+  Config C;
+  C.InlayHints.TypeNameLimit = 0;
+  WithContextValue WithCfg(Config::Key, std::move(C));
+
+  auto Hints = hintsOfKind(AST, InlayHintKind::Type);
+
+  EXPECT_THAT(
+      HintAt(Hints, "1"),
+      ElementsAre(
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{": Container<"}, Source),
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{"Package", "Package"},
+                                Source),
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<char, int>>"},
+                                Source)));
+
+  EXPECT_THAT(
+      HintAt(Hints, "2"),
+      ElementsAre(
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{": Container<"}, Source),
+          HintLabelPieceMatcher(
+              ExpectedHintLabelPiece{"Package", "SpecializationOfPackage"},
+              Source),
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<float, const int>>"},
+                                Source)));
+
+  EXPECT_THAT(
+      HintAt(Hints, "3"),
+      ElementsAre(
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{": Container<"}, Source),
+          HintLabelPieceMatcher(
+              ExpectedHintLabelPiece{"Container", "Container"}, Source),
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<int, int>, long>"},
+                                Source)));
+
+  EXPECT_THAT(HintAt(Hints, "4"),
+              ElementsAre(HintLabelPieceMatcher(
+                  ExpectedHintLabelPiece{
+                      ": NttpContainer<D, E, ScopedEnum::X, Enum::E>"},
+                  Source)));
+  EXPECT_THAT(
+      HintAt(Hints, "5"),
+      ElementsAre(
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{": Nested<"}, Source),
+          HintLabelPieceMatcher(
+              ExpectedHintLabelPiece{"Container", "Container"}, Source),
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<int>>::Class<"},
+                                Source),
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{"Package", "Package"},
+                                Source),
+          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<char, int>>"},
+                                Source)));
 }
 
 TEST(DesignatorHints, Basic) {

>From fa5fb4a93ca64163428cc5ccab25391d4ff4580d Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 31 Mar 2024 23:13:03 +0800
Subject: [PATCH 04/36] Fix an uninitialization bug

---
 clang-tools-extra/clangd/InlayHints.cpp            | 14 ++++++++------
 .../clangd/unittests/InlayHintTests.cpp            |  5 ++++-
 2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 7304775a3d951..11254c8fc81af 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -391,7 +391,7 @@ std::optional<Location> toLocation(SourceManager &SM, SourceRange Range) {
 
 class TypeInlayHintLabelPartBuilder
     : public TypeVisitor<TypeInlayHintLabelPartBuilder> {
-  QualType Current;
+  QualType CurrentType;
   ASTContext &Context;
   const PrintingPolicy &PP;
   std::vector<InlayHintLabelPart> &LabelChunks;
@@ -404,12 +404,13 @@ class TypeInlayHintLabelPartBuilder
     bool PreviousShouldAddLinksToTagTypes;
     CurrentTypeRAII(TypeInlayHintLabelPartBuilder &Builder, QualType New,
                     bool ShouldAddLinksToTagTypes)
-        : Builder(Builder), PreviousType(Builder.Current) {
-      Builder.Current = New;
+        : Builder(Builder), PreviousType(Builder.CurrentType),
+          PreviousShouldAddLinksToTagTypes(Builder.ShouldAddLinksToTagTypes) {
+      Builder.CurrentType = New;
       Builder.ShouldAddLinksToTagTypes = ShouldAddLinksToTagTypes;
     }
     ~CurrentTypeRAII() {
-      Builder.Current = PreviousType;
+      Builder.CurrentType = PreviousType;
       Builder.ShouldAddLinksToTagTypes = PreviousShouldAddLinksToTagTypes;
     }
   };
@@ -511,13 +512,14 @@ class TypeInlayHintLabelPartBuilder
                                 bool ShouldAddLinksToTagTypes,
                                 llvm::StringRef Prefix,
                                 std::vector<InlayHintLabelPart> &LabelChunks)
-      : Current(Current), Context(Context), PP(PP), LabelChunks(LabelChunks) {
+      : CurrentType(Current), Context(Context), PP(PP),
+        LabelChunks(LabelChunks) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());
   }
 
-  void VisitType(const Type *) { addLabel(Current.getAsString(PP)); }
+  void VisitType(const Type *) { addLabel(CurrentType.getAsString(PP)); }
 
   void VisitTagType(const TagType *TT) {
     if (!ShouldAddLinksToTagTypes)
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 20f32bc7b7f6b..2464172a3c68a 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -62,7 +62,7 @@ struct ExpectedHintLabelPiece {
   friend llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
                                        const ExpectedHintLabelPiece &Hint) {
     Stream << Hint.Label;
-    if (!Hint.TargetRangeName)
+    if (Hint.TargetRangeName)
       Stream << " that points to $" << Hint.TargetRangeName;
     return Stream;
   }
@@ -1769,12 +1769,15 @@ TEST(TypeHints, Links) {
                   ExpectedHintLabelPiece{
                       ": NttpContainer<D, E, ScopedEnum::X, Enum::E>"},
                   Source)));
+
   EXPECT_THAT(
       HintAt(Hints, "5"),
       ElementsAre(
           HintLabelPieceMatcher(ExpectedHintLabelPiece{": Nested<"}, Source),
           HintLabelPieceMatcher(
               ExpectedHintLabelPiece{"Container", "Container"}, Source),
+          // We don't have links on the inner 'Class' because the location is
+          // where the 'auto' links to.
           HintLabelPieceMatcher(ExpectedHintLabelPiece{"<int>>::Class<"},
                                 Source),
           HintLabelPieceMatcher(ExpectedHintLabelPiece{"Package", "Package"},

>From 31a0dafeae52e3b1b90bc191c0e595596d3b6a26 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 31 Mar 2024 23:30:18 +0800
Subject: [PATCH 05/36] format

---
 clang-tools-extra/clangd/InlayHints.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 11254c8fc81af..5045eb9b6fd26 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -561,8 +561,9 @@ class TypeInlayHintLabelPartBuilder
       case NestedNameSpecifier::TypeSpecWithTemplate:
         CurrentTypeRAII Guard(
             *this,
-            QualType(NNS->getAsType(),
-                     /*Quals=*/0), // Do we need cv-qualifiers on type specifiers?
+            QualType(
+                NNS->getAsType(),
+                /*Quals=*/0), // Do we need cv-qualifiers on type specifiers?
             ShouldAddLinksToTagTypes);
         Visit(NNS->getAsType());
         addLabel("::");

>From 9edd0f63541630a0594a4db8b5f517d6e682ddc2 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 1 Apr 2024 13:28:27 +0800
Subject: [PATCH 06/36] Handle reference/pointer types & Preserve qualifiers &
 Refactor tests slightly

---
 clang-tools-extra/clangd/InlayHints.cpp       |  30 +++
 .../clangd/unittests/InlayHintTests.cpp       | 174 +++++++++---------
 2 files changed, 122 insertions(+), 82 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 5045eb9b6fd26..c28e7671cb401 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -506,6 +506,14 @@ class TypeInlayHintLabelPartBuilder
     addLabel(">");
   }
 
+  void maybeAddQualifiers() {
+    auto Quals = CurrentType.split().Quals;
+    if (!Quals.empty()) {
+      addLabel(Quals.getAsString());
+      addLabel(" ");
+    }
+  }
+
 public:
   TypeInlayHintLabelPartBuilder(QualType Current, ASTContext &Context,
                                 const PrintingPolicy &PP,
@@ -540,12 +548,14 @@ class TypeInlayHintLabelPartBuilder
   void VisitAutoType(const AutoType *AT) {
     if (!AT->isDeduced() || AT->getDeducedType()->isDecltypeType())
       return;
+    maybeAddQualifiers();
     CurrentTypeRAII Guard(*this, AT->getDeducedType(),
                           ShouldAddLinksToTagTypes);
     return Visit(AT->getDeducedType().getTypePtr());
   }
 
   void VisitElaboratedType(const ElaboratedType *ET) {
+    maybeAddQualifiers();
     if (auto *NNS = ET->getQualifier()) {
       switch (NNS->getKind()) {
       case NestedNameSpecifier::Identifier:
@@ -574,7 +584,26 @@ class TypeInlayHintLabelPartBuilder
     return Visit(ET->getNamedType().getTypePtr());
   }
 
+  void VisitReferenceType(const ReferenceType *RT) {
+    maybeAddQualifiers();
+    CurrentTypeRAII Guard(*this, RT->getPointeeTypeAsWritten(), ShouldAddLinksToTagTypes);
+    Visit(RT->getPointeeTypeAsWritten().getTypePtr());
+    if (RT->isLValueReferenceType())
+      addLabel(" &");
+    if (RT->isRValueReferenceType())
+      addLabel(" &&");
+  }
+
+  void VisitPointerType(const PointerType *PT) {
+    maybeAddQualifiers();
+    CurrentTypeRAII Guard(*this, PT->getPointeeType(),
+                          ShouldAddLinksToTagTypes);
+    Visit(PT->getPointeeType().getTypePtr());
+    addLabel(" *");
+  }
+
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
+    maybeAddQualifiers();
     SourceRange Range;
     if (auto *Specialization =
             dyn_cast_if_present<ClassTemplateSpecializationDecl>(
@@ -585,6 +614,7 @@ class TypeInlayHintLabelPartBuilder
   }
 
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
+    maybeAddQualifiers();
     CurrentTypeRAII Guard(*this, ST->getReplacementType(),
                           /*ShouldAddLinksToTagTypes=*/true);
     return Visit(ST->getReplacementType().getTypePtr());
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 2464172a3c68a..238a0a120a46e 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -57,13 +57,13 @@ struct ExpectedHint {
 
 struct ExpectedHintLabelPiece {
   std::string Label;
-  std::optional<std::string> TargetRangeName = std::nullopt;
+  std::optional<std::string> PointsTo = std::nullopt;
 
   friend llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
                                        const ExpectedHintLabelPiece &Hint) {
     Stream << Hint.Label;
-    if (Hint.TargetRangeName)
-      Stream << " that points to $" << Hint.TargetRangeName;
+    if (Hint.PointsTo)
+      Stream << " that points to $" << Hint.PointsTo;
     return Stream;
   }
 };
@@ -89,26 +89,26 @@ MATCHER_P2(HintMatcher, Expected, Code, llvm::to_string(Expected)) {
 MATCHER_P2(HintLabelPieceMatcher, Expected, Code, llvm::to_string(Expected)) {
   llvm::StringRef ExpectedView(Expected.Label);
   std::string ResultLabel = arg.value;
-  if (ResultLabel != ExpectedView.trim(" ")) {
+  if (ResultLabel != ExpectedView) {
     *result_listener << "label is '" << ResultLabel << "'";
     return false;
   }
-  if (!Expected.TargetRangeName && !arg.location)
+  if (!Expected.PointsTo && !arg.location)
     return true;
-  if (Expected.TargetRangeName && !arg.location) {
-    *result_listener << " range " << *Expected.TargetRangeName
+  if (Expected.PointsTo && !arg.location) {
+    *result_listener << " range " << *Expected.PointsTo
                      << " is expected, but we have nothing.";
     return false;
   }
-  if (!Expected.TargetRangeName && arg.location) {
+  if (!Expected.PointsTo && arg.location) {
     *result_listener << " the link points to " << llvm::to_string(arg.location)
                      << ", but we expect nothing.";
     return false;
   }
-  if (arg.location->range != Code.range(*Expected.TargetRangeName)) {
+  if (arg.location->range != Code.range(*Expected.PointsTo)) {
     *result_listener << "range is " << llvm::to_string(arg.location->range)
-                     << " but $" << *Expected.TargetRangeName << " points to "
-                     << llvm::to_string(Code.range(*Expected.TargetRangeName));
+                     << " but $" << *Expected.PointsTo << " points to "
+                     << llvm::to_string(Code.range(*Expected.PointsTo));
     return false;
   }
   return true;
@@ -1678,8 +1678,33 @@ TEST(TypeHints, SubstTemplateParameterAliases) {
                         ExpectedHint{": static_vector<int>", "vector_name"});
 }
 
+template <typename ...Labels>
+void assertTypeLinkHints(StringRef Code, StringRef HintRange, Labels ...ExpectedLabels) {
+  Annotations Source(Code);
+  auto HintAt = [&](llvm::ArrayRef<InlayHint> InlayHints,
+                    llvm::StringRef Range) {
+    auto *Hint = llvm::find_if(InlayHints, [&](const InlayHint &InlayHint) {
+      return InlayHint.range == Source.range(Range);
+    });
+    assert(Hint && "No range was found");
+    return llvm::ArrayRef(Hint->label);
+  };
+
+  TestTU TU = TestTU::withCode(Source.code());
+  TU.ExtraArgs.push_back("-std=c++2c");
+  auto AST = TU.build();
+
+  Config C;
+  C.InlayHints.TypeNameLimit = 0;
+  WithContextValue WithCfg(Config::Key, std::move(C));
+
+  auto Hints = hintsOfKind(AST, InlayHintKind::Type);
+  EXPECT_THAT(HintAt(Hints, HintRange),
+              ElementsAre(HintLabelPieceMatcher(ExpectedLabels, Source)...));
+}
+
 TEST(TypeHints, Links) {
-  Annotations Source(R"cpp(
+  StringRef Source(R"cpp(
     $Package[[template <class T, class U>
     struct Package {]]};
 
@@ -1701,89 +1726,74 @@ TEST(TypeHints, Links) {
     };
 
     namespace ns {
-      template <class T>
+      $Nested[[template <class T>
       struct Nested {
-        template <class U>
-        struct Class {
+        $NestedClass[[template <class U>
+        struct ]]Class {
         };
-      };
+      ]]};
     }
 
-    void foo() {
+    void basic() {
       auto $1[[C]] = Container<Package<char, int>>();
       auto $2[[D]] = Container<Package<float, const int>>();
       auto $3[[E]] = Container<Container<int, int>, long>();
       auto $4[[F]] = NttpContainer<D, E, ScopedEnum::X, Enum::E>();
       auto $5[[G]] = ns::Nested<Container<int>>::Class<Package<char, int>>();
     }
-  )cpp");
-  TestTU TU = TestTU::withCode(Source.code());
-  TU.ExtraArgs.push_back("-std=c++2c");
-  auto AST = TU.build();
-
-  auto HintAt = [&](llvm::ArrayRef<InlayHint> InlayHints,
-                    llvm::StringRef Range) {
-    auto *Hint = llvm::find_if(InlayHints, [&](const InlayHint &InlayHint) {
-      return InlayHint.range == Source.range(Range);
-    });
-    assert(Hint && "No range was found");
-    return llvm::ArrayRef(Hint->label);
-  };
 
-  Config C;
-  C.InlayHints.TypeNameLimit = 0;
-  WithContextValue WithCfg(Config::Key, std::move(C));
+    void compounds() {
+      auto $6[[A]] = Container<ns::Nested<int>::Class<float>&>();
+      auto $7[[B]] = Container<ns::Nested<int>::Class<float>&&>();
+      auto $8[[C]] = Container<ns::Nested<int>::Class<const Container<int>> *>();
+    }
 
-  auto Hints = hintsOfKind(AST, InlayHintKind::Type);
+  )cpp");
 
-  EXPECT_THAT(
-      HintAt(Hints, "1"),
-      ElementsAre(
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{": Container<"}, Source),
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{"Package", "Package"},
-                                Source),
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<char, int>>"},
-                                Source)));
-
-  EXPECT_THAT(
-      HintAt(Hints, "2"),
-      ElementsAre(
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{": Container<"}, Source),
-          HintLabelPieceMatcher(
-              ExpectedHintLabelPiece{"Package", "SpecializationOfPackage"},
-              Source),
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<float, const int>>"},
-                                Source)));
-
-  EXPECT_THAT(
-      HintAt(Hints, "3"),
-      ElementsAre(
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{": Container<"}, Source),
-          HintLabelPieceMatcher(
-              ExpectedHintLabelPiece{"Container", "Container"}, Source),
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<int, int>, long>"},
-                                Source)));
-
-  EXPECT_THAT(HintAt(Hints, "4"),
-              ElementsAre(HintLabelPieceMatcher(
-                  ExpectedHintLabelPiece{
-                      ": NttpContainer<D, E, ScopedEnum::X, Enum::E>"},
-                  Source)));
-
-  EXPECT_THAT(
-      HintAt(Hints, "5"),
-      ElementsAre(
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{": Nested<"}, Source),
-          HintLabelPieceMatcher(
-              ExpectedHintLabelPiece{"Container", "Container"}, Source),
-          // We don't have links on the inner 'Class' because the location is
-          // where the 'auto' links to.
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<int>>::Class<"},
-                                Source),
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{"Package", "Package"},
-                                Source),
-          HintLabelPieceMatcher(ExpectedHintLabelPiece{"<char, int>>"},
-                                Source)));
+  assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": Container<"},
+                      ExpectedHintLabelPiece{"Package", "Package"},
+                      ExpectedHintLabelPiece{"<char, int>>"});
+
+  assertTypeLinkHints(
+      Source, "2", ExpectedHintLabelPiece{": Container<"},
+      ExpectedHintLabelPiece{"Package", "SpecializationOfPackage"},
+      ExpectedHintLabelPiece{"<float, const int>>"});
+
+  assertTypeLinkHints(Source, "3", ExpectedHintLabelPiece{": Container<"},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<int, int>, long>"});
+
+  assertTypeLinkHints(
+      Source, "4",
+      ExpectedHintLabelPiece{": NttpContainer<D, E, ScopedEnum::X, Enum::E>"});
+
+  assertTypeLinkHints(Source, "5", ExpectedHintLabelPiece{": Nested<"},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      // We don't have links on the inner 'Class' because the
+                      // location is where the 'auto' links to.
+                      ExpectedHintLabelPiece{"<int>>::Class<"},
+                      ExpectedHintLabelPiece{"Package", "Package"},
+                      ExpectedHintLabelPiece{"<char, int>>"});
+
+  assertTypeLinkHints(Source, "6", ExpectedHintLabelPiece{": Container<"},
+                      ExpectedHintLabelPiece{"Nested", "Nested"},
+                      ExpectedHintLabelPiece{"<int>::"},
+                      ExpectedHintLabelPiece{"Class", "NestedClass"},
+                      ExpectedHintLabelPiece{"<float> &>"});
+
+  assertTypeLinkHints(Source, "7", ExpectedHintLabelPiece{": Container<"},
+                      ExpectedHintLabelPiece{"Nested", "Nested"},
+                      ExpectedHintLabelPiece{"<int>::"},
+                      ExpectedHintLabelPiece{"Class", "NestedClass"},
+                      ExpectedHintLabelPiece{"<float> &&>"});
+
+  assertTypeLinkHints(Source, "8", ExpectedHintLabelPiece{": Container<"},
+                      ExpectedHintLabelPiece{"Nested", "Nested"},
+                      ExpectedHintLabelPiece{"<int>::"},
+                      ExpectedHintLabelPiece{"Class", "NestedClass"},
+                      ExpectedHintLabelPiece{"<const "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<int>> *>"});
 }
 
 TEST(DesignatorHints, Basic) {

>From 48e21a102c11d4e9f6f6258bbe68085354ce6ef8 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 1 Apr 2024 13:29:38 +0800
Subject: [PATCH 07/36] Forgot to format, sorry

---
 clang-tools-extra/clangd/InlayHints.cpp               | 3 ++-
 clang-tools-extra/clangd/unittests/InlayHintTests.cpp | 5 +++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index c28e7671cb401..73701c702e3f5 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -586,7 +586,8 @@ class TypeInlayHintLabelPartBuilder
 
   void VisitReferenceType(const ReferenceType *RT) {
     maybeAddQualifiers();
-    CurrentTypeRAII Guard(*this, RT->getPointeeTypeAsWritten(), ShouldAddLinksToTagTypes);
+    CurrentTypeRAII Guard(*this, RT->getPointeeTypeAsWritten(),
+                          ShouldAddLinksToTagTypes);
     Visit(RT->getPointeeTypeAsWritten().getTypePtr());
     if (RT->isLValueReferenceType())
       addLabel(" &");
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 238a0a120a46e..a69422d825f30 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1678,8 +1678,9 @@ TEST(TypeHints, SubstTemplateParameterAliases) {
                         ExpectedHint{": static_vector<int>", "vector_name"});
 }
 
-template <typename ...Labels>
-void assertTypeLinkHints(StringRef Code, StringRef HintRange, Labels ...ExpectedLabels) {
+template <typename... Labels>
+void assertTypeLinkHints(StringRef Code, StringRef HintRange,
+                         Labels... ExpectedLabels) {
   Annotations Source(Code);
   auto HintAt = [&](llvm::ArrayRef<InlayHint> InlayHints,
                     llvm::StringRef Range) {

>From 8c8c036994783c70a16446771a703c6c99094805 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 1 Apr 2024 14:02:26 +0800
Subject: [PATCH 08/36] Fix regressions

---
 clang-tools-extra/clangd/InlayHints.cpp | 32 ++++++++++++++++++-------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 73701c702e3f5..7a842e2f76423 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -514,6 +514,14 @@ class TypeInlayHintLabelPartBuilder
     }
   }
 
+  // When printing a reference, the referenced type might also be a reference.
+  // If so, we want to skip that before printing the inner type.
+  static QualType skipTopLevelReferences(QualType T) {
+    if (auto *Ref = T->getAs<ReferenceType>())
+      return skipTopLevelReferences(Ref->getPointeeTypeAsWritten());
+    return T;
+  }
+
 public:
   TypeInlayHintLabelPartBuilder(QualType Current, ASTContext &Context,
                                 const PrintingPolicy &PP,
@@ -586,21 +594,27 @@ class TypeInlayHintLabelPartBuilder
 
   void VisitReferenceType(const ReferenceType *RT) {
     maybeAddQualifiers();
-    CurrentTypeRAII Guard(*this, RT->getPointeeTypeAsWritten(),
-                          ShouldAddLinksToTagTypes);
-    Visit(RT->getPointeeTypeAsWritten().getTypePtr());
+    QualType Next = skipTopLevelReferences(RT->getPointeeTypeAsWritten());
+    CurrentTypeRAII Guard(*this, Next, ShouldAddLinksToTagTypes);
+    Visit(Next.getTypePtr());
+    if (Next->getPointeeType().isNull())
+      addLabel(" ");
     if (RT->isLValueReferenceType())
-      addLabel(" &");
+      addLabel("&");
     if (RT->isRValueReferenceType())
-      addLabel(" &&");
+      addLabel("&&");
   }
 
   void VisitPointerType(const PointerType *PT) {
+    QualType Next = PT->getPointeeType();
+    std::optional<CurrentTypeRAII> Guard(std::in_place, *this, Next,
+                                         ShouldAddLinksToTagTypes);
+    Visit(Next.getTypePtr());
+    if (Next->getPointeeType().isNull())
+      addLabel(" ");
+    addLabel("*");
+    Guard.reset();
     maybeAddQualifiers();
-    CurrentTypeRAII Guard(*this, PT->getPointeeType(),
-                          ShouldAddLinksToTagTypes);
-    Visit(PT->getPointeeType().getTypePtr());
-    addLabel(" *");
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {

>From 0e25c18f6722d4afcf0a479fd0e88ba8f26bb218 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 1 Apr 2024 14:59:44 +0800
Subject: [PATCH 09/36] Initialize ShouldAddLinksToTagTypes

---
 clang-tools-extra/clangd/InlayHints.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 7a842e2f76423..8d8fbc4bcde04 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -396,7 +396,7 @@ class TypeInlayHintLabelPartBuilder
   const PrintingPolicy &PP;
   std::vector<InlayHintLabelPart> &LabelChunks;
 
-  bool ShouldAddLinksToTagTypes = false;
+  bool ShouldAddLinksToTagTypes;
 
   struct CurrentTypeRAII {
     TypeInlayHintLabelPartBuilder &Builder;
@@ -529,7 +529,8 @@ class TypeInlayHintLabelPartBuilder
                                 llvm::StringRef Prefix,
                                 std::vector<InlayHintLabelPart> &LabelChunks)
       : CurrentType(Current), Context(Context), PP(PP),
-        LabelChunks(LabelChunks) {
+        LabelChunks(LabelChunks),
+        ShouldAddLinksToTagTypes(ShouldAddLinksToTagTypes) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());

>From 20607772fdc6e2f054485c3a56ebeab2feaf9e10 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Fri, 12 Apr 2024 17:58:32 +0800
Subject: [PATCH 10/36] Handle TypeAliases & UsingTypes

---
 clang-tools-extra/clangd/InlayHints.cpp       | 15 +++++++++++++++
 .../clangd/unittests/InlayHintTests.cpp       | 19 +++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 8d8fbc4bcde04..9c02f3307611a 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -618,6 +618,21 @@ class TypeInlayHintLabelPartBuilder
     maybeAddQualifiers();
   }
 
+  void VisitUsingType(const UsingType *UT) {
+    addLabel([&](llvm::raw_ostream &OS) { UT->getFoundDecl()->printName(OS); },
+             [&] {
+               BaseUsingDecl *Introducer = UT->getFoundDecl()->getIntroducer();
+               if (auto *UD = dyn_cast<UsingDecl>(Introducer))
+                 return UD->getSourceRange();
+               return Introducer->getSourceRange();
+             });
+  }
+
+  void VisitTypedefType(const TypedefType *TT) {
+    addLabel([&](llvm::raw_ostream &OS) { TT->getDecl()->printName(OS); },
+             [&] { return TT->getDecl()->getSourceRange(); });
+  }
+
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
     maybeAddQualifiers();
     SourceRange Range;
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index a69422d825f30..a43273a35fcd9 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1733,6 +1733,8 @@ TEST(TypeHints, Links) {
         struct ]]Class {
         };
       ]]};
+
+      $NestedInt[[using NestedInt = Nested<int]]>;
     }
 
     void basic() {
@@ -1749,6 +1751,15 @@ TEST(TypeHints, Links) {
       auto $8[[C]] = Container<ns::Nested<int>::Class<const Container<int>> *>();
     }
 
+    namespace nns {
+      $UsingShadow[[using ns::]]NestedInt;
+
+      void aliases() {
+        auto $9[[A]] = Container<NestedInt>();
+        auto $10[[B]] = Container<ns::NestedInt>();
+      }
+    }
+
   )cpp");
 
   assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": Container<"},
@@ -1795,6 +1806,14 @@ TEST(TypeHints, Links) {
                       ExpectedHintLabelPiece{"<const "},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<int>> *>"});
+
+  assertTypeLinkHints(Source, "9", ExpectedHintLabelPiece{": Container<"},
+                      ExpectedHintLabelPiece{"NestedInt", "UsingShadow"},
+                      ExpectedHintLabelPiece{">"});
+
+  assertTypeLinkHints(Source, "10", ExpectedHintLabelPiece{": Container<"},
+                      ExpectedHintLabelPiece{"NestedInt", "NestedInt"},
+                      ExpectedHintLabelPiece{">"});
 }
 
 TEST(DesignatorHints, Basic) {

>From acbf62ed201f9ad22dde81dbc870b4cd056b8f9b Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sat, 1 Jun 2024 19:22:45 +0800
Subject: [PATCH 11/36] Fix printing on NNS; Always offer links; Address some
 of the feedback

---
 clang-tools-extra/clangd/InlayHints.cpp       | 76 ++++++++++---------
 .../clangd/unittests/InlayHintTests.cpp       | 75 +++++++++++-------
 2 files changed, 87 insertions(+), 64 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 9c02f3307611a..39bf40c0e8b00 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -392,26 +392,26 @@ std::optional<Location> toLocation(SourceManager &SM, SourceRange Range) {
 class TypeInlayHintLabelPartBuilder
     : public TypeVisitor<TypeInlayHintLabelPartBuilder> {
   QualType CurrentType;
+  NestedNameSpecifier *CurrentNestedNameSpecifier;
   ASTContext &Context;
   const PrintingPolicy &PP;
   std::vector<InlayHintLabelPart> &LabelChunks;
 
-  bool ShouldAddLinksToTagTypes;
-
   struct CurrentTypeRAII {
     TypeInlayHintLabelPartBuilder &Builder;
     QualType PreviousType;
-    bool PreviousShouldAddLinksToTagTypes;
+    NestedNameSpecifier *PreviousNestedNameSpecifier;
     CurrentTypeRAII(TypeInlayHintLabelPartBuilder &Builder, QualType New,
-                    bool ShouldAddLinksToTagTypes)
+                    NestedNameSpecifier *NNS = nullptr)
         : Builder(Builder), PreviousType(Builder.CurrentType),
-          PreviousShouldAddLinksToTagTypes(Builder.ShouldAddLinksToTagTypes) {
+          PreviousNestedNameSpecifier(Builder.CurrentNestedNameSpecifier) {
       Builder.CurrentType = New;
-      Builder.ShouldAddLinksToTagTypes = ShouldAddLinksToTagTypes;
+      if (NNS)
+        Builder.CurrentNestedNameSpecifier = NNS;
     }
     ~CurrentTypeRAII() {
       Builder.CurrentType = PreviousType;
-      Builder.ShouldAddLinksToTagTypes = PreviousShouldAddLinksToTagTypes;
+      Builder.CurrentNestedNameSpecifier = PreviousNestedNameSpecifier;
     }
   };
 
@@ -420,8 +420,6 @@ class TypeInlayHintLabelPartBuilder
     std::string Label;
     llvm::raw_string_ostream OS(Label);
     NamePrinter(OS);
-    if (!ShouldAddLinksToTagTypes)
-      return addLabel(std::move(Label));
     auto &Name = LabelChunks.emplace_back();
     Name.value = std::move(Label);
     Name.location = toLocation(Context.getSourceManager(), SourceRangeGetter());
@@ -458,8 +456,7 @@ class TypeInlayHintLabelPartBuilder
     case TemplateArgument::Pack:
       return printTemplateArgumentList(TA.pack_elements());
     case TemplateArgument::Type: {
-      CurrentTypeRAII Guard(*this, TA.getAsType(),
-                            /*ShouldAddLinksToTagTypes=*/true);
+      CurrentTypeRAII Guard(*this, TA.getAsType());
       return Visit(TA.getAsType().getTypePtr());
     }
     // TODO: Add support for NTTP arguments.
@@ -485,16 +482,32 @@ class TypeInlayHintLabelPartBuilder
                                SourceRange TemplateNameRange = SourceRange()) {
     SourceRange Range;
     TemplateDecl *TD = nullptr;
+    auto PrintType = TemplateName::Qualified::AsWritten;
     switch (TN.getKind()) {
     case TemplateName::Template:
       TD = TN.getAsTemplateDecl();
       Range = TD->getSourceRange();
-      LLVM_FALLTHROUGH;
-    default:
+      break;
+    case TemplateName::QualifiedTemplate:
+      if (NestedNameSpecifier *NNS =
+              TN.getAsQualifiedTemplateName()->getQualifier();
+          NNS == CurrentNestedNameSpecifier) {
+        // We have handled the NNS in VisitElaboratedType(). Avoid printing it
+        // twice.
+        TN = TN.getAsQualifiedTemplateName()->getUnderlyingTemplate();
+        PrintType = TemplateName::Qualified::None;
+      }
+      break;
+    case TemplateName::OverloadedTemplate:
+    case TemplateName::AssumedTemplate:
+    case TemplateName::DependentTemplate:
+    case TemplateName::SubstTemplateTemplateParm:
+    case TemplateName::SubstTemplateTemplateParmPack:
+    case TemplateName::UsingTemplate:
       break;
     }
 
-    addLabel([&](llvm::raw_ostream &OS) { TN.print(OS, PP); },
+    addLabel([&](llvm::raw_ostream &OS) { TN.print(OS, PP, PrintType); },
              [&] {
                if (TemplateNameRange.isValid())
                  return TemplateNameRange;
@@ -525,12 +538,10 @@ class TypeInlayHintLabelPartBuilder
 public:
   TypeInlayHintLabelPartBuilder(QualType Current, ASTContext &Context,
                                 const PrintingPolicy &PP,
-                                bool ShouldAddLinksToTagTypes,
                                 llvm::StringRef Prefix,
                                 std::vector<InlayHintLabelPart> &LabelChunks)
-      : CurrentType(Current), Context(Context), PP(PP),
-        LabelChunks(LabelChunks),
-        ShouldAddLinksToTagTypes(ShouldAddLinksToTagTypes) {
+      : CurrentType(Current), CurrentNestedNameSpecifier(nullptr), Context(Context), PP(PP),
+        LabelChunks(LabelChunks) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());
@@ -539,8 +550,6 @@ class TypeInlayHintLabelPartBuilder
   void VisitType(const Type *) { addLabel(CurrentType.getAsString(PP)); }
 
   void VisitTagType(const TagType *TT) {
-    if (!ShouldAddLinksToTagTypes)
-      return VisitType(TT);
     auto *D = TT->getDecl();
     if (auto *Specialization = dyn_cast<ClassTemplateSpecializationDecl>(D))
       return handleTemplateSpecialization(
@@ -558,8 +567,7 @@ class TypeInlayHintLabelPartBuilder
     if (!AT->isDeduced() || AT->getDeducedType()->isDecltypeType())
       return;
     maybeAddQualifiers();
-    CurrentTypeRAII Guard(*this, AT->getDeducedType(),
-                          ShouldAddLinksToTagTypes);
+    CurrentTypeRAII Guard(*this, AT->getDeducedType());
     return Visit(AT->getDeducedType().getTypePtr());
   }
 
@@ -582,21 +590,21 @@ class TypeInlayHintLabelPartBuilder
             *this,
             QualType(
                 NNS->getAsType(),
-                /*Quals=*/0), // Do we need cv-qualifiers on type specifiers?
-            ShouldAddLinksToTagTypes);
+                /*Quals=*/0) // Do we need cv-qualifiers on type specifiers?
+        );
         Visit(NNS->getAsType());
         addLabel("::");
         break;
       }
     }
-    CurrentTypeRAII Guard(*this, ET->getNamedType(), ShouldAddLinksToTagTypes);
+    CurrentTypeRAII Guard(*this, ET->getNamedType(), ET->getQualifier());
     return Visit(ET->getNamedType().getTypePtr());
   }
 
   void VisitReferenceType(const ReferenceType *RT) {
     maybeAddQualifiers();
     QualType Next = skipTopLevelReferences(RT->getPointeeTypeAsWritten());
-    CurrentTypeRAII Guard(*this, Next, ShouldAddLinksToTagTypes);
+    CurrentTypeRAII Guard(*this, Next);
     Visit(Next.getTypePtr());
     if (Next->getPointeeType().isNull())
       addLabel(" ");
@@ -608,8 +616,7 @@ class TypeInlayHintLabelPartBuilder
 
   void VisitPointerType(const PointerType *PT) {
     QualType Next = PT->getPointeeType();
-    std::optional<CurrentTypeRAII> Guard(std::in_place, *this, Next,
-                                         ShouldAddLinksToTagTypes);
+    std::optional<CurrentTypeRAII> Guard(std::in_place, *this, Next);
     Visit(Next.getTypePtr());
     if (Next->getPointeeType().isNull())
       addLabel(" ");
@@ -646,8 +653,7 @@ class TypeInlayHintLabelPartBuilder
 
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
     maybeAddQualifiers();
-    CurrentTypeRAII Guard(*this, ST->getReplacementType(),
-                          /*ShouldAddLinksToTagTypes=*/true);
+    CurrentTypeRAII Guard(*this, ST->getReplacementType());
     return Visit(ST->getReplacementType().getTypePtr());
   }
 };
@@ -1332,17 +1338,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
     // type in other cases.
     auto Desugared = maybeDesugar(AST, T);
     std::vector<InlayHintLabelPart> Chunks;
-    TypeInlayHintLabelPartBuilder Builder(
-        Desugared, AST, TypeHintPolicy,
-        /*ShouldAddLinksToTagTypes=*/T != Desugared, Prefix, Chunks);
+    TypeInlayHintLabelPartBuilder Builder(Desugared, AST, TypeHintPolicy,
+                                          Prefix, Chunks);
     Builder.Visit(Desugared.getTypePtr());
     if (T != Desugared && !shouldPrintTypeHint(Chunks)) {
       // If the desugared type is too long to display, fallback to the sugared
       // type.
       Chunks.clear();
-      TypeInlayHintLabelPartBuilder Builder(T, AST, TypeHintPolicy,
-                                            /*ShouldAddLinksToTagTypes=*/false,
-                                            Prefix, Chunks);
+      TypeInlayHintLabelPartBuilder Builder(T, AST, TypeHintPolicy, Prefix,
+                                            Chunks);
       Builder.Visit(T.getTypePtr());
     }
     if (shouldPrintTypeHint(Chunks))
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index a43273a35fcd9..b719984d47164 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -57,6 +57,7 @@ struct ExpectedHint {
 
 struct ExpectedHintLabelPiece {
   std::string Label;
+  // Stores the name of a range annotation
   std::optional<std::string> PointsTo = std::nullopt;
 
   friend llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
@@ -87,9 +88,8 @@ MATCHER_P2(HintMatcher, Expected, Code, llvm::to_string(Expected)) {
 }
 
 MATCHER_P2(HintLabelPieceMatcher, Expected, Code, llvm::to_string(Expected)) {
-  llvm::StringRef ExpectedView(Expected.Label);
   std::string ResultLabel = arg.value;
-  if (ResultLabel != ExpectedView) {
+  if (ResultLabel != Expected.Label) {
     *result_listener << "label is '" << ResultLabel << "'";
     return false;
   }
@@ -1682,14 +1682,6 @@ template <typename... Labels>
 void assertTypeLinkHints(StringRef Code, StringRef HintRange,
                          Labels... ExpectedLabels) {
   Annotations Source(Code);
-  auto HintAt = [&](llvm::ArrayRef<InlayHint> InlayHints,
-                    llvm::StringRef Range) {
-    auto *Hint = llvm::find_if(InlayHints, [&](const InlayHint &InlayHint) {
-      return InlayHint.range == Source.range(Range);
-    });
-    assert(Hint && "No range was found");
-    return llvm::ArrayRef(Hint->label);
-  };
 
   TestTU TU = TestTU::withCode(Source.code());
   TU.ExtraArgs.push_back("-std=c++2c");
@@ -1700,7 +1692,11 @@ void assertTypeLinkHints(StringRef Code, StringRef HintRange,
   WithContextValue WithCfg(Config::Key, std::move(C));
 
   auto Hints = hintsOfKind(AST, InlayHintKind::Type);
-  EXPECT_THAT(HintAt(Hints, HintRange),
+  auto Hint = llvm::find_if(Hints, [&](const InlayHint &InlayHint) {
+    return InlayHint.range == Source.range(HintRange);
+  });
+  ASSERT_TRUE(Hint != Hints.end()) << "No hint was found at " << HintRange;
+  EXPECT_THAT(Hint->label,
               ElementsAre(HintLabelPieceMatcher(ExpectedLabels, Source)...));
 }
 
@@ -1761,57 +1757,80 @@ TEST(TypeHints, Links) {
     }
 
   )cpp");
-
-  assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": Container<"},
+  assertTypeLinkHints(Source, "1",
+                      ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"Package", "Package"},
                       ExpectedHintLabelPiece{"<char, int>>"});
 
   assertTypeLinkHints(
-      Source, "2", ExpectedHintLabelPiece{": Container<"},
+      Source, "2", 
+      ExpectedHintLabelPiece{": "},
+      ExpectedHintLabelPiece{"Container", "Container"},
+      ExpectedHintLabelPiece{"<"},
       ExpectedHintLabelPiece{"Package", "SpecializationOfPackage"},
       ExpectedHintLabelPiece{"<float, const int>>"});
 
-  assertTypeLinkHints(Source, "3", ExpectedHintLabelPiece{": Container<"},
+  assertTypeLinkHints(Source, "3", 
+                      ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<int, int>, long>"});
 
+  // TODO: Support links on NTTP arguments
   assertTypeLinkHints(
       Source, "4",
-      ExpectedHintLabelPiece{": NttpContainer<D, E, ScopedEnum::X, Enum::E>"});
+      ExpectedHintLabelPiece{": "},
+      ExpectedHintLabelPiece{"NttpContainer", "NttpContainer"},
+      ExpectedHintLabelPiece{"<D, E, ScopedEnum::X, Enum::E>"});
 
-  assertTypeLinkHints(Source, "5", ExpectedHintLabelPiece{": Nested<"},
+  assertTypeLinkHints(Source, "5", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"ns::Nested", "Nested"},
+                      ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"Container", "Container"},
-                      // We don't have links on the inner 'Class' because the
-                      // location is where the 'auto' links to.
-                      ExpectedHintLabelPiece{"<int>>::Class<"},
+                      ExpectedHintLabelPiece{"<int>>::"},
+                      ExpectedHintLabelPiece{"Class", "NestedClass"},
+                      ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"Package", "Package"},
                       ExpectedHintLabelPiece{"<char, int>>"});
 
-  assertTypeLinkHints(Source, "6", ExpectedHintLabelPiece{": Container<"},
-                      ExpectedHintLabelPiece{"Nested", "Nested"},
+  assertTypeLinkHints(Source, "6", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<"},
+                      ExpectedHintLabelPiece{"ns::Nested", "Nested"},
                       ExpectedHintLabelPiece{"<int>::"},
                       ExpectedHintLabelPiece{"Class", "NestedClass"},
                       ExpectedHintLabelPiece{"<float> &>"});
 
-  assertTypeLinkHints(Source, "7", ExpectedHintLabelPiece{": Container<"},
-                      ExpectedHintLabelPiece{"Nested", "Nested"},
+  assertTypeLinkHints(Source, "7", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<"},
+                      ExpectedHintLabelPiece{"ns::Nested", "Nested"},
                       ExpectedHintLabelPiece{"<int>::"},
                       ExpectedHintLabelPiece{"Class", "NestedClass"},
                       ExpectedHintLabelPiece{"<float> &&>"});
 
-  assertTypeLinkHints(Source, "8", ExpectedHintLabelPiece{": Container<"},
-                      ExpectedHintLabelPiece{"Nested", "Nested"},
+  assertTypeLinkHints(Source, "8", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<"},
+                      ExpectedHintLabelPiece{"ns::Nested", "Nested"},
                       ExpectedHintLabelPiece{"<int>::"},
                       ExpectedHintLabelPiece{"Class", "NestedClass"},
                       ExpectedHintLabelPiece{"<const "},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<int>> *>"});
 
-  assertTypeLinkHints(Source, "9", ExpectedHintLabelPiece{": Container<"},
+  assertTypeLinkHints(Source, "9", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"NestedInt", "UsingShadow"},
                       ExpectedHintLabelPiece{">"});
 
-  assertTypeLinkHints(Source, "10", ExpectedHintLabelPiece{": Container<"},
+  assertTypeLinkHints(Source, "10", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"NestedInt", "NestedInt"},
                       ExpectedHintLabelPiece{">"});
 }

>From d862ce4263fbc5a4fd29996d7dec30754506e7b5 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sat, 1 Jun 2024 20:13:26 +0800
Subject: [PATCH 12/36] Forget to print non-type specifiers. Oof

---
 clang-tools-extra/clangd/InlayHints.cpp               | 3 ++-
 clang-tools-extra/clangd/unittests/InlayHintTests.cpp | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 39bf40c0e8b00..878ed2b799c32 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -491,7 +491,7 @@ class TypeInlayHintLabelPartBuilder
     case TemplateName::QualifiedTemplate:
       if (NestedNameSpecifier *NNS =
               TN.getAsQualifiedTemplateName()->getQualifier();
-          NNS == CurrentNestedNameSpecifier) {
+          NNS && NNS == CurrentNestedNameSpecifier) {
         // We have handled the NNS in VisitElaboratedType(). Avoid printing it
         // twice.
         TN = TN.getAsQualifiedTemplateName()->getUnderlyingTemplate();
@@ -582,6 +582,7 @@ class TypeInlayHintLabelPartBuilder
       case NestedNameSpecifier::Super: {
         std::string Label;
         llvm::raw_string_ostream OS(Label);
+        NNS->print(OS, PP);
         addLabel(std::move(Label));
       } break;
       case NestedNameSpecifier::TypeSpec:
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index b719984d47164..fe1fd6eda863c 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1779,7 +1779,7 @@ TEST(TypeHints, Links) {
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<int, int>, long>"});
 
-  // TODO: Support links on NTTP arguments
+  // TODO: Support links on NTTP arguments.
   assertTypeLinkHints(
       Source, "4",
       ExpectedHintLabelPiece{": "},

>From 9ebbbd6170b0ffd733e0416727bd557d358c9269 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sat, 1 Jun 2024 20:14:10 +0800
Subject: [PATCH 13/36] Run clang-format

---
 clang-tools-extra/clangd/InlayHints.cpp         |  4 ++--
 .../clangd/unittests/InlayHintTests.cpp         | 17 ++++++-----------
 2 files changed, 8 insertions(+), 13 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 878ed2b799c32..3a010adf95e32 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -540,8 +540,8 @@ class TypeInlayHintLabelPartBuilder
                                 const PrintingPolicy &PP,
                                 llvm::StringRef Prefix,
                                 std::vector<InlayHintLabelPart> &LabelChunks)
-      : CurrentType(Current), CurrentNestedNameSpecifier(nullptr), Context(Context), PP(PP),
-        LabelChunks(LabelChunks) {
+      : CurrentType(Current), CurrentNestedNameSpecifier(nullptr),
+        Context(Context), PP(PP), LabelChunks(LabelChunks) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index fe1fd6eda863c..05598fd973610 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1757,34 +1757,29 @@ TEST(TypeHints, Links) {
     }
 
   )cpp");
-  assertTypeLinkHints(Source, "1",
-                      ExpectedHintLabelPiece{": "},
+  assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"Package", "Package"},
                       ExpectedHintLabelPiece{"<char, int>>"});
 
   assertTypeLinkHints(
-      Source, "2", 
-      ExpectedHintLabelPiece{": "},
+      Source, "2", ExpectedHintLabelPiece{": "},
       ExpectedHintLabelPiece{"Container", "Container"},
       ExpectedHintLabelPiece{"<"},
       ExpectedHintLabelPiece{"Package", "SpecializationOfPackage"},
       ExpectedHintLabelPiece{"<float, const int>>"});
 
-  assertTypeLinkHints(Source, "3", 
-                      ExpectedHintLabelPiece{": "},
+  assertTypeLinkHints(Source, "3", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<int, int>, long>"});
 
   // TODO: Support links on NTTP arguments.
-  assertTypeLinkHints(
-      Source, "4",
-      ExpectedHintLabelPiece{": "},
-      ExpectedHintLabelPiece{"NttpContainer", "NttpContainer"},
-      ExpectedHintLabelPiece{"<D, E, ScopedEnum::X, Enum::E>"});
+  assertTypeLinkHints(Source, "4", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"NttpContainer", "NttpContainer"},
+                      ExpectedHintLabelPiece{"<D, E, ScopedEnum::X, Enum::E>"});
 
   assertTypeLinkHints(Source, "5", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"ns::Nested", "Nested"},

>From fcf6b6b0cf293df25475a59edd220c1123fbbbcf Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sat, 1 Jun 2024 22:54:41 +0800
Subject: [PATCH 14/36] Fix tests

---
 clang-tools-extra/clangd/InlayHints.cpp               | 2 +-
 clang-tools-extra/clangd/unittests/InlayHintTests.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 3a010adf95e32..3953bf37d762f 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -491,7 +491,7 @@ class TypeInlayHintLabelPartBuilder
     case TemplateName::QualifiedTemplate:
       if (NestedNameSpecifier *NNS =
               TN.getAsQualifiedTemplateName()->getQualifier();
-          NNS && NNS == CurrentNestedNameSpecifier) {
+          NNS == CurrentNestedNameSpecifier) {
         // We have handled the NNS in VisitElaboratedType(). Avoid printing it
         // twice.
         TN = TN.getAsQualifiedTemplateName()->getUnderlyingTemplate();
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 05598fd973610..6e596b11b9a5d 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1825,7 +1825,7 @@ TEST(TypeHints, Links) {
 
   assertTypeLinkHints(Source, "10", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"Container", "Container"},
-                      ExpectedHintLabelPiece{"<"},
+                      ExpectedHintLabelPiece{"<ns::"},
                       ExpectedHintLabelPiece{"NestedInt", "NestedInt"},
                       ExpectedHintLabelPiece{">"});
 }

>From d7b0715f78787d5f33293ad4d551ce7aac6840b1 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 16 Jun 2024 22:05:15 +0800
Subject: [PATCH 15/36] Use getLocation() for identifier's location. Handle
 enum Decls.

---
 clang-tools-extra/clangd/AST.cpp              | 24 ++++++++
 clang-tools-extra/clangd/AST.h                |  3 +
 clang-tools-extra/clangd/InlayHints.cpp       | 58 +++++++++++--------
 clang-tools-extra/clangd/XRefs.cpp            | 23 --------
 .../clangd/unittests/InlayHintTests.cpp       | 44 ++++++++------
 5 files changed, 88 insertions(+), 64 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 98fe19000d97b..a634438edaef8 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -9,6 +9,7 @@
 #include "AST.h"
 
 #include "SourceCode.h"
+#include "support/Logger.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTTypeTraits.h"
 #include "clang/AST/Decl.h"
@@ -174,6 +175,29 @@ SourceLocation nameLocation(const clang::Decl &D, const SourceManager &SM) {
   return SM.getExpansionLoc(L);
 }
 
+// Expects Loc to be a SpellingLocation, will bail out otherwise as it can't
+// figure out a filename.
+std::optional<Location> makeLocation(const ASTContext &AST, SourceLocation Loc,
+                                     llvm::StringRef TUPath) {
+  const auto &SM = AST.getSourceManager();
+  const auto F = SM.getFileEntryRefForID(SM.getFileID(Loc));
+  if (!F)
+    return std::nullopt;
+  auto FilePath = getCanonicalPath(*F, SM.getFileManager());
+  if (!FilePath) {
+    log("failed to get path!");
+    return std::nullopt;
+  }
+  Location L;
+  L.uri = URIForFile::canonicalize(*FilePath, TUPath);
+  // We call MeasureTokenLength here as TokenBuffer doesn't store spelled tokens
+  // outside the main file.
+  auto TokLen = Lexer::MeasureTokenLength(Loc, SM, AST.getLangOpts());
+  L.range = halfOpenToRange(
+      SM, CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(TokLen)));
+  return L;
+}
+
 std::string printQualifiedName(const NamedDecl &ND) {
   std::string QName;
   llvm::raw_string_ostream OS(QName);
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 3ae624b1ab741..e048815b2870b 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -45,6 +45,9 @@ bool isImplementationDetail(const Decl *D);
 /// this function.
 SourceLocation nameLocation(const clang::Decl &D, const SourceManager &SM);
 
+std::optional<Location> makeLocation(const ASTContext &AST, SourceLocation Loc,
+                                     llvm::StringRef TUPath);
+
 /// Returns the qualified name of ND. The scope doesn't contain unwritten scopes
 /// like inline namespaces.
 std::string printQualifiedName(const NamedDecl &ND);
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 3953bf37d762f..e0fef5758ef02 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -394,7 +394,9 @@ class TypeInlayHintLabelPartBuilder
   QualType CurrentType;
   NestedNameSpecifier *CurrentNestedNameSpecifier;
   ASTContext &Context;
+  StringRef MainFilePath;
   const PrintingPolicy &PP;
+  SourceManager &SM;
   std::vector<InlayHintLabelPart> &LabelChunks;
 
   struct CurrentTypeRAII {
@@ -416,13 +418,13 @@ class TypeInlayHintLabelPartBuilder
   };
 
   void addLabel(llvm::function_ref<void(llvm::raw_ostream &)> NamePrinter,
-                llvm::function_ref<SourceRange()> SourceRangeGetter) {
+                llvm::function_ref<SourceLocation()> SourceLocationGetter) {
     std::string Label;
     llvm::raw_string_ostream OS(Label);
     NamePrinter(OS);
     auto &Name = LabelChunks.emplace_back();
     Name.value = std::move(Label);
-    Name.location = toLocation(Context.getSourceManager(), SourceRangeGetter());
+    Name.location = makeLocation(Context, SourceLocationGetter(), MainFilePath);
   }
 
   void addLabel(std::string Label) {
@@ -476,17 +478,16 @@ class TypeInlayHintLabelPartBuilder
     addLabel(std::move(Label));
   }
 
-  void
-  handleTemplateSpecialization(TemplateName TN,
-                               llvm::ArrayRef<TemplateArgument> Args,
-                               SourceRange TemplateNameRange = SourceRange()) {
-    SourceRange Range;
+  void handleTemplateSpecialization(
+      TemplateName TN, llvm::ArrayRef<TemplateArgument> Args,
+      SourceLocation TemplateNameLocation = SourceLocation()) {
+    SourceLocation Location;
     TemplateDecl *TD = nullptr;
     auto PrintType = TemplateName::Qualified::AsWritten;
     switch (TN.getKind()) {
     case TemplateName::Template:
       TD = TN.getAsTemplateDecl();
-      Range = TD->getSourceRange();
+      Location = nameLocation(*TD, SM);
       break;
     case TemplateName::QualifiedTemplate:
       if (NestedNameSpecifier *NNS =
@@ -509,9 +510,9 @@ class TypeInlayHintLabelPartBuilder
 
     addLabel([&](llvm::raw_ostream &OS) { TN.print(OS, PP, PrintType); },
              [&] {
-               if (TemplateNameRange.isValid())
-                 return TemplateNameRange;
-               return Range;
+               if (TemplateNameLocation.isValid())
+                 return TemplateNameLocation;
+               return Location;
              });
 
     addLabel("<");
@@ -537,11 +538,13 @@ class TypeInlayHintLabelPartBuilder
 
 public:
   TypeInlayHintLabelPartBuilder(QualType Current, ASTContext &Context,
+                                StringRef MainFilePath,
                                 const PrintingPolicy &PP,
                                 llvm::StringRef Prefix,
                                 std::vector<InlayHintLabelPart> &LabelChunks)
       : CurrentType(Current), CurrentNestedNameSpecifier(nullptr),
-        Context(Context), PP(PP), LabelChunks(LabelChunks) {
+        Context(Context), MainFilePath(MainFilePath), PP(PP),
+        SM(Context.getSourceManager()), LabelChunks(LabelChunks) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());
@@ -559,10 +562,16 @@ class TypeInlayHintLabelPartBuilder
         RD && !RD->getTemplateInstantiationPattern())
       return addLabel(
           [&](llvm::raw_ostream &OS) { return RD->printName(OS, PP); },
-          [&] { return RD->getSourceRange(); });
+          [&] { return nameLocation(*RD, SM); });
     return VisitType(TT);
   }
 
+  void VisitEnumType(const EnumType *ET) {
+    return addLabel(
+        [&](llvm::raw_ostream &OS) { return ET->getDecl()->printName(OS, PP); },
+        [&] { return nameLocation(*ET->getDecl(), SM); });
+  }
+
   void VisitAutoType(const AutoType *AT) {
     if (!AT->isDeduced() || AT->getDeducedType()->isDecltypeType())
       return;
@@ -631,25 +640,25 @@ class TypeInlayHintLabelPartBuilder
              [&] {
                BaseUsingDecl *Introducer = UT->getFoundDecl()->getIntroducer();
                if (auto *UD = dyn_cast<UsingDecl>(Introducer))
-                 return UD->getSourceRange();
-               return Introducer->getSourceRange();
+                 return nameLocation(*UD, SM);
+               return nameLocation(*Introducer, SM);
              });
   }
 
   void VisitTypedefType(const TypedefType *TT) {
     addLabel([&](llvm::raw_ostream &OS) { TT->getDecl()->printName(OS); },
-             [&] { return TT->getDecl()->getSourceRange(); });
+             [&] { return nameLocation(*TT->getDecl(), SM); });
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
     maybeAddQualifiers();
-    SourceRange Range;
+    SourceLocation Location;
     if (auto *Specialization =
             dyn_cast_if_present<ClassTemplateSpecializationDecl>(
                 TST->desugar().getCanonicalType()->getAsCXXRecordDecl()))
-      Range = Specialization->getSourceRange();
+      Location = nameLocation(*Specialization, SM);
     return handleTemplateSpecialization(TST->getTemplateName(),
-                                        TST->template_arguments(), Range);
+                                        TST->template_arguments(), Location);
   }
 
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
@@ -680,7 +689,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
       : Results(Results), AST(AST.getASTContext()), Tokens(AST.getTokens()),
         Cfg(Cfg), RestrictRange(std::move(RestrictRange)),
         MainFileID(AST.getSourceManager().getMainFileID()),
-        Resolver(AST.getHeuristicResolver()),
+        MainFilePath(AST.tuPath()), Resolver(AST.getHeuristicResolver()),
         TypeHintPolicy(this->AST.getPrintingPolicy()) {
     bool Invalid = false;
     llvm::StringRef Buf =
@@ -1339,15 +1348,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
     // type in other cases.
     auto Desugared = maybeDesugar(AST, T);
     std::vector<InlayHintLabelPart> Chunks;
-    TypeInlayHintLabelPartBuilder Builder(Desugared, AST, TypeHintPolicy,
-                                          Prefix, Chunks);
+    TypeInlayHintLabelPartBuilder Builder(Desugared, AST, MainFilePath,
+                                          TypeHintPolicy, Prefix, Chunks);
     Builder.Visit(Desugared.getTypePtr());
     if (T != Desugared && !shouldPrintTypeHint(Chunks)) {
       // If the desugared type is too long to display, fallback to the sugared
       // type.
       Chunks.clear();
-      TypeInlayHintLabelPartBuilder Builder(T, AST, TypeHintPolicy, Prefix,
-                                            Chunks);
+      TypeInlayHintLabelPartBuilder Builder(T, AST, MainFilePath,
+                                            TypeHintPolicy, Prefix, Chunks);
       Builder.Visit(T.getTypePtr());
     }
     if (shouldPrintTypeHint(Chunks))
@@ -1452,6 +1461,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
   std::optional<Range> RestrictRange;
   FileID MainFileID;
   StringRef MainFileBuf;
+  StringRef MainFilePath;
   const HeuristicResolver *Resolver;
   PrintingPolicy TypeHintPolicy;
 };
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index f94cadeffaa29..b012edec9a8ff 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -209,29 +209,6 @@ getDeclAtPosition(ParsedAST &AST, SourceLocation Pos, DeclRelationSet Relations,
   return Result;
 }
 
-// Expects Loc to be a SpellingLocation, will bail out otherwise as it can't
-// figure out a filename.
-std::optional<Location> makeLocation(const ASTContext &AST, SourceLocation Loc,
-                                     llvm::StringRef TUPath) {
-  const auto &SM = AST.getSourceManager();
-  const auto F = SM.getFileEntryRefForID(SM.getFileID(Loc));
-  if (!F)
-    return std::nullopt;
-  auto FilePath = getCanonicalPath(*F, SM.getFileManager());
-  if (!FilePath) {
-    log("failed to get path!");
-    return std::nullopt;
-  }
-  Location L;
-  L.uri = URIForFile::canonicalize(*FilePath, TUPath);
-  // We call MeasureTokenLength here as TokenBuffer doesn't store spelled tokens
-  // outside the main file.
-  auto TokLen = Lexer::MeasureTokenLength(Loc, SM, AST.getLangOpts());
-  L.range = halfOpenToRange(
-      SM, CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(TokLen)));
-  return L;
-}
-
 // Treat #included files as symbols, to enable go-to-definition on them.
 std::optional<LocatedSymbol> locateFileReferent(const Position &Pos,
                                                 ParsedAST &AST,
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 6e596b11b9a5d..2bd2130e42907 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1702,35 +1702,35 @@ void assertTypeLinkHints(StringRef Code, StringRef HintRange,
 
 TEST(TypeHints, Links) {
   StringRef Source(R"cpp(
-    $Package[[template <class T, class U>
-    struct Package {]]};
+    template <class T, class U>
+    struct $Package[[Package]] {};
 
-    $SpecializationOfPackage[[template <>
-    struct Package<float, const int> {]]};
+    template <>
+    struct $SpecializationOfPackage[[Package]]<float, const int> {};
 
-    $Container[[template <class... T>
-    struct Container {]]};
+    template <class... T>
+    struct $Container[[Container]] {};
 
-    $NttpContainer[[template <auto... T>
-    struct NttpContainer {]]};
+    template <auto... T>
+    struct $NttpContainer[[NttpContainer]] {};
 
-    enum struct ScopedEnum {
+    enum struct $ScopedEnum[[ScopedEnum]] {
       X = 1,
     };
 
-    enum Enum {
+    enum $Enum[[Enum]] {
       E = 2,
     };
 
     namespace ns {
-      $Nested[[template <class T>
-      struct Nested {
-        $NestedClass[[template <class U>
-        struct ]]Class {
+      template <class T>
+      struct $Nested[[Nested]] {
+        template <class U>
+        struct $NestedClass[[Class]] {
         };
-      ]]};
+      };
 
-      $NestedInt[[using NestedInt = Nested<int]]>;
+      using $NestedInt[[NestedInt]] = Nested<int>;
     }
 
     void basic() {
@@ -1748,7 +1748,7 @@ TEST(TypeHints, Links) {
     }
 
     namespace nns {
-      $UsingShadow[[using ns::]]NestedInt;
+      using ns::$UsingShadow[[NestedInt]];
 
       void aliases() {
         auto $9[[A]] = Container<NestedInt>();
@@ -1756,6 +1756,8 @@ TEST(TypeHints, Links) {
       }
     }
 
+    auto $11[[A]] = Container<Enum, ScopedEnum>();
+
   )cpp");
   assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"Container", "Container"},
@@ -1828,6 +1830,14 @@ TEST(TypeHints, Links) {
                       ExpectedHintLabelPiece{"<ns::"},
                       ExpectedHintLabelPiece{"NestedInt", "NestedInt"},
                       ExpectedHintLabelPiece{">"});
+
+  assertTypeLinkHints(Source, "11", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"Container", "Container"},
+                      ExpectedHintLabelPiece{"<"},
+                      ExpectedHintLabelPiece{"Enum", "Enum"},
+                      ExpectedHintLabelPiece{", "},
+                      ExpectedHintLabelPiece{"ScopedEnum", "ScopedEnum"},
+                      ExpectedHintLabelPiece{">"});
 }
 
 TEST(DesignatorHints, Basic) {

>From 38662163231d958db7de22bfffa939653b4d9015 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 16 Jun 2024 23:00:33 +0800
Subject: [PATCH 16/36] Remove unused toLocation function

---
 clang-tools-extra/clangd/InlayHints.cpp | 15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index e0fef5758ef02..0594fa74ad665 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -374,21 +374,6 @@ maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
   return Params;
 }
 
-std::optional<Location> toLocation(SourceManager &SM, SourceRange Range) {
-  if (Range.isInvalid())
-    return std::nullopt;
-  if (auto URI =
-          toURI(SM.getFileEntryRefForID(SM.getFileID(Range.getBegin())))) {
-    Location L;
-    L.range.start = sourceLocToPosition(SM, Range.getBegin());
-    L.range.end = sourceLocToPosition(SM, Range.getEnd());
-    if (auto File = URIForFile::fromURI(*URI, ""))
-      L.uri = File.get();
-    return L;
-  }
-  return std::nullopt;
-}
-
 class TypeInlayHintLabelPartBuilder
     : public TypeVisitor<TypeInlayHintLabelPartBuilder> {
   QualType CurrentType;

>From 65493f0885cf8d8653e15f83c74b9c9609781bdd Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 7 Jul 2024 22:03:33 +0800
Subject: [PATCH 17/36] Address some nits

---
 clang-tools-extra/clangd/AST.cpp        |  2 --
 clang-tools-extra/clangd/AST.h          |  3 ++
 clang-tools-extra/clangd/InlayHints.cpp | 37 ++++++++++---------------
 3 files changed, 17 insertions(+), 25 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index a634438edaef8..dd645861dda8b 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -175,8 +175,6 @@ SourceLocation nameLocation(const clang::Decl &D, const SourceManager &SM) {
   return SM.getExpansionLoc(L);
 }
 
-// Expects Loc to be a SpellingLocation, will bail out otherwise as it can't
-// figure out a filename.
 std::optional<Location> makeLocation(const ASTContext &AST, SourceLocation Loc,
                                      llvm::StringRef TUPath) {
   const auto &SM = AST.getSourceManager();
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index e048815b2870b..f56759656997b 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -45,6 +45,9 @@ bool isImplementationDetail(const Decl *D);
 /// this function.
 SourceLocation nameLocation(const clang::Decl &D, const SourceManager &SM);
 
+/// Convert a \p SourceLocation to an LSP \p Location.
+/// Expects Loc to be a SpellingLocation, will bail out otherwise as it can't
+/// figure out a filename.
 std::optional<Location> makeLocation(const ASTContext &AST, SourceLocation Loc,
                                      llvm::StringRef TUPath);
 
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 0594fa74ad665..71d3fdd814cef 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -374,8 +374,7 @@ maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
   return Params;
 }
 
-class TypeInlayHintLabelPartBuilder
-    : public TypeVisitor<TypeInlayHintLabelPartBuilder> {
+class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   QualType CurrentType;
   NestedNameSpecifier *CurrentNestedNameSpecifier;
   ASTContext &Context;
@@ -385,10 +384,10 @@ class TypeInlayHintLabelPartBuilder
   std::vector<InlayHintLabelPart> &LabelChunks;
 
   struct CurrentTypeRAII {
-    TypeInlayHintLabelPartBuilder &Builder;
+    TypeHintBuilder &Builder;
     QualType PreviousType;
     NestedNameSpecifier *PreviousNestedNameSpecifier;
-    CurrentTypeRAII(TypeInlayHintLabelPartBuilder &Builder, QualType New,
+    CurrentTypeRAII(TypeHintBuilder &Builder, QualType New,
                     NestedNameSpecifier *NNS = nullptr)
         : Builder(Builder), PreviousType(Builder.CurrentType),
           PreviousNestedNameSpecifier(Builder.CurrentNestedNameSpecifier) {
@@ -522,11 +521,9 @@ class TypeInlayHintLabelPartBuilder
   }
 
 public:
-  TypeInlayHintLabelPartBuilder(QualType Current, ASTContext &Context,
-                                StringRef MainFilePath,
-                                const PrintingPolicy &PP,
-                                llvm::StringRef Prefix,
-                                std::vector<InlayHintLabelPart> &LabelChunks)
+  TypeHintBuilder(QualType Current, ASTContext &Context, StringRef MainFilePath,
+                  const PrintingPolicy &PP, llvm::StringRef Prefix,
+                  std::vector<InlayHintLabelPart> &LabelChunks)
       : CurrentType(Current), CurrentNestedNameSpecifier(nullptr),
         Context(Context), MainFilePath(MainFilePath), PP(PP),
         SM(Context.getSourceManager()), LabelChunks(LabelChunks) {
@@ -578,7 +575,8 @@ class TypeInlayHintLabelPartBuilder
         llvm::raw_string_ostream OS(Label);
         NNS->print(OS, PP);
         addLabel(std::move(Label));
-      } break;
+        break;
+      }
       case NestedNameSpecifier::TypeSpec:
       case NestedNameSpecifier::TypeSpecWithTemplate:
         CurrentTypeRAII Guard(
@@ -653,7 +651,7 @@ class TypeInlayHintLabelPartBuilder
   }
 };
 
-unsigned lengthOfInlayHintLabelPart(llvm::ArrayRef<InlayHintLabelPart> Labels) {
+unsigned lengthOfInlayHintLabel(llvm::ArrayRef<InlayHintLabelPart> Labels) {
   unsigned Size = 0;
   for (auto &P : Labels)
     Size += P.value.size();
@@ -1286,12 +1284,6 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
       return;
     bool PadLeft = Prefix.consume_front(" ");
     bool PadRight = Suffix.consume_back(" ");
-    if (Prefix.empty() && Suffix.empty()) {
-      Results.push_back(InlayHint{LSPPos,
-                                  /*label=*/Labels, Kind, PadLeft, PadRight,
-                                  LSPRange});
-      return;
-    }
     if (!Prefix.empty()) {
       if (auto &Label = Labels.front(); !Label.location)
         Label.value = Prefix.str() + Label.value;
@@ -1333,15 +1325,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
     // type in other cases.
     auto Desugared = maybeDesugar(AST, T);
     std::vector<InlayHintLabelPart> Chunks;
-    TypeInlayHintLabelPartBuilder Builder(Desugared, AST, MainFilePath,
-                                          TypeHintPolicy, Prefix, Chunks);
+    TypeHintBuilder Builder(Desugared, AST, MainFilePath, TypeHintPolicy,
+                            Prefix, Chunks);
     Builder.Visit(Desugared.getTypePtr());
     if (T != Desugared && !shouldPrintTypeHint(Chunks)) {
       // If the desugared type is too long to display, fallback to the sugared
       // type.
       Chunks.clear();
-      TypeInlayHintLabelPartBuilder Builder(T, AST, MainFilePath,
-                                            TypeHintPolicy, Prefix, Chunks);
+      TypeHintBuilder Builder(T, AST, MainFilePath, TypeHintPolicy, Prefix,
+                              Chunks);
       Builder.Visit(T.getTypePtr());
     }
     if (shouldPrintTypeHint(Chunks))
@@ -1359,8 +1351,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
   bool shouldPrintTypeHint(
       llvm::ArrayRef<InlayHintLabelPart> TypeLabels) const noexcept {
     return Cfg.InlayHints.TypeNameLimit == 0 ||
-           lengthOfInlayHintLabelPart(TypeLabels) <
-               Cfg.InlayHints.TypeNameLimit;
+           lengthOfInlayHintLabel(TypeLabels) < Cfg.InlayHints.TypeNameLimit;
   }
 
   void addBlockEndHint(SourceRange BraceRange, StringRef DeclPrefix,

>From 1195d25e1c96bb8547d3630b2d509624c8c1e399 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 7 Jul 2024 22:10:16 +0800
Subject: [PATCH 18/36] Restructure the logic in addTypeHint()

---
 clang-tools-extra/clangd/InlayHints.cpp | 28 +++++++++++++++++--------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 71d3fdd814cef..04e94bcc6774d 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -1317,6 +1317,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
                  sourceLocToPosition(SM, Spelled->back().endLocation())};
   }
 
+  std::vector<InlayHintLabelPart> buildTypeHint(QualType T,
+                                                llvm::StringRef Prefix) {
+    std::vector<InlayHintLabelPart> Chunks;
+    TypeHintBuilder Builder(T, AST, MainFilePath, TypeHintPolicy, Prefix,
+                            Chunks);
+    Builder.Visit(T.getTypePtr());
+    return Chunks;
+  }
+
   void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) {
     if (!Cfg.InlayHints.DeducedTypes || T.isNull())
       return;
@@ -1324,17 +1333,18 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
     // The sugared type is more useful in some cases, and the canonical
     // type in other cases.
     auto Desugared = maybeDesugar(AST, T);
-    std::vector<InlayHintLabelPart> Chunks;
-    TypeHintBuilder Builder(Desugared, AST, MainFilePath, TypeHintPolicy,
-                            Prefix, Chunks);
-    Builder.Visit(Desugared.getTypePtr());
-    if (T != Desugared && !shouldPrintTypeHint(Chunks)) {
+    auto Chunks = buildTypeHint(Desugared, Prefix);
+    if (T != Desugared) {
+      if (shouldPrintTypeHint(Chunks)) {
+        addInlayHint(R, HintSide::Right, InlayHintKind::Type,
+                     /*Prefix=*/"", // We have handled prefixes in the builder.
+                     std::move(Chunks),
+                     /*Suffix=*/"");
+        return;
+      }
       // If the desugared type is too long to display, fallback to the sugared
       // type.
-      Chunks.clear();
-      TypeHintBuilder Builder(T, AST, MainFilePath, TypeHintPolicy, Prefix,
-                              Chunks);
-      Builder.Visit(T.getTypePtr());
+      Chunks = buildTypeHint(T, Prefix);
     }
     if (shouldPrintTypeHint(Chunks))
       addInlayHint(R, HintSide::Right, InlayHintKind::Type,

>From e2b843c44ff252aea586118214e370d61bc647b1 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 7 Jul 2024 23:31:57 +0800
Subject: [PATCH 19/36] Special case ClassTemplateSpecializationDecl for
 forward declarations

---
 clang-tools-extra/clangd/InlayHints.cpp       | 32 ++++++++++++++-----
 .../clangd/unittests/InlayHintTests.cpp       | 29 +++++++++++++++++
 2 files changed, 53 insertions(+), 8 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 04e94bcc6774d..67e16673b1123 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -402,13 +402,13 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   };
 
   void addLabel(llvm::function_ref<void(llvm::raw_ostream &)> NamePrinter,
-                llvm::function_ref<SourceLocation()> SourceLocationGetter) {
+                SourceLocation Location) {
     std::string Label;
     llvm::raw_string_ostream OS(Label);
     NamePrinter(OS);
     auto &Name = LabelChunks.emplace_back();
     Name.value = std::move(Label);
-    Name.location = makeLocation(Context, SourceLocationGetter(), MainFilePath);
+    Name.location = makeLocation(Context, Location, MainFilePath);
   }
 
   void addLabel(std::string Label) {
@@ -497,7 +497,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
                if (TemplateNameLocation.isValid())
                  return TemplateNameLocation;
                return Location;
-             });
+             }());
 
     addLabel("<");
     printTemplateArgumentList(Args);
@@ -544,14 +544,14 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
         RD && !RD->getTemplateInstantiationPattern())
       return addLabel(
           [&](llvm::raw_ostream &OS) { return RD->printName(OS, PP); },
-          [&] { return nameLocation(*RD, SM); });
+          [&] { return nameLocation(*RD, SM); }());
     return VisitType(TT);
   }
 
   void VisitEnumType(const EnumType *ET) {
     return addLabel(
         [&](llvm::raw_ostream &OS) { return ET->getDecl()->printName(OS, PP); },
-        [&] { return nameLocation(*ET->getDecl(), SM); });
+        [&] { return nameLocation(*ET->getDecl(), SM); }());
   }
 
   void VisitAutoType(const AutoType *AT) {
@@ -625,21 +625,37 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
                if (auto *UD = dyn_cast<UsingDecl>(Introducer))
                  return nameLocation(*UD, SM);
                return nameLocation(*Introducer, SM);
-             });
+             }());
   }
 
   void VisitTypedefType(const TypedefType *TT) {
     addLabel([&](llvm::raw_ostream &OS) { TT->getDecl()->printName(OS); },
-             [&] { return nameLocation(*TT->getDecl(), SM); });
+             [&] { return nameLocation(*TT->getDecl(), SM); }());
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
     maybeAddQualifiers();
     SourceLocation Location;
+    // Special case the ClassTemplateSpecializationDecl:
+    // 1) We want the location of an explicit specialization, if present.
+    // 2) For implicit specializations, the associated CXXRecordDecl might be
+    // pointing to a *declaration*. We want to find out the definition if
+    // possible. Unfortunately, RecordDecl::getDefinition() doesn't work
+    // straightforwardly in this case.
     if (auto *Specialization =
             dyn_cast_if_present<ClassTemplateSpecializationDecl>(
-                TST->desugar().getCanonicalType()->getAsCXXRecordDecl()))
+                TST->desugar().getCanonicalType()->getAsCXXRecordDecl())) {
       Location = nameLocation(*Specialization, SM);
+      // The template argument might be associated to a Decl whose
+      // specialization kind is TSK_Undeclared.
+      if (!Specialization->isExplicitInstantiationOrSpecialization()) {
+        auto Pattern = Specialization->getSpecializedTemplateOrPartial();
+        if (auto *Template = Pattern.dyn_cast<ClassTemplateDecl *>())
+          if (CXXRecordDecl *Definition =
+                  Template->getTemplatedDecl()->getDefinition())
+            Location = nameLocation(*Definition, SM);
+      }
+    }
     return handleTemplateSpecialization(TST->getTemplateName(),
                                         TST->template_arguments(), Location);
   }
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 327931857a660..f4f21c9224d1f 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1840,6 +1840,35 @@ TEST(TypeHints, Links) {
                       ExpectedHintLabelPiece{">"});
 }
 
+TEST(TypeHints, LinksForForwardDeclarations) {
+  StringRef Source(R"cpp(
+    template <class... T>
+    struct $Container[[Container]] {};
+
+    // Test that we always prefer the location of the definition in the
+    // presence of a forward declaration.
+    template <class... T>
+    struct Container;
+
+    struct $S[[S]] {};
+
+    struct S;
+
+    struct $W[[W]];
+
+    auto $1[[Use]] = Container<Container<S>, W>();
+  )cpp");
+
+  assertTypeLinkHints(
+      Source, "1", ExpectedHintLabelPiece{": "},
+      ExpectedHintLabelPiece{"Container", "Container"},
+      ExpectedHintLabelPiece{"<"},
+      ExpectedHintLabelPiece{"Container", "Container"},
+      ExpectedHintLabelPiece{"<"}, ExpectedHintLabelPiece{"S", "S"},
+      ExpectedHintLabelPiece{">, "}, ExpectedHintLabelPiece{"W", "W"},
+      ExpectedHintLabelPiece{">"});
+}
+
 TEST(DesignatorHints, Basic) {
   assertDesignatorHints(R"cpp(
     struct S { int x, y, z; };

>From 6930a5582ad75bc3835e53d75b4ae376fa5fe29b Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 7 Jul 2024 23:36:41 +0800
Subject: [PATCH 20/36] Cleanup more inline lambdas

---
 clang-tools-extra/clangd/InlayHints.cpp | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 67e16673b1123..0f4d3371bf178 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -464,7 +464,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void handleTemplateSpecialization(
       TemplateName TN, llvm::ArrayRef<TemplateArgument> Args,
-      SourceLocation TemplateNameLocation = SourceLocation()) {
+      SourceLocation LocationForOverride = SourceLocation()) {
     SourceLocation Location;
     TemplateDecl *TD = nullptr;
     auto PrintType = TemplateName::Qualified::AsWritten;
@@ -492,12 +492,11 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
       break;
     }
 
+    if (LocationForOverride.isValid())
+      Location = LocationForOverride;
+
     addLabel([&](llvm::raw_ostream &OS) { TN.print(OS, PP, PrintType); },
-             [&] {
-               if (TemplateNameLocation.isValid())
-                 return TemplateNameLocation;
-               return Location;
-             }());
+             Location);
 
     addLabel("<");
     printTemplateArgumentList(Args);
@@ -544,14 +543,14 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
         RD && !RD->getTemplateInstantiationPattern())
       return addLabel(
           [&](llvm::raw_ostream &OS) { return RD->printName(OS, PP); },
-          [&] { return nameLocation(*RD, SM); }());
+          nameLocation(*RD, SM));
     return VisitType(TT);
   }
 
   void VisitEnumType(const EnumType *ET) {
     return addLabel(
         [&](llvm::raw_ostream &OS) { return ET->getDecl()->printName(OS, PP); },
-        [&] { return nameLocation(*ET->getDecl(), SM); }());
+        nameLocation(*ET->getDecl(), SM));
   }
 
   void VisitAutoType(const AutoType *AT) {
@@ -630,7 +629,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitTypedefType(const TypedefType *TT) {
     addLabel([&](llvm::raw_ostream &OS) { TT->getDecl()->printName(OS); },
-             [&] { return nameLocation(*TT->getDecl(), SM); }());
+             nameLocation(*TT->getDecl(), SM));
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {

>From b89cda3ea00cb4e416c5cd4cb49823f0328702b5 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 7 Jul 2024 23:47:47 +0800
Subject: [PATCH 21/36] Remove the dead codes

---
 clang-tools-extra/clangd/InlayHints.cpp | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 0f4d3371bf178..f621356e04c77 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -535,10 +535,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitTagType(const TagType *TT) {
     auto *D = TT->getDecl();
-    if (auto *Specialization = dyn_cast<ClassTemplateSpecializationDecl>(D))
-      return handleTemplateSpecialization(
-          TemplateName(Specialization->getSpecializedTemplate()),
-          Specialization->getTemplateArgs().asArray());
     if (auto *RD = dyn_cast<CXXRecordDecl>(D);
         RD && !RD->getTemplateInstantiationPattern())
       return addLabel(

>From c6e691ee052c8f523e001876b8d6bd1907b8208a Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 8 Jul 2024 00:08:52 +0800
Subject: [PATCH 22/36] Respect PP.SuppressScope

---
 clang-tools-extra/clangd/InlayHints.cpp       |  4 ++++
 .../clangd/unittests/InlayHintTests.cpp       | 20 ++-----------------
 2 files changed, 6 insertions(+), 18 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index f621356e04c77..4972943c4270d 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -566,6 +566,8 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
       case NestedNameSpecifier::NamespaceAlias:
       case NestedNameSpecifier::Global:
       case NestedNameSpecifier::Super: {
+        if (PP.SuppressScope)
+          break;
         std::string Label;
         llvm::raw_string_ostream OS(Label);
         NNS->print(OS, PP);
@@ -574,6 +576,8 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
       }
       case NestedNameSpecifier::TypeSpec:
       case NestedNameSpecifier::TypeSpecWithTemplate:
+        if (PP.SuppressScope)
+          break;
         CurrentTypeRAII Guard(
             *this,
             QualType(
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index f4f21c9224d1f..50f584720a54f 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1312,13 +1312,7 @@ TEST(TypeHints, NoQualifiers) {
     }
   )cpp",
                   ExpectedHint{": S1", "x"},
-                  // FIXME: We want to suppress scope specifiers
-                  //        here because we are into the whole
-                  //        brevity thing, but the ElaboratedType
-                  //        printer does not honor the SuppressScope
-                  //        flag by design, so we need to extend the
-                  //        PrintingPolicy to support this use case.
-                  ExpectedHint{": S2::Inner<int>", "y"});
+                  ExpectedHint{": Inner<int>", "y"});
 }
 
 TEST(TypeHints, Lambda) {
@@ -1784,10 +1778,6 @@ TEST(TypeHints, Links) {
                       ExpectedHintLabelPiece{"<D, E, ScopedEnum::X, Enum::E>"});
 
   assertTypeLinkHints(Source, "5", ExpectedHintLabelPiece{": "},
-                      ExpectedHintLabelPiece{"ns::Nested", "Nested"},
-                      ExpectedHintLabelPiece{"<"},
-                      ExpectedHintLabelPiece{"Container", "Container"},
-                      ExpectedHintLabelPiece{"<int>>::"},
                       ExpectedHintLabelPiece{"Class", "NestedClass"},
                       ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"Package", "Package"},
@@ -1796,24 +1786,18 @@ TEST(TypeHints, Links) {
   assertTypeLinkHints(Source, "6", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<"},
-                      ExpectedHintLabelPiece{"ns::Nested", "Nested"},
-                      ExpectedHintLabelPiece{"<int>::"},
                       ExpectedHintLabelPiece{"Class", "NestedClass"},
                       ExpectedHintLabelPiece{"<float> &>"});
 
   assertTypeLinkHints(Source, "7", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<"},
-                      ExpectedHintLabelPiece{"ns::Nested", "Nested"},
-                      ExpectedHintLabelPiece{"<int>::"},
                       ExpectedHintLabelPiece{"Class", "NestedClass"},
                       ExpectedHintLabelPiece{"<float> &&>"});
 
   assertTypeLinkHints(Source, "8", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"Container", "Container"},
                       ExpectedHintLabelPiece{"<"},
-                      ExpectedHintLabelPiece{"ns::Nested", "Nested"},
-                      ExpectedHintLabelPiece{"<int>::"},
                       ExpectedHintLabelPiece{"Class", "NestedClass"},
                       ExpectedHintLabelPiece{"<const "},
                       ExpectedHintLabelPiece{"Container", "Container"},
@@ -1827,7 +1811,7 @@ TEST(TypeHints, Links) {
 
   assertTypeLinkHints(Source, "10", ExpectedHintLabelPiece{": "},
                       ExpectedHintLabelPiece{"Container", "Container"},
-                      ExpectedHintLabelPiece{"<ns::"},
+                      ExpectedHintLabelPiece{"<"},
                       ExpectedHintLabelPiece{"NestedInt", "NestedInt"},
                       ExpectedHintLabelPiece{">"});
 

>From 71604ef5b8cde764947652efc8701b5311d0369f Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 8 Jul 2024 00:09:24 +0800
Subject: [PATCH 23/36] clang-format

---
 clang-tools-extra/clangd/unittests/InlayHintTests.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 50f584720a54f..064556214f32b 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1311,8 +1311,7 @@ TEST(TypeHints, NoQualifiers) {
       }
     }
   )cpp",
-                  ExpectedHint{": S1", "x"},
-                  ExpectedHint{": Inner<int>", "y"});
+                  ExpectedHint{": S1", "x"}, ExpectedHint{": Inner<int>", "y"});
 }
 
 TEST(TypeHints, Lambda) {

>From fd1761a11edae15abe2d674c7477ff4a47ee4a1f Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 8 Jul 2024 16:45:00 +0800
Subject: [PATCH 24/36] Ensure go-to-def always goes to the definition

---
 clang-tools-extra/clangd/InlayHints.cpp       | 67 +++++++++++++------
 .../clangd/unittests/InlayHintTests.cpp       | 32 ++++++---
 2 files changed, 72 insertions(+), 27 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 4972943c4270d..28bcb5e6ed158 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -471,7 +471,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     switch (TN.getKind()) {
     case TemplateName::Template:
       TD = TN.getAsTemplateDecl();
-      Location = nameLocation(*TD, SM);
+      Location = nameLocation(TD, SM);
       break;
     case TemplateName::QualifiedTemplate:
       if (NestedNameSpecifier *NNS =
@@ -519,6 +519,35 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     return T;
   }
 
+  static SourceLocation nameLocation(Decl *D, const SourceManager &SM) {
+    // If this is a definition, find its *forward declaration* if possible.
+    //
+    // Per LSP specification, code actions, e.g., hover/go-to-def on the type
+    // link, would be performed as if at the location we have given.
+    //
+    // Therefore, we should provide the type part with a location that points to
+    // its declaration because we would otherwise take users to the
+    // *declaration* if they're at the definition.
+    if (auto *TD = dyn_cast<TemplateDecl>(D))
+      D = TD->getTemplatedDecl();
+    bool IsDefinition =
+        isa<TagDecl>(D) && cast<TagDecl>(D)->isThisDeclarationADefinition();
+    if (IsDefinition) {
+      // Happy path: if the canonical declaration is a forward declaration.
+      if (!cast<TagDecl>(D)->getCanonicalDecl()->isThisDeclarationADefinition())
+        D = D->getCanonicalDecl();
+      else {
+        // Otherwise, look through the redeclarations.
+        for (auto *Redecl : D->redecls())
+          if (!cast<TagDecl>(Redecl)->isThisDeclarationADefinition()) {
+            D = Redecl;
+            break;
+          }
+      }
+    }
+    return ::clang::clangd::nameLocation(*D, SM);
+  }
+
 public:
   TypeHintBuilder(QualType Current, ASTContext &Context, StringRef MainFilePath,
                   const PrintingPolicy &PP, llvm::StringRef Prefix,
@@ -539,14 +568,14 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
         RD && !RD->getTemplateInstantiationPattern())
       return addLabel(
           [&](llvm::raw_ostream &OS) { return RD->printName(OS, PP); },
-          nameLocation(*RD, SM));
+          nameLocation(RD, SM));
     return VisitType(TT);
   }
 
   void VisitEnumType(const EnumType *ET) {
     return addLabel(
         [&](llvm::raw_ostream &OS) { return ET->getDecl()->printName(OS, PP); },
-        nameLocation(*ET->getDecl(), SM));
+        nameLocation(ET->getDecl(), SM));
   }
 
   void VisitAutoType(const AutoType *AT) {
@@ -622,38 +651,38 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
              [&] {
                BaseUsingDecl *Introducer = UT->getFoundDecl()->getIntroducer();
                if (auto *UD = dyn_cast<UsingDecl>(Introducer))
-                 return nameLocation(*UD, SM);
-               return nameLocation(*Introducer, SM);
+                 return nameLocation(UD, SM);
+               return nameLocation(Introducer, SM);
              }());
   }
 
   void VisitTypedefType(const TypedefType *TT) {
     addLabel([&](llvm::raw_ostream &OS) { TT->getDecl()->printName(OS); },
-             nameLocation(*TT->getDecl(), SM));
+             nameLocation(TT->getDecl(), SM));
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
     maybeAddQualifiers();
     SourceLocation Location;
-    // Special case the ClassTemplateSpecializationDecl:
-    // 1) We want the location of an explicit specialization, if present.
-    // 2) For implicit specializations, the associated CXXRecordDecl might be
-    // pointing to a *declaration*. We want to find out the definition if
-    // possible. Unfortunately, RecordDecl::getDefinition() doesn't work
-    // straightforwardly in this case.
+    // Special case the ClassTemplateSpecializationDecl because
+    // we want the location of an explicit specialization, if present.
+    // FIXME: In practice, populating the location with that of the
+    // specialization would still take us to the primary template because we're
+    // actually sending a go-to-def request there.
     if (auto *Specialization =
             dyn_cast_if_present<ClassTemplateSpecializationDecl>(
                 TST->desugar().getCanonicalType()->getAsCXXRecordDecl())) {
-      Location = nameLocation(*Specialization, SM);
-      // The template argument might be associated to a Decl whose
-      // specialization kind is TSK_Undeclared.
+      // Quirk as it is, the Specialization might have no associated forward
+      // declarations. So we have to find them through the Pattern.
       if (!Specialization->isExplicitInstantiationOrSpecialization()) {
         auto Pattern = Specialization->getSpecializedTemplateOrPartial();
         if (auto *Template = Pattern.dyn_cast<ClassTemplateDecl *>())
-          if (CXXRecordDecl *Definition =
-                  Template->getTemplatedDecl()->getDefinition())
-            Location = nameLocation(*Definition, SM);
-      }
+          Location = nameLocation(Template, SM);
+        if (auto *Template =
+                Pattern.dyn_cast<ClassTemplatePartialSpecializationDecl *>())
+          Location = nameLocation(Template, SM);
+      } else
+        Location = nameLocation(Specialization, SM);
     }
     return handleTemplateSpecialization(TST->getTemplateName(),
                                         TST->template_arguments(), Location);
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 064556214f32b..230aa6ca3c8ca 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1824,29 +1824,45 @@ TEST(TypeHints, Links) {
 }
 
 TEST(TypeHints, LinksForForwardDeclarations) {
+  // Test that we always prefer the location of the forward declaration in the
+  // presence of it. This way, the LSP client would take us to the definition.
   StringRef Source(R"cpp(
     template <class... T>
-    struct $Container[[Container]] {};
+    struct Container {};
 
-    // Test that we always prefer the location of the definition in the
-    // presence of a forward declaration.
     template <class... T>
-    struct Container;
+    struct $Container[[Container]];
+
+    // The order of occurrence matters.
+    template <class>
+    struct $optional[[optional]];
+
+    template <class>
+    struct optional {};
+
+    // FIXME: go-to-def on `optional<int>` would take us to its primary template.
+    template <>
+    struct optional<int> {};
 
-    struct $S[[S]] {};
+    template <>
+    struct optional<int>;
+
+    struct S {};
 
-    struct S;
+    struct $S[[S]];
 
     struct $W[[W]];
 
-    auto $1[[Use]] = Container<Container<S>, W>();
+    struct W {};
+
+    auto $1[[Use]] = Container<optional<S>, W>();
   )cpp");
 
   assertTypeLinkHints(
       Source, "1", ExpectedHintLabelPiece{": "},
       ExpectedHintLabelPiece{"Container", "Container"},
       ExpectedHintLabelPiece{"<"},
-      ExpectedHintLabelPiece{"Container", "Container"},
+      ExpectedHintLabelPiece{"optional", "optional"},
       ExpectedHintLabelPiece{"<"}, ExpectedHintLabelPiece{"S", "S"},
       ExpectedHintLabelPiece{">, "}, ExpectedHintLabelPiece{"W", "W"},
       ExpectedHintLabelPiece{">"});

>From 57efdd4b42d698c7d8c370e187803413fe97fd17 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 8 Jul 2024 18:14:07 +0800
Subject: [PATCH 25/36] Handle links for structure bindings

---
 clang-tools-extra/clangd/InlayHints.cpp       | 83 +++++++++++++------
 .../clangd/unittests/InlayHintTests.cpp       | 14 ++++
 2 files changed, 70 insertions(+), 27 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 28bcb5e6ed158..480ffb2c531a7 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -381,7 +381,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   StringRef MainFilePath;
   const PrintingPolicy &PP;
   SourceManager &SM;
-  std::vector<InlayHintLabelPart> &LabelChunks;
+  std::vector<InlayHintLabelPart> LabelChunks;
 
   struct CurrentTypeRAII {
     TypeHintBuilder &Builder;
@@ -495,6 +495,8 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     if (LocationForOverride.isValid())
       Location = LocationForOverride;
 
+    assert(Location.isValid() || LocationForOverride.isValid());
+
     addLabel([&](llvm::raw_ostream &OS) { TN.print(OS, PP, PrintType); },
              Location);
 
@@ -548,13 +550,29 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     return ::clang::clangd::nameLocation(*D, SM);
   }
 
+  SourceLocation getPreferredLocationFromSpecialization(
+      ClassTemplateSpecializationDecl *Specialization) const {
+    SourceLocation Location;
+    // Quirk as it is, the Specialization might have no associated forward
+    // declarations. So we have to find them through the Pattern.
+    if (!Specialization->isExplicitInstantiationOrSpecialization()) {
+      auto Pattern = Specialization->getSpecializedTemplateOrPartial();
+      if (auto *Template = Pattern.dyn_cast<ClassTemplateDecl *>())
+        Location = nameLocation(Template, SM);
+      if (auto *Template =
+              Pattern.dyn_cast<ClassTemplatePartialSpecializationDecl *>())
+        Location = nameLocation(Template, SM);
+    } else
+      Location = nameLocation(Specialization, SM);
+    return Location;
+  }
+
 public:
   TypeHintBuilder(QualType Current, ASTContext &Context, StringRef MainFilePath,
-                  const PrintingPolicy &PP, llvm::StringRef Prefix,
-                  std::vector<InlayHintLabelPart> &LabelChunks)
+                  const PrintingPolicy &PP, llvm::StringRef Prefix)
       : CurrentType(Current), CurrentNestedNameSpecifier(nullptr),
         Context(Context), MainFilePath(MainFilePath), PP(PP),
-        SM(Context.getSourceManager()), LabelChunks(LabelChunks) {
+        SM(Context.getSourceManager()) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());
@@ -563,12 +581,34 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   void VisitType(const Type *) { addLabel(CurrentType.getAsString(PP)); }
 
   void VisitTagType(const TagType *TT) {
-    auto *D = TT->getDecl();
-    if (auto *RD = dyn_cast<CXXRecordDecl>(D);
-        RD && !RD->getTemplateInstantiationPattern())
+    // Note that we have cases where the type of a template specialization is
+    // modeled as a RecordType rather than a TemplateSpecializationType. (Type
+    // sugars are not preserved?)
+    // Example:
+    //
+    // template <typename, typename = int>
+    // struct A {};
+    // A<float> bar[1];
+    //
+    // auto [value] = bar;
+    //
+    // The type of value is modeled as a RecordType here.
+    auto *CXXRD = dyn_cast<CXXRecordDecl>(TT->getDecl());
+    if (!CXXRD)
+      return VisitType(TT);
+    CXXRecordDecl *Pattern = CXXRD->getTemplateInstantiationPattern();
+    if (!Pattern)
       return addLabel(
-          [&](llvm::raw_ostream &OS) { return RD->printName(OS, PP); },
-          nameLocation(RD, SM));
+          [&](llvm::raw_ostream &OS) { return CXXRD->printName(OS, PP); },
+          nameLocation(CXXRD, SM));
+
+    // FIXME: Do we have other kind of specializations?
+    if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD))
+      return handleTemplateSpecialization(
+          TemplateName(Pattern->getDescribedClassTemplate()),
+          CTSD->getTemplateArgs().asArray(),
+          getPreferredLocationFromSpecialization(CTSD));
+
     return VisitType(TT);
   }
 
@@ -668,22 +708,11 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     // we want the location of an explicit specialization, if present.
     // FIXME: In practice, populating the location with that of the
     // specialization would still take us to the primary template because we're
-    // actually sending a go-to-def request there.
+    // actually sending a go-to-def request from the explicit specialization.
     if (auto *Specialization =
             dyn_cast_if_present<ClassTemplateSpecializationDecl>(
-                TST->desugar().getCanonicalType()->getAsCXXRecordDecl())) {
-      // Quirk as it is, the Specialization might have no associated forward
-      // declarations. So we have to find them through the Pattern.
-      if (!Specialization->isExplicitInstantiationOrSpecialization()) {
-        auto Pattern = Specialization->getSpecializedTemplateOrPartial();
-        if (auto *Template = Pattern.dyn_cast<ClassTemplateDecl *>())
-          Location = nameLocation(Template, SM);
-        if (auto *Template =
-                Pattern.dyn_cast<ClassTemplatePartialSpecializationDecl *>())
-          Location = nameLocation(Template, SM);
-      } else
-        Location = nameLocation(Specialization, SM);
-    }
+                TST->desugar().getCanonicalType()->getAsCXXRecordDecl()))
+      Location = getPreferredLocationFromSpecialization(Specialization);
     return handleTemplateSpecialization(TST->getTemplateName(),
                                         TST->template_arguments(), Location);
   }
@@ -693,6 +722,8 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     CurrentTypeRAII Guard(*this, ST->getReplacementType());
     return Visit(ST->getReplacementType().getTypePtr());
   }
+
+  std::vector<InlayHintLabelPart> take() { return std::move(LabelChunks); }
 };
 
 unsigned lengthOfInlayHintLabel(llvm::ArrayRef<InlayHintLabelPart> Labels) {
@@ -1363,11 +1394,9 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
 
   std::vector<InlayHintLabelPart> buildTypeHint(QualType T,
                                                 llvm::StringRef Prefix) {
-    std::vector<InlayHintLabelPart> Chunks;
-    TypeHintBuilder Builder(T, AST, MainFilePath, TypeHintPolicy, Prefix,
-                            Chunks);
+    TypeHintBuilder Builder(T, AST, MainFilePath, TypeHintPolicy, Prefix);
     Builder.Visit(T.getTypePtr());
-    return Chunks;
+    return Builder.take();
   }
 
   void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) {
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 230aa6ca3c8ca..dcfbaa35b2872 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1868,6 +1868,20 @@ TEST(TypeHints, LinksForForwardDeclarations) {
       ExpectedHintLabelPiece{">"});
 }
 
+TEST(TypeHints, LinksForStructureBindings) {
+  StringRef Source(R"cpp(
+  template <typename>
+  struct $A[[A]] {};
+  A<float> bar[1];
+
+  auto [$1[[value]]] = bar;
+  )cpp");
+
+  assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"A", "A"},
+                      ExpectedHintLabelPiece{"<float>"});
+}
+
 TEST(DesignatorHints, Basic) {
   assertDesignatorHints(R"cpp(
     struct S { int x, y, z; };

>From 2973958743d6337cdba84f882efdcd3031d079c6 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 8 Jul 2024 18:31:59 +0800
Subject: [PATCH 26/36] Almost there!

---
 clang-tools-extra/clangd/InlayHints.cpp | 63 +++++++++++--------------
 1 file changed, 28 insertions(+), 35 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 480ffb2c531a7..4df0be7492a7f 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -441,10 +441,8 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     switch (TA.getKind()) {
     case TemplateArgument::Pack:
       return printTemplateArgumentList(TA.pack_elements());
-    case TemplateArgument::Type: {
-      CurrentTypeRAII Guard(*this, TA.getAsType());
-      return Visit(TA.getAsType().getTypePtr());
-    }
+    case TemplateArgument::Type:
+      return VisitQualType(TA.getAsType());
     // TODO: Add support for NTTP arguments.
     case TemplateArgument::Expression:
     case TemplateArgument::StructuralValue:
@@ -568,11 +566,10 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
 public:
-  TypeHintBuilder(QualType Current, ASTContext &Context, StringRef MainFilePath,
+  TypeHintBuilder(ASTContext &Context, StringRef MainFilePath,
                   const PrintingPolicy &PP, llvm::StringRef Prefix)
-      : CurrentType(Current), CurrentNestedNameSpecifier(nullptr),
-        Context(Context), MainFilePath(MainFilePath), PP(PP),
-        SM(Context.getSourceManager()) {
+      : CurrentNestedNameSpecifier(nullptr), Context(Context),
+        MainFilePath(MainFilePath), PP(PP), SM(Context.getSourceManager()) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());
@@ -580,6 +577,18 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitType(const Type *) { addLabel(CurrentType.getAsString(PP)); }
 
+  void VisitQualType(QualType Q, NestedNameSpecifier *NNS = nullptr) {
+    QualType PreviousType = CurrentType;
+    NestedNameSpecifier *PreviousNNS = CurrentNestedNameSpecifier;
+    CurrentType = Q;
+    CurrentNestedNameSpecifier = NNS;
+
+    TypeVisitor::Visit(Q.getTypePtr());
+
+    CurrentType = PreviousType;
+    CurrentNestedNameSpecifier = PreviousNNS;
+  }
+
   void VisitTagType(const TagType *TT) {
     // Note that we have cases where the type of a template specialization is
     // modeled as a RecordType rather than a TemplateSpecializationType. (Type
@@ -620,10 +629,9 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitAutoType(const AutoType *AT) {
     if (!AT->isDeduced() || AT->getDeducedType()->isDecltypeType())
-      return;
+      return VisitType(AT);
     maybeAddQualifiers();
-    CurrentTypeRAII Guard(*this, AT->getDeducedType());
-    return Visit(AT->getDeducedType().getTypePtr());
+    return VisitQualType(AT->getDeducedType());
   }
 
   void VisitElaboratedType(const ElaboratedType *ET) {
@@ -647,26 +655,19 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
       case NestedNameSpecifier::TypeSpecWithTemplate:
         if (PP.SuppressScope)
           break;
-        CurrentTypeRAII Guard(
-            *this,
-            QualType(
-                NNS->getAsType(),
-                /*Quals=*/0) // Do we need cv-qualifiers on type specifiers?
-        );
-        Visit(NNS->getAsType());
+        // Do we need cv-qualifiers on type specifiers?
+        VisitQualType(QualType(NNS->getAsType(), /*Quals=*/0));
         addLabel("::");
         break;
       }
     }
-    CurrentTypeRAII Guard(*this, ET->getNamedType(), ET->getQualifier());
-    return Visit(ET->getNamedType().getTypePtr());
+    return VisitQualType(ET->getNamedType(), ET->getQualifier());
   }
 
   void VisitReferenceType(const ReferenceType *RT) {
     maybeAddQualifiers();
     QualType Next = skipTopLevelReferences(RT->getPointeeTypeAsWritten());
-    CurrentTypeRAII Guard(*this, Next);
-    Visit(Next.getTypePtr());
+    VisitQualType(Next);
     if (Next->getPointeeType().isNull())
       addLabel(" ");
     if (RT->isLValueReferenceType())
@@ -677,23 +678,16 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitPointerType(const PointerType *PT) {
     QualType Next = PT->getPointeeType();
-    std::optional<CurrentTypeRAII> Guard(std::in_place, *this, Next);
-    Visit(Next.getTypePtr());
+    VisitQualType(Next);
     if (Next->getPointeeType().isNull())
       addLabel(" ");
     addLabel("*");
-    Guard.reset();
     maybeAddQualifiers();
   }
 
   void VisitUsingType(const UsingType *UT) {
     addLabel([&](llvm::raw_ostream &OS) { UT->getFoundDecl()->printName(OS); },
-             [&] {
-               BaseUsingDecl *Introducer = UT->getFoundDecl()->getIntroducer();
-               if (auto *UD = dyn_cast<UsingDecl>(Introducer))
-                 return nameLocation(UD, SM);
-               return nameLocation(Introducer, SM);
-             }());
+             nameLocation(UT->getFoundDecl()->getIntroducer(), SM));
   }
 
   void VisitTypedefType(const TypedefType *TT) {
@@ -719,8 +713,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
     maybeAddQualifiers();
-    CurrentTypeRAII Guard(*this, ST->getReplacementType());
-    return Visit(ST->getReplacementType().getTypePtr());
+    return VisitQualType(ST->getReplacementType());
   }
 
   std::vector<InlayHintLabelPart> take() { return std::move(LabelChunks); }
@@ -1394,8 +1387,8 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
 
   std::vector<InlayHintLabelPart> buildTypeHint(QualType T,
                                                 llvm::StringRef Prefix) {
-    TypeHintBuilder Builder(T, AST, MainFilePath, TypeHintPolicy, Prefix);
-    Builder.Visit(T.getTypePtr());
+    TypeHintBuilder Builder(AST, MainFilePath, TypeHintPolicy, Prefix);
+    Builder.VisitQualType(T);
     return Builder.take();
   }
 

>From d4e3ff742abaf7915da4f317f2d12d12094bf3af Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 8 Jul 2024 18:50:07 +0800
Subject: [PATCH 27/36] Add handling for C recordTypes.

---
 clang-tools-extra/clangd/InlayHints.cpp       | 20 ++++++++++---
 .../clangd/unittests/InlayHintTests.cpp       | 28 +++++++++++++++++--
 2 files changed, 42 insertions(+), 6 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 4df0be7492a7f..449524f31dffa 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -590,6 +590,21 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitTagType(const TagType *TT) {
+    auto *CXXRD = dyn_cast<CXXRecordDecl>(TT->getDecl());
+    if (!CXXRD) {
+      // This might be a C TagDecl.
+      if (auto *RD = dyn_cast<RecordDecl>(TT->getDecl())) {
+        // FIXME: Respect SuppressTagKeyword in other cases.
+        if (!PP.SuppressTagKeyword && !RD->getTypedefNameForAnonDecl()) {
+          addLabel(RD->getKindName().str());
+          addLabel(" ");
+        }
+        return addLabel(
+            [&](llvm::raw_ostream &OS) { return RD->printName(OS, PP); },
+            nameLocation(RD, SM));
+      }
+      return VisitType(TT);
+    }
     // Note that we have cases where the type of a template specialization is
     // modeled as a RecordType rather than a TemplateSpecializationType. (Type
     // sugars are not preserved?)
@@ -602,16 +617,13 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     // auto [value] = bar;
     //
     // The type of value is modeled as a RecordType here.
-    auto *CXXRD = dyn_cast<CXXRecordDecl>(TT->getDecl());
-    if (!CXXRD)
-      return VisitType(TT);
     CXXRecordDecl *Pattern = CXXRD->getTemplateInstantiationPattern();
     if (!Pattern)
       return addLabel(
           [&](llvm::raw_ostream &OS) { return CXXRD->printName(OS, PP); },
           nameLocation(CXXRD, SM));
 
-    // FIXME: Do we have other kind of specializations?
+    // FIXME: Do we have other kinds of specializations?
     if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD))
       return handleTemplateSpecialization(
           TemplateName(Pattern->getDescribedClassTemplate()),
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index dcfbaa35b2872..8f295c4608d18 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1671,13 +1671,23 @@ TEST(TypeHints, SubstTemplateParameterAliases) {
                         ExpectedHint{": static_vector<int>", "vector_name"});
 }
 
-template <typename... Labels>
+enum struct Language {
+  CXX26 = 1,
+  C23 = 2,
+};
+
+template <Language L = Language::CXX26, typename... Labels>
 void assertTypeLinkHints(StringRef Code, StringRef HintRange,
                          Labels... ExpectedLabels) {
   Annotations Source(Code);
 
   TestTU TU = TestTU::withCode(Source.code());
-  TU.ExtraArgs.push_back("-std=c++2c");
+  if constexpr (L == Language::CXX26) {
+    TU.ExtraArgs.push_back("-std=c++2c");
+  } else if constexpr (L == Language::C23) {
+    TU.ExtraArgs.push_back("-xc");
+    TU.ExtraArgs.push_back("-std=c23");
+  }
   auto AST = TU.build();
 
   Config C;
@@ -1882,6 +1892,20 @@ TEST(TypeHints, LinksForStructureBindings) {
                       ExpectedHintLabelPiece{"<float>"});
 }
 
+TEST(TypeHints, TypeDeductionForC) {
+  StringRef Source(R"cpp(
+    struct $Waldo[[Waldo]] {};
+    struct Waldo foo();
+    int main() {
+      auto $theres[[w]] /*: struct Waldo */ = foo();
+    }
+  )cpp");
+
+  assertTypeLinkHints<Language::C23>(Source, "theres",
+                                     ExpectedHintLabelPiece{": struct "},
+                                     ExpectedHintLabelPiece{"Waldo", "Waldo"});
+}
+
 TEST(DesignatorHints, Basic) {
   assertDesignatorHints(R"cpp(
     struct S { int x, y, z; };

>From 77b552520c866ac72008e63decc9c19c15fbe1ea Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 8 Jul 2024 18:52:00 +0800
Subject: [PATCH 28/36] Remove CurrentTypeRAII

---
 clang-tools-extra/clangd/InlayHints.cpp | 18 ------------------
 1 file changed, 18 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 449524f31dffa..45e2facd865f3 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -383,24 +383,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   SourceManager &SM;
   std::vector<InlayHintLabelPart> LabelChunks;
 
-  struct CurrentTypeRAII {
-    TypeHintBuilder &Builder;
-    QualType PreviousType;
-    NestedNameSpecifier *PreviousNestedNameSpecifier;
-    CurrentTypeRAII(TypeHintBuilder &Builder, QualType New,
-                    NestedNameSpecifier *NNS = nullptr)
-        : Builder(Builder), PreviousType(Builder.CurrentType),
-          PreviousNestedNameSpecifier(Builder.CurrentNestedNameSpecifier) {
-      Builder.CurrentType = New;
-      if (NNS)
-        Builder.CurrentNestedNameSpecifier = NNS;
-    }
-    ~CurrentTypeRAII() {
-      Builder.CurrentType = PreviousType;
-      Builder.CurrentNestedNameSpecifier = PreviousNestedNameSpecifier;
-    }
-  };
-
   void addLabel(llvm::function_ref<void(llvm::raw_ostream &)> NamePrinter,
                 SourceLocation Location) {
     std::string Label;

>From 1be1f754929649422d405051a824dbfc46f4fbf6 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 8 Jul 2024 19:02:22 +0800
Subject: [PATCH 29/36] Revert unrelated changes

---
 clang-tools-extra/clangd/AST.cpp               | 9 ---------
 clang-tools-extra/clangd/AST.h                 | 2 --
 clang-tools-extra/clangd/index/IndexAction.cpp | 9 ++++++---
 3 files changed, 6 insertions(+), 14 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index dd645861dda8b..e5d6ec748f689 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -1026,14 +1026,5 @@ bool isExpandedFromParameterPack(const ParmVarDecl *D) {
   return getUnderlyingPackType(D) != nullptr;
 }
 
-std::optional<URI> toURI(OptionalFileEntryRef File) {
-  if (!File)
-    return std::nullopt;
-  auto AbsolutePath = File->getFileEntry().tryGetRealPathName();
-  if (AbsolutePath.empty())
-    return std::nullopt;
-  return URI::create(AbsolutePath);
-}
-
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index f56759656997b..e1582f9d0e743 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -256,8 +256,6 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth = 10);
 /// reference to one (e.g. `Args&...` or `Args&&...`).
 bool isExpandedFromParameterPack(const ParmVarDecl *D);
 
-std::optional<URI> toURI(OptionalFileEntryRef File);
-
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/index/IndexAction.cpp b/clang-tools-extra/clangd/index/IndexAction.cpp
index f3fbb9fc307e2..ed56c2a9d2e81 100644
--- a/clang-tools-extra/clangd/index/IndexAction.cpp
+++ b/clang-tools-extra/clangd/index/IndexAction.cpp
@@ -32,9 +32,12 @@ namespace clangd {
 namespace {
 
 std::optional<std::string> toURI(OptionalFileEntryRef File) {
-  if (auto URI = clang::clangd::toURI(File))
-    return URI->toString();
-  return std::nullopt;
+  if (!File)
+    return std::nullopt;
+  auto AbsolutePath = File->getFileEntry().tryGetRealPathName();
+  if (AbsolutePath.empty())
+    return std::nullopt;
+  return URI::create(AbsolutePath).toString();
 }
 
 // Collects the nodes and edges of include graph during indexing action.

>From 35816e0d1fb4347432583d1c0265369c3ab0a49f Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Tue, 9 Jul 2024 20:40:46 +0800
Subject: [PATCH 30/36] Refactor handleTemplateSpecialization

---
 clang-tools-extra/clangd/InlayHints.cpp | 86 ++++++++++++-------------
 1 file changed, 42 insertions(+), 44 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 45e2facd865f3..5db46a58a4c4b 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -31,6 +31,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/Support/Casting.h"
+#include "llvm/Support/Compiler.h"
 #include "llvm/Support/SaveAndRestore.h"
 #include "llvm/Support/ScopedPrinter.h"
 #include "llvm/Support/raw_ostream.h"
@@ -442,44 +443,10 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     addLabel(std::move(Label));
   }
 
-  void handleTemplateSpecialization(
-      TemplateName TN, llvm::ArrayRef<TemplateArgument> Args,
-      SourceLocation LocationForOverride = SourceLocation()) {
-    SourceLocation Location;
-    TemplateDecl *TD = nullptr;
-    auto PrintType = TemplateName::Qualified::AsWritten;
-    switch (TN.getKind()) {
-    case TemplateName::Template:
-      TD = TN.getAsTemplateDecl();
-      Location = nameLocation(TD, SM);
-      break;
-    case TemplateName::QualifiedTemplate:
-      if (NestedNameSpecifier *NNS =
-              TN.getAsQualifiedTemplateName()->getQualifier();
-          NNS == CurrentNestedNameSpecifier) {
-        // We have handled the NNS in VisitElaboratedType(). Avoid printing it
-        // twice.
-        TN = TN.getAsQualifiedTemplateName()->getUnderlyingTemplate();
-        PrintType = TemplateName::Qualified::None;
-      }
-      break;
-    case TemplateName::OverloadedTemplate:
-    case TemplateName::AssumedTemplate:
-    case TemplateName::DependentTemplate:
-    case TemplateName::SubstTemplateTemplateParm:
-    case TemplateName::SubstTemplateTemplateParmPack:
-    case TemplateName::UsingTemplate:
-      break;
-    }
-
-    if (LocationForOverride.isValid())
-      Location = LocationForOverride;
-
-    assert(Location.isValid() || LocationForOverride.isValid());
-
-    addLabel([&](llvm::raw_ostream &OS) { TN.print(OS, PP, PrintType); },
-             Location);
-
+  void handleTemplateSpecialization(llvm::StringRef TemplateId,
+                                    llvm::ArrayRef<TemplateArgument> Args,
+                                    SourceLocation Location) {
+    addLabel([&](llvm::raw_ostream &OS) { OS << TemplateId; }, Location);
     addLabel("<");
     printTemplateArgumentList(Args);
     addLabel(">");
@@ -606,11 +573,14 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
           nameLocation(CXXRD, SM));
 
     // FIXME: Do we have other kinds of specializations?
-    if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD))
+    if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD)) {
+      std::string TemplateId;
+      llvm::raw_string_ostream OS(TemplateId);
+      CTSD->printName(OS);
       return handleTemplateSpecialization(
-          TemplateName(Pattern->getDescribedClassTemplate()),
-          CTSD->getTemplateArgs().asArray(),
+          TemplateId, CTSD->getTemplateArgs().asArray(),
           getPreferredLocationFromSpecialization(CTSD));
+    }
 
     return VisitType(TT);
   }
@@ -690,8 +660,33 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
-    maybeAddQualifiers();
     SourceLocation Location;
+    TemplateName Name = TST->getTemplateName();
+    TemplateName::Qualified PrintQual = TemplateName::Qualified::AsWritten;
+    switch (Name.getKind()) {
+    case TemplateName::Template:
+    case TemplateName::QualifiedTemplate: {
+      QualifiedTemplateName *Qual = Name.getAsQualifiedTemplateName();
+      if (Qual->getQualifier() == CurrentNestedNameSpecifier) {
+        // We have handled the NNS in VisitElaboratedType(). Avoid printing it
+        // twice.
+        Name = Qual->getUnderlyingTemplate();
+        PrintQual = TemplateName::Qualified::None;
+      }
+      [[fallthrough]];
+    }
+    case TemplateName::SubstTemplateTemplateParm:
+    case TemplateName::UsingTemplate:
+      Location = Name.getAsTemplateDecl()->getLocation();
+      break;
+    case TemplateName::OverloadedTemplate:
+    case TemplateName::AssumedTemplate:
+    case TemplateName::DependentTemplate:
+    case TemplateName::SubstTemplateTemplateParmPack:
+      // FIXME: Handle these cases.
+      return VisitType(TST);
+    }
+    maybeAddQualifiers();
     // Special case the ClassTemplateSpecializationDecl because
     // we want the location of an explicit specialization, if present.
     // FIXME: In practice, populating the location with that of the
@@ -701,8 +696,11 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
             dyn_cast_if_present<ClassTemplateSpecializationDecl>(
                 TST->desugar().getCanonicalType()->getAsCXXRecordDecl()))
       Location = getPreferredLocationFromSpecialization(Specialization);
-    return handleTemplateSpecialization(TST->getTemplateName(),
-                                        TST->template_arguments(), Location);
+    std::string TemplateId;
+    llvm::raw_string_ostream OS(TemplateId);
+    Name.print(OS, PP, PrintQual);
+    return handleTemplateSpecialization(TemplateId, TST->template_arguments(),
+                                        Location);
   }
 
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {

>From 96611a3b3b9a95ea758712bf2fa8d16a5ace3229 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Thu, 11 Jul 2024 20:09:31 +0800
Subject: [PATCH 31/36] Avoid a nullptr dereference

---
 clang-tools-extra/clangd/InlayHints.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 5db46a58a4c4b..ba2c440c42069 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -666,8 +666,8 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     switch (Name.getKind()) {
     case TemplateName::Template:
     case TemplateName::QualifiedTemplate: {
-      QualifiedTemplateName *Qual = Name.getAsQualifiedTemplateName();
-      if (Qual->getQualifier() == CurrentNestedNameSpecifier) {
+      if (QualifiedTemplateName *Qual = Name.getAsQualifiedTemplateName();
+          Qual && Qual->getQualifier() == CurrentNestedNameSpecifier) {
         // We have handled the NNS in VisitElaboratedType(). Avoid printing it
         // twice.
         Name = Qual->getUnderlyingTemplate();

>From 49f9ec5999214dd9b7388c827b6c68cd2d8bc0e4 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sat, 13 Jul 2024 00:34:17 +0800
Subject: [PATCH 32/36] Use getTemplateSpecializationKind() instead of
 getTemplateInstantiationPattern() for specializations

---
 clang-tools-extra/clangd/InlayHints.cpp | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index ba2c440c42069..70f7815618311 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -566,12 +566,14 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     // auto [value] = bar;
     //
     // The type of value is modeled as a RecordType here.
-    CXXRecordDecl *Pattern = CXXRD->getTemplateInstantiationPattern();
-    if (!Pattern)
-      return addLabel(
-          [&](llvm::raw_ostream &OS) { return CXXRD->printName(OS, PP); },
-          nameLocation(CXXRD, SM));
 
+    // The ClassTemplateSpecializationDecl could be of TSK_Undeclared
+    // kind. So handle the ClassTemplateSpecializationDecl case first for
+    // template arguments.
+    // E.g. when we're inside a range-based for loop:
+    //   template <class T, class U> struct Pair;
+    //   for (auto p : SmallVector<Pair<SourceLocation, SourceRange>>()) {}
+    // (Pair is of TSK_Undeclared kind here.)
     // FIXME: Do we have other kinds of specializations?
     if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD)) {
       std::string TemplateId;
@@ -582,6 +584,12 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
           getPreferredLocationFromSpecialization(CTSD));
     }
 
+    // We don't have a template arguments now. Find the name and its location.
+    if (!CXXRD->getTemplateSpecializationKind())
+      return addLabel(
+          [&](llvm::raw_ostream &OS) { return CXXRD->printName(OS, PP); },
+          nameLocation(CXXRD, SM));
+
     return VisitType(TT);
   }
 

>From 5595bdf9c81a7d46e72d8ba4005ae2fb410c7d5d Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 14 Jul 2024 12:45:35 +0800
Subject: [PATCH 33/36] Handle DeducedTemplateSpecializationType for CTAD

---
 clang-tools-extra/clangd/InlayHints.cpp       | 11 +++++++++-
 .../clangd/unittests/InlayHintTests.cpp       | 22 +++++++++++++++++++
 2 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 70f7815618311..ea0ab646c8973 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -668,6 +668,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
+    maybeAddQualifiers();
     SourceLocation Location;
     TemplateName Name = TST->getTemplateName();
     TemplateName::Qualified PrintQual = TemplateName::Qualified::AsWritten;
@@ -694,7 +695,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
       // FIXME: Handle these cases.
       return VisitType(TST);
     }
-    maybeAddQualifiers();
     // Special case the ClassTemplateSpecializationDecl because
     // we want the location of an explicit specialization, if present.
     // FIXME: In practice, populating the location with that of the
@@ -711,6 +711,15 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
                                         Location);
   }
 
+  void VisitDeducedTemplateSpecializationType(
+      const DeducedTemplateSpecializationType *TST) {
+    maybeAddQualifiers();
+    // FIXME: The TST->getTemplateName() might differ from the name of
+    // DeducedType, e.g. when the deduction guide is formed against a type alias
+    // Decl.
+    return VisitQualType(TST->getDeducedType());
+  }
+
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
     maybeAddQualifiers();
     return VisitQualType(ST->getReplacementType());
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 8f295c4608d18..dbab49b516ea3 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1892,6 +1892,28 @@ TEST(TypeHints, LinksForStructureBindings) {
                       ExpectedHintLabelPiece{"<float>"});
 }
 
+TEST(TypeHints, LinksForDeductionGuides) {
+  StringRef Source(R"cpp(
+  template <class T>
+  struct S { S(T); };
+
+  template <>
+  struct $S[[S]]<int> { S(int); };
+
+  template <class U>
+  using SS = S<U>;
+
+  // FIXME: Would better be SS<int>.
+  // (However, TypePrinter doesn't print SS<int> either.)
+  const auto $1[[w]] = S(42);
+  )cpp");
+
+  assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": const "},
+                      ExpectedHintLabelPiece{"S", "S"},
+                      ExpectedHintLabelPiece{"<int>"});
+}
+
+
 TEST(TypeHints, TypeDeductionForC) {
   StringRef Source(R"cpp(
     struct $Waldo[[Waldo]] {};

>From df828281ab8f4bc4b2168bc3b3a0d54840b97d43 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Tue, 16 Jul 2024 17:57:54 +0800
Subject: [PATCH 34/36] Refactor the handling of Qualifiers

---
 clang-tools-extra/clangd/InlayHints.cpp       | 117 ++++++++++++++----
 .../clangd/unittests/InlayHintTests.cpp       |  17 ++-
 2 files changed, 108 insertions(+), 26 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index ea0ab646c8973..3213f7d028e9b 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -383,12 +383,15 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   const PrintingPolicy &PP;
   SourceManager &SM;
   std::vector<InlayHintLabelPart> LabelChunks;
+  bool AppendSpaceToQuals;
 
   void addLabel(llvm::function_ref<void(llvm::raw_ostream &)> NamePrinter,
-                SourceLocation Location) {
+                SourceLocation Location = SourceLocation()) {
     std::string Label;
     llvm::raw_string_ostream OS(Label);
     NamePrinter(OS);
+    if (!Location.isValid())
+      return addLabel(std::move(Label));
     auto &Name = LabelChunks.emplace_back();
     Name.value = std::move(Label);
     Name.location = makeLocation(Context, Location, MainFilePath);
@@ -452,12 +455,11 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     addLabel(">");
   }
 
-  void maybeAddQualifiers() {
-    auto Quals = CurrentType.split().Quals;
-    if (!Quals.empty()) {
-      addLabel(Quals.getAsString());
-      addLabel(" ");
-    }
+  void maybeAddQualifiers(bool AppendSpaceToQuals) {
+    addLabel([&](llvm::raw_ostream &OS) {
+      CurrentType.split().Quals.print(
+          OS, PP, /*appendSpaceIfNonEmpty=*/AppendSpaceToQuals);
+    });
   }
 
   // When printing a reference, the referenced type might also be a reference.
@@ -497,6 +499,55 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     return ::clang::clangd::nameLocation(*D, SM);
   }
 
+  // CanPrefixQualifiers - We prefer to print type qualifiers
+  // before the type, so that we get "const int" instead of "int const", but we
+  // can't do this if the type is complex.  For example if the type is "int*",
+  // we *must* print "int * const", printing "const int *" is different.  Only
+  // do this when the type expands to a simple string.
+  // This is similar to the private function \p
+  // TypePrinter::canPrefixQualifiers().
+  // FIXME: Refactor and share the same implementation.
+  static bool canPrefixQualifiers(const Type *T) {
+    bool CanPrefixQualifiers = false;
+    const Type *UnderlyingType = T;
+    if (const auto *AT = dyn_cast<AutoType>(T))
+      UnderlyingType = AT->desugar().getTypePtr();
+    if (const auto *Subst = dyn_cast<SubstTemplateTypeParmType>(T))
+      UnderlyingType = Subst->getReplacementType().getTypePtr();
+    Type::TypeClass TC = UnderlyingType->getTypeClass();
+
+    switch (TC) {
+    case Type::Adjusted:
+    case Type::Decayed:
+    case Type::ArrayParameter:
+    case Type::Pointer:
+    case Type::BlockPointer:
+    case Type::LValueReference:
+    case Type::RValueReference:
+    case Type::MemberPointer:
+    case Type::DependentAddressSpace:
+    case Type::DependentVector:
+    case Type::DependentSizedExtVector:
+    case Type::Vector:
+    case Type::ExtVector:
+    case Type::ConstantMatrix:
+    case Type::DependentSizedMatrix:
+    case Type::FunctionProto:
+    case Type::FunctionNoProto:
+    case Type::Paren:
+    case Type::PackExpansion:
+    case Type::SubstTemplateTypeParm:
+    case Type::MacroQualified:
+    case Type::CountAttributed:
+      CanPrefixQualifiers = false;
+      break;
+    default:
+      CanPrefixQualifiers = true;
+    }
+
+    return CanPrefixQualifiers;
+  }
+
   SourceLocation getPreferredLocationFromSpecialization(
       ClassTemplateSpecializationDecl *Specialization) const {
     SourceLocation Location;
@@ -518,24 +569,39 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   TypeHintBuilder(ASTContext &Context, StringRef MainFilePath,
                   const PrintingPolicy &PP, llvm::StringRef Prefix)
       : CurrentNestedNameSpecifier(nullptr), Context(Context),
-        MainFilePath(MainFilePath), PP(PP), SM(Context.getSourceManager()) {
+        MainFilePath(MainFilePath), PP(PP), SM(Context.getSourceManager()),
+        AppendSpaceToQuals(true) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());
   }
 
-  void VisitType(const Type *) { addLabel(CurrentType.getAsString(PP)); }
+  void VisitType(const Type *T) {
+    // We should have handled qualifiers in VisitQualType(). Don't print them
+    // twice.
+    addLabel(QualType(T, /*Quals=*/0).getAsString());
+  }
+
+  void VisitQualType(QualType Q, bool AppendSpaceToTopLevelQuals = true,
+                     NestedNameSpecifier *NNS = nullptr) {
+    QualType PreviousType = this->CurrentType;
+    NestedNameSpecifier *PreviousNNS = this->CurrentNestedNameSpecifier;
+    bool PrevAppendSpaceToQuals = this->AppendSpaceToQuals;
 
-  void VisitQualType(QualType Q, NestedNameSpecifier *NNS = nullptr) {
-    QualType PreviousType = CurrentType;
-    NestedNameSpecifier *PreviousNNS = CurrentNestedNameSpecifier;
-    CurrentType = Q;
-    CurrentNestedNameSpecifier = NNS;
+    this->AppendSpaceToQuals = AppendSpaceToTopLevelQuals;
+    this->CurrentType = Q;
+    this->CurrentNestedNameSpecifier = NNS;
+    bool CanPrefixQualifiers = canPrefixQualifiers(CurrentType.getTypePtr());
+    if (CanPrefixQualifiers)
+      maybeAddQualifiers(/*AppendSpaceToQuals=*/true);
 
     TypeVisitor::Visit(Q.getTypePtr());
 
-    CurrentType = PreviousType;
-    CurrentNestedNameSpecifier = PreviousNNS;
+    if (!CanPrefixQualifiers)
+      maybeAddQualifiers(/*AppendSpaceToQuals=*/AppendSpaceToTopLevelQuals);
+    this->CurrentType = PreviousType;
+    this->CurrentNestedNameSpecifier = PreviousNNS;
+    this->AppendSpaceToQuals = PrevAppendSpaceToQuals;
   }
 
   void VisitTagType(const TagType *TT) {
@@ -602,12 +668,11 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   void VisitAutoType(const AutoType *AT) {
     if (!AT->isDeduced() || AT->getDeducedType()->isDecltypeType())
       return VisitType(AT);
-    maybeAddQualifiers();
     return VisitQualType(AT->getDeducedType());
   }
 
   void VisitElaboratedType(const ElaboratedType *ET) {
-    maybeAddQualifiers();
+    // maybeAddQualifiers();
     if (auto *NNS = ET->getQualifier()) {
       switch (NNS->getKind()) {
       case NestedNameSpecifier::Identifier:
@@ -633,11 +698,13 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
         break;
       }
     }
-    return VisitQualType(ET->getNamedType(), ET->getQualifier());
+    return VisitQualType(ET->getNamedType(),
+                         /*AppendSpaceToTopLevelQuals=*/true,
+                         ET->getQualifier());
   }
 
   void VisitReferenceType(const ReferenceType *RT) {
-    maybeAddQualifiers();
+    // maybeAddQualifiers();
     QualType Next = skipTopLevelReferences(RT->getPointeeTypeAsWritten());
     VisitQualType(Next);
     if (Next->getPointeeType().isNull())
@@ -654,7 +721,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     if (Next->getPointeeType().isNull())
       addLabel(" ");
     addLabel("*");
-    maybeAddQualifiers();
+    // maybeAddQualifiers();
   }
 
   void VisitUsingType(const UsingType *UT) {
@@ -668,7 +735,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
-    maybeAddQualifiers();
+    // maybeAddQualifiers();
     SourceLocation Location;
     TemplateName Name = TST->getTemplateName();
     TemplateName::Qualified PrintQual = TemplateName::Qualified::AsWritten;
@@ -713,7 +780,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitDeducedTemplateSpecializationType(
       const DeducedTemplateSpecializationType *TST) {
-    maybeAddQualifiers();
+    // maybeAddQualifiers();
     // FIXME: The TST->getTemplateName() might differ from the name of
     // DeducedType, e.g. when the deduction guide is formed against a type alias
     // Decl.
@@ -721,7 +788,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
-    maybeAddQualifiers();
+    // maybeAddQualifiers();
     return VisitQualType(ST->getReplacementType());
   }
 
@@ -1397,7 +1464,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
   std::vector<InlayHintLabelPart> buildTypeHint(QualType T,
                                                 llvm::StringRef Prefix) {
     TypeHintBuilder Builder(AST, MainFilePath, TypeHintPolicy, Prefix);
-    Builder.VisitQualType(T);
+    Builder.VisitQualType(T, /*AppendSpaceToTopLevelQuals=*/false);
     return Builder.take();
   }
 
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index dbab49b516ea3..27d2eda602ed0 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1294,7 +1294,7 @@ TEST(TypeHints, DecltypeAuto) {
                   ExpectedHint{": int &", "z"});
 }
 
-TEST(TypeHints, NoQualifiers) {
+TEST(TypeHints, NoNNS) {
   assertTypeHints(R"cpp(
     namespace A {
       namespace B {
@@ -1913,6 +1913,21 @@ TEST(TypeHints, LinksForDeductionGuides) {
                       ExpectedHintLabelPiece{"<int>"});
 }
 
+TEST(TypeHints, Qualifiers) {
+  StringRef Source(R"cpp(
+  struct Base { virtual ~Base() = default; };
+
+  struct $Derived[[Derived]] : virtual Base {};
+
+  Base *make();
+
+  const auto $1[[ptr]] = dynamic_cast<const Derived *>(make());
+  )cpp");
+
+  assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": const "},
+                      ExpectedHintLabelPiece{"Derived", "Derived"},
+                      ExpectedHintLabelPiece{" *const"});
+}
 
 TEST(TypeHints, TypeDeductionForC) {
   StringRef Source(R"cpp(

>From dfb317e3850e4e663fccad5670cbae3ef79c721c Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Tue, 16 Jul 2024 18:47:03 +0800
Subject: [PATCH 35/36] cleanup

---
 clang-tools-extra/clangd/InlayHints.cpp | 25 +++++++------------------
 1 file changed, 7 insertions(+), 18 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 3213f7d028e9b..7542ece575bb7 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -383,7 +383,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   const PrintingPolicy &PP;
   SourceManager &SM;
   std::vector<InlayHintLabelPart> LabelChunks;
-  bool AppendSpaceToQuals;
 
   void addLabel(llvm::function_ref<void(llvm::raw_ostream &)> NamePrinter,
                 SourceLocation Location = SourceLocation()) {
@@ -569,8 +568,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   TypeHintBuilder(ASTContext &Context, StringRef MainFilePath,
                   const PrintingPolicy &PP, llvm::StringRef Prefix)
       : CurrentNestedNameSpecifier(nullptr), Context(Context),
-        MainFilePath(MainFilePath), PP(PP), SM(Context.getSourceManager()),
-        AppendSpaceToQuals(true) {
+        MainFilePath(MainFilePath), PP(PP), SM(Context.getSourceManager()) {
     LabelChunks.reserve(16);
     if (!Prefix.empty())
       addLabel(Prefix.str());
@@ -584,13 +582,11 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitQualType(QualType Q, bool AppendSpaceToTopLevelQuals = true,
                      NestedNameSpecifier *NNS = nullptr) {
-    QualType PreviousType = this->CurrentType;
-    NestedNameSpecifier *PreviousNNS = this->CurrentNestedNameSpecifier;
-    bool PrevAppendSpaceToQuals = this->AppendSpaceToQuals;
+    QualType PreviousType = CurrentType;
+    NestedNameSpecifier *PreviousNNS = CurrentNestedNameSpecifier;
 
-    this->AppendSpaceToQuals = AppendSpaceToTopLevelQuals;
-    this->CurrentType = Q;
-    this->CurrentNestedNameSpecifier = NNS;
+    CurrentType = Q;
+    CurrentNestedNameSpecifier = NNS;
     bool CanPrefixQualifiers = canPrefixQualifiers(CurrentType.getTypePtr());
     if (CanPrefixQualifiers)
       maybeAddQualifiers(/*AppendSpaceToQuals=*/true);
@@ -599,9 +595,8 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
     if (!CanPrefixQualifiers)
       maybeAddQualifiers(/*AppendSpaceToQuals=*/AppendSpaceToTopLevelQuals);
-    this->CurrentType = PreviousType;
-    this->CurrentNestedNameSpecifier = PreviousNNS;
-    this->AppendSpaceToQuals = PrevAppendSpaceToQuals;
+    CurrentType = PreviousType;
+    CurrentNestedNameSpecifier = PreviousNNS;
   }
 
   void VisitTagType(const TagType *TT) {
@@ -672,7 +667,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitElaboratedType(const ElaboratedType *ET) {
-    // maybeAddQualifiers();
     if (auto *NNS = ET->getQualifier()) {
       switch (NNS->getKind()) {
       case NestedNameSpecifier::Identifier:
@@ -704,7 +698,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitReferenceType(const ReferenceType *RT) {
-    // maybeAddQualifiers();
     QualType Next = skipTopLevelReferences(RT->getPointeeTypeAsWritten());
     VisitQualType(Next);
     if (Next->getPointeeType().isNull())
@@ -721,7 +714,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     if (Next->getPointeeType().isNull())
       addLabel(" ");
     addLabel("*");
-    // maybeAddQualifiers();
   }
 
   void VisitUsingType(const UsingType *UT) {
@@ -735,7 +727,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitTemplateSpecializationType(const TemplateSpecializationType *TST) {
-    // maybeAddQualifiers();
     SourceLocation Location;
     TemplateName Name = TST->getTemplateName();
     TemplateName::Qualified PrintQual = TemplateName::Qualified::AsWritten;
@@ -780,7 +771,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
 
   void VisitDeducedTemplateSpecializationType(
       const DeducedTemplateSpecializationType *TST) {
-    // maybeAddQualifiers();
     // FIXME: The TST->getTemplateName() might differ from the name of
     // DeducedType, e.g. when the deduction guide is formed against a type alias
     // Decl.
@@ -788,7 +778,6 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *ST) {
-    // maybeAddQualifiers();
     return VisitQualType(ST->getReplacementType());
   }
 

>From 8e3826e37a8aa44619ac44f6ff5c5e5dca28473d Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Tue, 16 Jul 2024 23:09:03 +0800
Subject: [PATCH 36/36] Fix qualifiers further more

---
 clang-tools-extra/clangd/InlayHints.cpp       | 23 +++++++++---
 .../clangd/unittests/InlayHintTests.cpp       | 36 ++++++++++++++++++-
 2 files changed, 53 insertions(+), 6 deletions(-)

diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 7542ece575bb7..d342ccf7d2da6 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -383,6 +383,7 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   const PrintingPolicy &PP;
   SourceManager &SM;
   std::vector<InlayHintLabelPart> LabelChunks;
+  bool AppendTrailingSpaceBeforeRightQual = true;
 
   void addLabel(llvm::function_ref<void(llvm::raw_ostream &)> NamePrinter,
                 SourceLocation Location = SourceLocation()) {
@@ -588,13 +589,23 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
     CurrentType = Q;
     CurrentNestedNameSpecifier = NNS;
     bool CanPrefixQualifiers = canPrefixQualifiers(CurrentType.getTypePtr());
+    bool PrevAppendTrailingSpaceBeforeRightQual =
+        AppendTrailingSpaceBeforeRightQual;
     if (CanPrefixQualifiers)
       maybeAddQualifiers(/*AppendSpaceToQuals=*/true);
 
     TypeVisitor::Visit(Q.getTypePtr());
 
-    if (!CanPrefixQualifiers)
+    bool HaveTrailingQuals =
+        !CanPrefixQualifiers && !CurrentType.split().Quals.empty();
+    if (AppendTrailingSpaceBeforeRightQual && HaveTrailingQuals)
+      addLabel(" ");
+    if (HaveTrailingQuals) {
       maybeAddQualifiers(/*AppendSpaceToQuals=*/AppendSpaceToTopLevelQuals);
+      AppendTrailingSpaceBeforeRightQual =
+          PrevAppendTrailingSpaceBeforeRightQual;
+    }
+
     CurrentType = PreviousType;
     CurrentNestedNameSpecifier = PreviousNNS;
   }
@@ -605,10 +616,9 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
       // This might be a C TagDecl.
       if (auto *RD = dyn_cast<RecordDecl>(TT->getDecl())) {
         // FIXME: Respect SuppressTagKeyword in other cases.
-        if (!PP.SuppressTagKeyword && !RD->getTypedefNameForAnonDecl()) {
-          addLabel(RD->getKindName().str());
-          addLabel(" ");
-        }
+        if (!PP.SuppressTagKeyword && !RD->getTypedefNameForAnonDecl())
+          addLabel(
+              [&](llvm::raw_ostream &OS) { OS << RD->getKindName() << " "; });
         return addLabel(
             [&](llvm::raw_ostream &OS) { return RD->printName(OS, PP); },
             nameLocation(RD, SM));
@@ -709,6 +719,9 @@ class TypeHintBuilder : public TypeVisitor<TypeHintBuilder> {
   }
 
   void VisitPointerType(const PointerType *PT) {
+    // We don't want a trailing space after the last asterisk, if it is followed
+    // by a qualifier. E.g. 'int *const' rather than 'int * const'.
+    AppendTrailingSpaceBeforeRightQual = false;
     QualType Next = PT->getPointeeType();
     VisitQualType(Next);
     if (Next->getPointeeType().isNull())
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 27d2eda602ed0..d28bbff714e1b 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1913,7 +1913,7 @@ TEST(TypeHints, LinksForDeductionGuides) {
                       ExpectedHintLabelPiece{"<int>"});
 }
 
-TEST(TypeHints, Qualifiers) {
+TEST(TypeHints, LinksWithQualifiers) {
   StringRef Source(R"cpp(
   struct Base { virtual ~Base() = default; };
 
@@ -1922,11 +1922,45 @@ TEST(TypeHints, Qualifiers) {
   Base *make();
 
   const auto $1[[ptr]] = dynamic_cast<const Derived *>(make());
+
+  const Derived *volatile const *volatile *const *volatile p = nullptr;
+  
+  volatile auto $2[[paranoid]] = p;
   )cpp");
 
   assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": const "},
                       ExpectedHintLabelPiece{"Derived", "Derived"},
                       ExpectedHintLabelPiece{" *const"});
+  assertTypeLinkHints(
+      Source, "2", ExpectedHintLabelPiece{": const "},
+      ExpectedHintLabelPiece{"Derived", "Derived"},
+      ExpectedHintLabelPiece{" *const volatile *volatile *const *volatile"});
+}
+
+TEST(TypeHints, LinksWithRangeBasedForLoop) {
+  StringRef Source(R"cpp(
+    template <class T>
+    struct vector {
+      struct iterator {
+        T operator *() {}
+        bool operator==(iterator) const;
+        bool operator!=(iterator) const;
+        iterator operator++();
+      };
+
+      iterator begin();
+      iterator end();
+    };
+
+    void foo() {
+      struct $S[[S]] {};
+      for (const auto $1[[i]] : vector<S>()) {
+      }
+    }
+  )cpp");
+  assertTypeLinkHints(Source, "1", ExpectedHintLabelPiece{": "},
+                      ExpectedHintLabelPiece{"S", "S"},
+                      ExpectedHintLabelPiece{" const"});
 }
 
 TEST(TypeHints, TypeDeductionForC) {



More information about the cfe-commits mailing list