fix(pipeline): suppress weak Python member calls - #1903
Merged
Conversation
Python attribute calls were not classified as member calls, so weak short-name strategies (suffix_match / unique_name / field_type_hint) could bind `obj.method()` to an arbitrary same-named project symbol — `accelerator.print()` resolving to MockAccelerator.print. Flag a Python attribute call is_method when its receiver is unresolved, and extend the existing TS/JS weak-member guard to Python. Receivers that ARE known stay exempt: self/cls, super(), and any identifier bound by one of the file's imports (including the root of an attribute chain), so Python's canonical `module.function()` cross-file call still resolves. The guard helper is renamed cbm_tsjs_suppress_weak_method_match -> cbm_suppress_weak_member_match. Its drop-list is unchanged, and the per-language gate stays at the two call sites, which enumerate the same language set in pass_calls.c and pass_parallel.c so the sequential and parallel resolvers cannot diverge. ArkTS remains in that set. python/S6/inherited_method in the resolution probe now asserts calls == 0, recording the same trade TypeScript took when the receiver-aware guard landed: the edge it used to produce came from a unique_name registry fallback (measured on main: strategy=unique_name, cands=1, conf=0.7500) where a weak short-name guess happened to be right in a 2-file fixture. It flips back to >= 1 once py_lsp_cross resolves inheritance, matching the flip-back note already carried on the TypeScript S6 case. Fixes #1276 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com> Co-authored-by: Enferlain <15861396+Enferlain@users.noreply.github.com>
parallel_lsp_index_exact_ambiguity_does_not_fall_through_to_legacy asserted alpha_edges == 1, which the Python weak-member guard suppresses. That assertion is a side effect, not the test's subject. Its subject — that an ambiguous exact LSP match must not fall through to the lower-ranked legacy semantic row — is unchanged: beta_edges == 0, legacy_edges == 0, and all four probe assertions (ambiguity.injected, legacy_injected, carrier_allows_fallback, shared_matcher_failed_closed) still hold. Verified before changing the number, not after. The harness deliberately injects an ambiguous exact match and forces the shared matcher to fail closed, so value.render() drops past the LSP into the registry with three same-named candidates (Alpha/Beta/Legacy.render) and bound by suffix_match -- measured: strategy=suffix_match, cands=3, conf=0.5500. That is a 1-in-3 guess that happened to land on Alpha: exactly the weak member-call class this change suppresses, and the same accepted trade recorded on python/S6 in test_lsp_resolution_probe.c. Not over-suppression: sibling tests in this suite index the same value.render() source and resolve it via lsp_method (cands=1, conf=0.9000), a strategy the guard keeps -- they still assert 1. Only the sabotaged-LSP path degrades to a weak textual guess. Flips back to 1 once py_lsp_cross resolves the receiver on this path. Refs #1276 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This was referenced Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
注册 for free
to join this conversation on GitHub.
Already have an account?
登录 to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1276. Distilled from #1324 with
Co-authored-by:credit to @Enferlain, whose design and tests this is.What it does
Python
obj.method()calls where the receiver is neitherself/cls/super()nor rooted in one of the file's imports are markedis_method, which enables the existing weak-member guard for them — suppressingsuffix_match/unique_name/field_type_hint/fuzzyresolutions. Those are short-name coincidences: #1276'saccelerator.print()binding toMockAccelerator.print,trainer.backward(),.step().The
cbm_tsjs_suppress_weak_method_matchhelper is renamed tocbm_suppress_weak_member_matchwith the language gate moved from the helper body to its two call sites. The body logic is byte-identical — the same drop-list, andsame_module/import_map/qualified_suffix/lsp_*all still kept.Scoped per-language, deliberately
This follows the house pattern rather than restricting
cbm_registry_resolveglobally: gated on file language, wired at bothpass_calls.candpass_parallel.c(wiring one produces a sequential/parallel divergence that would threaten the MT byte-identical invariant), and it keepssame_moduleas all three existing guards do. The lockstep requirement is now written into the code at both sites and into the helper's contract comment, so a language cannot be added to one resolver only.ArkTS is retained. Main's gate had grown
CBM_LANG_ARKTSafter0df990aa; the original PR predates that, and taking its line verbatim would have silently dropped ArkTS from the weak-member guard, reintroducing the #592/#606 false-edge class. Both gates now readPYTHON || JAVASCRIPT || TYPESCRIPT || TSX || ARKTS.python/S6 now records a trade this repo already accepted for TypeScript
The guard costs one real edge class: an inherited method called through a parameter-held receiver.
test_lsp_resolution_probe.c's python/S6 case is rewritten to assertcalls == 0exactly, with the same anti-vacuous tripwire TypeScript uses.That is not a new concession — it is the same decision, measured. TS's S6 was rewritten from
>= 1to== 0when the receiver-aware guard landed, and its comment explains why: the pre-guard pass came from "a unique_name registry fallback" where "a weak short-name guess happened to be right" in a 2-file fixture. Python was measured to be in precisely that state —strategy=unique_name,cands=1,conf=0.7500— and that measurement is now in the probe comment so the next reader inherits it rather than re-deriving it.The flip-back condition is recorded alongside:
>= 1oncepy_lsp_crossresolves inheritance, mirroring what TS's probe already says. That work is underway separately.Verification
-Werror, asserted before trusting any run.lsp_resolution_probe87 passed, 0 failed — same total as main; the S6 case executed by name.registry extraction pipeline complexity639 passed, 0 failed.FAIL test_lsp_resolution_probe.c:975: calls == 1, expected 0; restoring returns 87 passed. The rewritten assertion passes at 0 and fails at 1, so it binds rather than merely accepting whatever happens.import tools as toolkit→toolkit.format()), pinned by test —#1371changed Python aliased-from-import handling since the original PR, andCBMImport.local_namesurvives it.Notes
python_receiver_is_exemptlinear-scans the file's imports per Python attribute call. That is O(file), not O(corpus) —imports.countis tens — so it cannot trip the complexity guard, and the bound is documented in the helper rather than optimised into allocation and lifetime complexity on the extraction hot path.Whole-file
clang-formatis not a usable local signal here: pristine main reports thousands of violations under Homebrew LLVM 22.1.8. Changed ranges only: 0 violations across all 10 files. CI'slint-ciremains the authority.