A C++ class deriving from a template — `class Derived : public Base<int>`, a CRTP base `class App : public CRTPBase<App>`, a struct inheriting a template, or a templated base mixed into a multi-base clause — recorded its base as the full instantiation text (`Base<int>`). That never name-matched the template, which is indexed as the bare node `Base`, so the `extends` edge never resolved and the derived class looked like it inherited from nothing — callers/impact analysis stopped at the boundary. Strip the template arguments from the base-type reference name in the `base_class_clause` handler via a new `stripCppTemplateArgs` helper: it removes every balanced `<…>` group (any nesting/position), so `Base<int>` → `Base` and `ns::Tpl<int>` → `ns::Tpl`. The remaining qualified head is exactly what the non-templated base case already produces, so resolution treats templated and non-templated bases identically; a name with no template args passes through unchanged. Covers same-file and same-namespace bases (the dominant real-world patterns). A base in a different namespace referenced with its qualifier (`other_ns::Tpl<int>`) still doesn't resolve, but that's a pre-existing, orthogonal namespace-resolution gap — the non-templated `other_ns::Plain` fails identically — not a template issue. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4c0c87ff6e
commit
f4e03e9cdc
@@ -84,6 +84,33 @@ export function normalizeCppReturnType(raw: string): string | undefined {
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip C++ template arguments from a base-type reference name so it matches the
|
||||
* bare class/struct the template was DEFINED as. `template<typename T> class
|
||||
* Base { … }` is indexed as a node named `Base`, but a derived class
|
||||
* `class D : public Base<int>` records its base as the full `Base<int>` (and
|
||||
* `class Q : public ns::Tpl<int>` as `ns::Tpl<int>`) — neither name-matches
|
||||
* `Base` / `ns::Tpl`, so the `extends` edge never resolves and the derived class
|
||||
* looks like it inherits from nothing (#1043).
|
||||
*
|
||||
* Removes every balanced `<…>` group regardless of nesting or position, so
|
||||
* `Base<int>` → `Base`, `ns::Tpl<Foo<int>>` → `ns::Tpl`, and the rare
|
||||
* `Outer<int>::Inner` → `Outer::Inner`. The remaining qualified head is exactly
|
||||
* what the non-templated base case already produces, so resolution treats them
|
||||
* identically. A name with no template args passes through unchanged.
|
||||
*/
|
||||
export function stripCppTemplateArgs(name: string): string {
|
||||
if (!name.includes('<')) return name;
|
||||
let out = '';
|
||||
let depth = 0;
|
||||
for (const ch of name) {
|
||||
if (ch === '<') depth++;
|
||||
else if (ch === '>') { if (depth > 0) depth--; }
|
||||
else if (depth === 0) out += ch;
|
||||
}
|
||||
return out.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* A function/method's return type lives in the `function_definition`'s `type`
|
||||
* field (`Metrics& Metrics::instance()` → `Metrics`). Constructors, destructors,
|
||||
|
||||
@@ -21,6 +21,7 @@ import { FN_REF_SPECS, captureFnRefCandidates, type FnRefSpec, type FnRefCandida
|
||||
import { isGeneratedFile } from './generated-detection';
|
||||
import type { LanguageExtractor, ExtractorContext } from './tree-sitter-types';
|
||||
import { EXTRACTORS } from './languages';
|
||||
import { stripCppTemplateArgs } from './languages/c-cpp';
|
||||
import { LiquidExtractor } from './liquid-extractor';
|
||||
import { RazorExtractor } from './razor-extractor';
|
||||
import { SvelteExtractor } from './svelte-extractor';
|
||||
@@ -4455,7 +4456,11 @@ export class TreeSitterExtractor {
|
||||
|
||||
// C++ base classes: `class Derived : public Base, private Other` →
|
||||
// base_class_clause holds access specifiers + base type(s). Emit an extends
|
||||
// ref per base type (skip the public/private/protected keywords).
|
||||
// ref per base type (skip the public/private/protected keywords). A
|
||||
// templated base (`Base<int>`, `ns::Tpl<int>`) arrives as a `template_type`
|
||||
// or a `qualified_identifier` wrapping one; strip the `<…>` args so the ref
|
||||
// matches the bare class the template was defined as — `Base`, `ns::Tpl` —
|
||||
// instead of never resolving (#1043).
|
||||
if (child.type === 'base_class_clause') {
|
||||
for (const t of child.namedChildren) {
|
||||
if (
|
||||
@@ -4465,7 +4470,7 @@ export class TreeSitterExtractor {
|
||||
) {
|
||||
this.unresolvedReferences.push({
|
||||
fromNodeId: classId,
|
||||
referenceName: getNodeText(t, this.source),
|
||||
referenceName: stripCppTemplateArgs(getNodeText(t, this.source)),
|
||||
referenceKind: 'extends',
|
||||
line: t.startPosition.row + 1,
|
||||
column: t.startPosition.column,
|
||||
|
||||
Reference in New Issue
Block a user