package_root_of returns EMPTY when a SRC_DIRS name is the first path component, so src/- and lib/-headed trees can anchor no qualifier — #175's own repro still fails because of it #247
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#247
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the #168/#175 lane, and it is why #175's headline repro still does not resolve after #175 was fixed. Two repositories in the pinned corpus hit it, by two different names.
The mechanism
index::package_root_ofreturns the prefix before the first conventional source directory:When one of those names is the first component, the loop matches at index 0 and returns
"".pkg_tailis then empty,temp.pkg_langskips it (WHERE fp.pkg_tail != ''), and the package can anchor no qualifier at all.Two measured instances, two repositories
python-flask. Every file undersrc/flask/has an empty package root, soflaskanchors on nothing. This is whyflask.url_for— the worked example in #175's own body — still fails after #175's fix. #175 correctly made the plugin emit a module-qualified call as a qualified call rather than amethod_call; the class is fixed, and this repro needs this defect closed too.rust-analyzer. #168 recorded the same thing underlib/: four real packages live atlib/line-index,lib/la-arena,lib/lsp-server, andlib/line-index/src/lib.rsmatcheslibat index 0.So it is not a Python quirk or a Rust quirk — it is the rule, and any repository that puts packages under a top-level directory whose name happens to be in that list loses qualifier anchoring for all of them.
Why the two directions #168 named were refused, and what changed
#168 proposed dropping
libfromSRC_DIRS, or preferring the manifest. Both were measured and refused there. But #168 has since been fixed by a different shape —qual_root_scope_sql, which gates a stem-anchored candidate on the qualifier's root — and that fix does not touchpackage_root_of. So this half is now cleanly separable in a way it was not when #168 was filed:package_root_ofis about which package a file belongs to.qual_root_scope_sqlis about which package a qualifier may reach into.They were entangled while both were broken. Only the first is left.
Shape of a fix, from #168's own direction 2
manifest_package_dirs/nearest_package_diralready know thatlib/line-indexholds aCargo.tomland thatsrc/flasksits under apyproject.toml.pkg_tailis still derived from the lexicalpackage_root_of. Deriving the tail frompkg_dirwhen a manifest exists fixes this class without touching the lexical fallback where no manifest does — the same "manifests do not guess" argument that settled #134.The narrower alternative, also from #168: accept a
SRC_DIRSname only when it is not the first component.lib/as a package container andmypkg/lib/as a source directory are different things, and only the second is what the list is for.What must NOT be done
lib(orsrc) from the list. Every Ruby and PHP layout that useslib/as a genuine source root depends on it — measurable onruby-sinatraandphp-guzzle, and that is exactly why #168 refused this.−16on python-flask that was pure phantom removal. Adjudicate binds at source, joined on(path, line, col, kind, occurrence)against the prior binary.What a fix must prove
flask.url_forinpython-flaskresolves tosrc/flask/helpers.py:200— not tosrc/flask/app.py:1102, which is theFlaskmethod and was the phantom #175 removed. A repro that resolves to the wrong one is worse than one that resolves to nothing.lib/line-indexinrust-analyzergets a non-empty package tail.ruby-sinatraandphp-guzzle, whoselib/genuinely IS a source root, do not move. That set is the reason #168 refused the naive fix and it must be re-measured, not assumed.Related
#168 (recorded this under
lib/, fixed the anchor half, left this open), #175 (whose headline repro this blocks), #134 (the "manifests do not guess" precedent), #246 (the other live resolver residual).Filed 2026-09-10 against
mastera54cc8f.Both directions in "Shape of a fix" fail this issue's own acceptance, in opposite places. Measured before anyone builds either, so the discovery does not happen at the acceptance gate.
The manifest route does not fix the headline repro
The proposal is to derive
pkg_tailfrompkg_dirwhen a manifest exists. Where the manifests actually are, in the pinned corpus:So for
python-flaskthe nearest manifest dir is"", the derived tail is empty, andflaskstill anchors nothing — which is the repro this issue is named for. It fixesrust-analyzer(whose packages really are manifest-bearing subdirectories) and correctly leavesruby-sinatraandphp-guzzlealone, but misses its own first acceptance bullet.The narrower alternative fixes both anchors and breaks both controls
"Accept a
SRC_DIRSname only when it is not the first component", simulated against the exact function body atindex.rs:2492:src/flask/helpers.pyflasklib/line-index/src/lib.rsline-indexlib/sinatra/base.rbsinatrasrc/Client.phpsrcThe
php-guzzlerow is the clearest tell that this direction is wrong on its own terms: it yields a package tail ofsrc, a source-directory name promoted to a package identity.What actually distinguishes the four
The same directory name means two different things, and neither the name nor the presence of a manifest somewhere separates them. What separates them is what the directory contains:
lib/orsrc/is…Cargo.toml__init__.pySo the candidate rule is one clause: a
SRC_DIRSname at index 0 is skipped only when its immediate children are themselves package units — manifest-bearing, or__init__.py-bearing for Python. Otherwise it is a genuine source root and today's behaviour is already right.That covers all four rows above and rests on a structural fact rather than a per-language list. It is a DIRECTION, not a result: it needs the full corpus bind census this issue already specifies, adjudicated at source, with
ruby-sinatraandphp-guzzleproven byte-identical rather than assumed.One correction to the issue body
Worth stating precisely, because I censused it today (
3aea49d): after #175,flask.url_forintests/test_blueprints.pydoes not resolve at all — it previously resolved tosrc/flask/app.py:1102, theFlask.url_forMETHOD, and that was one of the 16 phantoms #175 removed (verified at source, all 16). So the current state is "correctly unresolved", not "still wrong", and this issue's acceptance bullet already says the right thing: it must reachsrc/flask/helpers.py:200, and a repro that resolves toapp.py:1102would be worse than one that resolves to nothing.The headline fix is already on master in
cbbdc04aba. The remaining historical census and mutation-test work is now tracked explicitly in #263. Closing this implementation issue does not claim those residuals are complete.