fix(extract): drop C++ template type parameters from references

A `template<typename T>` binds `T` as a local name, not a corpus symbol, but
`_cpp_collect_type_refs` appended the bare identifiers used in a template
signature or member as type references and ran them through the sourceless-stub
path. Every template in the corpus then collapsed its `T` onto one bare `t`
node — a god node that pollutes god-node rankings, since single-letter type
parameters are not in the analyzer's builtin-noise filter. C#, Java,
TypeScript, Kotlin, Scala, and Go already exclude theirs.

Mirrors that pattern: a new `_cpp_type_parameters_in_scope` helper walks up to
every enclosing `template_declaration`'s `template_parameter_list` and collects
names from its `type_parameter_declaration`/`variadic_type_parameter_declaration`
children (a non-type `int N` parameter is a value and is left alone), and
`_cpp_collect_type_refs` auto-computes and threads that skip set. Genuine user
types and the generic container head still reference.
This commit is contained in:
rajatnagda45 committed 2026-10-13 13:43:45 +05:30
1 parent a9860f70dc
commit e3bf52b318
2 files changed
+100 -7

No files matched your search

+45 -7
View File
@@ -1345,41 +1345,79 @@ def _c_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str
if c.is_named:
_c_collect_type_refs(c, source, generic, out)
def _cpp_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]]) -> None:
def _cpp_type_parameters_in_scope(node, source: bytes) -> frozenset[str]:
"""Return C++ template type-parameter names visible from ``node``.
A `template<typename T, class U>` binds `T`/`U` as local names, not corpus
symbols. Walking up to every enclosing `template_declaration`'s
`template_parameter_list` collects the names from its
`type_parameter_declaration`/`variadic_type_parameter_declaration` children.
A non-type parameter (`int N`) is a value, not a type, and is left alone.
"""
names: set[str] = set()
scope = node
while scope is not None:
for child in scope.children:
if child.type != "template_parameter_list":
continue
for param in child.children:
if param.type not in (
"type_parameter_declaration",
"variadic_type_parameter_declaration",
):
continue
for sub in param.children:
if sub.type == "type_identifier":
name = _read_text(sub, source)
if name:
names.add(name)
break
scope = scope.parent
return frozenset(names)
def _cpp_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]],
skip: frozenset[str] | None = None) -> None:
"""Walk a C++ type expression; append (name, role) tuples.
Resolves qualified_identifier tails (std::string → string) and template_type
base + arguments (std::vector<HttpClient> → vector + HttpClient as generic_arg)."""
base + arguments (std::vector<HttpClient> → vector + HttpClient as generic_arg).
Template type parameters in scope (``skip``) are local bindings, not
references; emitting one mints a sourceless stub (the bare `t` god node a
`template<typename T>` class or function would otherwise share corpus-wide)."""
if node is None or node.type in _C_PRIMITIVE_TYPE_NODES:
return
if skip is None:
skip = _cpp_type_parameters_in_scope(node, source)
t = node.type
if t == "type_identifier":
text = _read_text(node, source)
if text:
if text and text not in skip:
out.append((text, "generic_arg" if generic else "type"))
return
if t == "qualified_identifier":
name_node = node.child_by_field_name("name")
if name_node is not None:
_cpp_collect_type_refs(name_node, source, generic, out)
_cpp_collect_type_refs(name_node, source, generic, out, skip)
return
if t == "template_type":
name_node = node.child_by_field_name("name")
if name_node is not None:
text = _read_text(name_node, source)
if text:
if text and text not in skip:
out.append((text, "generic_arg" if generic else "type"))
args_node = node.child_by_field_name("arguments")
if args_node is not None:
for c in args_node.children:
if c.is_named:
_cpp_collect_type_refs(c, source, True, out)
_cpp_collect_type_refs(c, source, True, out, skip)
return
if t in ("type_descriptor", "pointer_declarator", "reference_declarator",
"array_declarator", "type_qualifier", "abstract_pointer_declarator",
"abstract_reference_declarator", "abstract_array_declarator"):
for c in node.children:
if c.is_named:
_cpp_collect_type_refs(c, source, generic, out)
_cpp_collect_type_refs(c, source, generic, out, skip)
def _scala_given_synthetic_name(node, source: bytes) -> str | None:
"""A stable label for an anonymous Scala 3 given (`given Show[Int] with`).
+55
View File
@@ -0,0 +1,55 @@
"""C++ template type parameters must not mint sourceless god nodes.
A `template<typename T>` binds `T` as a local name, not a corpus symbol. The
C++ type-ref pass appended the bare `T`/`K`/`V` identifiers used in a template
signature or member as type references and ran them through the sourceless-stub
path, so every template in the corpus collapsed its `T` onto one bare `t` node
— a god node (single-letter type params are not in the analyzer's builtin-noise
filter). C#, Java, TypeScript, Kotlin, Scala, and Go already exclude their type
parameters; C++ now matches.
"""
from pathlib import Path
from graphify.extract import extract
def _sourceless(r):
return sorted({(n["id"], n.get("label")) for n in r["nodes"] if not n.get("source_file")})
def test_class_and_function_template_params_mint_no_stub(tmp_path):
(tmp_path / "b.cpp").write_text(
"template<typename T>\n"
"class Box {\n"
" T value;\n"
"public:\n"
" T get() { return value; }\n"
"};\n"
"template<typename K, typename V>\n"
"V lookup(K key) { V v; return v; }\n"
)
r = extract([tmp_path / "b.cpp"], cache_root=tmp_path, parallel=False)
assert _sourceless(r) == []
assert not any(e.get("target") in ("t", "k", "v") for e in r["edges"])
def test_non_type_param_and_real_types_unaffected(tmp_path):
# `N` is a non-type (value) template parameter, not a type; a genuine user
# type in the signature still references.
(tmp_path / "b.cpp").write_text(
"struct User { int id; };\n"
"template<typename T, int N>\n"
"class Buffer {\n"
" T data[N];\n"
"public:\n"
" User owner() { return User(); }\n"
"};\n"
)
r = extract([tmp_path / "b.cpp"], cache_root=tmp_path, parallel=False)
assert not any(e.get("target") == "t" for e in r["edges"])
refs = {
rn["label"]
for e in r["edges"] if e.get("relation") == "references"
for rn in r["nodes"] if rn["id"] == e["target"]
}
assert "User" in refs