diff --git a/src/poetry/core/constraints/generic/union_constraint.py b/src/poetry/core/constraints/generic/union_constraint.py index 98b5ac411..75a5db800 100644 --- a/src/poetry/core/constraints/generic/union_constraint.py +++ b/src/poetry/core/constraints/generic/union_constraint.py @@ -141,7 +141,6 @@ def union(self, other: BaseConstraint) -> BaseConstraint: # 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 our_new_constraints: list[BaseConstraint] = [] @@ -179,6 +178,24 @@ def union(self, other: BaseConstraint) -> BaseConstraint: if any(c in other.constraints for c in self._constraints): return self + # (A or B) or (not A and D) => A or B or D + simplified = False + our_simple_constraints = [ + c for c in self._constraints if isinstance(c, Constraint) + ] + their_new_constraints = [] + for their_constraint in other.constraints: + if any( + c.union(their_constraint).is_any() for c in our_simple_constraints + ): + simplified = True + else: + their_new_constraints.append(their_constraint) + if simplified: + if not their_new_constraints: + return AnyConstraint() + return self.union(UnionConstraint(*their_new_constraints)) + # (A or B) or (C and D) => nothing to do new_constraints = [*self._constraints, other] diff --git a/tests/constraints/generic/test_constraint.py b/tests/constraints/generic/test_constraint.py index c066c3cdc..fe4bc4d65 100644 --- a/tests/constraints/generic/test_constraint.py +++ b/tests/constraints/generic/test_constraint.py @@ -1129,20 +1129,12 @@ def test_intersect_extra( ( UnionConstraint(Constraint("win32"), Constraint("linux")), MultiConstraint(Constraint("win32", "!="), Constraint("darwin", "!=")), - UnionConstraint( - Constraint("win32"), - Constraint("linux"), - MultiConstraint(Constraint("win32", "!="), Constraint("darwin", "!=")), - ), + Constraint("darwin", "!="), ), ( UnionConstraint(Constraint("win32"), Constraint("linux")), MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), - UnionConstraint( - Constraint("win32"), - Constraint("linux"), - MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), - ), + AnyConstraint(), ), ( MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), @@ -1513,28 +1505,33 @@ def test_union( ( UnionConstraint(ExtraConstraint("extra1"), ExtraConstraint("extra2")), ExtraMultiConstraint( - ExtraConstraint("extra1", "!="), ExtraConstraint("extra3", "!=") + ExtraConstraint("extra3", "!="), ExtraConstraint("extra4", "!=") ), UnionConstraint( ExtraConstraint("extra1"), ExtraConstraint("extra2"), ExtraMultiConstraint( - ExtraConstraint("extra1", "!="), ExtraConstraint("extra3", "!=") + ExtraConstraint("extra3", "!="), ExtraConstraint("extra4", "!=") ), ), ), ( UnionConstraint(ExtraConstraint("extra1"), ExtraConstraint("extra2")), ExtraMultiConstraint( - ExtraConstraint("extra1", "!="), ExtraConstraint("extra2", "!=") + ExtraConstraint("extra1", "!="), ExtraConstraint("extra3", "!=") ), UnionConstraint( ExtraConstraint("extra1"), ExtraConstraint("extra2"), - ExtraMultiConstraint( - ExtraConstraint("extra1", "!="), ExtraConstraint("extra2", "!=") - ), + ExtraConstraint("extra3", "!="), + ), + ), + ( + UnionConstraint(ExtraConstraint("extra1"), ExtraConstraint("extra2")), + ExtraMultiConstraint( + ExtraConstraint("extra1", "!="), ExtraConstraint("extra2", "!=") ), + AnyConstraint(), ), ( ExtraMultiConstraint(ExtraConstraint("extra1"), ExtraConstraint("extra2")), diff --git a/tests/version/test_markers.py b/tests/version/test_markers.py index acd877935..61c588225 100644 --- a/tests/version/test_markers.py +++ b/tests/version/test_markers.py @@ -717,12 +717,17 @@ def test_single_marker_union_with_inverse() -> None: ( 'extra != "a" and extra != "b"', 'extra == "a" or extra == "b"', - 'extra != "a" and extra != "b" or extra == "a" or extra == "b"', + "", ), ( 'extra != "a" and extra != "b"', 'extra == "a" or extra == "c"', - 'extra != "a" and extra != "b" or extra == "a" or extra == "c"', + 'extra == "a" or extra == "c" or extra != "b"', + ), + ( + 'extra != "a" and extra != "b"', + 'extra == "c" or extra == "d"', + 'extra != "a" and extra != "b" or extra == "c" or extra == "d"', ), ( 'extra == "a" or extra == "b"', @@ -1869,6 +1874,26 @@ def test_union_should_drop_markers_if_their_complement_is_present( assert parse_marker(expected) == m +def test_inverse_atomic_markers() -> None: + m1 = parse_marker('sys_platform == "win32" or sys_platform == "linux"') + m2 = parse_marker('sys_platform != "win32" and sys_platform != "linux"') + + assert m1.intersect(m2).is_empty() + assert m2.intersect(m1).is_empty() + assert m1.union(m2).is_any() + assert m2.union(m1).is_any() + + +def test_partially_inverse_atomic_markers() -> None: + m1 = parse_marker('sys_platform == "win32" or sys_platform == "linux"') + m2 = parse_marker('sys_platform != "win32" and sys_platform != "darwin"') + + assert str(m1.intersect(m2)) == 'sys_platform == "linux"' + assert str(m2.intersect(m1)) == 'sys_platform == "linux"' + assert str(m1.union(m2)) == 'sys_platform != "darwin"' + assert str(m2.union(m1)) == 'sys_platform != "darwin"' + + @pytest.mark.parametrize( "scheme, marker, expected", [