mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-15 08:29:26 +00:00
fix(python): preserve nested class identities by enclosing class
Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit e85a748445)
This commit is contained in:
1 parent
86e3c1dc7a
commit
b50e1b3590
5 files changed
+96
-4
No files matched your search
@@ -0,0 +1,83 @@
|
||||
"""Nested Python class definitions must keep their lexical owner (#4220)."""
|
||||
|
||||
from graphify.extract import extract
|
||||
|
||||
|
||||
SOURCE = (
|
||||
"class Forward:\n"
|
||||
" class Params:\n"
|
||||
" def value(self): return 1\n"
|
||||
" class Wrapper:\n"
|
||||
" class Params:\n"
|
||||
" def value(self): return 2\n"
|
||||
"class Backward:\n"
|
||||
" class Params:\n"
|
||||
" def value(self): return 3\n"
|
||||
)
|
||||
|
||||
|
||||
def _extract(root, source=SOURCE):
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
path = root / "scheduler.py"
|
||||
path.write_text(source, encoding="utf-8")
|
||||
return extract([path], root=root, cache_root=root / "cache", parallel=False)
|
||||
|
||||
|
||||
def test_same_named_nested_classes_keep_nodes_methods_and_parent(tmp_path):
|
||||
result = _extract(tmp_path)
|
||||
params = [node for node in result["nodes"] if node["label"] == "Params"]
|
||||
assert len(params) == 3
|
||||
assert len({node["id"] for node in params}) == 3
|
||||
assert {node["source_location"] for node in params} == {"L2", "L5", "L8"}
|
||||
owners = {node["id"]: node["label"] for node in result["nodes"]}
|
||||
expected = {"L2": "Forward", "L5": "Wrapper", "L8": "Backward"}
|
||||
for node in params:
|
||||
parents = [edge["source"] for edge in result["edges"]
|
||||
if edge["relation"] == "contains" and edge["target"] == node["id"]]
|
||||
assert len(parents) == 1
|
||||
assert owners[parents[0]] == expected[node["source_location"]]
|
||||
methods = [edge["target"] for edge in result["edges"]
|
||||
if edge["relation"] == "method" and edge["source"] == node["id"]]
|
||||
assert len(methods) == 1
|
||||
assert owners[methods[0]] == ".value()"
|
||||
assert len([node for node in result["nodes"] if node["label"] == ".value()"]) == 3
|
||||
|
||||
|
||||
def test_same_name_as_parent_has_distinct_identity(tmp_path):
|
||||
result = _extract(tmp_path, "class Params:\n class Params:\n pass\n")
|
||||
params = [node for node in result["nodes"] if node["label"] == "Params"]
|
||||
assert len(params) == 2
|
||||
outer, inner = sorted(params, key=lambda node: node["source_location"])
|
||||
assert outer["id"] == "scheduler_params"
|
||||
assert outer["id"] != inner["id"]
|
||||
assert any(edge["source"] == outer["id"] and edge["target"] == inner["id"]
|
||||
and edge["relation"] == "contains" for edge in result["edges"])
|
||||
|
||||
|
||||
def test_nested_class_identity_is_stable_across_cache_and_checkout(tmp_path):
|
||||
first = _extract(tmp_path / "first")
|
||||
warm = _extract(tmp_path / "first")
|
||||
relocated = _extract(tmp_path / "second")
|
||||
assert first["nodes"] == warm["nodes"] == relocated["nodes"]
|
||||
assert first["edges"] == warm["edges"] == relocated["edges"]
|
||||
|
||||
|
||||
def test_nested_class_decorators_and_docstrings_have_real_owners(tmp_path):
|
||||
result = _extract(tmp_path, (
|
||||
"def decorate(cls): return cls\n"
|
||||
"class Outer:\n"
|
||||
" @decorate\n"
|
||||
" class Inner:\n"
|
||||
" \"\"\"This class holds the nested parameters for the outer class.\"\"\"\n"
|
||||
" def value(self):\n"
|
||||
" \"\"\"Return the value owned by this nested class instance.\"\"\"\n"
|
||||
" return 1\n"
|
||||
))
|
||||
nodes = {node["id"]: node for node in result["nodes"]}
|
||||
inner = next(node["id"] for node in nodes.values() if node["label"] == "Inner")
|
||||
method = next(node["id"] for node in nodes.values() if node["label"] == ".value()")
|
||||
assert any(edge["source"] == inner and edge.get("context") == "decorator"
|
||||
for edge in result["edges"])
|
||||
rationales = [edge for edge in result["edges"] if edge["relation"] == "rationale_for"]
|
||||
assert {edge["target"] for edge in rationales} == {inner, method}
|
||||
assert all(edge["source"] in nodes and edge["target"] in nodes for edge in rationales)
|
||||
Reference in new issue
Block a user