Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang] Template Specialization Resugaring - TypeDecl #132441

Open
wants to merge 1 commit into
base: users/mizvekov/clang-sugar-sugared-converted-tst
Choose a base branch
from

Conversation

mizvekov
Copy link
Contributor

This is the introductory patch for a larger work which intends to solve the long standing C++ issue with losing type sugar when acessing template specializations.

The well known example here is specializing a template with std::string, but getting diagnostics related to std::basic_string<char> instead.

This implements a transform which, upon member access, propagates type sugar from the naming context into the accessed entity.

It also implements a single use of this transform, resugaring access to TypeDecls.

For more details and discussion see:
https://discourse.llvm.org/t/rfc-improving-diagnostics-with-template-specialization-resugaring/64294

Even though this patch is ready for review, some dependent patches are not, and might not be ready for some time.

There is some more stuff that could be done either here or in follow ups:

  • Its worth exploring if a global resugaring cache is worthwhile, besides the current operational cache. A global cache would be more expensive to index, so there is a tradeoff, and maybe should be used of the whole result of the operation, while keeping the existing cache for sub-results.
  • It would be ideal if the transform could live in ASTContext instead of Sema. There are a few dependencies that would have to be tackled.
    • Template arguments deduced for partial specializations.
    • Some kinds of type adjustments currently require Sema.

Differential Revision: https://reviews.llvm.org/D127695

@mizvekov mizvekov self-assigned this Mar 21, 2025
@mizvekov mizvekov requested a review from JDevlieghere as a code owner March 21, 2025 18:16
@llvmbot llvmbot added clang Clang issues not falling into any other category clang-tools-extra lldb clang-tidy clang:frontend Language frontend issues, e.g. anything involving "Sema" coroutines C++20 coroutines clang:openmp OpenMP related changes to Clang labels Mar 21, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 21, 2025

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

@llvm/pr-subscribers-clang

Author: Matheus Izvekov (mizvekov)

Changes

This is the introductory patch for a larger work which intends to solve the long standing C++ issue with losing type sugar when acessing template specializations.

The well known example here is specializing a template with std::string, but getting diagnostics related to std::basic_string&lt;char&gt; instead.

This implements a transform which, upon member access, propagates type sugar from the naming context into the accessed entity.

It also implements a single use of this transform, resugaring access to TypeDecls.

For more details and discussion see:
https://discourse.llvm.org/t/rfc-improving-diagnostics-with-template-specialization-resugaring/64294

Even though this patch is ready for review, some dependent patches are not, and might not be ready for some time.

There is some more stuff that could be done either here or in follow ups:

  • Its worth exploring if a global resugaring cache is worthwhile, besides the current operational cache. A global cache would be more expensive to index, so there is a tradeoff, and maybe should be used of the whole result of the operation, while keeping the existing cache for sub-results.
  • It would be ideal if the transform could live in ASTContext instead of Sema. There are a few dependencies that would have to be tackled.
    • Template arguments deduced for partial specializations.
    • Some kinds of type adjustments currently require Sema.

Differential Revision: https://reviews.llvm.org/D127695


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

17 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp (+1-1)
  • (modified) clang/include/clang/AST/ASTContext.h (+7-6)
  • (modified) clang/include/clang/AST/Type.h (+6-5)
  • (modified) clang/include/clang/Sema/Sema.h (+6-2)
  • (modified) clang/lib/AST/ASTContext.cpp (+8-9)
  • (modified) clang/lib/Sema/SemaCXXScopeSpec.cpp (+3-2)
  • (modified) clang/lib/Sema/SemaCoroutine.cpp (+1)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+5-4)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+721-1)
  • (modified) clang/lib/Sema/SemaTemplateDeduction.cpp (+4-2)
  • (modified) clang/lib/Sema/SemaType.cpp (+6-4)
  • (modified) clang/test/AST/ast-dump-openmp-begin-declare-variant_reference.cpp (+2-6)
  • (modified) clang/test/AST/ast-dump-template-name.cpp (+2-5)
  • (modified) clang/test/CXX/temp/temp.param/p15-cxx0x.cpp (+2-2)
  • (added) clang/test/Sema/Resugar/resugar-types.cpp (+209)
  • (modified) lldb/test/API/commands/expression/import-std-module/iterator/TestIteratorFromStdModule.py (+1-1)
diff --git a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp
index e843c593a92cc..679fbd75d2479 100644
--- a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp
@@ -126,7 +126,7 @@ void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) {
   auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType()));
   auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) {
     return autoType(hasDeducedType(
-        hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...)))));
+        hasCanonicalType(pointerType(pointee(InnerMatchers...)))));
   };
 
   Finder->addMatcher(
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index ef596f99262be..23d789d8466d3 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1319,8 +1319,6 @@ class ASTContext : public RefCountedBase<ASTContext> {
 
   QualType getTypeDeclTypeSlow(const TypeDecl *Decl) const;
 
-  QualType getPipeType(QualType T, bool ReadOnly) const;
-
 public:
   /// Return the uniqued reference to the type for an address space
   /// qualified type with the specified type and address space.
@@ -1500,6 +1498,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// blocks.
   QualType getBlockDescriptorType() const;
 
+  // Return a pipe type for the specified type.
+  QualType getPipeType(QualType T, bool ReadOnly) const;
+
   /// Return a read_only pipe type for the specified type.
   QualType getReadPipeType(QualType T) const;
 
@@ -1901,10 +1902,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// C++11 decltype.
   QualType getDecltypeType(Expr *e, QualType UnderlyingType) const;
 
-  QualType getPackIndexingType(QualType Pattern, Expr *IndexExpr,
-                               bool FullySubstituted = false,
-                               ArrayRef<QualType> Expansions = {},
-                               int Index = -1) const;
+  QualType getPackIndexingType(
+      QualType Pattern, Expr *IndexExpr, bool FullySubstituted = false,
+      ArrayRef<QualType> Expansions = {},
+      std::optional<unsigned> SelectedIndex = std::nullopt) const;
 
   /// Unary type transforms
   QualType getUnaryTransformType(QualType BaseType, QualType UnderlyingType,
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 642881f185c07..56ff0f853a799 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -643,10 +643,10 @@ class Qualifiers {
   void addQualifiers(Qualifiers Q) {
     // If the other set doesn't have any non-boolean qualifiers, just
     // bit-or it in.
-    if (!(Q.Mask & ~CVRMask))
+    if (!(Q.Mask & ~CVRUMask))
       Mask |= Q.Mask;
     else {
-      Mask |= (Q.Mask & CVRMask);
+      Mask |= (Q.Mask & CVRUMask);
       if (Q.hasAddressSpace())
         addAddressSpace(Q.getAddressSpace());
       if (Q.hasObjCGCAttr())
@@ -662,10 +662,10 @@ class Qualifiers {
   void removeQualifiers(Qualifiers Q) {
     // If the other set doesn't have any non-boolean qualifiers, just
     // bit-and the inverse in.
-    if (!(Q.Mask & ~CVRMask))
+    if (!(Q.Mask & ~CVRUMask))
       Mask &= ~Q.Mask;
     else {
-      Mask &= ~(Q.Mask & CVRMask);
+      Mask &= ~(Q.Mask & CVRUMask);
       if (getObjCGCAttr() == Q.getObjCGCAttr())
         removeObjCGCAttr();
       if (getObjCLifetime() == Q.getObjCLifetime())
@@ -805,12 +805,13 @@ class Qualifiers {
 
   static constexpr uint64_t UMask = 0x8;
   static constexpr uint64_t UShift = 3;
+  static constexpr uint64_t CVRUMask = CVRMask | UMask;
   static constexpr uint64_t GCAttrMask = 0x30;
   static constexpr uint64_t GCAttrShift = 4;
   static constexpr uint64_t LifetimeMask = 0x1C0;
   static constexpr uint64_t LifetimeShift = 6;
   static constexpr uint64_t AddressSpaceMask =
-      ~(CVRMask | UMask | GCAttrMask | LifetimeMask);
+      ~(CVRUMask | GCAttrMask | LifetimeMask);
   static constexpr uint64_t AddressSpaceShift = 9;
   static constexpr uint64_t PtrAuthShift = 32;
   static constexpr uint64_t PtrAuthMask = uint64_t(0xffffffff) << PtrAuthShift;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e215f07e2bf0a..047ba88511dac 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3193,7 +3193,8 @@ class Sema final : public SemaBase {
   /// Returns the TypeDeclType for the given type declaration,
   /// as ASTContext::getTypeDeclType would, but
   /// performs the required semantic checks for name lookup of said entity.
-  QualType getTypeDeclType(DeclContext *LookupCtx, DiagCtorKind DCK,
+  QualType getTypeDeclType(const NestedNameSpecifier *NNS,
+                           DeclContext *LookupCtx, DiagCtorKind DCK,
                            TypeDecl *TD, SourceLocation NameLoc);
 
   /// If the identifier refers to a type name within this scope,
@@ -13980,6 +13981,8 @@ class Sema final : public SemaBase {
   FunctionDecl *SubstSpaceshipAsEqualEqual(CXXRecordDecl *RD,
                                            FunctionDecl *Spaceship);
 
+  QualType resugar(const NestedNameSpecifier *NNS, QualType T);
+
   /// Performs template instantiation for all implicit template
   /// instantiations we have seen until this point.
   void PerformPendingInstantiations(bool LocalOnly = false,
@@ -14972,7 +14975,8 @@ class Sema final : public SemaBase {
   /// wasn't specified explicitly.  This handles method types formed from
   /// function type typedefs and typename template arguments.
   void adjustMemberFunctionCC(QualType &T, bool HasThisPointer,
-                              bool IsCtorOrDtor, SourceLocation Loc);
+                              bool IsCtorOrDtor, bool IsDeduced,
+                              SourceLocation Loc);
 
   // Check if there is an explicit attribute, but only look through parens.
   // The intent is to look for an attribute on the current declarator, but not
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 7a44fd4a5dd0e..79bdac607cc2a 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -6385,13 +6385,14 @@ QualType ASTContext::getDecltypeType(Expr *e, QualType UnderlyingType) const {
   return QualType(dt, 0);
 }
 
-QualType ASTContext::getPackIndexingType(QualType Pattern, Expr *IndexExpr,
-                                         bool FullySubstituted,
-                                         ArrayRef<QualType> Expansions,
-                                         int Index) const {
+QualType
+ASTContext::getPackIndexingType(QualType Pattern, Expr *IndexExpr,
+                                bool FullySubstituted,
+                                ArrayRef<QualType> Expansions,
+                                std::optional<unsigned> SelectedIndex) const {
   QualType Canonical;
-  if (FullySubstituted && Index != -1) {
-    Canonical = getCanonicalType(Expansions[Index]);
+  if (FullySubstituted && SelectedIndex) {
+    Canonical = getCanonicalType(Expansions[*SelectedIndex]);
   } else {
     llvm::FoldingSetNodeID ID;
     PackIndexingType::Profile(ID, *this, Pattern.getCanonicalType(), IndexExpr,
@@ -14103,9 +14104,7 @@ static QualType getCommonNonSugarTypeNode(ASTContext &Ctx, const Type *X,
   case Type::Pipe: {
     const auto *PX = cast<PipeType>(X), *PY = cast<PipeType>(Y);
     assert(PX->isReadOnly() == PY->isReadOnly());
-    auto MP = PX->isReadOnly() ? &ASTContext::getReadPipeType
-                               : &ASTContext::getWritePipeType;
-    return (Ctx.*MP)(getCommonElementType(Ctx, PX, PY));
+    return Ctx.getPipeType(getCommonElementType(Ctx, PX, PY), PX->isReadOnly());
   }
   case Type::TemplateTypeParm: {
     const auto *TX = cast<TemplateTypeParmType>(X),
diff --git a/clang/lib/Sema/SemaCXXScopeSpec.cpp b/clang/lib/Sema/SemaCXXScopeSpec.cpp
index 02cd699addd75..e8fe1cdd7336a 100644
--- a/clang/lib/Sema/SemaCXXScopeSpec.cpp
+++ b/clang/lib/Sema/SemaCXXScopeSpec.cpp
@@ -644,8 +644,9 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
       return false;
     }
 
-    QualType T =
-        Context.getTypeDeclType(cast<TypeDecl>(SD->getUnderlyingDecl()));
+    QualType T = resugar(
+        SS.getScopeRep(),
+        Context.getTypeDeclType(cast<TypeDecl>(SD->getUnderlyingDecl())));
 
     if (T->isEnumeralType())
       Diag(IdInfo.IdentifierLoc, diag::warn_cxx98_compat_enum_nested_name_spec);
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index 53536b0d14037..88d849b27db07 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -113,6 +113,7 @@ static QualType lookupPromiseType(Sema &S, const FunctionDecl *FD,
   }
   // The promise type is required to be a class type.
   QualType PromiseType = S.Context.getTypeDeclType(Promise);
+  // FIXME: resugar PromiseType.
 
   auto buildElaboratedType = [&]() {
     auto *NNS = NestedNameSpecifier::Create(S.Context, nullptr, S.getStdNamespace());
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 043e82414c052..aded29a9daf6d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -139,7 +139,8 @@ class TypeNameValidatorCCC final : public CorrectionCandidateCallback {
 
 } // end anonymous namespace
 
-QualType Sema::getTypeDeclType(DeclContext *LookupCtx, DiagCtorKind DCK,
+QualType Sema::getTypeDeclType(const NestedNameSpecifier *NNS,
+                               DeclContext *LookupCtx, DiagCtorKind DCK,
                                TypeDecl *TD, SourceLocation NameLoc) {
   auto *LookupRD = dyn_cast_or_null<CXXRecordDecl>(LookupCtx);
   auto *FoundRD = dyn_cast<CXXRecordDecl>(TD);
@@ -156,7 +157,7 @@ QualType Sema::getTypeDeclType(DeclContext *LookupCtx, DiagCtorKind DCK,
 
   DiagnoseUseOfDecl(TD, NameLoc);
   MarkAnyDeclReferenced(TD->getLocation(), TD, /*OdrUse=*/false);
-  return Context.getTypeDeclType(TD);
+  return resugar(NNS, Context.getTypeDeclType(TD));
 }
 
 namespace {
@@ -534,7 +535,7 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
     // C++ [class.qual]p2: A lookup that would find the injected-class-name
     // instead names the constructors of the class, except when naming a class.
     // This is ill-formed when we're not actually forming a ctor or dtor name.
-    T = getTypeDeclType(LookupCtx,
+    T = getTypeDeclType(SS ? SS->getScopeRep() : nullptr, LookupCtx,
                         IsImplicitTypename ? DiagCtorKind::Implicit
                                            : DiagCtorKind::None,
                         TD, NameLoc);
@@ -9872,7 +9873,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   if (D.isFirstDeclarationOfMember())
     adjustMemberFunctionCC(
         R, !(D.isStaticMember() || D.isExplicitObjectMemberFunction()),
-        D.isCtorOrDtor(), D.getIdentifierLoc());
+        D.isCtorOrDtor(), /*IsDeduced=*/false, D.getIdentifierLoc());
 
   bool isFriend = false;
   FunctionTemplateDecl *FunctionTemplate = nullptr;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c90ef04bbbe0a..330afbfa2d19d 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1248,7 +1248,7 @@ static QualType getTupleLikeElementType(Sema &S, SourceLocation Loc,
       S.Diag(R.getRepresentativeDecl()->getLocation(), diag::note_declared_at);
     return QualType();
   }
-
+  // FIXME: resugar
   return S.Context.getTypeDeclType(TD);
 }
 
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 7fe38402300a9..0b26f79ea3bfc 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -18,6 +18,7 @@
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/TemplateName.h"
+#include "clang/AST/TypeOrdering.h"
 #include "clang/AST/TypeVisitor.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/DiagnosticSema.h"
@@ -93,6 +94,725 @@ unsigned Sema::getTemplateDepth(Scope *S) const {
   return Depth;
 }
 
+namespace {
+
+class NameMap {
+  llvm::DenseMap<const Decl *, ArrayRef<TemplateArgument>> Map;
+
+  void insert(const Decl *AssociatedDecl, ArrayRef<TemplateArgument> Args) {
+    assert(!Args.empty());
+    Map.try_emplace(AssociatedDecl->getCanonicalDecl(), Args);
+  }
+
+public:
+  NameMap() = default;
+
+  const TemplateArgument *getArgument(const Decl *AssociatedDecl,
+                                      unsigned Index,
+                                      std::optional<unsigned> PackIndex) const {
+    auto It = Map.find(AssociatedDecl);
+    if (It == Map.end())
+      return nullptr;
+    ArrayRef<TemplateArgument> Args = It->second;
+    assert(Index < Args.size());
+    const TemplateArgument &Arg = Args[Index];
+    if (!PackIndex)
+      return &Arg;
+    ArrayRef<TemplateArgument> PackArgs = Arg.getPackAsArray();
+    assert(*PackIndex < PackArgs.size());
+    return &PackArgs[PackArgs.size() - 1 - *PackIndex];
+  }
+
+  void insert(Sema &SemaRef, const Type *T) {
+    const Type *CanonT = T->getCanonicalTypeInternal().getTypePtr();
+    if (auto TC = CanonT->getTypeClass(); TC != Type::Record) {
+      assert(TC == Type::Enum || TC == Type::InjectedClassName ||
+             T->isDependentType());
+      return;
+    }
+    const auto *TS = T->getAsNonAliasTemplateSpecializationType();
+    if (!TS)
+      return;
+    auto *CTSD = cast<ClassTemplateSpecializationDecl>(
+        cast<RecordType>(CanonT)->getDecl());
+    auto PU = CTSD->getInstantiatedFrom();
+    if (PU.isNull())
+      return;
+
+    ArrayRef<TemplateArgument> Args = TS->getConvertedArguments();
+    auto *CTPSD = PU.dyn_cast<ClassTemplatePartialSpecializationDecl *>();
+    if (!CTPSD)
+      return insert(CTSD, Args);
+    // FIXME: Don't deduce partial specialization args on resugaring.
+    TemplateParameterList *TPL = CTPSD->getTemplateParameters();
+    TemplateDeductionInfo Info(SourceLocation(), TPL->getDepth());
+    [[maybe_unused]] TemplateDeductionResult Res =
+        SemaRef.DeduceTemplateArguments(CTPSD, Args, Info);
+    assert(Res == TemplateDeductionResult::Success);
+    insert(CTSD, Info.takeSugared()->asArray());
+  }
+
+  void insert(Sema &SemaRef, const NestedNameSpecifier *NNS) {
+    for (/**/; NNS; NNS = NNS->getPrefix()) {
+      switch (NNS->getKind()) {
+      case NestedNameSpecifier::Global:
+      case NestedNameSpecifier::Namespace:
+      case NestedNameSpecifier::NamespaceAlias:
+      case NestedNameSpecifier::Super:
+        return;
+      case NestedNameSpecifier::Identifier:
+        continue;
+      case NestedNameSpecifier::TypeSpec:
+      case NestedNameSpecifier::TypeSpecWithTemplate:
+        insert(SemaRef, NNS->getAsType());
+        continue;
+      }
+      llvm_unreachable("Unknown NestedNameSpecifier Kind");
+    }
+  }
+
+  bool empty() const { return Map.empty(); }
+};
+
+class Resugarer {
+  Sema &SemaRef;
+  const NameMap *Names;
+  llvm::DenseMap<QualType, QualType> CacheTypes;
+
+public:
+  Resugarer(Sema &SemaRef, const NameMap &Names)
+      : SemaRef(SemaRef), Names(&Names) {}
+
+  template <class T>
+  SmallVector<T, 4> transform(ArrayRef<T> Es, bool &Changed) {
+    SmallVector<T, 4> TransformedEs(Es);
+    for (auto &E : TransformedEs)
+      E = transform(E, Changed);
+    return TransformedEs;
+  }
+
+  NestedNameSpecifier *transform(NestedNameSpecifier *NNS, bool &OutChanged) {
+    if (!NNS)
+      return NNS;
+
+    bool Changed = false;
+    switch (auto K = NNS->getKind()) {
+    case NestedNameSpecifier::Global:
+    case NestedNameSpecifier::Namespace:
+    case NestedNameSpecifier::NamespaceAlias:
+    case NestedNameSpecifier::Super:
+      return NNS;
+    case NestedNameSpecifier::Identifier: {
+      NestedNameSpecifier *Prefix = transform(NNS->getPrefix(), Changed);
+      if (!Changed)
+        return NNS;
+      OutChanged = true;
+      return NestedNameSpecifier::Create(SemaRef.Context, Prefix,
+                                         NNS->getAsIdentifier());
+    }
+    case NestedNameSpecifier::TypeSpec:
+    case NestedNameSpecifier::TypeSpecWithTemplate: {
+      const Type *T =
+          transform(QualType(NNS->getAsType(), 0), Changed).getTypePtr();
+      NestedNameSpecifier *Prefix;
+      if (const auto *ET = dyn_cast<ElaboratedType>(T)) {
+        Prefix = transform(ET->getQualifier(), Changed);
+        T = ET->getNamedType().getTypePtr();
+      } else {
+        Prefix = transform(NNS->getPrefix(), Changed);
+      }
+      if (!Changed)
+        return NNS;
+      OutChanged = true;
+      return NestedNameSpecifier::Create(
+          SemaRef.Context, Prefix,
+          K == NestedNameSpecifier::TypeSpecWithTemplate, T);
+    }
+    }
+    llvm_unreachable("Unknown NestedNameSpecifier Kind");
+  }
+
+  TemplateName transform(TemplateName TN, bool &OutChanged) {
+    auto build = [&](TemplateName NewTN) {
+      assert(SemaRef.Context.hasSameTemplateName(TN, NewTN));
+      OutChanged = true;
+      return NewTN;
+    };
+
+    bool Changed = false;
+    switch (TN.getKind()) {
+    case TemplateName::AssumedTemplate:
+    case TemplateName::OverloadedTemplate:
+    case TemplateName::Template:
+    case TemplateName::UsingTemplate:
+      return TN;
+    case TemplateName::DeducedTemplate: {
+      const auto *DTN = TN.getAsDeducedTemplateName();
+      TemplateName Underlying = transform(DTN->getUnderlying(), Changed);
+      DefaultArguments DefaultArgs = DTN->getDefaultArguments();
+      auto Args = transform(DefaultArgs.Args, Changed);
+      DefaultArgs.Args = Args;
+      if (!Changed)
+        return TN;
+      return build(
+          SemaRef.Context.getDeducedTemplateName(Underlying, DefaultArgs));
+    }
+    case TemplateName::DependentTemplate: {
+      const auto *DTN = TN.getAsDependentTemplateName();
+      NestedNameSpecifier *NNS = transform(DTN->getQualifier(), Changed);
+      if (!Changed)
+        return TN;
+      return build(
+          SemaRef.Context.getDependentTemplateName(NNS, DTN->getOperator()));
+    }
+    case TemplateName::QualifiedTemplate: {
+      const auto *QTN = TN.getAsQualifiedTemplateName();
+      NestedNameSpecifier *NNS = transform(QTN->getQualifier(), Changed);
+      TemplateName UTN = transform(QTN->getUnderlyingTemplate(), Changed);
+      if (!Changed)
+        return TN;
+      return build(SemaRef.Context.getQualifiedTemplateName(
+          NNS, QTN->hasTemplateKeyword(), UTN));
+    }
+    case TemplateName::SubstTemplateTemplateParm: {
+      const auto *STN = TN.getAsSubstTemplateTemplateParm();
+      const TemplateArgument *Arg = Names->getArgument(
+          STN->getAssociatedDecl(), STN->getIndex(), STN->getPackIndex());
+      if (!Arg)
+        return TN;
+      return build(Arg->getAsTemplate());
+    }
+    case TemplateName::SubstTemplateTemplateParmPack: {
+      const auto *STNP = TN.getAsSubstTemplateTemplateParmPack();
+      TemplateArgument Pack = transform(STNP->getArgumentPack(), Changed);
+      if (!Changed)
+        return TN;
+      return build(SemaRef.Context.getSubstTemplateTemplateParmPack(
+          Pack, STNP->getAssociatedDecl(), STNP->getIndex(), STNP->getFinal()));
+    }
+    }
+    llvm_unreachable("Unhandled TemplateName kind");
+  }
+
+  QualType buildType(QualType Orig, const Type *Ty, Qualifiers Quals) {
+    QualType NewT = SemaRef.Context.getQualifiedType(Ty, Quals);
+    assert(SemaRef.Context.hasSameType(Orig, NewT));
+    CacheTypes.find(Orig)->second = NewT;
+    return NewT;
+  }
+
+  QualType transform(QualType TT, bool &OutChanged) {
+    if (TT.isNull() || TT.isCanonical())
+      return TT;
+
+    if (auto [It, Created] = CacheTypes.try_emplace(TT, TT); !Created) {
+      QualType NewT = It->second;
+      OutChanged |= (NewT != TT);
+      return NewT;
+    }
+
+    SplitQualType ST = TT.split();
+    auto build = [&](QualType T) {
+      OutChanged = true;
+      return buildType(TT, T.getTypePtr(), ST.Quals);
+    };
+
+    bool Changed = false;
+    switch (ST.Ty->getTypeClass()) {
+    case Type::Adjusted: {
+      const auto *T = cast<AdjustedType>(ST.Ty);
+      QualType OT = transform(T->getOriginalType(), Changed);
+      // FIXME: Handle AdjustedType....
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Mar 21, 2025

@llvm/pr-subscribers-lldb

Author: Matheus Izvekov (mizvekov)

Changes

This is the introductory patch for a larger work which intends to solve the long standing C++ issue with losing type sugar when acessing template specializations.

The well known example here is specializing a template with std::string, but getting diagnostics related to std::basic_string&lt;char&gt; instead.

This implements a transform which, upon member access, propagates type sugar from the naming context into the accessed entity.

It also implements a single use of this transform, resugaring access to TypeDecls.

For more details and discussion see:
https://discourse.llvm.org/t/rfc-improving-diagnostics-with-template-specialization-resugaring/64294

Even though this patch is ready for review, some dependent patches are not, and might not be ready for some time.

There is some more stuff that could be done either here or in follow ups:

  • Its worth exploring if a global resugaring cache is worthwhile, besides the current operational cache. A global cache would be more expensive to index, so there is a tradeoff, and maybe should be used of the whole result of the operation, while keeping the existing cache for sub-results.
  • It would be ideal if the transform could live in ASTContext instead of Sema. There are a few dependencies that would have to be tackled.
    • Template arguments deduced for partial specializations.
    • Some kinds of type adjustments currently require Sema.

Differential Revision: https://reviews.llvm.org/D127695


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

17 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp (+1-1)
  • (modified) clang/include/clang/AST/ASTContext.h (+7-6)
  • (modified) clang/include/clang/AST/Type.h (+6-5)
  • (modified) clang/include/clang/Sema/Sema.h (+6-2)
  • (modified) clang/lib/AST/ASTContext.cpp (+8-9)
  • (modified) clang/lib/Sema/SemaCXXScopeSpec.cpp (+3-2)
  • (modified) clang/lib/Sema/SemaCoroutine.cpp (+1)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+5-4)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+721-1)
  • (modified) clang/lib/Sema/SemaTemplateDeduction.cpp (+4-2)
  • (modified) clang/lib/Sema/SemaType.cpp (+6-4)
  • (modified) clang/test/AST/ast-dump-openmp-begin-declare-variant_reference.cpp (+2-6)
  • (modified) clang/test/AST/ast-dump-template-name.cpp (+2-5)
  • (modified) clang/test/CXX/temp/temp.param/p15-cxx0x.cpp (+2-2)
  • (added) clang/test/Sema/Resugar/resugar-types.cpp (+209)
  • (modified) lldb/test/API/commands/expression/import-std-module/iterator/TestIteratorFromStdModule.py (+1-1)
diff --git a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp
index e843c593a92cc..679fbd75d2479 100644
--- a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp
@@ -126,7 +126,7 @@ void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) {
   auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType()));
   auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) {
     return autoType(hasDeducedType(
-        hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...)))));
+        hasCanonicalType(pointerType(pointee(InnerMatchers...)))));
   };
 
   Finder->addMatcher(
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index ef596f99262be..23d789d8466d3 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1319,8 +1319,6 @@ class ASTContext : public RefCountedBase<ASTContext> {
 
   QualType getTypeDeclTypeSlow(const TypeDecl *Decl) const;
 
-  QualType getPipeType(QualType T, bool ReadOnly) const;
-
 public:
   /// Return the uniqued reference to the type for an address space
   /// qualified type with the specified type and address space.
@@ -1500,6 +1498,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// blocks.
   QualType getBlockDescriptorType() const;
 
+  // Return a pipe type for the specified type.
+  QualType getPipeType(QualType T, bool ReadOnly) const;
+
   /// Return a read_only pipe type for the specified type.
   QualType getReadPipeType(QualType T) const;
 
@@ -1901,10 +1902,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// C++11 decltype.
   QualType getDecltypeType(Expr *e, QualType UnderlyingType) const;
 
-  QualType getPackIndexingType(QualType Pattern, Expr *IndexExpr,
-                               bool FullySubstituted = false,
-                               ArrayRef<QualType> Expansions = {},
-                               int Index = -1) const;
+  QualType getPackIndexingType(
+      QualType Pattern, Expr *IndexExpr, bool FullySubstituted = false,
+      ArrayRef<QualType> Expansions = {},
+      std::optional<unsigned> SelectedIndex = std::nullopt) const;
 
   /// Unary type transforms
   QualType getUnaryTransformType(QualType BaseType, QualType UnderlyingType,
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 642881f185c07..56ff0f853a799 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -643,10 +643,10 @@ class Qualifiers {
   void addQualifiers(Qualifiers Q) {
     // If the other set doesn't have any non-boolean qualifiers, just
     // bit-or it in.
-    if (!(Q.Mask & ~CVRMask))
+    if (!(Q.Mask & ~CVRUMask))
       Mask |= Q.Mask;
     else {
-      Mask |= (Q.Mask & CVRMask);
+      Mask |= (Q.Mask & CVRUMask);
       if (Q.hasAddressSpace())
         addAddressSpace(Q.getAddressSpace());
       if (Q.hasObjCGCAttr())
@@ -662,10 +662,10 @@ class Qualifiers {
   void removeQualifiers(Qualifiers Q) {
     // If the other set doesn't have any non-boolean qualifiers, just
     // bit-and the inverse in.
-    if (!(Q.Mask & ~CVRMask))
+    if (!(Q.Mask & ~CVRUMask))
       Mask &= ~Q.Mask;
     else {
-      Mask &= ~(Q.Mask & CVRMask);
+      Mask &= ~(Q.Mask & CVRUMask);
       if (getObjCGCAttr() == Q.getObjCGCAttr())
         removeObjCGCAttr();
       if (getObjCLifetime() == Q.getObjCLifetime())
@@ -805,12 +805,13 @@ class Qualifiers {
 
   static constexpr uint64_t UMask = 0x8;
   static constexpr uint64_t UShift = 3;
+  static constexpr uint64_t CVRUMask = CVRMask | UMask;
   static constexpr uint64_t GCAttrMask = 0x30;
   static constexpr uint64_t GCAttrShift = 4;
   static constexpr uint64_t LifetimeMask = 0x1C0;
   static constexpr uint64_t LifetimeShift = 6;
   static constexpr uint64_t AddressSpaceMask =
-      ~(CVRMask | UMask | GCAttrMask | LifetimeMask);
+      ~(CVRUMask | GCAttrMask | LifetimeMask);
   static constexpr uint64_t AddressSpaceShift = 9;
   static constexpr uint64_t PtrAuthShift = 32;
   static constexpr uint64_t PtrAuthMask = uint64_t(0xffffffff) << PtrAuthShift;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e215f07e2bf0a..047ba88511dac 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3193,7 +3193,8 @@ class Sema final : public SemaBase {
   /// Returns the TypeDeclType for the given type declaration,
   /// as ASTContext::getTypeDeclType would, but
   /// performs the required semantic checks for name lookup of said entity.
-  QualType getTypeDeclType(DeclContext *LookupCtx, DiagCtorKind DCK,
+  QualType getTypeDeclType(const NestedNameSpecifier *NNS,
+                           DeclContext *LookupCtx, DiagCtorKind DCK,
                            TypeDecl *TD, SourceLocation NameLoc);
 
   /// If the identifier refers to a type name within this scope,
@@ -13980,6 +13981,8 @@ class Sema final : public SemaBase {
   FunctionDecl *SubstSpaceshipAsEqualEqual(CXXRecordDecl *RD,
                                            FunctionDecl *Spaceship);
 
+  QualType resugar(const NestedNameSpecifier *NNS, QualType T);
+
   /// Performs template instantiation for all implicit template
   /// instantiations we have seen until this point.
   void PerformPendingInstantiations(bool LocalOnly = false,
@@ -14972,7 +14975,8 @@ class Sema final : public SemaBase {
   /// wasn't specified explicitly.  This handles method types formed from
   /// function type typedefs and typename template arguments.
   void adjustMemberFunctionCC(QualType &T, bool HasThisPointer,
-                              bool IsCtorOrDtor, SourceLocation Loc);
+                              bool IsCtorOrDtor, bool IsDeduced,
+                              SourceLocation Loc);
 
   // Check if there is an explicit attribute, but only look through parens.
   // The intent is to look for an attribute on the current declarator, but not
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 7a44fd4a5dd0e..79bdac607cc2a 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -6385,13 +6385,14 @@ QualType ASTContext::getDecltypeType(Expr *e, QualType UnderlyingType) const {
   return QualType(dt, 0);
 }
 
-QualType ASTContext::getPackIndexingType(QualType Pattern, Expr *IndexExpr,
-                                         bool FullySubstituted,
-                                         ArrayRef<QualType> Expansions,
-                                         int Index) const {
+QualType
+ASTContext::getPackIndexingType(QualType Pattern, Expr *IndexExpr,
+                                bool FullySubstituted,
+                                ArrayRef<QualType> Expansions,
+                                std::optional<unsigned> SelectedIndex) const {
   QualType Canonical;
-  if (FullySubstituted && Index != -1) {
-    Canonical = getCanonicalType(Expansions[Index]);
+  if (FullySubstituted && SelectedIndex) {
+    Canonical = getCanonicalType(Expansions[*SelectedIndex]);
   } else {
     llvm::FoldingSetNodeID ID;
     PackIndexingType::Profile(ID, *this, Pattern.getCanonicalType(), IndexExpr,
@@ -14103,9 +14104,7 @@ static QualType getCommonNonSugarTypeNode(ASTContext &Ctx, const Type *X,
   case Type::Pipe: {
     const auto *PX = cast<PipeType>(X), *PY = cast<PipeType>(Y);
     assert(PX->isReadOnly() == PY->isReadOnly());
-    auto MP = PX->isReadOnly() ? &ASTContext::getReadPipeType
-                               : &ASTContext::getWritePipeType;
-    return (Ctx.*MP)(getCommonElementType(Ctx, PX, PY));
+    return Ctx.getPipeType(getCommonElementType(Ctx, PX, PY), PX->isReadOnly());
   }
   case Type::TemplateTypeParm: {
     const auto *TX = cast<TemplateTypeParmType>(X),
diff --git a/clang/lib/Sema/SemaCXXScopeSpec.cpp b/clang/lib/Sema/SemaCXXScopeSpec.cpp
index 02cd699addd75..e8fe1cdd7336a 100644
--- a/clang/lib/Sema/SemaCXXScopeSpec.cpp
+++ b/clang/lib/Sema/SemaCXXScopeSpec.cpp
@@ -644,8 +644,9 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
       return false;
     }
 
-    QualType T =
-        Context.getTypeDeclType(cast<TypeDecl>(SD->getUnderlyingDecl()));
+    QualType T = resugar(
+        SS.getScopeRep(),
+        Context.getTypeDeclType(cast<TypeDecl>(SD->getUnderlyingDecl())));
 
     if (T->isEnumeralType())
       Diag(IdInfo.IdentifierLoc, diag::warn_cxx98_compat_enum_nested_name_spec);
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index 53536b0d14037..88d849b27db07 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -113,6 +113,7 @@ static QualType lookupPromiseType(Sema &S, const FunctionDecl *FD,
   }
   // The promise type is required to be a class type.
   QualType PromiseType = S.Context.getTypeDeclType(Promise);
+  // FIXME: resugar PromiseType.
 
   auto buildElaboratedType = [&]() {
     auto *NNS = NestedNameSpecifier::Create(S.Context, nullptr, S.getStdNamespace());
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 043e82414c052..aded29a9daf6d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -139,7 +139,8 @@ class TypeNameValidatorCCC final : public CorrectionCandidateCallback {
 
 } // end anonymous namespace
 
-QualType Sema::getTypeDeclType(DeclContext *LookupCtx, DiagCtorKind DCK,
+QualType Sema::getTypeDeclType(const NestedNameSpecifier *NNS,
+                               DeclContext *LookupCtx, DiagCtorKind DCK,
                                TypeDecl *TD, SourceLocation NameLoc) {
   auto *LookupRD = dyn_cast_or_null<CXXRecordDecl>(LookupCtx);
   auto *FoundRD = dyn_cast<CXXRecordDecl>(TD);
@@ -156,7 +157,7 @@ QualType Sema::getTypeDeclType(DeclContext *LookupCtx, DiagCtorKind DCK,
 
   DiagnoseUseOfDecl(TD, NameLoc);
   MarkAnyDeclReferenced(TD->getLocation(), TD, /*OdrUse=*/false);
-  return Context.getTypeDeclType(TD);
+  return resugar(NNS, Context.getTypeDeclType(TD));
 }
 
 namespace {
@@ -534,7 +535,7 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
     // C++ [class.qual]p2: A lookup that would find the injected-class-name
     // instead names the constructors of the class, except when naming a class.
     // This is ill-formed when we're not actually forming a ctor or dtor name.
-    T = getTypeDeclType(LookupCtx,
+    T = getTypeDeclType(SS ? SS->getScopeRep() : nullptr, LookupCtx,
                         IsImplicitTypename ? DiagCtorKind::Implicit
                                            : DiagCtorKind::None,
                         TD, NameLoc);
@@ -9872,7 +9873,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   if (D.isFirstDeclarationOfMember())
     adjustMemberFunctionCC(
         R, !(D.isStaticMember() || D.isExplicitObjectMemberFunction()),
-        D.isCtorOrDtor(), D.getIdentifierLoc());
+        D.isCtorOrDtor(), /*IsDeduced=*/false, D.getIdentifierLoc());
 
   bool isFriend = false;
   FunctionTemplateDecl *FunctionTemplate = nullptr;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c90ef04bbbe0a..330afbfa2d19d 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1248,7 +1248,7 @@ static QualType getTupleLikeElementType(Sema &S, SourceLocation Loc,
       S.Diag(R.getRepresentativeDecl()->getLocation(), diag::note_declared_at);
     return QualType();
   }
-
+  // FIXME: resugar
   return S.Context.getTypeDeclType(TD);
 }
 
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 7fe38402300a9..0b26f79ea3bfc 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -18,6 +18,7 @@
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/TemplateName.h"
+#include "clang/AST/TypeOrdering.h"
 #include "clang/AST/TypeVisitor.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/DiagnosticSema.h"
@@ -93,6 +94,725 @@ unsigned Sema::getTemplateDepth(Scope *S) const {
   return Depth;
 }
 
+namespace {
+
+class NameMap {
+  llvm::DenseMap<const Decl *, ArrayRef<TemplateArgument>> Map;
+
+  void insert(const Decl *AssociatedDecl, ArrayRef<TemplateArgument> Args) {
+    assert(!Args.empty());
+    Map.try_emplace(AssociatedDecl->getCanonicalDecl(), Args);
+  }
+
+public:
+  NameMap() = default;
+
+  const TemplateArgument *getArgument(const Decl *AssociatedDecl,
+                                      unsigned Index,
+                                      std::optional<unsigned> PackIndex) const {
+    auto It = Map.find(AssociatedDecl);
+    if (It == Map.end())
+      return nullptr;
+    ArrayRef<TemplateArgument> Args = It->second;
+    assert(Index < Args.size());
+    const TemplateArgument &Arg = Args[Index];
+    if (!PackIndex)
+      return &Arg;
+    ArrayRef<TemplateArgument> PackArgs = Arg.getPackAsArray();
+    assert(*PackIndex < PackArgs.size());
+    return &PackArgs[PackArgs.size() - 1 - *PackIndex];
+  }
+
+  void insert(Sema &SemaRef, const Type *T) {
+    const Type *CanonT = T->getCanonicalTypeInternal().getTypePtr();
+    if (auto TC = CanonT->getTypeClass(); TC != Type::Record) {
+      assert(TC == Type::Enum || TC == Type::InjectedClassName ||
+             T->isDependentType());
+      return;
+    }
+    const auto *TS = T->getAsNonAliasTemplateSpecializationType();
+    if (!TS)
+      return;
+    auto *CTSD = cast<ClassTemplateSpecializationDecl>(
+        cast<RecordType>(CanonT)->getDecl());
+    auto PU = CTSD->getInstantiatedFrom();
+    if (PU.isNull())
+      return;
+
+    ArrayRef<TemplateArgument> Args = TS->getConvertedArguments();
+    auto *CTPSD = PU.dyn_cast<ClassTemplatePartialSpecializationDecl *>();
+    if (!CTPSD)
+      return insert(CTSD, Args);
+    // FIXME: Don't deduce partial specialization args on resugaring.
+    TemplateParameterList *TPL = CTPSD->getTemplateParameters();
+    TemplateDeductionInfo Info(SourceLocation(), TPL->getDepth());
+    [[maybe_unused]] TemplateDeductionResult Res =
+        SemaRef.DeduceTemplateArguments(CTPSD, Args, Info);
+    assert(Res == TemplateDeductionResult::Success);
+    insert(CTSD, Info.takeSugared()->asArray());
+  }
+
+  void insert(Sema &SemaRef, const NestedNameSpecifier *NNS) {
+    for (/**/; NNS; NNS = NNS->getPrefix()) {
+      switch (NNS->getKind()) {
+      case NestedNameSpecifier::Global:
+      case NestedNameSpecifier::Namespace:
+      case NestedNameSpecifier::NamespaceAlias:
+      case NestedNameSpecifier::Super:
+        return;
+      case NestedNameSpecifier::Identifier:
+        continue;
+      case NestedNameSpecifier::TypeSpec:
+      case NestedNameSpecifier::TypeSpecWithTemplate:
+        insert(SemaRef, NNS->getAsType());
+        continue;
+      }
+      llvm_unreachable("Unknown NestedNameSpecifier Kind");
+    }
+  }
+
+  bool empty() const { return Map.empty(); }
+};
+
+class Resugarer {
+  Sema &SemaRef;
+  const NameMap *Names;
+  llvm::DenseMap<QualType, QualType> CacheTypes;
+
+public:
+  Resugarer(Sema &SemaRef, const NameMap &Names)
+      : SemaRef(SemaRef), Names(&Names) {}
+
+  template <class T>
+  SmallVector<T, 4> transform(ArrayRef<T> Es, bool &Changed) {
+    SmallVector<T, 4> TransformedEs(Es);
+    for (auto &E : TransformedEs)
+      E = transform(E, Changed);
+    return TransformedEs;
+  }
+
+  NestedNameSpecifier *transform(NestedNameSpecifier *NNS, bool &OutChanged) {
+    if (!NNS)
+      return NNS;
+
+    bool Changed = false;
+    switch (auto K = NNS->getKind()) {
+    case NestedNameSpecifier::Global:
+    case NestedNameSpecifier::Namespace:
+    case NestedNameSpecifier::NamespaceAlias:
+    case NestedNameSpecifier::Super:
+      return NNS;
+    case NestedNameSpecifier::Identifier: {
+      NestedNameSpecifier *Prefix = transform(NNS->getPrefix(), Changed);
+      if (!Changed)
+        return NNS;
+      OutChanged = true;
+      return NestedNameSpecifier::Create(SemaRef.Context, Prefix,
+                                         NNS->getAsIdentifier());
+    }
+    case NestedNameSpecifier::TypeSpec:
+    case NestedNameSpecifier::TypeSpecWithTemplate: {
+      const Type *T =
+          transform(QualType(NNS->getAsType(), 0), Changed).getTypePtr();
+      NestedNameSpecifier *Prefix;
+      if (const auto *ET = dyn_cast<ElaboratedType>(T)) {
+        Prefix = transform(ET->getQualifier(), Changed);
+        T = ET->getNamedType().getTypePtr();
+      } else {
+        Prefix = transform(NNS->getPrefix(), Changed);
+      }
+      if (!Changed)
+        return NNS;
+      OutChanged = true;
+      return NestedNameSpecifier::Create(
+          SemaRef.Context, Prefix,
+          K == NestedNameSpecifier::TypeSpecWithTemplate, T);
+    }
+    }
+    llvm_unreachable("Unknown NestedNameSpecifier Kind");
+  }
+
+  TemplateName transform(TemplateName TN, bool &OutChanged) {
+    auto build = [&](TemplateName NewTN) {
+      assert(SemaRef.Context.hasSameTemplateName(TN, NewTN));
+      OutChanged = true;
+      return NewTN;
+    };
+
+    bool Changed = false;
+    switch (TN.getKind()) {
+    case TemplateName::AssumedTemplate:
+    case TemplateName::OverloadedTemplate:
+    case TemplateName::Template:
+    case TemplateName::UsingTemplate:
+      return TN;
+    case TemplateName::DeducedTemplate: {
+      const auto *DTN = TN.getAsDeducedTemplateName();
+      TemplateName Underlying = transform(DTN->getUnderlying(), Changed);
+      DefaultArguments DefaultArgs = DTN->getDefaultArguments();
+      auto Args = transform(DefaultArgs.Args, Changed);
+      DefaultArgs.Args = Args;
+      if (!Changed)
+        return TN;
+      return build(
+          SemaRef.Context.getDeducedTemplateName(Underlying, DefaultArgs));
+    }
+    case TemplateName::DependentTemplate: {
+      const auto *DTN = TN.getAsDependentTemplateName();
+      NestedNameSpecifier *NNS = transform(DTN->getQualifier(), Changed);
+      if (!Changed)
+        return TN;
+      return build(
+          SemaRef.Context.getDependentTemplateName(NNS, DTN->getOperator()));
+    }
+    case TemplateName::QualifiedTemplate: {
+      const auto *QTN = TN.getAsQualifiedTemplateName();
+      NestedNameSpecifier *NNS = transform(QTN->getQualifier(), Changed);
+      TemplateName UTN = transform(QTN->getUnderlyingTemplate(), Changed);
+      if (!Changed)
+        return TN;
+      return build(SemaRef.Context.getQualifiedTemplateName(
+          NNS, QTN->hasTemplateKeyword(), UTN));
+    }
+    case TemplateName::SubstTemplateTemplateParm: {
+      const auto *STN = TN.getAsSubstTemplateTemplateParm();
+      const TemplateArgument *Arg = Names->getArgument(
+          STN->getAssociatedDecl(), STN->getIndex(), STN->getPackIndex());
+      if (!Arg)
+        return TN;
+      return build(Arg->getAsTemplate());
+    }
+    case TemplateName::SubstTemplateTemplateParmPack: {
+      const auto *STNP = TN.getAsSubstTemplateTemplateParmPack();
+      TemplateArgument Pack = transform(STNP->getArgumentPack(), Changed);
+      if (!Changed)
+        return TN;
+      return build(SemaRef.Context.getSubstTemplateTemplateParmPack(
+          Pack, STNP->getAssociatedDecl(), STNP->getIndex(), STNP->getFinal()));
+    }
+    }
+    llvm_unreachable("Unhandled TemplateName kind");
+  }
+
+  QualType buildType(QualType Orig, const Type *Ty, Qualifiers Quals) {
+    QualType NewT = SemaRef.Context.getQualifiedType(Ty, Quals);
+    assert(SemaRef.Context.hasSameType(Orig, NewT));
+    CacheTypes.find(Orig)->second = NewT;
+    return NewT;
+  }
+
+  QualType transform(QualType TT, bool &OutChanged) {
+    if (TT.isNull() || TT.isCanonical())
+      return TT;
+
+    if (auto [It, Created] = CacheTypes.try_emplace(TT, TT); !Created) {
+      QualType NewT = It->second;
+      OutChanged |= (NewT != TT);
+      return NewT;
+    }
+
+    SplitQualType ST = TT.split();
+    auto build = [&](QualType T) {
+      OutChanged = true;
+      return buildType(TT, T.getTypePtr(), ST.Quals);
+    };
+
+    bool Changed = false;
+    switch (ST.Ty->getTypeClass()) {
+    case Type::Adjusted: {
+      const auto *T = cast<AdjustedType>(ST.Ty);
+      QualType OT = transform(T->getOriginalType(), Changed);
+      // FIXME: Handle AdjustedType....
[truncated]

Copy link

github-actions bot commented Mar 21, 2025

✅ With the latest revision this PR passed the Python code formatter.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

I don't really have any comments, quite a few improvements here that are valuable.

This is the introductory patch for a larger work which
intends to solve the long standing C++ issue with losing
type sugar when acessing template specializations.

The well known example here is specializing a template
with `std::string`, but getting diagnostics related to
`std::basic_string<char>` instead.

This implements a transform which, upon member access,
propagates type sugar from the naming context into the
accessed entity.

It also implements a single use of this transform,
resugaring access to TypeDecls.

For more details and discussion see:
https://discourse.llvm.org/t/rfc-improving-diagnostics-with-template-specialization-resugaring/64294

This is ready for review, although maybe not finished and
there is some more stuff that could be done either here
or in follow ups.

* Its worth exploring if a global resugaring cache is
  worthwhile, besides the current operational cache.
  A global cache would be more expensive to index, so there
  is a tradeoff, and maybe should be used of the whole
  result of the operation, while keeping the existing
  cache for sub-results.
* It would be ideal if the transform could live in ASTContext
  instead of Sema. There are a few dependencies that would
  have to be tackled.
  * Template arguments deduced for partial specializations.
  * Some kinds of type adjustments currently require Sema.

Differential Revision: https://reviews.llvm.org/D127695
@mizvekov mizvekov force-pushed the users/mizvekov/clang-resugar-typedecl branch from e621da3 to 4571fad Compare March 21, 2025 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:openmp OpenMP related changes to Clang clang Clang issues not falling into any other category clang-tidy clang-tools-extra coroutines C++20 coroutines lldb
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants