#175 residual: 13 phantoms where a PROJECT module shadows a STDLIB module name (django/utils/{inspect,html}.py) #251
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#251
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?
Disclosed by #175 itself ("THE RESIDUAL IS NOT ZERO", 13 phantoms added) and confirmed
independently by a bind-for-bind census of py-django between
1d3228eandbd1c4e2.Filing it so the class has a home, with the exact sites.
The shape
import <mod>binds<mod>to a module, and #175 correctly requalifies<mod>.func(...)into a module-qualified call. Where the project happens to contain afile with the SAME BASENAME as the stdlib module, the qualifier now anchors on that file.
py-django ships four such collisions:
The 13 sites, all gained (unresolved → resolved, wrongly)
django/utils/inspect.py:149,151,tests/basic/tests.py:842,tests/deprecation/test_deprecate_posargs.py:421×2,tests/template_tests/syntax_tests/i18n/test_blocktranslate.py:38,.../test_translate.py:34inspect.signature(...)django/utils/inspect.py#signaturedjango/utils/inspect.py:133,138,140inspect.getfullargspec(...)django/utils/inspect.py#getfullargspecdjango/test/html.py:177,196,django/utils/html.py:62html.escape(...)django/utils/html.py#escapedjango/utils/inspect.pyline 2 is a plainimport inspect, so inside that very fileinspect.signaturemeans the STDLIB — and it now binds the file's owndef signatureat line 143. A self-shadowing phantom.
Why #168's gate does not catch it
Deliberately, and the exclusion is documented in
qual_root_scope_sql: "A qualifierwhose first segment names no package. No root evidence, no gate."
inspectnames astdlib module, which has no package root in this tree, so
NOT EXISTS (temp.qual_root)passes and the stem anchor is ungated rather than refused.
That exclusion is right — refusing on absence of evidence cost recall when it was
measured. So this is not a bug in #168; it is a gap between the two rules.
Possible direction (not a decision)
The distinguishing fact is available: the call's qualifier was bound by a plain
import <name>whose module string has no dotted prefix inside this project — i.e. the nameresolves to nothing the walker ever indexed. A ref whose qualifier names a module the
index does not contain is a candidate for "leave unresolved" rather than "anchor on a
basename collision". Worth measuring bind-for-bind before building; the same predicate
would also cover
copy,json,os,reand every other stdlib name.Counted and bounded, not urgent: 13 phantoms against 150 removed in the same change.
A sharper distinguishing fact than "the index does not contain it"
The filed direction reads: "a ref whose qualifier names a module the
index does not contain is a candidate for leave-unresolved."
That predicate does not hold on these sites, and it is worth saying why
before anyone builds it:
django/utils/inspect.pyIS in the index.The qualifier
inspectnames something the walker indexed — that isexactly how it acquired a wrong anchor. A rule phrased on "not in the
index" would leave all 13 sites bound.
The fact that actually separates them is a language rule:
So inside
django/utils/inspect.py, line 2'simport inspectmeans thestdlib, and it cannot mean the file it is written in — regardless of what
the index holds.
django/utils/inspect.pyis the moduledjango.utils.inspect; its tail isinspect, its full path isnot. The anchor is matching the tail where the import's semantics demand
the full path.
That reframes this from "absence of evidence" (which #168's
qual_root_scope_sqldocuments as having cost recall, correctly) to apositive structural test, which is a much better place to stand.
The machinery is already there
Nothing new is needed to express it:
index::package_root_of_in(crates/indexer/src/index.rs:2513) andpackage_root_of(:2492)temp.file_pkgalready carriespkg_root,pkg_dirandpkg_tailside by side (
index.rs:~4639-4705)—
LENGTH(sp.pkg_tail) = LENGTH(sp.seg)— andcrates/indexer/tests/tier1q_root_scope.rs:157,320carries a RUNmutation for dropping it
The candidate clause is therefore one predicate on an existing relation:
for a single-segment qualifier bound by a plain
import X,require the anchor file to be a top-level module of its package root
(
pkg_dir = pkg_root), not merely a file whose stem isX. A project'sown top-level
inspect.pystill binds;django/utils/inspect.pyisrefused.
One clause, resting on a structural fact, covering
copy,json,os,reand every other stdlib name at once — rather than a fix percollision.
Two things that must be measured before it is built
It is a PYTHON rule and must be language-scoped. Ruby, PHP and
JavaScript do not share Python's absolute-import semantics, and this
repository has already measured what an unscoped variant of a
neighbouring clause costs:
tier1q_root_scope.rs:19records derivingpkg_tailfrompkg_dirat −1244 on rust-analyzer. An unscopedversion of this would move corpora that have nothing to do with the
defect.
Bind-for-bind, split by
resolved_by, before blessing anything.The failure mode to look for is a project that legitimately imports
its own top-level module sharing a stdlib name — there the new
predicate must still bind, and a census is the only thing that shows
it. The installed binary is a free "before" for that census, joined
on
(path, start_line, start_col, name)with kind excluded fromthe key.
Filed as analysis, not as a decision — the "counted and bounded, not
urgent" framing above still stands: 13 phantoms against 150 removed.
views.serveandstorage.request.COOKIES#250Released in v0.28.1, build
340a75a, via #264 and #265. Release CI passed, including the Windows archive round-trip smoke test. Using the actual published CLI to fresh-index pinned Django, all 13 reported inspect/html call occurrences remain present and no longer bind to the project basename collisions. The full nine-repository prior projection also reproduced exactly. This is a source-site check of the reported phantoms, separate from the synthetic precision gate. Closing this residual.buildagent referenced this issue2026-09-11 20:25:15 +02:00