From 8b325288029fa56a4117b64a6d225533a0bacb73 Mon Sep 17 00:00:00 2001 From: safishamsi Date: Tue, 20 Oct 2026 10:55:51 +0100 Subject: [PATCH] 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 Co-Authored-By: Claude Opus 4.8 --- tests/test_python_module_call_exact_name.py | 74 +++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/test_python_module_call_exact_name.py diff --git a/tests/test_python_module_call_exact_name.py b/tests/test_python_module_call_exact_name.py new file mode 100644 index 000000000..1d09f3d6b --- /dev/null +++ b/tests/test_python_module_call_exact_name.py @@ -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