mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 15:40:05 +00:00
Inside a class body a `virtual double area() = 0;` (pure virtual), a
`virtual void draw();` and a plain prototype `int run();` parse as a
`field_declaration` whose declarator is a `function_declarator`. The field
branch recognised that shape only to skip emitting a type reference — it still
emitted a node for every declarator as a DATA MEMBER (a bare `area` label with
a `defines`/field edge).
So a pure-virtual interface class had no method nodes, only phantom fields; a
header-declared method and its out-of-line/overriding definition did not share
one node (`area` field vs `.area()` method); and a call to such a method
resolved to a field-shaped node.
Fix: in the field-declaration branch, emit a function_declarator with the same
shape a defined method uses — a `.name()` label, a `method` edge, and callable
— so the declaration and its definition collapse to one method node (parity
with the Java/TypeScript/Scala abstract-method contract). A genuine data member
still gets its `defines` field edge.
This corrects the label of every header-declared method, so a few existing
cross-file tests that asserted the old bare `bar`/`run` field label now assert
`.bar()`/`.run()`; the resolved call edges (source, target, confidence) are
unchanged, and the decl/def merge still produces exactly one node carrying the
definition site.
Added tests/test_cpp_method_declarations.py.
(cherry picked from commit 0038af9394)
103 lines
3.3 KiB
Python
103 lines
3.3 KiB
Python
"""A C++ member-function DECLARATION is a method, not a data member.
|
|
|
|
Inside a class body a `virtual double area() = 0;` (pure virtual), a
|
|
`virtual void draw();` and a plain prototype `int run();` all parse as a
|
|
`field_declaration` whose declarator is a `function_declarator`. The field
|
|
branch emitted a node for every declarator as a data member (a bare `area`
|
|
label with a `defines`/field edge), so:
|
|
|
|
* a pure-virtual interface class had NO method nodes, only phantom fields;
|
|
* an out-of-line or overriding definition did not share the declaration's
|
|
node, because one was `.area()` (method) and the other `area` (field);
|
|
* a call to the method had nothing of the right shape to resolve to.
|
|
|
|
The fix emits a function_declarator with the same shape as a defined method
|
|
(`.name()` label, `method` edge, callable), while a genuine data member still
|
|
gets its `defines` field edge.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from graphify.extract import extract
|
|
|
|
|
|
def _extract(tmp_path, files: dict[str, str]) -> dict:
|
|
for name, body in files.items():
|
|
p = tmp_path / name
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
p.write_text(body)
|
|
old = os.getcwd()
|
|
try:
|
|
os.chdir(tmp_path)
|
|
return extract([Path(n) for n in files], cache_root=Path(tempfile.mkdtemp()))
|
|
finally:
|
|
os.chdir(old)
|
|
|
|
|
|
def _rel(r: dict, relation: str, src: str, tgt: str) -> bool:
|
|
labels = {n["id"]: str(n.get("label", "")) for n in r["nodes"]}
|
|
return any(
|
|
e.get("relation") == relation
|
|
and labels.get(e.get("source")) == src
|
|
and labels.get(e.get("target")) == tgt
|
|
for e in r["edges"]
|
|
)
|
|
|
|
|
|
def _labels(r: dict) -> list[str]:
|
|
return [str(n.get("label", "")) for n in r["nodes"]]
|
|
|
|
|
|
def test_pure_virtual_method_is_a_method_node(tmp_path):
|
|
r = _extract(tmp_path, {"s.cpp": (
|
|
"class Shape {\n"
|
|
"public:\n"
|
|
" virtual double area() = 0;\n"
|
|
"};\n"
|
|
)})
|
|
assert _rel(r, "method", "Shape", ".area()")
|
|
# Not misclassified as a data member.
|
|
assert not _rel(r, "defines", "Shape", "area")
|
|
assert "area" not in _labels(r)
|
|
|
|
|
|
def test_declared_virtual_and_prototype_are_methods(tmp_path):
|
|
r = _extract(tmp_path, {"s.cpp": (
|
|
"class Svc {\n"
|
|
"public:\n"
|
|
" virtual void draw();\n"
|
|
" int run();\n"
|
|
"};\n"
|
|
)})
|
|
assert _rel(r, "method", "Svc", ".draw()")
|
|
assert _rel(r, "method", "Svc", ".run()")
|
|
|
|
|
|
def test_data_member_is_still_a_field(tmp_path):
|
|
r = _extract(tmp_path, {"s.cpp": (
|
|
"class Box {\n"
|
|
"public:\n"
|
|
" int count;\n"
|
|
" double area() = 0;\n"
|
|
"};\n"
|
|
)})
|
|
assert _rel(r, "defines", "Box", "count")
|
|
assert _rel(r, "method", "Box", ".area()")
|
|
|
|
|
|
def test_override_definition_shares_the_declaration_node(tmp_path):
|
|
# The abstract declaration and the concrete override are both `.area()`,
|
|
# so a call through the base resolves instead of hitting a phantom field.
|
|
r = _extract(tmp_path, {"s.cpp": (
|
|
"class Shape {\n"
|
|
"public:\n"
|
|
" virtual double area() = 0;\n"
|
|
" double describe() { return area(); }\n"
|
|
"};\n"
|
|
)})
|
|
assert _rel(r, "method", "Shape", ".area()")
|
|
assert _rel(r, "calls", ".describe()", ".area()")
|