From 7eb847bcf7a63a97de638023ff6b4f4e48897bba Mon Sep 17 00:00:00 2001 From: Synvoya <16019863+Synvoya@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:00:01 +1000 Subject: [PATCH] fix(rust): emit field type references for tuple structs extract_rust() only traversed field_declaration_list (named-struct bodies), so tuple structs -- whose positional fields nest under ordered_field_declaration_list -- had every field type reference silently dropped from the graph. This is the same node shape the enum handler already accounts for (tuple variants nest their types under ordered_field_declaration_list); the struct path was simply left behind. Add an additive branch that, for each type node in a tuple struct's ordered_field_declaration_list, collects type refs via _rust_collect_type_refs and emits references edges with the appropriate field / generic_arg context. The named-struct path is untouched. For `struct Wrapper(Logger, Config);` with Logger/Config defined in-file, no field edges were produced before; both are now emitted. Adds test_rust_tuple_struct_field_references and a tuple struct to the shared Rust fixture covering plain and generic positional field types. --- graphify/extract.py | 21 +++++++++++++++++++++ tests/fixtures/sample.rs | 2 ++ tests/test_multilang.py | 13 +++++++++++++ 3 files changed, 36 insertions(+) diff --git a/graphify/extract.py b/graphify/extract.py index 4ad8e014..9b51db18 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -8180,6 +8180,27 @@ def extract_rust(path: Path) -> dict: if tgt != item_nid: add_edge(item_nid, tgt, "references", field.start_point[0] + 1, context=ctx) + # Tuple structs (`struct Wrapper(pub Logger, Config);`) nest their + # positional field types directly under ordered_field_declaration_list + # with no field_declaration wrapper -- the same shape handled for tuple + # enum variants below. Without this branch these field type references + # are silently dropped. + for c in node.children: + if c.type != "ordered_field_declaration_list": + continue + fline = c.start_point[0] + 1 + for tc in c.children: + if tc.type not in ("type_identifier", "generic_type", + "scoped_type_identifier", "reference_type", + "primitive_type", "tuple_type", "array_type"): + continue + refs = [] + _rust_collect_type_refs(tc, source, False, refs) + for ref_name, role in refs: + ctx = "generic_arg" if role == "generic_arg" else "field" + tgt = ensure_named_node(ref_name, fline) + if tgt != item_nid: + add_edge(item_nid, tgt, "references", fline, context=ctx) if t == "enum_item": # Variant payload types nest under enum_variant_list -> # enum_variant -> ordered_field_declaration_list (tuple variant, diff --git a/tests/fixtures/sample.rs b/tests/fixtures/sample.rs index 9c1e39d8..1a909029 100644 --- a/tests/fixtures/sample.rs +++ b/tests/fixtures/sample.rs @@ -56,3 +56,5 @@ enum GraphEvent { NodeAdded(Graph), Processed { proc: DataProcessor }, } + +struct GraphPair(Graph, Result); diff --git a/tests/test_multilang.py b/tests/test_multilang.py index 216ba95f..5ad78f72 100644 --- a/tests/test_multilang.py +++ b/tests/test_multilang.py @@ -365,6 +365,19 @@ def test_rust_struct_field_emits_field_context(): assert ("DataProcessor", "DataProcessor") not in _edge_labels(r, "references", "field") +def test_rust_tuple_struct_field_references(): + """Tuple struct fields (`struct Wrapper(A, B);`) nest their positional types + under ordered_field_declaration_list with no field_declaration wrapper -- the + same shape as tuple enum variants. That path was not traversed for structs, so + tuple-struct field type references were silently dropped. + """ + r = extract_rust(FIXTURES / "sample.rs") + field_refs = _edge_labels(r, "references", "field") + assert ("GraphPair", "Graph") in field_refs, "tuple-struct field reference missing" + assert ("GraphPair", "Result") in field_refs, "tuple-struct generic field reference missing" + assert ("GraphPair", "DataProcessor") in _edge_labels(r, "references", "generic_arg") + + def test_rust_method_parameter_return_and_generic_contexts(): r = extract_rust(FIXTURES / "sample.rs") assert ("build", "DataProcessor") in _edge_labels(r, "references", "parameter_type")