Skip to content

Commit 49252d3

Browse files
safishamsiclaude
andcommitted
feat(extract): cross-file member-call resolution for C++ and ObjC (Graphify-Labs#1547, Graphify-Labs#1556)
Connects paired classes across files: Main.cpp's `Foo f; f.bar()` now resolves to Foo::bar, and ObjC `Foo *f = [[Foo alloc] init]; [f doThing]` to Foo's doThing — the "connect with other classes" goal of Graphify-Labs#1547/Graphify-Labs#1556. Design grounded in prior-art research (ctags qualified-name matching, Doxygen's name-keyed false-edge failure modes, PAIGE's receiver-type approach, Clang USR): resolve by RECEIVER TYPE, never bare name, and skip when the type can't be inferred rather than guess (a false call edge / god-node is worse than a missing one). Mirrors the existing Swift/Python/Ruby/TS member-call resolvers. - C++ extractor now captures the member-call receiver (field_expression / qualified_identifier / pointer access) and builds a per-file type table from local declarations (`Foo f;`, `Foo* f;`, `Foo *f = ...;`); emits raw_calls. - ObjC extractor emits raw_calls for message sends with the receiver + selector and a type table from `Foo *f = ...;` locals (existing in-file selector / alloc-init / dot-syntax / @selector matching preserved). - New _resolve_cpp_member_calls / _resolve_objc_member_calls, registered for their suffixes. Receiver tiers: `Foo::bar()` / capitalized ObjC receiver and this/self/super (enclosing class) -> EXTRACTED; local-var-typed -> INFERRED. Single-definition god-node guard (skip unless exactly one type def matches); the just-shipped decl/def class merge makes a paired class one def so the guard resolves it. Verified: a.run() -> A::run only (not a same-named B::run); an uninferable receiver with run() in two classes emits zero edges (no fan-out); ObjC [f doThing] -> Foo only. - build.py: the cross-language INFERRED-call prune treated .h/.cpp/.m as different families and dropped header/impl interop calls; unified the C family (.c .h .cc .cpp .hpp .cxx .hh .hxx .cu .cuh .metal .m .mm) so a .cpp/.m call to a .h-declared method survives. Still open (tracked on Graphify-Labs#1547/Graphify-Labs#1556): the file-level `#include` edge can stay uncanonicalized when the project root isn't symlink-resolved (the extract() id-remap `continue`s on a /var-vs-/private/var mismatch) — the class connection above is robust to it; include-reachability candidate narrowing and ObjC dynamic-dispatch/id-typed receivers also deferred (expected low ObjC recall, per the research). Reported by @c0dezer019 (Graphify-Labs#1547) and @JabberYQ (Graphify-Labs#1556). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3bc3fee commit 49252d3

3 files changed

Lines changed: 688 additions & 5 deletions

File tree

‎graphify/build.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -547,8 +547,15 @@ def build_from_json(extraction: dict, *, directed: bool = False, root: str | Pat
547547
".ts": "js", ".tsx": "js",
548548
".go": "go", ".rs": "rs",
549549
".java": "jvm", ".kt": "jvm", ".scala": "jvm", ".groovy": "jvm",
550-
".c": "c", ".h": "c", ".cc": "cpp", ".cpp": "cpp", ".hpp": "cpp",
551-
".cu": "cpp", ".cuh": "cpp", ".metal": "cpp",
550+
# C, C++, and ObjC interoperate within one compilation unit: a method
551+
# declared in a shared `.h` is defined/called from a `.c`/`.cpp`/`.m`
552+
# sibling, so a cross-file INFERRED call from impl to its header decl
553+
# is legitimate, not a phantom name-collision across languages. Treat
554+
# the whole C family as one so the receiver-typed C++/ObjC member-call
555+
# resolvers' header-targeting edges survive build (#1547/#1556).
556+
".c": "c", ".h": "c", ".cc": "c", ".cpp": "c", ".hpp": "c",
557+
".cxx": "c", ".hh": "c", ".hxx": "c",
558+
".cu": "c", ".cuh": "c", ".metal": "c", ".m": "c", ".mm": "c",
552559
".rb": "rb", ".php": "php", ".cs": "cs", ".swift": "swift", ".lua": "lua",
553560
}
554561
src_ext = Path(G.nodes[src].get("source_file") or "").suffix.lower()

0 commit comments

Comments
 (0)