test(python): module-call exact-name tests, incl. a cross-module negative

Carries the tests that belong with the module-call exact-name fix (#4577): the
fix landed via the PR's follow-up perf commit, which did not include the test
file. Adds the PR author's five cases (exact-name function/class resolution,
aliased import, fold-only fail-closed) plus a negative cross-module case proving a
call never binds to a same-named child of an UNRELATED module -- the one headline
risk of re-bucketing by exact name.

Co-Authored-By: madhusudanpatnaik <madhusudanpatnaik@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
committed 2026-10-20 10:55:51 +01:00
1 parent 374adfadab
commit 8b32528802
1 file changed
+74
@@ -0,0 +1,74 @@
"""`module.func()` resolves to the child named exactly `func` (#1883 module arm).
The module arm looked the callee up among the module's children by a key that
drops every non-alphanumeric character and folds case, so `dataclass_schema()`
and `class DataclassSchema` share one key. Python names are case-sensitive and
`_` is significant, yet any such pair made the lookup ambiguous and the call was
dropped: every `core_schema.x_schema(...)` call in pydantic-core (each function
sits beside its `XSchema` TypedDict) got no edge. Among children that share the
folded key, the one whose name is exactly the callee now wins. A call matching
only on the folded key, with no exact name, still gets no edge.
"""
from __future__ import annotations
from pathlib import Path
from graphify.extract import extract
_CORE = (
"from typing import TypedDict\n\n\n"
"class DataclassSchema(TypedDict):\n type: str\n\n\n"
"def dataclass_schema(cls):\n return {'type': 'dataclass'}\n\n\n"
"def compute_value():\n return 1\n\n\n"
"class ComputeValue:\n pass\n"
)
def _calls(tmp_path: Path, caller_body: str) -> list[tuple[str, str, str]]:
(tmp_path / "core_schema.py").write_text(_CORE, encoding="utf-8")
(tmp_path / "caller.py").write_text(caller_body, encoding="utf-8")
result = extract([tmp_path / "caller.py", tmp_path / "core_schema.py"], root=tmp_path,
cache_root=tmp_path / "graphify-out", parallel=False)
label = {n["id"]: n["label"] for n in result["nodes"]}
return [(label.get(e["source"], ""), label.get(e["target"], ""), e["confidence"])
for e in result["edges"]
if e["relation"] == "calls" and label.get(e["source"]) == "build()"]
def test_a_function_beside_a_same_key_class_resolves_by_its_exact_name(tmp_path: Path):
calls = _calls(tmp_path, "import core_schema\n\n\ndef build():\n return core_schema.dataclass_schema(1)\n")
assert calls == [("build()", "dataclass_schema()", "EXTRACTED")]
def test_the_class_itself_resolves_by_its_exact_name(tmp_path: Path):
calls = _calls(tmp_path, "import core_schema\n\n\ndef build():\n return core_schema.DataclassSchema(type='x')\n")
assert calls == [("build()", "DataclassSchema", "EXTRACTED")]
def test_an_aliased_module_import_resolves_the_same_way(tmp_path: Path):
calls = _calls(tmp_path, "import core_schema as cs\n\n\ndef build():\n return cs.dataclass_schema(1)\n")
assert calls == [("build()", "dataclass_schema()", "EXTRACTED")]
def test_a_name_that_matches_only_after_folding_still_gets_no_edge(tmp_path: Path):
"""`computevalue` is neither `compute_value` nor `ComputeValue`: fail closed."""
assert _calls(tmp_path, "import core_schema\n\n\ndef build():\n return core_schema.computevalue()\n") == []
def test_a_call_never_binds_to_an_unrelated_modules_child(tmp_path: Path):
"""The receiver's own import decides the module. A same-named child of a
DIFFERENT corpus module must never be bound: the exact-name lookup only
re-buckets within the module the receiver already resolved to."""
(tmp_path / "core_schema.py").write_text(_CORE, encoding="utf-8")
(tmp_path / "other.py").write_text("def unrelated():\n return 0\n", encoding="utf-8")
(tmp_path / "caller.py").write_text(
"import other\n\n\ndef build():\n return other.dataclass_schema(1)\n",
encoding="utf-8")
result = extract(
[tmp_path / "caller.py", tmp_path / "core_schema.py", tmp_path / "other.py"],
root=tmp_path, cache_root=tmp_path / "graphify-out", parallel=False)
label = {n["id"]: n["label"] for n in result["nodes"]}
targets = [label.get(e["target"], "") for e in result["edges"]
if e["relation"] == "calls" and label.get(e["source"]) == "build()"]
# `other` has no dataclass_schema; core_schema's must NOT be hijacked.
assert "dataclass_schema()" not in targets, targets