[clang] [clang-tools-extra] [Clang][AST] Introduce `ExplicitInstantiationDecl` to preserve source info and fix diagnostic locations (PR #191658)

via cfe-commits cfe-commits at lists.llvm.org
Sat Apr 11 20:48:34 PDT 2026


================
@@ -2196,23 +2208,23 @@ bool RecursiveASTVisitor<Derived>::TraverseTemplateArgumentLocsHelper(
        the source code anywhere.  (Note the instatiated *type* --              \
        set<int> -- is written, and will still get a callback of                \
        TemplateSpecializationType).  For explicit instantiations               \
-       ("template set<int>;"), we do need a callback, since this               \
-       is the only callback that's made for this instantiation.                \
-       We use getTemplateArgsAsWritten() to distinguish. */                    \
+       ("template set<int>;"), the ExplicitInstantiationDecl node              \
+       handles traversal of template args and qualifier.                       \
+       For explicit specializations ("template<> set<int> {...};"),            \
+       we traverse template args here since there is no EID. */                \
     if (const auto *ArgsWritten = D->getTemplateArgsAsWritten()) {             \
       assert(D->getTemplateSpecializationKind() != TSK_ImplicitInstantiation); \
-      /* The args that remains unspecialized. */                               \
-      TRY_TO(TraverseTemplateArgumentLocsHelper(                               \
-          ArgsWritten->getTemplateArgs(), ArgsWritten->NumTemplateArgs));      \
+      if (D->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) {  \
+        TRY_TO(TraverseTemplateArgumentLocsHelper(                             \
+            ArgsWritten->getTemplateArgs(), ArgsWritten->NumTemplateArgs));    \
+      }                                                                        \
     }                                                                          \
                                                                                \
     if (getDerived().shouldVisitTemplateInstantiations() ||                    \
         D->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) {    \
       /* Traverse base definition for explicit specializations */              \
       TRY_TO(Traverse##DECLKIND##Helper(D));                                   \
     } else {                                                                   \
-      TRY_TO(TraverseNestedNameSpecifierLoc(D->getQualifierLoc()));            \
-                                                                               \
----------------
16bit-ykiko wrote:

The canonical decl for an explicit instantiation here is the `ClassTemplateSpecializationDecl` / `VarTemplateSpecializationDecl`. Before EID, these nodes partially assumed the responsibility of recording the source location information for explicit instantiations (though sometimes inaccurately, like with the NNS), which is why RAV traversed those locations here.

Now that we have EID, the location information is both more complete and accurate, so the traversal should be handled uniformly when visiting the EID instead. If we also traverse the specialization node, downstream tools like clang-tidy will receive duplicate callbacks (for example, `avoid-c-arrays` would report two warnings for the same type, which was fixed in a previous commit).

Note that we didn't just delete the traversal entirely; the `TSK_ExplicitSpecialization` branch is kept because explicit specializations (`template<>`) do not have a corresponding EID, meaning they still need to traverse their own template arguments.

https://github.com/llvm/llvm-project/pull/191658


More information about the cfe-commits mailing list