fix(python): resolve aliased from-import CALLS to real def - #1371
fix(python): resolve aliased from-import CALLS to real def#1371Joseph-MingEn wants to merge 5 commits into
Conversation
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
|
Rebased onto current
Ready for CI when the merge queue picks this up. |
41793cc to
b81c1e2
Compare
|
Conflict recheck: rebased/force-pushed head |
Web conflict editor reports conflicts too complex; local merge vs main is a clean fast-forward. Empty commit forces recompute. Co-authored-by: Cursor <cursoragent@cursor.com>
Web conflict editor reports conflicts too complex; local merge vs main is a clean fast-forward. Empty commit forces recompute. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
a4aff88 to
345a350
Compare
|
Thank you for this — and welcome. Reviewed properly rather than skimmed, because the root cause you found is a good one. The diagnosis is right and I verified it on main. I also went looking for the failure mode that would make this dangerous — resolving to a same-named symbol in a different module — and it cannot happen. The member QN is built strictly inside the resolved module's namespace and must hit an existing node by exact QN with a targetable label, with Module and File excluded. Cross-module leakage is not reachable. Import maps are per-file, so the same alias in two files cannot collide either. There is a nice side benefit too: Two things need your hand, one trivial and one that needs the maintainer. 1. The lint failure is hiding something important. Run 2. The change is wider than the title, and that part is the maintainer's call. Because plain and aliased from-imports store the same That is a graph-shape decision rather than a bug fix, and it lands in the exact mechanism that once regressed about 11,000 edges (#1000), so I have put it in front of the maintainer instead of deciding it in review. Two things would help it along: state the broader effect explicitly in the PR description, and — if you are able — a before/after edge-count diff on a Django-scale repository. That evidence is what would derisk it. One test I would like: a pure negative — Smaller notes: the PR body says the change makes Fix the formatting and this gets a real CI run; I will come back to you on the IMPORTS-edge question either way. |
from M import f as g + g() must emit CALLS→f, not invent M.g. Use resolve_import_node in sequential import maps, and prefer an exact registry hit when the IMPORTS target is already the def QN. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
resolve_module often lands on the Module for pkg.mod.symbol paths. When the member Function/Class exists, IMPORTS must target it so \`from M import f as g\` + g() can CALLS→f (Yui bridge_execute shape). Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
Web conflict editor reports conflicts too complex; local merge vs main is a clean fast-forward. Empty commit forces recompute. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
Unblock CI lint so the test job runs. Prefer Homebrew LLVM formatting on the member IMPORTS retarget. Add contract that `from ghost_module import nope as alias; alias()` emits no CALLS. Relabel registry alias pin as adversarial (behavior already on main). Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
345a350 to
e56e965
Compare
|
Thanks for the careful review — addressed:
Please re-check once CI runs |
|
CI note: the red Failure site: On that same job, Re-running failed jobs now. If it flakes again we can wait for maintainer guidance or another rerun — not planning to touch |
Upstream workflow rerun requires admin; empty signed commit to re-queue. Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Your diagnosis is correct, and you do not need to prove it — it is already on our list.
So: not yours, and you were right not to touch Your PR itself is still with the maintainer for the graph-shape question raised earlier; that is a direction call, not a quality one, and nothing further is needed from you while it waits. Thank you for the clean triage — reporting the failing site with the surrounding evidence ( |
|
Thank you @Joseph-MingEn — and thank you especially for flagging the graph-shape consequence yourself instead of letting it land quietly. That is the part that decides this PR. The alias fix is clearly right: But as you say, the member-preference block retargets IMPORTS Module → member def for every Python from-import that resolves that way, not only aliased ones. That is more precise in isolation and it changes module-dependency shape, Your offer to split or gate is the likely path: the alias resolution could land now on its own merits, with the broader Module→member retarget as a separate, deliberate decision that gets a before/after edge-count comparison on a large repo. If he prefers that, would you be up for splitting it? If not, I will do the split myself and keep you as author on both halves. Either way the work is not going to sit — it is in the active batch, just gated on one decision rather than on review. |
|
@Joseph-MingEn — the maintainer came back with a better option than the either/or I put to you, and I think it dissolves the direction question entirely. Emit both edges instead of retargeting. Keep I checked that the graph can actually hold that: edge identity is What that buys, compared to retargeting:
The cost is edge growth: one extra IMPORTS edge per from-import that resolves to a member, which on a Python-heavy repo is not nothing. Two things worth doing about that:
Would you like to take that direction? If you would rather not rework it, say so and I will implement it with you as co-author — the analysis and the ghost-module control are the hard parts and they are already done. Your instinct to flag the shape change rather than bury it is what made this outcome possible; a quieter PR would have shipped the regression. |
|
Correction to my previous comment, because I framed the edge growth wrongly and the maintainer pushed back on it. I called the extra edges a "cost" to be measured before deciding whether to gate. That was over-cautious. These are true additions: An edge count that rises because the graph finally represents something true is this project working as intended, not a regression to be defended against. Graph quality comes first here. So the proposal stands unchanged in shape, but without the gating implication:
Same offer as before: yours to take, or I implement it with you as co-author. And apologies for the mixed signal — the second read was better than my first. |
Summary
build_import_mapnow usesresolve_import_node(notfqn_moduleonpkg.symbolpaths) sofrom M import f as glands in the import map.module_pathnames a member Function/Class, prefer the member as the IMPORTS target so bare alias calls canimport_mapto the real def.from ghost_module import nope as alias; alias()→ no CALLS); registry adversarial pin for def-QN-under-alias-key (behavior already on main via Python: CALLS / trace_path blind to import aliases (import scan_bash as _scan_bash) #875/fix(registry): resolve aliased direct-symbol imports in import_map (#875) #979 — comment-onlyregistry.c).Broader graph-shape note (maintainer call)
Plain and aliased from-imports share the same
module_path("M.f"), so the member-preference block retargets IMPORTS Module → member def for every Python from-import that resolves that way — not only aliases. That is more precise forfrom M import f, but it changes:detect_changesimpact propagation that walked Module IMPORTSThis is intentionally in front of the maintainer (same mechanism class as the ~11K-edge #1000 regression). Happy to split or gate if preferred.
Evidence (not Django-scale)
./build/c/test-runner registry lang_contract→ 97 passed (includes new ghost-module negative).trace_path(submit_task)hop-1 lands onservices.satori_bridge.gate.executewithout changing theimport execute as bridge_executealias in source. Graph after ≈ 11549 nodes / 46879 edges. No Django-scale before/after edge-count diff available from this contributor machine.Test plan
make -f Makefile.cbm lint-format(Homebrew LLVMclang-format)./build/c/test-runner registry lang_contract— 97 passedlint+test(was skipped while lint failed) +ci-ok