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:
@@ -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
|
||||
|
||||
Vendored
+12
@@ -40,3 +40,15 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@protocol Base
|
||||
|
||||
- (void)baseMethod;
|
||||
|
||||
@end
|
||||
|
||||
@protocol Derived <Base>
|
||||
|
||||
- (void)derivedMethod;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user