<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/96098>96098</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            ClassTemplateSpecializationDecl has wrong/misleading getSourceRange after: declare, specialize, define, use
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            new issue
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          smcpeak
      </td>
    </tr>
</table>

<pre>
    If a `ClassTemplateSpecializationDecl` is created by first instantiating a non-definition (forward) template declaration, then later filling it out by instantiating the template definition, its `getSourceRange()` (and consequently its `getBeginLoc()` and `getEndLoc()`) comes from the template forward declaration, while its `getLocation()` is from the template definition.  This presents a somewhat confusing situation to API users, who would normally expect `getLocation()` to be within `getSourceRange()`.

Moreover, API users generally expect that asking for the source location of an instantiation will yield the location of the template declaration from which (the bulk of) that instantiation was created.  But the behavior of `CTSD::getSourceRange()` breaks that.

The impact (such as on a dependency analysis) can be substantial, as these locations could be in different files.

Example input:

```cpp
// Forward-declare the template.
template <class T>
struct S;

// Cause the specialization declaration to be instantiated.  The
// "instantiated from" declaration is set to the one above.
typedef S<int> S_of_int;

// Now define the template.
template <class T>
struct S {
  T t;
};

// Then instantiate it.  The resulting ClassTemplateSpecializationDecl
// has a getSourceRange() derived from the template's forward
// declaration but a getLocation() derived from the template definition.
S_of_int s;
```

Relevant line of `clang++ -fsyntax-only -Xclang -ast-dump test.cc`:

```text
| `-ClassTemplateSpecializationDecl 0x55bbd7d14c98 <line:2:1, line:3:8> line:11:8 struct S definition implicit_instantiation
 ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^
 from the fwd decl   from defn
```

I observe this with Clang-18.1.4 built from source, as well as the latest trunk version: https://godbolt.org/z/nKP747cb8

## Some exploration of the cause

[`ClassTemplateSpecializationDecl::getSourceRange()`](https://github.com/llvm/llvm-project/blob/main/clang/lib/AST/DeclTemplate.cpp#L1001) begins:

```cpp
SourceRange
ClassTemplateSpecializationDecl::getSourceRange() const {
  switch (getSpecializationKind()) {
  case TSK_Undeclared:
 case TSK_ImplicitInstantiation: {
 llvm::PointerUnion<ClassTemplateDecl *,
 ClassTemplatePartialSpecializationDecl *>
        Pattern = getSpecializedTemplateOrPartial();
    assert(!Pattern.isNull() &&
 "Class template specialization without pattern?");
    if (const auto *CTPSD =
 Pattern.dyn_cast<ClassTemplatePartialSpecializationDecl *>())
      return CTPSD->getSourceRange();
    return Pattern.get<ClassTemplateDecl *>()->getSourceRange();
  }
```

It is getting the location from `getSpecializedTemplateOrPartial()`, which returns the `SpecializedTemplate` field.  This code appears to assume that the specialized template (or partial) is the "pattern" from which the specialization was instantiated, but in the case above that is not so, since `SpecializedTemplate` is the non-definition declaration.

In contrast, [`CXXRecordDecl::getTemplateInstantiationPattern()`](https://github.com/llvm/llvm-project/blob/main/clang/lib/AST/DeclCXX.cpp#L1930) has:

```cpp
const CXXRecordDecl *CXXRecordDecl::getTemplateInstantiationPattern() const {
  auto GetDefinitionOrSelf =
      [](const CXXRecordDecl *D) -> const CXXRecordDecl * {
    if (auto *Def = D->getDefinition())
      return Def;
    return D;
  };

  // If it's a class template specialization, find the template or partial
  // specialization from which it was instantiated.
  if (auto *TD = dyn_cast<ClassTemplateSpecializationDecl>(this)) {
    auto From = TD->getInstantiatedFrom();
    if (auto *CTD = From.dyn_cast<ClassTemplateDecl *>()) {
      while (auto *NewCTD = CTD->getInstantiatedFromMemberTemplate()) {
        if (NewCTD->isMemberSpecialization())
          break;
        CTD = NewCTD;
      }
      return GetDefinitionOrSelf(CTD->getTemplatedDecl());
          // ^^^^^^^^^^^^^^^^^^^ key difference
    }
```

The `getDefinition` call ensures this function returns the template definition, not the non-definition declaration.

The seemingly-erroneous logic in `ClassTemplateSpecializationDecl::getSourceRange()` originates in commit fd3a455ac7.  I suspect that the case of instantiating from a later definition was simply overlooked at that time.

This is vaguely similar to issue #26057, although the symptoms and cause are different.

## Suggested remedy

My naive thought is that `ClassTemplateSpecializationDecl::getSourceRange()` should check to see if it is a definition, and if so, also get the definition of the template (like `CXXRecordDecl::getTemplateInstantiationPattern()` does) and return its source range.  But I have not made any attempt to validate that solution.

I also acknowledge that in this case there is a sense in which both template declarations contributed to the existence of the instantiation, since when the specialization's declaration was first created, only the non-definition template declaration was available.  Thus, another possible remedy might be to document this behavior as intentional, although that is not what I would recommend.

But, for clarity, I'm just reporting this issue, not intending to try to fix it myself.

As an aside, I'm not sure why `CXXRecordDecl::getTemplateInstantiationPattern()` does not check whether the specialization is a definition.  If it is not, then it would seem incorrect to me to call `getDefinition()` on the templated declaration, since in that case the template definition in fact played no role.

</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJy8WV1v6jjz_zTuzagInFLgohctlL-qfau2rHTujpxkQrx17PxtB8p--kfjOJBQ2u6effQgdE4h9m9ePPOb8SCck1uNeMemD2y6uhKNL429c1VWo3i9Sk1-uHsqQAC7HS-VcG6DVa2Ex5caMymU_Et4afQKM8VuxyAdZBaFxxzSAxTSOg9SOy-0l8JLvQUB2ujrHAupJe0ExueFsXthc8YX4CM85JgpYQM440vwJWqgBxYKqRQhSQ-m8SRnKMGX2IfpBBGK9I4M2aJ_MY3N8Heht8j4nPEFac_4XOgcMqMd_n-D2qtDb8sDbqX-2WSn9bS4ffao8_4TsiQzFToorKmGGkVjzw3cl1JhT9rPJovPOmnyEtrJvhHAppQOaosOtXcgwJkK96XwZFHROHKOk74JuOAN3D8_QePQulYBA3vTqBy0sZVQ6gD4VmPmP1TIG0gR9tKXUn_i1hEbr9j4vv33F2PR7NCSxKN42KJG2xfpSWvhXknlwthgswvYoKIeYAoQun_2RsNeKgUHiSoPW_prz9x2dH7r1H0ps5ICgJaljXoFU4R4JEXOZIhjkI8AHhofoFMsxU4aS6IoVzYvK5bcs-T-o2BLLYpXFwQMPLQpEWRVC3I8n7smK0E4MBoE5FijzlFnBxBaqIOTLkSa0HQQrkmjmoq8Kwgb3ckJDrJwvCmC1JDLokCL2lM-oRuo8PgmqpqiUdeNJyt6zyi6wzur6_gNXzO-hnUb19eta3Hg74h-dD9LlhmRCWxY8tg-c942mYcXljwMxLXgS9G4FtINeGdwkG08ng4rnM-mxAES47y_Ipw-43wAJB049IRHEo1GEKnZHa041JhjQaoupfYseYSX76b4Hv6-pPyvZt_m6Q87BdgsAgNsoCdmtrosckN02bMTpG99ARZdowJPfkXnfbxSEJ9cCmXI0cpddOTAPsZnrmO7AVjf1WnjW-AhvXyM2me8FrVzPriTL7og7bvmd1S4E9qDopNo0zRTQm8Zf2D8Aa4Ld9BevF0brQ5w_S08g2vh_HXeVDV4dH6UZYT6QUp4fPPdwSwJ_voLH8P4bTpN03yWT26yxZxCgJRjyT1nyf2E0jh-TlhyP6dQi58nE_oCjgHSK6iyqpXMpP8-oK0YPmz6-MPvTze38MfjKvZthQNov8ux0J-czROY1KHdUYZIF2oKxafeXk_mo8noBtJGKt9CtXUgUtwelYpUF9oD58HbRr_CDq0js5N7KL2vHZ1ZCL-tyVOj_MjYLePrvxhf65-eZzezLJ0PEylhPIEXUyEVJWXsoJJkREiD9dOHv9EifVYR2HTF-PxMWenLJh1lxFFrpXbdf9e1NX9i5hlfp8qkjK8rITXj6xjQayXpy_uXDeNrktzpNCLa5snPk_F4QnmWUlPjvuT4vr7hix82NHRXvk9obi99W3tp_QDqJ6nz6B--6O_JhEPYvPz0_Q8d601-tOH08ClmwtMgEZL7HlLwaVD22Ujt0f6hw5rlwMCQq4zfM76M-waPn4Wlqnshv2lPR-YQX8_Ce7QaWLKCvsWYd3i_2YgYbU8eTgjCObQ-PJhEpJF0vzYqLgbGb-kd053zoOmJPs_qJ2UaddF1C8WSNeP8XKYs6HTagxONN2TWcvP8siIb4rJOl_ygv2fC-XMXfumj7pj7zrLoG6shyLpmyePFzOlrGjd0ymzxnR7vRP4NXCqzn3CXp4Zhi_54-zi2nYGvYmf89THTvWEZO9HWkJbX2O34wm7qIQvqdLvGPzM5gqhrFNZR7yKcaypsO9hB54T5KRwYnxsLdafHgkwJMjnvQoLzfot8oQejhrjfU5EVVNeljlTpYgMVu2kH2nhwhtY5qbPPLIzqnN0Zex3EoHF90kQv3lL88SVETv727XfMjM0HxNQJGXDDc2fy_4KSl9--Hdl4kYQ7Yym-puI2DQdGhXz8QSvf83HI8P9Dvzr6-zf7gqroJXt4tRODIzG802hF6JRd8MGCvtCOZDp6WWGQB13ar3r3-I-JYoXFJTpYneXysGUGiH3pUwHSh65VQPYZa1JsFVLnw8a0l0dD3LN06SWT9O-SZ9RtHvpjE7gWPmLXSxX4MdxmwxXxrHrGM14HdkpWsOm8_NRTZB0uRu8YdqjWMupFiz-k_kssP1QH4vijh_wr7jvw5Yf6_YJVivbIFx-Bd1q3mIQlXbv15excL4QWvcJdfeAGenX6tbBnj481YxCgF9KK8fnJwM6UNouP6pxLPobWj_fyr3g4zgAyPMF_Xus2bTkaJuTtGDKhFKB2jUXXtvBFo7MQ7v1K9sFYjsrBP2B5UsIhVlJv1eEarTUaTeNAma3MoJ1F_ZtGHIyVW6npNkFomakq6aHIE3EznYpsNgJ4Ate406TqWOZMcTaMDMku4uSyZxulvaOb2gHMDq0y5hVzEB2erPDMZOmoFu7EtkF1oK1SCUtlXjrXUOYk_HY8nYVrkaKebhtr9aGqvalcGFaGWwsIi6fxz-jSpafZbtF5zMFihflhMMA7gBYy1HKS4dsKLfy_drorw3QqKzF7JbscIqWtDBLEWcSQMbKIPYRQzlD_Fezt-fh86Mf4XMnXEMD_oieA3GAYu5EOMa2ld9140pJVcS74BKXYYQjvSlBzpg9AaFUdhks7oWROigX_OaOa9w1Na5zIXrXZK8y3XQ-l2ywLQedLtNh6yaF2YbzX1pfU-PLi0NO1fZJMGzrlOOjCN-k8sUHnueEA4dit7UvUF_rAUDn7sx2K8fYngDguJYgwXLmQ7BdHs4QgdkIqkSoMfW7j2uM3ZDTUxjmZKoxxCpWkiEyRLMpN1lSofeuo43g2lFuPmvDjoPSULqfuNIzNn-JA3CJxAOp8cDYPTWgyC2OpW7DSH-jjE-OzCv5snAeLtbHxUhDS1zXY0V3QIQ_PDHh7oP8K-UbhXh0cqmIg6p7SF4STOZ5khCa6sXQeh_9GTAfANv32JQb_Xmj2z5KRuLBLUm388aca6myC74ioQerMWBvo0kAVjidUjHel5ETBepC7734uaSMxpIHwxzS4VGBoUSEyD7USB8xBG7BGdfR6ld8l-SJZiCu8m8wm8_lkkcymV-VdMZ7OJovF7W0-FbwobnE659lsgulNusBZmlzJOz7mN-PbyWIyu1kk09H8NpkueDJejIuFEJlgN2OshFQjuh2MjN1ehQi4W9yOF_MrJVJULvzsxrnGfRcenE1XV_Yu3CjSZuvYzVhJ590JxUuv8O6ryWIpHOytCTePSjqFIkTbkH1BFB4tS-6je0N0ne6J9KkdXNNfjcOrxqq7f3wdCpY5xtet5bs7_p8AAAD___vjLco">