fix(objc): emit implements edge for protocol-to-protocol adoption

`@protocol Derived <Base>` dropped the protocol-adoption (inheritance)
edge. The protocol_declaration handler in extract_objc walked children
for method declarations but ignored the protocol_reference_list child
that holds the adopted protocols, so no implements edge was ever emitted
for protocol-on-protocol adoption.

The extractor already handled `@interface Foo <Proto>` adoption, but that
nests the protocol name under a parameterized_arguments node; protocol-on-
protocol adoption uses a different grammar node (protocol_reference_list)
whose adopted-name is a direct `identifier` child, so it was never
matched. Walk protocol_reference_list and emit an implements edge for each
adopted protocol, mirroring the @interface handling.

Adds a defined Base/Derived protocol pair to the ObjC fixture and a
regression test asserting the Derived->Base implements edge.
This commit is contained in:
Synvoya
2026-07-01 16:36:48 +01:00
committed by safishamsi
parent a129ff2cd6
commit cd3a376030
3 changed files with 34 additions and 0 deletions
+12
View File
@@ -11625,6 +11625,18 @@ def extract_objc(path: Path) -> dict:
proto_nid = _make_id(stem, name)
add_node(proto_nid, f"<{name}>", line)
add_edge(file_nid, proto_nid, "contains", line)
# Adopted protocols: `@protocol Derived <Base, Other>`. These
# nest under a protocol_reference_list node (distinct from the
# parameterized_arguments node used by @interface adoption), so
# they were never emitted. Emit an `implements` edge for each,
# matching how @interface protocol adoption is handled.
for child in node.children:
if child.type == "protocol_reference_list":
for sub in child.children:
if sub.type == "identifier":
base_nid = ensure_named_node(_read(sub), line)
if base_nid != proto_nid:
add_edge(proto_nid, base_nid, "implements", line)
for child in node.children:
walk(child, proto_nid)
return
+12
View File
@@ -40,3 +40,15 @@
}
@end
@protocol Base
- (void)baseMethod;
@end
@protocol Derived <Base>
- (void)derivedMethod;
@end
+10
View File
@@ -1064,6 +1064,16 @@ def test_objc_splits_inherits_and_implements():
assert ("Animal", "SampleDelegate") in _edge_labels(r, "implements")
def test_objc_protocol_adopts_protocol():
"""`@protocol Derived <Base>` must emit an implements edge Derived->Base.
Protocol-on-protocol adoption nests under a protocol_reference_list node
(distinct from the parameterized_arguments node used by @interface
adoption), so the edge was previously dropped. Protocol nodes are labeled
`<Name>`, so the edge reads (<Derived>, <Base>)."""
r = extract_objc(FIXTURES / "sample.m")
assert ("<Derived>", "<Base>") in _edge_labels(r, "implements")
def test_objc_property_type_context():
r = extract_objc(FIXTURES / "sample.m")
assert ("Animal", "NSString") in _edge_labels(r, "references", "field")