Skip to content

Intersect all ranges in and chains - #50

Merged
ericmj merged 2 commits into
mainfrom
intersect-and-chains
Aug 26, 2026
Merged

Intersect all ranges in and chains#50
ericmj merged 2 commits into
mainfrom
intersect-and-chains

Conversation

@ericmj

@ericmj ericmj commented Aug 26, 2026

Copy link
Copy Markdown
Member

HexSolver.Requirement only handled two-term and groups and special-cased their operator combinations, so requirements that Version.parse_requirement/1 accepts and hexpm lets through on publish either crashed with FunctionClauseError (>= 1.0.0 and < 2.0.0 and > 1.5.0, >= 1.0.0 and > 1.5.0, == 1.0.0 and >= 1.0.0, ~> 1.0 and >= 2.0.0) or were rejected (~> 1.0.0 and ~> 1.0). delex/2 now folds every and group through Constraint.intersect/2, which replaces all to_range/4 clauses. Semantics match core: a property checks Constraint.allows?/2 against Version.match?/2 for random one to four term chains over a version grid.

An empty intersection raises the new HexSolver.UnsatisfiableRequirementError, whose message names the two disjoint parts: requirement "~> 1.0 and >= 2.0.0" is unsatisfiable because "~> 1.0" and ">= 2.0.0" are disjoint. parse_constraint/1 keeps returning :error.

The first commit fixes a separate solver bug found while checking what an empty constraint does downstream. When a dependency's constraint is %Empty{} (reachable with a published < 0.0.0-0 requirement since #44) and the depender has another version to backtrack to, HexSolver.run/4 never returned: allows_any?/2 returned true against an empty constraint, so the no-versions incompatibility related as overlapping and unit propagation derived not bar empty indefinitely. The Range, Version and Union clauses now return false. Empty.allows_any?/2 is unchanged on purpose: flipping it too makes the dependency incompatibility conflict with itself and the failure message degrades to depends on both "bar empty" and "bar empty".

Closes #49.

Range.allows_any?/2, Version.allows_any?/2 and Union.allows_any?/2
returned true for an empty constraint, so a positive term with an empty
constraint related as overlapping instead of disjoint to any assignment.
When a dependency's constraint was empty and the depender had another
version to backtrack to, unit propagation derived the same term from
the no-versions incompatibility forever.
@ericmj
ericmj marked this pull request as ready for review August 26, 2026 21:31
Requirements with more than two and terms, two bounds in the same
direction, == inside an and, or two pessimistic ranges either crashed
with FunctionClauseError or were rejected, even though
Version.parse_requirement/1 accepts them and hexpm allows publishing
them. Fold each and group through Constraint.intersect/2 instead of the
special-cased to_range/4 clauses.

An empty intersection raises UnsatisfiableRequirementError naming the
two disjoint parts.

Closes #49.
@ericmj
ericmj force-pushed the intersect-and-chains branch from 324b8d8 to a8bc4fa Compare August 26, 2026 21:34
@ericmj
ericmj merged commit c836bff into main Aug 26, 2026
20 checks passed
@ericmj
ericmj deleted the intersect-and-chains branch August 26, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Solver requirement parser rejects core-valid pessimistic-and chains and crashes on 3-term bound chains

1 participant