diff --git a/src/resolvelib/providers.py b/src/resolvelib/providers.py index 524e3d8..061d354 100644 --- a/src/resolvelib/providers.py +++ b/src/resolvelib/providers.py @@ -194,3 +194,30 @@ def narrow_requirement_selection( Iterable[KT]: A non-empty subset of `identifiers`. """ return identifiers + + def disjoint( + self, backtrack_causes: Sequence[RequirementInformation[RT, CT]] + ) -> Sequence[RequirementInformation[RT, CT]]: + """Filter backtrack causes to retain only disjoint instances. + + Given a sequence of backtrack causes, return those that are disjoint. + This filtered sequence will attached to the resolution state, passed to + the provider methods `get_preference` and `narrow_requirement_selection`, + and the reporter method `resolving_conflicts`. + + :param backtrack_causes: A sequence of *requirement information* that are + the requirements causing the resolver to most recently + backtrack. + + A *requirement information* instance is a named tuple with two members: + + * ``requirement`` specifies a requirement contributing to the current + list of candidates. + * ``parent`` specifies the candidate that provides (is depended on for) + the requirement, or ``None`` to indicate a root requirement. + + Returns: + Sequence[RequirementInformation[RT, CT]]: A subset of at least two + disjoint `backtrack_causes`. + """ + return backtrack_causes diff --git a/src/resolvelib/resolvers/resolution.py b/src/resolvelib/resolvers/resolution.py index da3c66e..0d5277b 100644 --- a/src/resolvelib/resolvers/resolution.py +++ b/src/resolvelib/resolvers/resolution.py @@ -3,7 +3,7 @@ import collections import itertools import operator -from typing import TYPE_CHECKING, Collection, Generic, Iterable, Mapping +from typing import TYPE_CHECKING, Collection, Generic, Iterable, Mapping, Sequence from ..structs import ( CT, @@ -274,7 +274,7 @@ def _patch_criteria( ) return True - def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool: + def _backjump(self, causes: Sequence[RequirementInformation[RT, CT]]) -> bool: """Perform backjumping. When we enter here, the stack is like this:: @@ -365,9 +365,16 @@ def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool: def _extract_causes( self, criteron: list[Criterion[RT, CT]] - ) -> list[RequirementInformation[RT, CT]]: + ) -> Sequence[RequirementInformation[RT, CT]]: """Extract causes from list of criterion and deduplicate""" - return list({id(i): i for c in criteron for i in c.information}.values()) + causes = list({id(i): i for c in criteron for i in c.information}.values()) + + # Two causes *should* always be disjoint, so only check for + # disjoint causes when there are more than two + if len(causes) > 2: + return self._p.disjoint(causes) + + return causes def resolve(self, requirements: Iterable[RT], max_rounds: int) -> State[RT, CT, KT]: if self._states: