mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-16 17:09:17 +00:00
fix(java): a superclass method wins over an interface default for a bare call
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
This commit is contained in:
1 parent
724626659a
commit
85fab922d2
2 files changed
+35
-5
No files matched your search
+19
-3
@@ -5512,10 +5512,26 @@ def _resolve_java_member_calls(
|
||||
return None
|
||||
|
||||
def _inherited_method(class_nid: str, callee_key: str) -> str | None:
|
||||
# Level by level up the supertypes, as Java looks a simple method name
|
||||
# up in the class's members; the class itself was searched in-file.
|
||||
# Java's "class wins": a method declared up the superclass chain beats
|
||||
# an interface default, whatever the depth. Only then the interfaces,
|
||||
# level by level. The class itself was searched in-file.
|
||||
seen = {class_nid}
|
||||
level = list(dict.fromkeys(supertypes.get(class_nid, [])))
|
||||
chain: list[str] = []
|
||||
cls = class_nid
|
||||
while len(parents := [b for b in inherits_bases.get(cls, []) if b not in seen]) == 1:
|
||||
cls = parents[0]
|
||||
seen.add(cls)
|
||||
chain.append(cls)
|
||||
for nid in chain:
|
||||
source_file = node_by_id.get(nid, {}).get("source_file")
|
||||
if source_file and _lang_family(source_file) != "jvm":
|
||||
return None
|
||||
declared = method_index.get((nid, callee_key))
|
||||
if declared:
|
||||
return next(iter(declared)) if len(declared) == 1 else None
|
||||
level = list(dict.fromkeys(
|
||||
base for nid in [class_nid, *chain] for base in supertypes.get(nid, []) if base not in seen
|
||||
))
|
||||
while level:
|
||||
hits: set[str] = set()
|
||||
for nid in level:
|
||||
|
||||
@@ -430,13 +430,27 @@ def test_bare_call_takes_the_nearest_supertype_declaring_it(tmp_path: Path):
|
||||
assert (run, _find(result, ".stop()", "root_stop")) not in calls
|
||||
|
||||
|
||||
def test_bare_call_declared_twice_on_one_level_binds_nothing(tmp_path: Path):
|
||||
def test_superclass_method_wins_over_an_interface_default(tmp_path: Path):
|
||||
# Java's "class wins": Root.log() beats Loud's default, even from a deeper level.
|
||||
calls, result = _calls(tmp_path, {
|
||||
"Base.java": "class Base { void log() {} }\n",
|
||||
"Root.java": "class Root { void log() {} }\n",
|
||||
"Base.java": "class Base extends Root {}\n",
|
||||
"Loud.java": "interface Loud { default void log() {} }\n",
|
||||
"Child.java": "class Child extends Base implements Loud { void run() { log(); } }\n",
|
||||
})
|
||||
|
||||
run = _find(result, ".run()", "child_run")
|
||||
assert (run, _find(result, ".log()", "root_log")) in calls
|
||||
assert (run, _find(result, ".log()", "loud_log")) not in calls
|
||||
|
||||
|
||||
def test_bare_call_declared_by_two_interfaces_binds_nothing(tmp_path: Path):
|
||||
calls, result = _calls(tmp_path, {
|
||||
"Loud.java": "interface Loud { void log(); }\n",
|
||||
"Quiet.java": "interface Quiet { void log(); }\n",
|
||||
"Child.java": "abstract class Child implements Loud, Quiet { void run() { log(); } }\n",
|
||||
})
|
||||
|
||||
run = _find(result, ".run()", "child_run")
|
||||
assert not any(source == run and "log" in target for source, target in calls)
|
||||
|
||||
|
||||
Reference in new issue
Block a user