Ignore an undeclared extra, as pip and uv do - #63
Merged
Merged
Conversation
Refs rstudio/package-manager#20988. An extra a version does not declare no longer excludes that version (dc2dae5 / #30's rule) -- it is ignored, like pip and uv, and reported as data in the new Resolution.MissingExtras (MissingExtra, Requester) so each caller decides how to show it. - provider: the extra branch of dependenciesFrom keeps the version selectable and adds no extra-only requirements; records the requester -> package[extra] edge and the undeclared fact. - resolver: Resolve filters those against the final solution (not what the search merely encountered) to build MissingExtras, and drops an undeclared extra from Resolution.Extras. - SAT oracle and packse: extras/missing-extra and extras/extra-does-not-exist-backtrack now pass as regular scenarios. - CLAUDE.md: MissingExtra and Requester join the package's exported surface. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
base[extraA] asking for other[extraB] was recorded as a request from plain base, dropping extraA. If base stayed pinned while base[extraA] was abandoned, missingExtras could report a phantom MissingExtra for other[extraB]. Record the requester as-is and require its own extra to still be active in the final solution. Refs rstudio/package-manager#20988 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merged
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in 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.
An extra a version does not declare is now ignored, as pip and uv do, instead of excluding that version. It's reported as structured data (
Resolution.MissingExtras) so each caller decides how to show it.Refs rstudio/package-manager#20988
Decided by Jon 2026-09-25: one solving rule for every caller, no strict/lenient option. This reverses go-pyresolver's deliberate "an undeclared extra must fail loudly" rule from
dc2dae5(#30): a misspelledpkg[tests]no longer fails; the warning is what keeps it visible.What changed
provider/dependencies.go: the extra branch ofdependenciesFromkeeps the version selectable when the extra is undeclared, adds no extra-only requirements, and records the requester →package[extra]edge plus the undeclared fact. The requester is recorded as-is (not reduced to its base name), so a request made bybase[extraA]is never mistaken for one made by plainbase.resolver/resolver.go:Resolvefilters those recordings against the final solution (not everything the search merely visited and later backtracked past) to buildResolution.MissingExtras, and drops an undeclared extra fromResolution.Extras(which promises "what a caller needs to reproduce the same install"). When the requester is itself an extra, its own extra must also still be active in the final solution — a name+version match on the base alone would let a phantom warning through ifbase[extraA]were backtracked away while plainbasestayed pinned at the same version.resolver/satoracle_test.go: the SAT oracle'srequirementFormulanow treats an undeclared extra as satisfied by any admissible base version, with no extra variable and no extra-only clauses.resolver/packse_test.go:extras/missing-extraandextras/extra-does-not-exist-backtrackare removed fromknownFailand now pass as regular scenarios;missing-extraadditionally assertsMissingExtrasnamesa[extra], requested by the root.API addition
New exported types in
resolver:MissingExtra(Package,Version,Extra,RequestedBy) andRequester(Root,Package,Version), plus the fieldResolution.MissingExtras []MissingExtra.CLAUDE.md's exported-surface line is updated to name them.What pip and uv do (verified against upstream source)
pypa/pipmaina7002c9,src/pip/_internal/resolution/resolvelib/candidates.py:535-549): logs a warning ("%s %s does not provide the extra '%s'") and yields dependencies forvalid_extrasonly. Does not backtrack.astral-sh/uvmain30de9e2): theMissingExtradiagnostic (crates/uv-distribution-types/src/resolution.rs:145) is shown as a warning.crates/uv/tests/pip/pip_install_scenarios.rs:621-626installsa==3.0.0for the backtrack scenario,:822-827installsa==1.0.0formissing-extra.Review follow-up: an extra-requests-extra chain could leak a phantom warning
A reviewer flagged that
recordExtraRequests(Project(pkg.Name), v, expanded)droppedpkg's own extra whenpkgwas itself an extra node, so a request frombase[extraA]toother[extraB]was attributed only to plainbase.missingExtrasonly checked thatbasewas pinned at that version, not thatextraAsurvived intores.Extras[base]. Ifbasestayed pinned whilebase[extraA]was backtracked away, the warning could leak.Reproduced it with
TestResolveDoesNotReportAMissingExtraFromAnAbandonedExtraOfAPinnedBase:x2.0 asks forz[declared](a real extra ofz), which asks forother[missing](undeclared); a diamond conflict introduced by a low-priorityybackjumps pastz[declared]afterother[missing]has already been decided, soxreverts to 1.0 whilezandotherstay pinned at the same versions via their own separate root requirements. Before the fix this reports a phantomMissingExtra{other, missing, RequestedBy: z}. Fixed by recording the requester as-is (pkginstead ofProject(pkg.Name)) and requiring the requester's own extra, when it has one, to still be inres.Extrasfor the final solution.Mutation proof: reverting just this change (
provider/dependencies.go'srecordExtraRequestscall andresolver/resolver.go's extra check) turns the new test RED; restoring it turns it GREEN.Verification
TestPackse: 147 scenarios = 94 pass (+2 from 92) + 2 known-fail (-2 from 4) + 6 divergence + 4 unsupported + 41 out-of-scope.TestSATOracle: 102 scenarios cross-checked, 8 disagreements, down from 9 before this PR.extras/missing-extrawas one of the 9 (packse said sat, resolver and oracle said unsat) and now agrees across packse, resolver, and oracle. The remaining 8 are unrelated (prereleases,requires_python, yanked).TestResolutionTranscriptMatchesGolden: unaffected — no pinned version in the corpus moved.MissingExtrasfrom every recorded edge without final-solution filtering, a nilMissingExtras, and reverting the SAT oracle's extras model. Plus 1 more for the review follow-up above.go test -count=1 ./...andgo test -race -count=1 ./...both green.golangci-lint run ./...(v2.11.2, config verify) 0 issues.gofmt -l .clean.PPM follow-up (separate work, tracked by #20988's tracking issue)
go-pyresolverpin ingo.mod(currently v0.11.0).get pypitest with a transitive extra that the newest version dropped.Resolution.MissingExtrasto users in the offline downloader output.🤖 Generated with Claude Code