From b60cf8d9cb6776e5df85f080b5bb3fba252e154c Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 2 May 2026 22:59:23 +0900 Subject: fix(resolver): honor root self-provide/replace as require fulfilment Port Composer's RuleSetGenerator::createRequireRule self-fulfilling branch: when the root composer.json's `provide` or `replace` covers a name it also requires (with intersecting constraints), skip emitting an install-one-of rule for that root require. Composer relies on the root package being a fixed entry in the pool so whatProvides() includes it; Mozart does not yet add the root to the pool, so the same decision is made via explicit `root_provide` / `root_replace` tables threaded through ResolveRequest. Without this, an inline repo package whose name matches the root's provide was being force-installed. Fixes installer fixtures `provider_satisfies_its_own_requirement` and `replacer_satisfies_its_own_requirement`. --- crates/mozart-registry/src/lockfile.rs | 2 ++ crates/mozart-registry/src/resolver.rs | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'crates/mozart-registry') diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs index 045b189..99e87c8 100644 --- a/crates/mozart-registry/src/lockfile.rs +++ b/crates/mozart-registry/src/lockfile.rs @@ -1400,6 +1400,8 @@ mod tests { ))), temporary_constraints: HashMap::new(), raw_repositories: vec![], + root_provide: HashMap::new(), + root_replace: HashMap::new(), }; let resolved = resolve(&resolve_request) diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs index a83304f..e8076b4 100644 --- a/crates/mozart-registry/src/resolver.rs +++ b/crates/mozart-registry/src/resolver.rs @@ -569,6 +569,15 @@ pub struct ResolveRequest { /// preload that still live in `resolve()` (Step B follow-up will move /// these through `RepositorySet` too). pub raw_repositories: Vec, + /// Root composer.json's `provide` map (target → constraint string). Drives + /// the self-fulfilling-rule check in the SAT generator: when a root + /// `require` names something the root itself `provide`s with a matching + /// constraint, no install-one-of rule is emitted, mirroring Composer's + /// `RuleSetGenerator::createRequireRule` self-fulfillment branch. + pub root_provide: HashMap, + /// Root composer.json's `replace` map. Same role as `root_provide` for the + /// `replace` link: a replaced target counts as fulfilled by the root. + pub root_replace: HashMap, } /// A single package in the resolution output. @@ -848,7 +857,12 @@ pub async fn resolve(request: &ResolveRequest) -> Result, R let mut generator = RuleSetGenerator::new(&mut pool); generator.set_ignore_platform_reqs(ignore_set); generator.set_ignore_all_platform_reqs(request.ignore_platform_reqs); - let rules = generator.generate(&root_requires, &fixed_ids); + let rules = generator.generate( + &root_requires, + &fixed_ids, + &request.root_provide, + &request.root_replace, + ); // Create policy and solve let policy = DefaultPolicy::new(request.prefer_stable, request.prefer_lowest); @@ -1247,7 +1261,7 @@ mod tests { requires.insert("foo/foo".to_string(), Some("^1.0".to_string())); let generator = RuleSetGenerator::new(&mut pool); - let rules = generator.generate(&requires, &[]); + let rules = generator.generate(&requires, &[], &HashMap::new(), &HashMap::new()); let policy = DefaultPolicy::default(); let solver = Solver::new(rules, &pool, policy, HashSet::new()); @@ -1282,6 +1296,8 @@ mod tests { ))), temporary_constraints: HashMap::new(), raw_repositories: vec![], + root_provide: HashMap::new(), + root_replace: HashMap::new(), }; let result = resolve(&request).await; -- cgit v1.3.1