diff --git a/tests/test_builtin_global_type_refs.py b/tests/test_builtin_global_type_refs.py index 0fe1fd72..25ecb151 100644 --- a/tests/test_builtin_global_type_refs.py +++ b/tests/test_builtin_global_type_refs.py @@ -50,3 +50,34 @@ def test_nonbuiltin_receiver_type_still_resolves(tmp_path): } assert any(t and "charge" in str(t).lower() for _, t, _ in resolved), \ f"user member-call must still resolve; got {resolved}" + + +def test_builtin_static_call_does_not_bind_to_user_symbol(tmp_path): + # #1726 (static-call shape, credit PR #1727 / @2loch-ness6): `Date.now()` treats + # the capitalized receiver `Date` as a type name; without the builtin guard it + # binds to a same-spelled user `const DATE`, a false god node. A typed param in + # the same class arms the cross-file member-call resolver (the real service shape). + (tmp_path / "format.ts").write_text( + "const DATE = new Intl.DateTimeFormat('en-US', {});\n" + "export function fmt(x: number): string { return DATE.format(x); }\n") + (tmp_path / "svc.ts").write_text( + "export class Svc {\n" + " expiry(d: Date): Date { return d; }\n" + " stamp(): number { return Date.now(); }\n" + " when(): string { return new Date().toISOString(); }\n" + "}\n") + r = extract(sorted(tmp_path.glob("*.ts")), cache_root=tmp_path, parallel=False) + lbl = _labels_by_id(r) + by_id = {n["id"]: n for n in r["nodes"]} + date_ids = [n["id"] for n in r["nodes"] if n.get("label") == "DATE"] + date_sf = {str(by_id[i].get("source_file", "")) for i in date_ids} + # A same-file reference to the real const DATE (fmt() -> DATE in format.ts) is + # legitimate. The bug is a CROSS-FILE bind: svc.ts's Date.now()/new Date() + # resolving to format.ts's const DATE. Assert no such cross-file phantom. + for e in r["edges"]: + if e.get("target") in date_ids and e.get("relation") == "references": + src_sf = str(by_id.get(e["source"], {}).get("source_file", "")) + assert src_sf in date_sf, ( + f"cross-file builtin phantom: {lbl.get(e['source'])!r} in {src_sf} " + f"bound to user DATE" + )