mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-17 09:29:27 +00:00
fix(commonlisp): keep inherits edge when the superclass is in another file
A defclass whose superclass is defined in another file lost its inherits edge. Mint a sourceless stub for the cross-file base (source_file empty, origin_file set) like the import-stub path, so the corpus rewire collapses it onto the real definition and the inherits edge survives; a same-file superclass still binds locally.
This commit is contained in:
1 parent
3c0eda3bdd
commit
46d3e5d5b8
2 files changed
+59
-1
No files matched your search
@@ -154,6 +154,31 @@ def extract_commonlisp(path: Path) -> dict:
|
||||
})
|
||||
return nid
|
||||
|
||||
def ensure_class_ref(name: str, line: int) -> str:
|
||||
"""Resolve a superclass name to a node id.
|
||||
|
||||
If the class is defined in THIS file, bind to its local node. Otherwise
|
||||
mint a SOURCELESS stub (like `add_import_stub`) so the corpus-level
|
||||
rewire can collapse it onto the real `defclass` in another file. Without
|
||||
this, a cross-file superclass had no matching node and its `inherits`
|
||||
edge was pruned by the dangling-edge filter — silently erasing every
|
||||
inheritance edge whose parent lives in a different file."""
|
||||
local = _cl_id(stem, name)
|
||||
if local in seen_ids:
|
||||
return local
|
||||
stub = _cl_id(name)
|
||||
if stub not in seen_ids:
|
||||
seen_ids.add(stub)
|
||||
nodes.append({
|
||||
"id": stub,
|
||||
"label": name,
|
||||
"file_type": "code",
|
||||
"source_file": "",
|
||||
"source_location": "",
|
||||
"origin_file": str_path,
|
||||
})
|
||||
return stub
|
||||
|
||||
def _first_sym(node) -> str | None:
|
||||
"""Get the first sym_lit text from a list_lit's children."""
|
||||
for child in node.children:
|
||||
@@ -227,7 +252,7 @@ def extract_commonlisp(path: Path) -> dict:
|
||||
for sc in superclass_list.children:
|
||||
if sc.type == "sym_lit":
|
||||
sc_name = _text(sc)
|
||||
sc_nid = _cl_id(stem, sc_name)
|
||||
sc_nid = ensure_class_ref(sc_name, superclass_list.start_point[0] + 1)
|
||||
add_edge(class_nid, sc_nid, "inherits",
|
||||
superclass_list.start_point[0] + 1)
|
||||
|
||||
|
||||
@@ -3497,6 +3497,39 @@ def test_cl_inherits():
|
||||
assert len(inherit_edges) >= 1
|
||||
assert any("ssl_server" in e["source"] and "server" in e["target"] for e in inherit_edges)
|
||||
|
||||
@_needs_commonlisp
|
||||
def test_cl_crossfile_superclass_inherits_edge_survives(tmp_path):
|
||||
"""A superclass defined in another file must still yield an inherits edge.
|
||||
|
||||
The edge target was a file-scoped id with no backing node, so when the
|
||||
parent class lived in a different file the dangling-edge filter pruned the
|
||||
inherits edge entirely. Cross-file references must resolve to a sourceless
|
||||
stub (like imports do) so the corpus rewire can collapse it onto the real
|
||||
defclass — while a same-file parent still binds to its local node.
|
||||
"""
|
||||
f = tmp_path / "dogs.lisp"
|
||||
f.write_text(
|
||||
"(defclass animal () ())\n"
|
||||
"(defclass dog (animal) ())\n"
|
||||
"(defclass service-dog (base-animal) ())\n"
|
||||
)
|
||||
r = extract_commonlisp(f)
|
||||
assert "error" not in r
|
||||
id_to_node = {n["id"]: n for n in r["nodes"]}
|
||||
inherits = {
|
||||
(id_to_node[e["source"]]["label"], id_to_node[e["target"]]["label"])
|
||||
for e in r["edges"]
|
||||
if e["relation"] == "inherits"
|
||||
and e["source"] in id_to_node and e["target"] in id_to_node
|
||||
}
|
||||
# same-file parent binds locally
|
||||
assert ("dog", "animal") in inherits
|
||||
# cross-file parent survives via a sourceless stub instead of being dropped
|
||||
assert ("service-dog", "base-animal") in inherits
|
||||
base = next(n for n in r["nodes"] if n["label"] == "base-animal")
|
||||
assert base["source_file"] == "", "cross-file superclass must be a sourceless stub"
|
||||
|
||||
|
||||
@_needs_commonlisp
|
||||
def test_cl_imports():
|
||||
r = extract_commonlisp(FIXTURES / "sample.lisp")
|
||||
|
||||
Reference in new issue
Block a user