Skip to content

Commit

Permalink
constraints: add full support for unions of generic constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
radoering committed Nov 28, 2022
1 parent 375f793 commit 4c4f817
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/poetry/core/constraints/generic/empty_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ def allows_any(self, other: BaseConstraint) -> bool:
def intersect(self, other: BaseConstraint) -> BaseConstraint:
return self

def union(self, other: BaseConstraint) -> BaseConstraint:
return other

def difference(self, other: BaseConstraint) -> BaseConstraint:
return self

Expand Down
21 changes: 21 additions & 0 deletions src/poetry/core/constraints/generic/multi_constraint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from poetry.core.constraints.generic import AnyConstraint
from poetry.core.constraints.generic import EmptyConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.constraint import Constraint
Expand Down Expand Up @@ -77,6 +78,26 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint:

return MultiConstraint(*self._constraints, other)

def union(self, other: BaseConstraint) -> BaseConstraint:
if not isinstance(other, Constraint):
return other.union(self)

if other in self._constraints:
return other

if other.value not in (c.value for c in self._constraints):
if other.operator == "!=":
return AnyConstraint()

return self

constraints = [c for c in self._constraints if c.value != other.value]

if len(constraints) == 1:
return constraints[0]

return MultiConstraint(*constraints)

def __eq__(self, other: object) -> bool:
if not isinstance(other, MultiConstraint):
return False
Expand Down
45 changes: 38 additions & 7 deletions src/poetry/core/constraints/generic/union_constraint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from poetry.core.constraints.generic import AnyConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.constraint import Constraint
from poetry.core.constraints.generic.empty_constraint import EmptyConstraint
Expand Down Expand Up @@ -108,15 +109,45 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint:

return UnionConstraint(*new_constraints)

def union(self, other: BaseConstraint) -> UnionConstraint:
if not isinstance(other, Constraint):
raise ValueError("Unimplemented constraint union")
def union(self, other: BaseConstraint) -> BaseConstraint:
if other.is_any():
return other

if other.is_empty():
return self

if isinstance(other, Constraint):
# (A or B) or C => A or B or C
# just a special case of UnionConstraint
other = UnionConstraint(other)

new_constraints: list[BaseConstraint] = []
if isinstance(other, UnionConstraint):
# (A or B) or (C or D) => A or B or C or D
for our_constraint in self._constraints:
for their_constraint in other.constraints:
union = our_constraint.union(their_constraint)
if union.is_any():
return AnyConstraint()
if isinstance(union, Constraint):
if union not in new_constraints:
new_constraints.append(union)
else:
if our_constraint not in new_constraints:
new_constraints.append(our_constraint)
if their_constraint not in new_constraints:
new_constraints.append(their_constraint)

else:
assert isinstance(other, MultiConstraint)
# (A or B) or (C and D) => nothing to do

constraints = self._constraints
if other not in self._constraints:
constraints += (other,)
new_constraints = [*self._constraints, other]

return UnionConstraint(*constraints)
if len(new_constraints) == 1:
return new_constraints[0]

return UnionConstraint(*new_constraints)

def __eq__(self, other: object) -> bool:
if not isinstance(other, UnionConstraint):
Expand Down
123 changes: 123 additions & 0 deletions tests/constraints/generic/test_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,51 @@ def test_intersect(
@pytest.mark.parametrize(
("constraint1", "constraint2", "expected"),
[
(
EmptyConstraint(),
Constraint("win32"),
Constraint("win32"),
),
(
EmptyConstraint(),
MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")),
MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")),
),
(
EmptyConstraint(),
UnionConstraint(Constraint("win32"), Constraint("linux")),
UnionConstraint(Constraint("win32"), Constraint("linux")),
),
(
AnyConstraint(),
Constraint("win32"),
AnyConstraint(),
),
(
AnyConstraint(),
MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")),
AnyConstraint(),
),
(
AnyConstraint(),
UnionConstraint(Constraint("win32"), Constraint("linux")),
AnyConstraint(),
),
(
EmptyConstraint(),
AnyConstraint(),
AnyConstraint(),
),
(
EmptyConstraint(),
EmptyConstraint(),
EmptyConstraint(),
),
(
AnyConstraint(),
AnyConstraint(),
AnyConstraint(),
),
(
Constraint("win32"),
Constraint("win32"),
Expand All @@ -227,6 +272,25 @@ def test_intersect(
Constraint("linux"),
UnionConstraint(Constraint("win32"), Constraint("linux")),
),
(
Constraint("win32"),
MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")),
MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")),
),
(
Constraint("win32"),
MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")),
Constraint("linux", "!="),
),
(
Constraint("win32"),
MultiConstraint(
Constraint("win32", "!="),
Constraint("linux", "!="),
Constraint("darwin", "!="),
),
MultiConstraint(Constraint("linux", "!="), Constraint("darwin", "!=")),
),
(
Constraint("win32"),
UnionConstraint(Constraint("win32"), Constraint("linux")),
Expand Down Expand Up @@ -254,6 +318,65 @@ def test_intersect(
Constraint("linux", "!="),
AnyConstraint(),
),
(
Constraint("win32", "!="),
MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")),
Constraint("win32", "!="),
),
(
Constraint("darwin", "!="),
MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")),
AnyConstraint(),
),
(
Constraint("win32", "!="),
UnionConstraint(Constraint("win32"), Constraint("linux")),
AnyConstraint(),
),
(
Constraint("win32", "!="),
UnionConstraint(Constraint("linux"), Constraint("linux2")),
Constraint("win32", "!="),
),
(
UnionConstraint(Constraint("win32"), Constraint("linux")),
UnionConstraint(Constraint("win32"), Constraint("darwin")),
UnionConstraint(
Constraint("win32"), Constraint("linux"), Constraint("darwin")
),
),
(
UnionConstraint(
Constraint("win32"), Constraint("linux"), Constraint("darwin")
),
UnionConstraint(
Constraint("win32"), Constraint("cygwin"), Constraint("darwin")
),
UnionConstraint(
Constraint("win32"),
Constraint("linux"),
Constraint("darwin"),
Constraint("cygwin"),
),
),
(
UnionConstraint(Constraint("win32"), Constraint("linux")),
MultiConstraint(Constraint("win32", "!="), Constraint("darwin", "!=")),
UnionConstraint(
Constraint("win32"),
Constraint("linux"),
MultiConstraint(Constraint("win32", "!="), Constraint("darwin", "!=")),
),
),
(
UnionConstraint(Constraint("win32"), Constraint("linux")),
MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")),
UnionConstraint(
Constraint("win32"),
Constraint("linux"),
MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")),
),
),
],
)
def test_union(
Expand Down

0 comments on commit 4c4f817

Please sign in to comment.