diff --git a/src/poetry/core/version/markers.py b/src/poetry/core/version/markers.py index 57715f259..6ce2ccb42 100644 --- a/src/poetry/core/version/markers.py +++ b/src/poetry/core/version/markers.py @@ -501,6 +501,9 @@ def union_simplify(self, other: BaseMarker) -> BaseMarker | None: ): return other + if not any(isinstance(m, MarkerUnion) for m in new_markers): + return self.of(*new_markers) + elif isinstance(other, MultiMarker): common_markers = [ marker for marker in self.markers if marker in other.markers @@ -525,6 +528,23 @@ def union_simplify(self, other: BaseMarker) -> BaseMarker | None: if not isinstance(unique_union, MarkerUnion): return self.of(*common_markers).intersect(unique_union) + else: + # Usually this operation just complicates things, but the special case + # where it doesn't allows the collapse of adjacent ranges eg + # + # 'python_version >= "3.6" and python_version < "3.6.2"' union + # 'python_version >= "3.6.2" and python_version < "3.7"' -> + # + # 'python_version >= "3.6" and python_version < "3.7"'. + unions = [ + m1.union(m2) for m2 in other_unique_markers for m1 in unique_markers + ] + conjunction = self.of(*unions) + if not isinstance(conjunction, MultiMarker) or not any( + isinstance(m, MarkerUnion) for m in conjunction.markers + ): + return conjunction + return None def validate(self, environment: dict[str, Any] | None) -> bool: diff --git a/tests/version/test_markers.py b/tests/version/test_markers.py index 65dc70516..9bf900c47 100644 --- a/tests/version/test_markers.py +++ b/tests/version/test_markers.py @@ -481,6 +481,90 @@ def test_multi_marker_union_multi_is_multi( assert str(m2.union(m1)) == expected +@pytest.mark.parametrize( + "marker1, marker2, expected", + [ + # Ranges with same start + ( + 'python_version >= "3.6" and python_full_version < "3.6.2"', + 'python_version >= "3.6" and python_version < "3.7"', + 'python_version >= "3.6" and python_version < "3.7"', + ), + ( + 'python_version > "3.6" and python_full_version < "3.6.2"', + 'python_version > "3.6" and python_version < "3.7"', + 'python_version > "3.6" and python_version < "3.7"', + ), + # Ranges meet exactly + ( + 'python_version >= "3.6" and python_full_version < "3.6.2"', + 'python_full_version >= "3.6.2" and python_version < "3.7"', + 'python_version >= "3.6" and python_version < "3.7"', + ), + ( + 'python_version >= "3.6" and python_full_version <= "3.6.2"', + 'python_full_version > "3.6.2" and python_version < "3.7"', + 'python_version >= "3.6" and python_version < "3.7"', + ), + # Ranges overlap + ( + 'python_version >= "3.6" and python_full_version <= "3.6.8"', + 'python_full_version >= "3.6.2" and python_version < "3.7"', + 'python_version >= "3.6" and python_version < "3.7"', + ), + # Ranges with same end. Ideally the union would give the lower version first. + ( + 'python_version >= "3.6" and python_version < "3.7"', + 'python_full_version >= "3.6.2" and python_version < "3.7"', + 'python_version < "3.7" and python_version >= "3.6"', + ), + ( + 'python_version >= "3.6" and python_version <= "3.7"', + 'python_full_version >= "3.6.2" and python_version <= "3.7"', + 'python_version <= "3.7" and python_version >= "3.6"', + ), + # A range covers an exact marker. + ( + 'python_version >= "3.6" and python_version <= "3.7"', + 'python_version == "3.6"', + 'python_version >= "3.6" and python_version <= "3.7"', + ), + ( + 'python_version >= "3.6" and python_version <= "3.7"', + 'python_version == "3.6" and implementation_name == "cpython"', + 'python_version >= "3.6" and python_version <= "3.7"', + ), + ( + 'python_version >= "3.6" and python_version <= "3.7"', + 'python_full_version == "3.6.2"', + 'python_version >= "3.6" and python_version <= "3.7"', + ), + ( + 'python_version >= "3.6" and python_version <= "3.7"', + 'python_full_version == "3.6.2" and implementation_name == "cpython"', + 'python_version >= "3.6" and python_version <= "3.7"', + ), + ( + 'python_version >= "3.6" and python_version <= "3.7"', + 'python_version == "3.7"', + 'python_version >= "3.6" and python_version <= "3.7"', + ), + ( + 'python_version >= "3.6" and python_version <= "3.7"', + 'python_version == "3.7" and implementation_name == "cpython"', + 'python_version >= "3.6" and python_version <= "3.7"', + ), + ], +) +def test_version_ranges_collapse_on_union( + marker1: str, marker2: str, expected: str +) -> None: + m1 = parse_marker(marker1) + m2 = parse_marker(marker2) + assert str(m1.union(m2)) == expected + assert str(m2.union(m1)) == expected + + def test_multi_marker_union_with_union() -> None: m = parse_marker('sys_platform == "darwin" and implementation_name == "cpython"') @@ -964,8 +1048,8 @@ def test_union_of_multi_with_a_containing_single() -> None: 'python_version < "3.6" or python_version >= "4.0"', ), ( - 'python_version ~= "3.6.3"', - 'python_version < "3.6.3" or python_version >= "3.7.0"', + 'python_full_version ~= "3.6.3"', + 'python_full_version < "3.6.3" or python_full_version >= "3.7.0"', ), ], )