Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

markers: avoid combinatorial explosion in certain cases #568

Merged
merged 2 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/poetry/core/constraints/generic/any_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ def allows_all(self, other: BaseConstraint) -> bool:
def allows_any(self, other: BaseConstraint) -> bool:
return True

def invert(self) -> BaseConstraint:
return EmptyConstraint()

def difference(self, other: BaseConstraint) -> BaseConstraint:
if other.is_any():
return EmptyConstraint()
Expand Down
3 changes: 3 additions & 0 deletions src/poetry/core/constraints/generic/base_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ def allows_all(self, other: BaseConstraint) -> bool:
def allows_any(self, other: BaseConstraint) -> bool:
raise NotImplementedError()

def invert(self) -> BaseConstraint:
raise NotImplementedError()

def difference(self, other: BaseConstraint) -> BaseConstraint:
raise NotImplementedError()

Expand Down
3 changes: 3 additions & 0 deletions src/poetry/core/constraints/generic/constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ def allows_any(self, other: BaseConstraint) -> bool:

return other.allows(self)

def invert(self) -> Constraint:
return Constraint(self._value, "!=" if self._operator == "==" else "==")

def difference(self, other: BaseConstraint) -> Constraint | EmptyConstraint:
if other.allows(self):
return EmptyConstraint()
Expand Down
5 changes: 5 additions & 0 deletions src/poetry/core/constraints/generic/empty_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ def allows_all(self, other: BaseConstraint) -> bool:
def allows_any(self, other: BaseConstraint) -> bool:
return False

def invert(self) -> BaseConstraint:
from poetry.core.constraints.generic.any_constraint import AnyConstraint

return AnyConstraint()

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

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

from typing import TYPE_CHECKING

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


if TYPE_CHECKING:
from poetry.core.constraints.generic import UnionConstraint


class MultiConstraint(BaseConstraint):
def __init__(self, *constraints: Constraint) -> None:
if any(c.operator == "==" for c in constraints):
Expand Down Expand Up @@ -62,6 +68,11 @@ def allows_any(self, other: BaseConstraint) -> bool:

return False

def invert(self) -> UnionConstraint:
from poetry.core.constraints.generic import UnionConstraint

return UnionConstraint(*(c.invert() for c in self._constraints))

def intersect(self, other: BaseConstraint) -> BaseConstraint:
if not isinstance(other, Constraint):
return other.intersect(self)
Expand Down
31 changes: 25 additions & 6 deletions src/poetry/core/constraints/generic/union_constraint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import itertools

from poetry.core.constraints.generic import AnyConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.constraint import Constraint
Expand Down Expand Up @@ -64,6 +66,14 @@ def allows_all(self, other: BaseConstraint) -> bool:

return their_constraint is None

def invert(self) -> MultiConstraint:
inverted_constraints = [c.invert() for c in self._constraints]
if any(not isinstance(c, Constraint) for c in inverted_constraints):
raise NotImplementedError(
"Inversion of complex union constraints not implemented"
)
return MultiConstraint(*inverted_constraints) # type: ignore[arg-type]

def intersect(self, other: BaseConstraint) -> BaseConstraint:
if other.is_any():
return self
Expand Down Expand Up @@ -124,19 +134,28 @@ def union(self, other: BaseConstraint) -> BaseConstraint:
new_constraints: list[BaseConstraint] = []
if isinstance(other, UnionConstraint):
# (A or B) or (C or D) => A or B or C or D
our_new_constraints: list[BaseConstraint] = []
their_new_constraints: list[BaseConstraint] = []
merged_new_constraints: list[BaseConstraint] = []
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)
if union not in merged_new_constraints:
merged_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)
if our_constraint not in our_new_constraints:
our_new_constraints.append(our_constraint)
if their_constraint not in their_new_constraints:
their_new_constraints.append(their_constraint)
new_constraints = our_new_constraints
for constraint in itertools.chain(
their_new_constraints, merged_new_constraints
):
if constraint not in new_constraints:
new_constraints.append(constraint)

else:
assert isinstance(other, MultiConstraint)
Expand Down
8 changes: 6 additions & 2 deletions src/poetry/core/packages/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,12 @@ def marker(self, marker: str | BaseMarker) -> None:
self.deactivate()

for or_ in markers["extra"]:
for _, extra in or_:
self.in_extras.append(canonicalize_name(extra))
for op, extra in or_:
if op == "==":
self.in_extras.append(canonicalize_name(extra))
elif op == "" and "||" in extra:
for _extra in extra.split(" || "):
self.in_extras.append(canonicalize_name(_extra))

# Recalculate python versions.
self._python_versions = "*"
Expand Down
17 changes: 13 additions & 4 deletions src/poetry/core/packages/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from poetry.core.constraints.version import VersionRange
from poetry.core.constraints.version import parse_constraint
from poetry.core.pyproject.toml import PyProjectTOML
from poetry.core.version.markers import SingleMarkerLike
from poetry.core.version.markers import dnf


Expand Down Expand Up @@ -179,10 +180,18 @@ def add_constraint(
for i, sub_marker in enumerate(conjunctions):
if isinstance(sub_marker, MultiMarker):
for m in sub_marker.markers:
assert isinstance(m, SingleMarker)
add_constraint(m.name, (m.operator, m.value), i)
elif isinstance(sub_marker, SingleMarker):
add_constraint(sub_marker.name, (sub_marker.operator, sub_marker.value), i)
assert isinstance(m, SingleMarkerLike)
if isinstance(m, SingleMarker):
add_constraint(m.name, (m.operator, m.value), i)
else:
add_constraint(m.name, ("", str(m.constraint)), i)
elif isinstance(sub_marker, SingleMarkerLike):
if isinstance(sub_marker, SingleMarker):
add_constraint(
sub_marker.name, (sub_marker.operator, sub_marker.value), i
)
else:
add_constraint(sub_marker.name, ("", str(sub_marker.constraint)), i)

for group_name in requirements:
# remove duplicates
Expand Down
Loading