From 0b156124d854d4dc7b754db048fefc180a3355b9 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:41:17 +0100 Subject: [PATCH] Use a Vec for incompatibilities and package assignments --- src/internal/arena.rs | 1 + src/internal/core.rs | 39 ++-- src/internal/partial_solution.rs | 308 +++++++++++++++---------------- src/solver.rs | 15 +- 4 files changed, 175 insertions(+), 188 deletions(-) diff --git a/src/internal/arena.rs b/src/internal/arena.rs index 1e3b6c67..43cf90ab 100644 --- a/src/internal/arena.rs +++ b/src/internal/arena.rs @@ -10,6 +10,7 @@ use std::ops::{Index, IndexMut, Range}; /// that we actually don't need since it is phantom. /// /// +#[repr(transparent)] pub(crate) struct Id { raw: u32, _ty: PhantomData T>, diff --git a/src/internal/core.rs b/src/internal/core.rs index 8831b199..59594bc0 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -9,7 +9,7 @@ use smallvec::SmallVec; use crate::{ internal::{ - Arena, DecisionLevel, IncompDpId, Incompatibility, PartialSolution, Relation, + Arena, DecisionLevel, Id, IncompDpId, Incompatibility, PartialSolution, Relation, SatisfierSearch, }, DependencyProvider, DerivationTree, Map, PackageArena, PackageId, Set, Term, VersionIndex, @@ -22,12 +22,10 @@ pub(crate) struct State { root_package_id: PackageId, root_version_index: VersionIndex, - #[allow(clippy::type_complexity)] - incompatibilities: Map>>, + incompatibilities: Vec>>, /// All incompatibilities expressing dependencies, /// with common dependents merged. - #[allow(clippy::type_complexity)] merged_dependencies: Map<(PackageId, PackageId), SmallVec<[IncompDpId; 4]>>, /// Partial solution. @@ -51,13 +49,14 @@ impl State { root_package_id, root_version_index, )); - let mut incompatibilities = Map::default(); - incompatibilities.insert(root_package_id, vec![not_root_id]); + let root_package_idx = root_package_id.0 as usize; + let mut incompatibilities = vec![vec![]; root_package_idx + 1]; + incompatibilities[root_package_idx].push(not_root_id); Self { root_package_id, root_version_index, incompatibilities, - partial_solution: PartialSolution::empty(), + partial_solution: PartialSolution::empty(root_package_id), incompatibility_store, unit_propagation_buffer: Vec::new(), merged_dependencies: Map::default(), @@ -107,7 +106,8 @@ impl State { // to evaluate first the newest incompatibilities. let mut conflict_id = None; // We only care about incompatibilities if it contains the current package. - for &incompat_id in self.incompatibilities[¤t_package].iter().rev() { + let idx = current_package.0 as usize; + for &incompat_id in self.incompatibilities[idx].iter().rev() { let current_incompat = &mut self.incompatibility_store[incompat_id]; if self.partial_solution.is_contradicted(current_incompat) { continue; @@ -172,7 +172,6 @@ impl State { /// Return the root cause or the terminal incompatibility. /// CF - #[allow(clippy::type_complexity)] #[cold] fn conflict_resolution( &mut self, @@ -253,6 +252,14 @@ impl State { /// We could collapse them into { foo (1.0.0 ∪ 1.1.0), not bar ^1.0.0 } /// without having to check the existence of other versions though. fn merge_incompatibility(&mut self, mut id: IncompDpId) { + fn get_or_default(v: &mut Vec>>, package_id: PackageId) -> &mut Vec> { + let pkg_idx = package_id.0 as usize; + if pkg_idx + 1 > v.len() { + v.resize(pkg_idx + 1, Vec::new()); + } + &mut v[pkg_idx] + } + if let Some((pid1, pid2)) = self.incompatibility_store[id].as_dependency() { // If we are a dependency, there's a good chance we can be merged with a previous dependency let deps_lookup = self.merged_dependencies.entry((pid1, pid2)).or_default(); @@ -263,10 +270,7 @@ impl State { }) { let new = self.incompatibility_store.alloc(merged); for (package_id, _) in self.incompatibility_store[new].iter() { - self.incompatibilities - .entry(package_id) - .or_default() - .retain(|id| id != past); + get_or_default(&mut self.incompatibilities, package_id).retain(|id| id != past); } *past = new; id = new; @@ -275,13 +279,8 @@ impl State { } } for (package_id, term) in self.incompatibility_store[id].iter() { - if cfg!(debug_assertions) { - assert_ne!(term, Term::any()); - } - self.incompatibilities - .entry(package_id) - .or_default() - .push(id); + debug_assert_ne!(term, Term::any()); + get_or_default(&mut self.incompatibilities, package_id).push(id); } } diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index cd957bfc..278f4352 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -12,8 +12,8 @@ use smallvec::{smallvec, SmallVec}; use crate::Map; use crate::{ internal::{Arena, IncompDpId, IncompId, Incompatibility, Relation, SmallMap}, - DependencyProvider, FxIndexMap, PackageArena, PackageId, SelectedDependencies, Term, - VersionIndex, VersionSet, + DependencyProvider, PackageArena, PackageId, SelectedDependencies, Term, VersionIndex, + VersionSet, }; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] @@ -32,23 +32,16 @@ impl DecisionLevel { pub(crate) struct PartialSolution { next_global_index: u32, current_decision_level: DecisionLevel, - /// `package_assignments` is primarily a HashMap from a package to its - /// `PackageAssignments`. But it can also keep the items in an order. - /// We maintain three sections in this order: - /// 1. `[..current_decision_level]` Are packages that have had a decision made sorted by the `decision_level`. - /// This makes it very efficient to extract the solution, And to backtrack to a particular decision level. - /// 2. `[current_decision_level..changed_this_decision_level]` Are packages that have **not** had there assignments - /// changed since the last time `prioritize` has been called. Within this range there is no sorting. - /// 3. `[changed_this_decision_level..]` Contains all packages that **have** had there assignments changed since - /// the last time `prioritize` has been called. The inverse is not necessarily true, some packages in the range - /// did not have a change. Within this range there is no sorting. - #[allow(clippy::type_complexity)] - package_assignments: FxIndexMap>, + package_assignments: Vec>, + package_assignments_indices: Vec, + package_assignments_lengths: Vec, + /// A package is a potential pick if there isn't an already selected version (no "decision") + /// and if it contains at least one positive derivation term in the partial solution. + potential_picks: Vec, /// `prioritized_potential_packages` is primarily a HashMap from a package with no decision and a positive assignment /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order. prioritized_potential_packages: PriorityQueue>, - changed_this_decision_level: usize, last_valid_decision_levels: Vec, } @@ -73,11 +66,13 @@ impl<'a, DP: DependencyProvider> Display for PartialSolutionDisplay<'a, DP> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let partial_solution = self.partial_solution; let mut assignments: Vec<_> = partial_solution - .package_assignments + .package_assignments_indices .iter() - .map(|(&pid, pa)| { + .filter_map(|&pa_idx| { + let pa = partial_solution.package_assignments.get(pa_idx as usize)?; + let pid = pa.package_id; let pn = self.package_store.pkg(pid).unwrap(); - format!("{pn}: {pa}") + Some(format!("{pn}: {pa}")) }) .collect(); assignments.sort(); @@ -96,7 +91,7 @@ impl<'a, DP: DependencyProvider> Display for PartialSolutionDisplay<'a, DP> { /// as well as the intersection of terms by all of these. #[derive(Clone, Debug)] struct PackageAssignments { - smallest_decision_level: DecisionLevel, + package_id: PackageId, highest_decision_level: DecisionLevel, dated_derivations: SmallVec<[DatedDerivation; 1]>, assignments_intersection: AssignmentsIntersection, @@ -111,8 +106,7 @@ impl Display for PackageAssignments { .collect(); write!( f, - "decision range: {:?}..{:?}\nderivations:\n {}\n,assignments_intersection: {}", - self.smallest_decision_level, + "highest_decision_level: {:?}\nderivations:\n {}\n,assignments_intersection: {}", self.highest_decision_level, derivations.join("\n "), self.assignments_intersection @@ -186,14 +180,16 @@ pub(crate) enum SatisfierSearch { type SatisfiedMap = SmallMap>, u32, DecisionLevel)>; impl PartialSolution { - /// Initialize an empty PartialSolution. - pub(crate) fn empty() -> Self { + /// Initialize an empty `PartialSolution`. + pub(crate) fn empty(root_package: PackageId) -> Self { Self { next_global_index: 0, current_decision_level: DecisionLevel(0), - package_assignments: FxIndexMap::default(), + potential_picks: Vec::new(), + package_assignments: Vec::new(), + package_assignments_indices: vec![u32::MAX; root_package.0 as usize + 1], + package_assignments_lengths: Vec::new(), prioritized_potential_packages: PriorityQueue::default(), - changed_this_decision_level: 0, last_valid_decision_levels: vec![DecisionLevel(0)], } } @@ -213,43 +209,32 @@ impl PartialSolution { /// Add a decision. pub(crate) fn add_decision(&mut self, package_id: PackageId, version_index: VersionIndex) { + let len = self.package_assignments.len(); + let pa = &mut self + .package_assignments + .get_mut(self.package_assignments_indices[package_id.0 as usize] as usize) + .expect("Derivations must already exist"); + // Check that add_decision is never used in the wrong context. if cfg!(debug_assertions) { - let pa = self - .package_assignments - .get_mut(&package_id) - .expect("Derivations must already exist"); + let pai = &pa.assignments_intersection; // Cannot be called when a decision has already been taken. - assert!( - !pa.assignments_intersection.is_decision, - "Already existing decision", - ); + assert!(!pai.is_decision, "Already existing decision"); // Cannot be called if the versions is not contained in the terms' intersection. - let term = pa.assignments_intersection.term; assert!( - term.contains(version_index), + pai.term.contains(version_index), "{} was expected to be contained in {}", version_index.get(), - term, - ); - assert_eq!( - self.changed_this_decision_level, - self.package_assignments.len(), + pai.term, ); + assert!(self.potential_picks.is_empty()); } - let new_idx = self.current_decision_level.0 as usize; + + self.package_assignments_lengths.push(len as u32); self.current_decision_level = self.current_decision_level.increment(); - let (old_idx, _, pa) = self - .package_assignments - .get_full_mut(&package_id) - .expect("Derivations must already exist"); pa.highest_decision_level = self.current_decision_level; pa.assignments_intersection = AssignmentsIntersection::decision(self.next_global_index, version_index); - // Maintain that the beginning of the `package_assignments` Have all decisions in sorted order. - if new_idx != old_idx { - self.package_assignments.swap_indices(new_idx, old_idx); - } self.next_global_index += 1; } @@ -260,7 +245,6 @@ impl PartialSolution { cause: IncompDpId, incompat_term: Term, ) { - use indexmap::map::Entry; let mut dated_derivation = DatedDerivation { global_index: self.next_global_index, decision_level: self.current_decision_level, @@ -268,40 +252,35 @@ impl PartialSolution { accumulated_intersection: incompat_term.negate(), }; self.next_global_index += 1; - let pa_last_index = self.package_assignments.len().saturating_sub(1); - match self.package_assignments.entry(package_id) { - Entry::Occupied(mut occupied) => { - let idx = occupied.index(); - let pa = occupied.get_mut(); - pa.highest_decision_level = self.current_decision_level; - // Check that add_derivation is never called in the wrong context. - assert!( - !pa.assignments_intersection.is_decision, - "add_derivation should not be called after a decision", - ); - let t = &mut pa.assignments_intersection.term; - *t = t.intersection(dated_derivation.accumulated_intersection); - dated_derivation.accumulated_intersection = *t; - if t.is_positive() { - // we can use `swap_indices` to make `changed_this_decision_level` only go down by 1 - // but the copying is slower then the larger search - self.changed_this_decision_level = - std::cmp::min(self.changed_this_decision_level, idx); - } - pa.dated_derivations.push(dated_derivation); + + self.resize_package_assignments_indices(package_id); + let pa_idx = &mut self.package_assignments_indices[package_id.0 as usize]; + + if let Some(pa) = self.package_assignments.get_mut(*pa_idx as usize) { + pa.highest_decision_level = self.current_decision_level; + // Check that add_derivation is never called in the wrong context. + assert!( + !pa.assignments_intersection.is_decision, + "add_derivation should not be called after a decision", + ); + let term = &mut pa.assignments_intersection.term; + *term = term.intersection(dated_derivation.accumulated_intersection); + dated_derivation.accumulated_intersection = *term; + pa.dated_derivations.push(dated_derivation); + if term.is_positive() { + self.potential_picks.push(package_id); } - Entry::Vacant(v) => { - let term = dated_derivation.accumulated_intersection; - if term.is_positive() { - self.changed_this_decision_level = - std::cmp::min(self.changed_this_decision_level, pa_last_index); - } - v.insert(PackageAssignments { - smallest_decision_level: self.current_decision_level, - highest_decision_level: self.current_decision_level, - dated_derivations: smallvec![dated_derivation], - assignments_intersection: AssignmentsIntersection::derivations(term), - }); + } else { + let term = dated_derivation.accumulated_intersection; + *pa_idx = self.package_assignments.len() as u32; + self.package_assignments.push(PackageAssignments { + package_id, + highest_decision_level: self.current_decision_level, + dated_derivations: smallvec![dated_derivation], + assignments_intersection: AssignmentsIntersection::derivations(term), + }); + if term.is_positive() { + self.potential_picks.push(package_id); } } } @@ -311,28 +290,22 @@ impl PartialSolution { &mut self, mut prioritizer: impl FnMut(PackageId, VersionSet) -> DP::Priority, ) -> Option { - let check_all = self.changed_this_decision_level - == self.current_decision_level.0.saturating_sub(1) as usize; - let current_decision_level = self.current_decision_level; - let prioritized_potential_packages = &mut self.prioritized_potential_packages; - self.package_assignments - .get_range(self.changed_this_decision_level..) - .unwrap() - .iter() - .filter(|(_, pa)| { - // We only actually need to update the package if it has been changed - // since the last time we called prioritize. - // Which means it's highest decision level is the current decision level, - // or if we backtracked in the meantime. - check_all || pa.highest_decision_level == current_decision_level - }) - .filter_map(|(&pid, pa)| pa.assignments_intersection.potential_package_filter(pid)) - .for_each(|(pid, r)| { - let priority = prioritizer(pid, r); - prioritized_potential_packages.push(pid, priority); - }); - self.changed_this_decision_level = self.package_assignments.len(); - prioritized_potential_packages.pop().map(|(pid, _)| pid) + self.prioritized_potential_packages + .extend(self.potential_picks.iter().map(|&pid| { + let vs = self + .package_assignments + .get(self.package_assignments_indices[pid.0 as usize] as usize) + .expect("potential picks should have valid assignments") + .assignments_intersection + .term + .version_set(); + + (pid, prioritizer(pid, vs)) + })); + self.potential_picks.clear(); + self.prioritized_potential_packages + .pop() + .map(|(pid, _)| pid) } /// If a partial solution has, for every positive derivation, @@ -343,15 +316,15 @@ impl PartialSolution { package_store: PackageArena, ) -> SelectedDependencies { let used = self - .package_assignments + .package_assignments_indices .iter() - .take(self.current_decision_level.0 as usize) - .map(|(&pid, pa)| { - assert!( - pa.assignments_intersection.is_decision, - "Derivations in the Decision part", - ); - (pid, pa.assignments_intersection.decision_version_index) + .filter_map(|&pa_idx| { + let pa = self.package_assignments.get(pa_idx as usize)?; + if !pa.assignments_intersection.is_decision { + return None; + } + let version_index = pa.assignments_intersection.decision_version_index; + Some((pa.package_id, version_index)) }) .collect::>(); @@ -365,27 +338,32 @@ impl PartialSolution { /// Backtrack the partial solution to a given decision level. pub(crate) fn backtrack(&mut self, decision_level: DecisionLevel) { self.current_decision_level = decision_level; - self.package_assignments.retain(|_p, pa| { - if pa.smallest_decision_level > decision_level { - // Remove all entries that have a smallest decision level higher than the backtrack target. - false - } else if pa.highest_decision_level <= decision_level { - // Do not change entries older than the backtrack decision level target. - true - } else { - // smallest_decision_level <= decision_level < highest_decision_level - // + self.potential_picks.clear(); + + let max_len = self.package_assignments_lengths[decision_level.0 as usize] as usize; + + for pa in &self.package_assignments[max_len..] { + self.package_assignments_indices[pa.package_id.0 as usize] = u32::MAX; + } + + self.package_assignments_lengths + .truncate(decision_level.0 as usize); + + self.package_assignments.truncate(max_len); + + for pa in &mut self.package_assignments { + if pa.highest_decision_level > decision_level { // Since decision_level < highest_decision_level, - // We can be certain that there will be no decision in this package assignments + // we can be certain that there will be no decision in this package assignments // after backtracking, because such decision would have been the last // assignment and it would have the "highest_decision_level". // Truncate the history. - while pa.dated_derivations.last().map(|dd| dd.decision_level) > Some(decision_level) - { - pa.dated_derivations.pop(); - } - debug_assert!(!pa.dated_derivations.is_empty()); + let last_idx = pa + .dated_derivations + .partition_point(|dd| dd.decision_level <= decision_level); + + pa.dated_derivations.truncate(last_idx); let last = pa.dated_derivations.last().unwrap(); @@ -395,12 +373,16 @@ impl PartialSolution { // Reset the assignments intersection. pa.assignments_intersection = AssignmentsIntersection::derivations(last.accumulated_intersection); - true } - }); + + let pai = &pa.assignments_intersection; + if !pai.is_decision && pai.term.is_positive() { + self.potential_picks.push(pa.package_id); + } + } + // Throw away all stored priority levels, And mark that they all need to be recomputed. self.prioritized_potential_packages.clear(); - self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize; // Update list of last valid contradicted decision levels self.last_valid_decision_levels @@ -427,6 +409,14 @@ impl PartialSolution { package_store: &PackageArena, dependency_provider: &DP, ) { + let max_idx = store[new_incompatibilities.clone()] + .iter() + .flat_map(|incompat| incompat.iter().map(|(p, _)| p.0)) + .max() + .unwrap_or(0); + + self.resize_package_assignments_indices(PackageId(max_idx)); + if self.last_valid_decision_levels.len() == 1 { // Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem. // So let's live with a little bit of risk and add the decision without checking the dependencies. @@ -473,6 +463,13 @@ impl PartialSolution { } } + fn resize_package_assignments_indices(&mut self, package: PackageId) { + let idx = package.0 as usize; + if idx + 1 > self.package_assignments_indices.len() { + self.package_assignments_indices.resize(idx + 1, u32::MAX); + } + } + /// Check if the terms in the partial solution satisfy the incompatibility. pub(crate) fn relation(&self, incompat: &Incompatibility) -> Relation { incompat.relation(|package_id| self.term_intersection_for_package(package_id)) @@ -481,29 +478,26 @@ impl PartialSolution { /// Retrieve intersection of terms related to package. pub(crate) fn term_intersection_for_package(&self, package_id: PackageId) -> Option { self.package_assignments - .get(&package_id) + .get(self.package_assignments_indices[package_id.0 as usize] as usize) .map(|pa| pa.assignments_intersection.term) } /// Figure out if the satisfier and previous satisfier are of different decision levels. - #[allow(clippy::type_complexity)] pub(crate) fn satisfier_search( &self, incompat: &Incompatibility, store: &Arena>, package_store: &PackageArena, ) -> (PackageId, SatisfierSearch) { - let satisfied_map = - Self::find_satisfier(incompat, &self.package_assignments, package_store); + let satisfied_map = self.find_satisfier(incompat, package_store); let (&satisfier_pid, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map .iter() .max_by_key(|(_, (_, global_index, _))| global_index) .unwrap(); - let previous_satisfier_level = Self::find_previous_satisfier( + let previous_satisfier_level = self.find_previous_satisfier( incompat, satisfier_pid, satisfied_map, - &self.package_assignments, store, package_store, ); @@ -528,19 +522,20 @@ impl PartialSolution { /// Question: This is possible since we added a "global_index" to every dated_derivation. /// It would be nice if we could get rid of it, but I don't know if then it will be possible /// to return a coherent previous_satisfier_level. - #[allow(clippy::type_complexity)] fn find_satisfier( + &self, incompat: &Incompatibility, - package_assignments: &FxIndexMap>, package_store: &PackageArena, ) -> SatisfiedMap { let mut satisfied = SmallMap::Empty; for (package_id, incompat_term) in incompat.iter() { - let pa = package_assignments.get(&package_id).expect("Must exist"); - satisfied.insert( - package_id, - pa.satisfier::(package_id, incompat_term.negate(), package_store), - ); + let satisfied_info = self + .package_assignments + .get(self.package_assignments_indices[package_id.0 as usize] as usize) + .unwrap() + .satisfier::(package_id, incompat_term.negate(), package_store); + + satisfied.insert(package_id, satisfied_info); } satisfied } @@ -548,17 +543,20 @@ impl PartialSolution { /// Earliest assignment in the partial solution before satisfier /// such that incompatibility is satisfied by the partial solution up to /// and including that assignment plus satisfier. - #[allow(clippy::type_complexity)] fn find_previous_satisfier( + &self, incompat: &Incompatibility, satisfier_pid: PackageId, mut satisfied_map: SatisfiedMap, - package_assignments: &FxIndexMap>, store: &Arena>, package_store: &PackageArena, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. - let satisfier_pa = package_assignments.get(&satisfier_pid).unwrap(); + let satisfier_pa = self + .package_assignments + .get(self.package_assignments_indices[satisfier_pid.0 as usize] as usize) + .unwrap(); + let satisfier_cause = satisfied_map.get(&satisfier_pid).unwrap().0; let accum_term = if let Some(cause) = satisfier_cause { @@ -631,17 +629,3 @@ impl PackageAssignments { (None, decision_global_index, self.highest_decision_level) } } - -impl AssignmentsIntersection { - /// A package is a potential pick if there isn't an already - /// selected version (no "decision") - /// and if it contains at least one positive derivation term - /// in the partial solution. - fn potential_package_filter(&self, package_id: PackageId) -> Option<(PackageId, VersionSet)> { - if !self.is_decision && self.term.is_positive() { - Some((package_id, self.term.unwrap_positive())) - } else { - None - } - } -} diff --git a/src/solver.rs b/src/solver.rs index fb98c7ee..9303fbdc 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -53,7 +53,6 @@ //! to satisfy the dependencies of that package and version pair. //! If there is no solution, the reason will be provided as clear as possible. -use std::collections::BTreeSet; use std::error::Error; use std::fmt::{Debug, Display}; use std::hash::Hash; @@ -62,7 +61,7 @@ use log::{debug, info}; use crate::{ internal::{Incompatibility, State}, - DependencyConstraints, DerivationTree, External, Map, NoSolutionError, PackageArena, PackageId, + DependencyConstraints, DerivationTree, External, NoSolutionError, PackageArena, PackageId, PubGrubError, SelectedDependencies, VersionIndex, VersionSet, }; @@ -77,7 +76,7 @@ pub fn resolve( let mut package_store = PackageArena::new(); let package_id = package_store.insert(package); let mut state: State = State::init(package_id, version_index); - let mut added_dependencies: Map> = Map::default(); + let mut added_dependencies = Vec::new(); let mut next = package_id; loop { dependency_provider @@ -151,9 +150,14 @@ pub fn resolve( )); } - let is_new_dependency = added_dependencies.entry(next).or_default().insert(v); + // Check if the package version has already been selected. + let idx = next.0 as usize; + if idx + 1 > added_dependencies.len() { + added_dependencies.resize(idx + 1, VersionSet::empty()); + } + if !added_dependencies[idx].contains(v) { + added_dependencies[idx] = added_dependencies[idx].r#union(VersionSet::singleton(v)); - if is_new_dependency { // Retrieve that package dependencies. let pid = next; let dependencies = dependency_provider @@ -279,7 +283,6 @@ pub trait DependencyProvider { /// Retrieves the package dependencies. /// Return [Dependencies::Unavailable] if its dependencies are unavailable. - #[allow(clippy::type_complexity)] fn get_dependencies( &mut self, package_id: PackageId,