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:
rajatnagda45 authored and safishamsi committed 2026-08-24 13:16:39 +01:00
1 parent 3c0eda3bdd
commit 46d3e5d5b8
2 files changed
+59 -1

No files matched your search

+26 -1
View File
@@ -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)
+33
View File
@@ -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")