fix(extract): emit Java record component references (#1519)
A Java record's component types produced no edges, so a record's data dependencies were invisible. The record_declaration handler now emits `references` edges from each component type (field context; generic_arg for type arguments), skipping primitives and — via #1518's in-scope type-parameter filtering — the record's own type parameters. Handles standard, generic, primitive, and varargs components. Ported from PR #1520 by @oleksii-tumanov (clean 3-way onto v8; builds on #1518). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
safishamsi
co-authored by
Claude Opus 4.8
parent
35fb437365
commit
981e2b93cf
@@ -2863,6 +2863,34 @@ def _extract_generic(
|
||||
add_edge(class_nid, target_nid, "references", line,
|
||||
context="attribute")
|
||||
|
||||
if t == "record_declaration":
|
||||
components = node.child_by_field_name("parameters")
|
||||
if components is not None:
|
||||
for component in components.children:
|
||||
if component.type == "formal_parameter":
|
||||
type_node = component.child_by_field_name("type")
|
||||
elif component.type == "spread_parameter":
|
||||
type_node = next(
|
||||
(
|
||||
child
|
||||
for child in component.children
|
||||
if child.is_named
|
||||
and child.type not in ("modifiers", "variable_declarator")
|
||||
),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
continue
|
||||
refs: list[tuple[str, str]] = []
|
||||
_java_collect_type_refs(type_node, source, False, refs)
|
||||
component_line = component.start_point[0] + 1
|
||||
for ref_name, role in refs:
|
||||
ctx = "generic_arg" if role == "generic_arg" else "field"
|
||||
target_nid = ensure_named_node(ref_name, component_line)
|
||||
if target_nid != class_nid:
|
||||
add_edge(class_nid, target_nid, "references",
|
||||
component_line, context=ctx)
|
||||
|
||||
# Scala: extends_clause carries `extends Base with Trait1 with Trait2`.
|
||||
# The first base after `extends` is `inherits`; each subsequent
|
||||
# type after `with` is `mixes_in`. Also walk class_parameters for
|
||||
|
||||
@@ -434,6 +434,44 @@ def test_java_field_type_references_have_field_context(tmp_path):
|
||||
)
|
||||
|
||||
|
||||
def test_java_record_component_type_references(tmp_path):
|
||||
source = tmp_path / "RecordComponents.java"
|
||||
source.write_text(
|
||||
"class Payload {}\n"
|
||||
"class Item {}\n"
|
||||
"class Attachment {}\n"
|
||||
"record Order(Payload payload, List<Item> items, int count, "
|
||||
"Attachment... attachments) {}\n"
|
||||
)
|
||||
|
||||
result = extract_java(source)
|
||||
|
||||
assert ("Order", "Payload") in _edge_labels(result, "references", "field")
|
||||
assert ("Order", "List") in _edge_labels(result, "references", "field")
|
||||
assert ("Order", "Item") in _edge_labels(result, "references", "generic_arg")
|
||||
assert ("Order", "Attachment") in _edge_labels(result, "references", "field")
|
||||
|
||||
|
||||
def test_java_record_components_skip_type_parameters(tmp_path):
|
||||
source = tmp_path / "GenericRecord.java"
|
||||
source.write_text(
|
||||
"class Payload {}\n"
|
||||
"class Box<X> {}\n"
|
||||
"record Batch<T>(T value, Box<T> boxed, Box<Payload> retained) {}\n"
|
||||
)
|
||||
|
||||
result = extract_java(source)
|
||||
|
||||
assert ("Batch", "T") not in _edge_labels(result, "references")
|
||||
assert not [
|
||||
node
|
||||
for node in result["nodes"]
|
||||
if node.get("label") == "T" and not node.get("source_file")
|
||||
]
|
||||
assert ("Batch", "Box") in _edge_labels(result, "references", "field")
|
||||
assert ("Batch", "Payload") in _edge_labels(result, "references", "generic_arg")
|
||||
|
||||
|
||||
def test_java_type_annotations_have_attribute_context(tmp_path):
|
||||
source = tmp_path / "TypeAnnotations.java"
|
||||
source.write_text(
|
||||
|
||||
Reference in New Issue
Block a user