mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-14 16:09:26 +00:00
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:
1 parent
a9860f70dc
commit
e3bf52b318
2 files changed
+100
-7
No files matched your search
@@ -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`).
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user