aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-semver
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-03 13:14:00 +0900
committernsfisis <nsfisis@gmail.com>2026-05-03 13:14:00 +0900
commit3c0527aa63574f17c9f372b6187d5690e0cbaff0 (patch)
tree8159e09b685f7faf2870970d465b9698093b8973 /crates/mozart-semver
parentc53dfc52f6449c8d5ca0b160a2a25f99790711f2 (diff)
downloadphp-mozart-3c0527aa63574f17c9f372b6187d5690e0cbaff0.tar.gz
php-mozart-3c0527aa63574f17c9f372b6187d5690e0cbaff0.tar.zst
php-mozart-3c0527aa63574f17c9f372b6187d5690e0cbaff0.zip
fix(resolver): apply root "X as Y" aliases via pool second pass
Mirrors Composer's `RootPackageLoader::extractAliases` + `PoolBuilder::loadPackage` flow: strip the `as` clause from each root require so the SAT side sees only the LEFT-hand constraint, and after every package is loaded run a second pass that materializes an alias entry for any input matching `(name, version_normalized)`. Locked-only packages in a partial update are excluded via a new `ResolveRequest::locked_package_names` so they don't pick up the alias (`propagateUpdate=false` in Composer). Two adjacent fixes uncovered while making `install_aliased_alias` green: - `Version::cmp` treated unnamed wildcard branches (`1.0.x-dev`, `is_dev_branch=true && name=None`) as below every numeric version. They are semantically the same as the four-segment `*-dev` form Composer's `normalizeBranch` emits, so let only *named* branches take the shortcut. - `Constraint::Exact` / `NotEqual` used the derived `==`, which compared `is_dev_branch` field-by-field and missed the wildcard/numeric equivalence. Switch to `cmp` so both forms count as equal. - `Pool::matches_package` now falls back to parsing `pretty_version` when the `version` parse doesn't match the constraint, so a `dev-master` query lines up with a pool entry stored as the internal `9999999.x.x.x-dev` expansion. Net effect on installer fixtures: `install_aliased_alias` newly green, plus `aliased_priority`, `aliased_priority_conflicting`, and `install_dev_using_dist` come along for the ride. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-semver')
-rw-r--r--crates/mozart-semver/src/lib.rs37
1 files changed, 28 insertions, 9 deletions
diff --git a/crates/mozart-semver/src/lib.rs b/crates/mozart-semver/src/lib.rs
index a15db13..5f6b5fe 100644
--- a/crates/mozart-semver/src/lib.rs
+++ b/crates/mozart-semver/src/lib.rs
@@ -49,10 +49,17 @@ impl PartialOrd for Version {
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
- // Dev branches are always lowest
- match (self.is_dev_branch, other.is_dev_branch) {
+ // Named dev branches (`dev-foo`) sort below every numeric version.
+ // A wildcard `1.0.x-dev` parses with `is_dev_branch=true` but
+ // `dev_branch_name=None` and is semantically identical to its
+ // normalized form `1.0.9999999.9999999-dev` (which parses with
+ // `is_dev_branch=false`). Only the *named* case takes the
+ // branch-comparison shortcut; unnamed wildcards fall through to
+ // numeric comparison so the two forms compare equal.
+ let self_named = self.is_dev_branch && self.dev_branch_name.is_some();
+ let other_named = other.is_dev_branch && other.dev_branch_name.is_some();
+ match (self_named, other_named) {
(true, true) => {
- // Compare branch names
return self.dev_branch_name.cmp(&other.dev_branch_name);
}
(true, false) => return Ordering::Less,
@@ -351,12 +358,19 @@ pub enum Constraint {
impl Constraint {
pub fn matches(&self, v: &Version) -> bool {
match self {
- Constraint::Exact(target) => v == target,
+ // Compare via `Ord` (rather than the derived `PartialEq`) so
+ // wildcard-branch / numeric-dev pairs that represent the same
+ // normalized version — e.g. `1.0.x-dev` (`is_dev_branch=true,
+ // name=None`) and its expanded form `1.0.9999999.9999999-dev`
+ // (`is_dev_branch=false`) — count as equal. The derived `==`
+ // would compare `is_dev_branch` field-by-field and miss the
+ // match.
+ Constraint::Exact(target) => v.cmp(target).is_eq(),
Constraint::GreaterThan(target) => v > target,
Constraint::GreaterThanOrEqual(target) => v >= target,
Constraint::LessThan(target) => v < target,
Constraint::LessThanOrEqual(target) => v <= target,
- Constraint::NotEqual(target) => v != target,
+ Constraint::NotEqual(target) => !v.cmp(target).is_eq(),
Constraint::Any => true,
}
}
@@ -2266,11 +2280,16 @@ mod tests {
#[test]
fn test_x_dev_ordering_within_range() {
- // "2.x-dev" version has patch=9999999, build=9999999 and is a dev branch.
- // Dev branches are always lowest. So "2.x-dev" < "2.0.0" < "3.0.0".
+ // `2.x-dev` is the in-progress 2.x branch and normalizes to
+ // `2.9999999.9999999.9999999-dev`. Numerically that sorts above any
+ // concrete `2.N.M` release — Composer relies on this so a wildcard
+ // branch alias compares as the *latest* candidate within its major.
+ // Only *named* dev branches (`dev-foo`) sort below numeric versions.
let x_dev = Version::parse("2.x-dev").unwrap();
- let stable = Version::parse("2.0.0").unwrap();
- assert!(x_dev < stable);
+ let stable_low = Version::parse("2.0.0").unwrap();
+ let stable_next_major = Version::parse("3.0.0").unwrap();
+ assert!(x_dev > stable_low);
+ assert!(x_dev < stable_next_major);
}
#[test]