Skip to content

Commit d89efbf

Browse files
richtextclaude
authored andcommitted
fix(extract): scope Pascal/Delphi call resolution + resolve inherited calls across files (#1739)
Both Pascal extractors resolved every call via a single file-wide {method_name: node_id} dict, so two unrelated classes declaring a same-named method (property accessors, generated COM/TLB wrapper classes) collapsed onto whichever declaration was inserted last, producing wrong cross-class `calls` edges. Resolution is now scoped: own class -> ancestor chain (inherits) -> file-level free function -> unambiguous file-wide match; ambiguous at every level emits no edge rather than guessing (same god-node guard as the Ruby resolver). Adds graphify/pascal_resolution.py, a corpus-wide post-extraction resolver (registered via resolver_registry) that walks the inherits chain across file boundaries, so a call from a manual descendant to a method it inherits from a base class in a separate unit (the generated-base/manual-descendant split) resolves. Also stops both extractors from emitting a duplicate base-class stub carrying the referencing file's source_file, which collided with the real node under cross-file id disambiguation. cache.py gives the new raw_calls bucket the same portable-path treatment as nodes/edges so it round-trips. Re-applied to the post-#1737 module layout (extractor hunks land in graphify/extractors/pascal.py; registration stays in extract.py). Added one adaptation the original PR predated: the cross-file resolver's god-node guard now counts DISTINCT method nids, because the tree-sitter extractor emits a method edge for both the interface declaration and the implementation, so the same method_nid arrives twice -- without deduping, every inherited call looked ambiguous and resolved to nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2f9deae commit d89efbf

11 files changed

Lines changed: 656 additions & 55 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Full release notes with details on each version: [GitHub Releases](https://githu
44

55
## 0.9.11 (2026-07-08)
66

7+
- Fix: Pascal/Delphi call resolution is scoped to the caller's class + inherits chain, and calls to methods inherited across file boundaries now resolve (#1739, thanks @richtext). Both extractors previously resolved every call via a single file-wide `{name: node_id}` dict, so two unrelated classes with a same-named method (property accessors, generated COM/TLB wrappers) collapsed onto whichever was inserted last, producing wrong cross-class `calls` edges. Resolution now walks own-class then ancestor chain then file-level free functions, emitting no edge when ambiguous (same god-node guard as the Ruby resolver). A new corpus-wide resolver (`graphify/pascal_resolution.py`) resolves calls from a descendant to a base-class method declared in a different file (the common generated-base/manual-descendant split). Also stops emitting a duplicate cross-file base-class stub carrying the wrong `source_file`.
8+
- Fix: query ranking no longer lets a lone generic term that exact-matches a short leaf label hijack seed selection in multi-term queries (#1602/#1724, thanks @fkhawajagh). `_score_nodes` scales the per-term exact/prefix tiers by squared term coverage; single-term and full-coverage queries are unchanged.
79
- Fix: Kotlin enum entries are extracted as nodes with `case_of` edges to their enum (#1700, thanks @ivanzhilovich). Closes the Kotlin half of #1700 (the Java half shipped in 0.9.10 via #1719); `enum class ChatType { NORMAL, GROUP, SYSTEM }` now yields NORMAL/GROUP/SYSTEM nodes and "where is ChatType.X used" works for Kotlin.
810
- Fix: SKILL.md's POSIX interpreter probe no longer silently falls back to a graphify-less system python (#1735, thanks @mohammedMsgm). Step 1 ran `uv tool run graphifyy python -c ...`, but the `graphifyy` package's executable is `graphify`, so uv treated `python` as a missing `graphifyy` command; `2>/dev/null` hid uv's own `--from` hint, leaving `PYTHON` on an interpreter without graphify. The probe now runs `uv tool run --from graphifyy python -c ...`. The PowerShell path was already correct.
911

graphify/cache.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,11 @@ def _relativize_source_files_in(payload: dict, root: Path) -> None:
276276
root_resolved = Path(root).resolve()
277277
except OSError:
278278
return
279-
for bucket in ("nodes", "edges", "hyperedges"):
279+
# raw_calls (#: Pascal/Delphi cross-file inherited-call resolution) carries
280+
# source_file the same way nodes/edges/hyperedges do, so it needs the same
281+
# portable-path treatment for cache entries to round-trip correctly across
282+
# machines/checkout directories.
283+
for bucket in ("nodes", "edges", "hyperedges", "raw_calls"):
280284
for item in payload.get(bucket, []):
281285
if not isinstance(item, dict):
282286
continue
@@ -307,7 +311,7 @@ def _absolutize_source_files_in(payload: dict, root: Path) -> None:
307311
root_resolved = Path(root).resolve()
308312
except OSError:
309313
return
310-
for bucket in ("nodes", "edges", "hyperedges"):
314+
for bucket in ("nodes", "edges", "hyperedges", "raw_calls"):
311315
for item in payload.get(bucket, []):
312316
if not isinstance(item, dict):
313317
continue
@@ -400,7 +404,7 @@ def save_cached(path: Path, result: dict, root: Path = Path("."), kind: str = "a
400404
# source_file field's original absolute form. Mutating the input here would
401405
# silently break those remaps on the first extraction pass.
402406
on_disk = result
403-
if isinstance(result, dict) and any(result.get(k) for k in ("nodes", "edges", "hyperedges")):
407+
if isinstance(result, dict) and any(result.get(k) for k in ("nodes", "edges", "hyperedges", "raw_calls")):
404408
import copy as _copy
405409
on_disk = _copy.deepcopy(result)
406410
_relativize_source_files_in(on_disk, root)

graphify/extract.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
run_language_resolvers,
2121
)
2222
from .ruby_resolution import resolve_ruby_member_calls
23+
from .pascal_resolution import resolve_pascal_inherited_calls
2324

2425
# --- migrated to graphify/extractors/ (see graphify/extractors/MIGRATION.md) ---
2526
from graphify.extractors.base import ( # noqa: F401
@@ -2642,6 +2643,19 @@ def _key(label: str) -> str:
26422643
register_language_resolver(
26432644
LanguageResolver("csharp_member_calls", frozenset({".cs"}), _resolve_csharp_member_calls)
26442645
)
2646+
# Pascal/Delphi cross-file inherited-method-call resolution: a call from a
2647+
# manual descendant class to a method it inherits from an ancestor declared
2648+
# in a DIFFERENT file (the common generated-base/manual-descendant split,
2649+
# e.g. Sistec's Th0Xxx/Th5Xxx) falls outside the per-file extractor's own
2650+
# scope. Lives in graphify.pascal_resolution; registered here as a consumer
2651+
# of the framework, same as the Ruby resolver above.
2652+
register_language_resolver(
2653+
LanguageResolver(
2654+
"pascal_inherited_calls",
2655+
frozenset({".pas", ".pp", ".dpr", ".dpk", ".inc"}),
2656+
resolve_pascal_inherited_calls,
2657+
)
2658+
)
26452659

26462660

26472661
# Inline markdown link: [text](target "optional title"). The negative lookbehind

0 commit comments

Comments
 (0)