diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-03 11:55:03 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-03 11:55:03 +0900 |
| commit | ae1aa6540761e54a76b8f7984cf93cd3a0d011d0 (patch) | |
| tree | f111e1c73977f0bffb6323b03f4210269b43b297 /crates/mozart-registry/src/resolver.rs | |
| parent | 30ae6c869adc7f3cb87a4d63edd6d0cda89d571d (diff) | |
| download | php-mozart-ae1aa6540761e54a76b8f7984cf93cd3a0d011d0.tar.gz php-mozart-ae1aa6540761e54a76b8f7984cf93cd3a0d011d0.tar.zst php-mozart-ae1aa6540761e54a76b8f7984cf93cd3a0d011d0.zip | |
refactor: switch internal maps/sets from HashMap to IndexMap
Adopt indexmap workspace-wide so iteration order is deterministic and
follows insertion order. The non-deterministic order of std HashMap
otherwise leaks into resolver decisions when multiple valid solutions
exist (e.g. cyclic require pairs under prefer-lowest), making behavior
flaky and divergent from Composer's PHP-array semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-registry/src/resolver.rs')
| -rw-r--r-- | crates/mozart-registry/src/resolver.rs | 58 |
1 files changed, 29 insertions, 29 deletions
diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs index 6499130..89d3a68 100644 --- a/crates/mozart-registry/src/resolver.rs +++ b/crates/mozart-registry/src/resolver.rs @@ -4,7 +4,7 @@ //! candidate packages, generates SAT rules, and runs the CDCL solver to find //! a compatible set of packages to install. -use std::collections::{HashMap, HashSet}; +use indexmap::{IndexMap, IndexSet}; use std::fmt; use std::sync::Arc; @@ -275,7 +275,7 @@ impl PackageName { /// Platform package configuration. /// Maps package names to version strings (normalized, e.g. "8.1.0.0"). pub struct PlatformConfig { - pub packages: HashMap<String, String>, + pub packages: IndexMap<String, String>, } impl Default for PlatformConfig { @@ -288,7 +288,7 @@ impl PlatformConfig { /// Detect platform packages from the local PHP installation. pub fn new() -> Self { let detected = mozart_core::platform::detect_platform(); - let mut packages = HashMap::new(); + let mut packages = IndexMap::new(); for pkg in detected { packages.insert(pkg.name, pkg.version); } @@ -308,7 +308,7 @@ impl PlatformConfig { for (name, value) in obj { let key = name.to_lowercase(); if value.as_bool() == Some(false) { - self.packages.remove(&key); + self.packages.shift_remove(&key); continue; } if let Some(s) = value.as_str() { @@ -318,7 +318,7 @@ impl PlatformConfig { } /// Parse platform packages into `Version` values. - pub fn to_versions(&self) -> HashMap<String, Version> { + pub fn to_versions(&self) -> IndexMap<String, Version> { self.packages .iter() .filter_map(|(name, version_str)| { @@ -380,7 +380,7 @@ fn passes_stability_filter( package_name: &str, version: &Version, minimum_stability: Stability, - stability_flags: &HashMap<String, Stability>, + stability_flags: &IndexMap<String, Stability>, ) -> bool { let min_stability = stability_flags .get(package_name) @@ -417,7 +417,7 @@ fn packagist_to_pool_inputs( package_name: &str, pv: &packagist::PackagistVersion, minimum_stability: Stability, - stability_flags: &HashMap<String, Stability>, + stability_flags: &IndexMap<String, Stability>, ) -> Vec<PoolPackageInput> { let mut results = Vec::new(); @@ -570,7 +570,7 @@ pub struct ResolveRequest { /// Minimum stability from composer.json. pub minimum_stability: Stability, /// Per-package stability overrides. - pub stability_flags: HashMap<String, Stability>, + pub stability_flags: IndexMap<String, Stability>, /// Whether prefer-stable is enabled. pub prefer_stable: bool, /// Whether prefer-lowest is enabled. @@ -589,7 +589,7 @@ pub struct ResolveRequest { pub repositories: Arc<RepositorySet>, /// Temporary version constraint overrides (from --with flag). /// Maps package name (lowercase) to constraint string. - pub temporary_constraints: HashMap<String, String>, + pub temporary_constraints: IndexMap<String, String>, /// VCS / inline-package repository entries from composer.json's /// `repositories` section, used by the eager VCS scan and inline-package /// preload that still live in `resolve()` (Step B follow-up will move @@ -600,10 +600,10 @@ pub struct ResolveRequest { /// `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<String, String>, + pub root_provide: IndexMap<String, String>, /// 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<String, String>, + pub root_replace: IndexMap<String, String>, } /// A single package in the resolution output. @@ -633,12 +633,12 @@ pub struct ResolvedPackage { /// or a human-readable error. pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, ResolveError> { // 1. Build root requirements - let mut root_requires: HashMap<String, Option<String>> = HashMap::new(); + let mut root_requires: IndexMap<String, Option<String>> = IndexMap::new(); // Per-package stability overrides extracted from `@dev`/`@beta`/etc. // suffixes on root constraints. Mirrors Composer's // `RootPackageLoader::extractStabilityFlags`. Merged on top of the // request's caller-supplied flags (which today are usually empty). - let mut stability_flags: HashMap<String, Stability> = request.stability_flags.clone(); + let mut stability_flags: IndexMap<String, Stability> = request.stability_flags.clone(); let minimum_stability = request.minimum_stability; let mut insert_root_require = |name: &str, constraint: &str| { @@ -700,7 +700,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R let mut builder = PoolBuilder::new(); // Set up ignore list for platform requirements - let mut ignore_set: HashSet<String> = HashSet::new(); + let mut ignore_set: IndexSet<String> = IndexSet::new(); for name in &request.ignore_platform_req_list { ignore_set.insert(name.clone()); } @@ -709,7 +709,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // Add platform packages as fixed entries let platform_config = request.platform.to_versions(); - let mut fixed_packages_by_name: HashMap<String, u32> = HashMap::new(); + let mut fixed_packages_by_name: IndexMap<String, u32> = IndexMap::new(); for (name, version) in &platform_config { if should_skip_platform_dep( name, @@ -734,7 +734,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // Scan VCS repositories and collect packages from them let vcs_packages = vcs_bridge::scan_vcs_repositories(&request.raw_repositories).await; - let mut vcs_package_names: HashSet<String> = HashSet::new(); + let mut vcs_package_names: IndexSet<String> = IndexSet::new(); for vpkg in &vcs_packages { vcs_package_names.insert(vpkg.name.clone()); } @@ -752,7 +752,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // network fetch; they go straight into the pool and are also tracked by // name so the Packagist seed/transitive loops below skip them. let inline_packages = crate::inline_package::collect_inline_packages(&request.raw_repositories); - let mut inline_package_names: HashSet<String> = HashSet::new(); + let mut inline_package_names: IndexSet<String> = IndexSet::new(); for ipkg in &inline_packages { inline_package_names.insert(ipkg.name.clone()); let inputs = packagist_to_pool_inputs( @@ -773,7 +773,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // into the pool, with names recorded so Packagist loops skip them. let composer_repo_packages = crate::composer_repo::collect_composer_packages(&request.raw_repositories); - let mut composer_repo_names: HashSet<String> = HashSet::new(); + let mut composer_repo_names: IndexSet<String> = IndexSet::new(); for cpkg in &composer_repo_packages { composer_repo_names.insert(cpkg.name.clone()); let inputs = packagist_to_pool_inputs( @@ -917,7 +917,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // Create policy and solve let policy = DefaultPolicy::new(request.prefer_stable, request.prefer_lowest); - let fixed_set: HashSet<u32> = fixed_ids.into_iter().collect(); + let fixed_set: IndexSet<u32> = fixed_ids.into_iter().collect(); let solver = Solver::new(rules, &pool, policy, fixed_set); match solver.solve() { @@ -1143,7 +1143,7 @@ mod tests { let rc_v = v_pre(1, 0, 0, 0, "RC1"); let dev_v = v_pre(1, 0, 0, 0, "dev"); - let flags = HashMap::new(); + let flags = IndexMap::new(); assert!(passes_stability_filter( "foo/foo", @@ -1184,7 +1184,7 @@ mod tests { let alpha_v = v_pre(1, 0, 0, 0, "alpha1"); let dev_v = v_pre(1, 0, 0, 0, "dev"); - let flags = HashMap::new(); + let flags = IndexMap::new(); assert!(passes_stability_filter( "foo/foo", @@ -1215,7 +1215,7 @@ mod tests { #[test] fn test_stability_filter_dev() { let dev_v = v_pre(1, 0, 0, 0, "dev"); - let flags = HashMap::new(); + let flags = IndexMap::new(); assert!(passes_stability_filter( "foo/foo", &dev_v, @@ -1308,14 +1308,14 @@ mod tests { vec![], ); - let mut requires = HashMap::new(); + let mut requires = IndexMap::new(); requires.insert("foo/foo".to_string(), Some("^1.0".to_string())); let generator = RuleSetGenerator::new(&mut pool); - let (rules, _) = generator.generate(&requires, &[], &HashMap::new(), &HashMap::new()); + let (rules, _) = generator.generate(&requires, &[], &IndexMap::new(), &IndexMap::new()); let policy = DefaultPolicy::default(); - let solver = Solver::new(rules, &pool, policy, HashSet::new()); + let solver = Solver::new(rules, &pool, policy, IndexSet::new()); let result = solver.solve().unwrap(); // Should install foo/foo (id=1) and bar/bar (id=2) @@ -1335,7 +1335,7 @@ mod tests { require_dev: vec![], include_dev: false, minimum_stability: Stability::Stable, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: true, prefer_lowest: false, platform: PlatformConfig::new(), @@ -1345,10 +1345,10 @@ mod tests { std::env::temp_dir().join("mozart-test-cache"), false, ))), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: vec![], - root_provide: HashMap::new(), - root_replace: HashMap::new(), + root_provide: IndexMap::new(), + root_replace: IndexMap::new(), }; let result = resolve(&request).await; |
