mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-14 07:59:40 +00:00
fix(rust): extract trait method declarations (#3366)
A trait got a node; the methods it declares did not. Across three real Rust
repositories 504 of 654 trait methods (77%) had no node at all, so a trait — the
API contract — appeared in the graph as a bare name with no operations attached,
and "what must an implementor provide?" was unanswerable.
Two gaps, both here:
- `function_signature_item`, the node type for a bodyless `fn greet(&self);`,
had no branch anywhere in the package, so a signature-only method had no
extraction path.
- the struct/enum/trait branch returns before descending, so nothing walked a
trait's body — a default-bodied method inside a trait was never reached
either.
The methods that did appear came from impl blocks that happened to define the
same name, and were anchored at the impl rather than the declaration.
This adds a `function_signature_item` branch mirroring `function_item` (minus the
body walk, since there is none) and descends into the trait body the way
`impl_item` already descends into its own, attributing each method to the trait
node with a `method` edge.
Before / after on the reproducer in #3366:
src/lonely.rs (a trait with no implementor in the corpus)
before: Lonely
after: Lonely, .only_signature() L3, .with_default() L4
+ method edges from Lonely
On a 937-file Rust service: 112 trait methods declared, 70 missing -> 0 missing;
9426 -> 9574 nodes, 28638 -> 29177 edges.
Full suite is unchanged: 17 failed / 5296 passed / 93 skipped both with and
without this commit (the 17 are pre-existing on v8, all in test_skillgen.py).
Note for anyone verifying: the AST cache is keyed on file content, not extractor
version, so `graphify-out/cache` must be removed to observe the change — `--force`
alone re-runs extraction but still serves cached per-file results.
This commit is contained in:
1 parent
c9f9901877
commit
e9fbe22503
1 file changed
+31
@@ -182,6 +182,27 @@ def extract_rust(path: Path) -> dict:
|
||||
function_bodies.append((func_nid, body))
|
||||
return
|
||||
|
||||
if t == "function_signature_item":
|
||||
# `fn greet(&self) -> String;` — a trait method with no body. This node type
|
||||
# had no branch at all, so a trait's required methods were unreachable: the
|
||||
# only method nodes in a graph came from impl blocks, and a trait with no
|
||||
# implementor in the corpus contributed none. Mirrors function_item, minus
|
||||
# the body walk (there is no body).
|
||||
name_node = node.child_by_field_name("name")
|
||||
if name_node:
|
||||
func_name = _read_text(name_node, source)
|
||||
line = node.start_point[0] + 1
|
||||
if parent_impl_nid:
|
||||
func_nid = _make_id(parent_impl_nid, func_name)
|
||||
add_node(func_nid, f".{func_name}()", line)
|
||||
add_edge(parent_impl_nid, func_nid, "method", line)
|
||||
else:
|
||||
func_nid = _make_id(stem, func_name)
|
||||
add_node(func_nid, f"{func_name}()", line)
|
||||
add_edge(file_nid, func_nid, "contains", line)
|
||||
emit_param_return_refs(node, func_nid, line)
|
||||
return
|
||||
|
||||
if t in ("struct_item", "enum_item", "trait_item"):
|
||||
name_node = node.child_by_field_name("name")
|
||||
if name_node:
|
||||
@@ -292,6 +313,16 @@ def extract_rust(path: Path) -> dict:
|
||||
continue
|
||||
type_node = field.child_by_field_name("type")
|
||||
_emit_enum_type(type_node, field.start_point[0] + 1)
|
||||
if t == "trait_item":
|
||||
# The methods a trait declares are its contract, and they were not
|
||||
# reached: this branch returns below, so nothing walked the body.
|
||||
# Descend the same way impl_item does, attributing each method to
|
||||
# the trait node — so `explain <Trait>` can list what an
|
||||
# implementor must provide.
|
||||
body = node.child_by_field_name("body")
|
||||
if body:
|
||||
for child in body.children:
|
||||
walk(child, parent_impl_nid=item_nid)
|
||||
return
|
||||
|
||||
if t == "impl_item":
|
||||
|
||||
Reference in new issue
Block a user