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")