diff --git a/src/poetry/core/version/markers.py b/src/poetry/core/version/markers.py index 00caa879f..c3b64fe10 100644 --- a/src/poetry/core/version/markers.py +++ b/src/poetry/core/version/markers.py @@ -397,39 +397,51 @@ def __init__(self, *markers: MarkerTypes) -> None: @classmethod def of(cls, *markers: MarkerTypes) -> MarkerTypes: - new_markers = [] - markers = _flatten_markers(markers, MultiMarker) + new_markers = _flatten_markers(markers, MultiMarker) + markers = [] - for marker in markers: - if marker in new_markers: - continue + while markers != new_markers: + markers = new_markers + new_markers = [] + for marker in markers: + if marker in new_markers: + continue - if marker.is_any(): - continue + if marker.is_any(): + continue - if isinstance(marker, SingleMarker): - intersected = False - for i, mark in enumerate(new_markers): - if ( - not isinstance(mark, SingleMarker) - or isinstance(mark, SingleMarker) - and mark.name != marker.name - ): + if isinstance(marker, SingleMarker): + intersected = False + for i, mark in enumerate(new_markers): + if isinstance(mark, SingleMarker) and mark.name == marker.name: + intersection = mark.constraint.intersect(marker.constraint) + if intersection == mark.constraint: + intersected = True + elif intersection == marker.constraint: + new_markers[i] = marker + intersected = True + elif intersection.is_empty(): + return EmptyMarker() + elif isinstance(mark, MarkerUnion): + intersection = mark.intersect(marker) + if isinstance(intersection, SingleMarker): + new_markers[i] = intersection + elif intersection.is_empty(): + return EmptyMarker() + if intersected: continue - intersection = mark.constraint.intersect(marker.constraint) - if intersection == mark.constraint: - intersected = True - elif intersection == marker.constraint: - new_markers[i] = marker - intersected = True - elif intersection.is_empty(): - return EmptyMarker() + elif isinstance(marker, MarkerUnion): + for mark in new_markers: + if isinstance(mark, SingleMarker): + intersection = marker.intersect(mark) + if isinstance(intersection, SingleMarker): + marker = intersection + break + elif intersection.is_empty(): + return EmptyMarker() - if intersected: - continue - - new_markers.append(marker) + new_markers.append(marker) if any(m.is_empty() for m in new_markers) or not new_markers: return EmptyMarker() @@ -576,7 +588,7 @@ def of(cls, *markers: BaseMarker) -> MarkerTypes: return AnyMarker() if not markers: - return AnyMarker() + return EmptyMarker() if len(markers) == 1: return markers[0] diff --git a/tests/version/test_markers.py b/tests/version/test_markers.py index a317205aa..ee2a3c62d 100644 --- a/tests/version/test_markers.py +++ b/tests/version/test_markers.py @@ -124,6 +124,24 @@ def test_single_marker_intersect_with_multi_compacts_constraint(): ) +def test_single_marker_intersect_with_union_leads_to_single_marker(): + m = parse_marker('python_version >= "3.6"') + + intersection = m.intersect( + parse_marker('python_version < "3.6" or python_version >= "3.7"') + ) + assert str(intersection) == 'python_version >= "3.7"' + + +def test_single_marker_intersect_with_union_leads_to_empty(): + m = parse_marker('python_version == "3.7"') + + intersection = m.intersect( + parse_marker('python_version < "3.7" or python_version >= "3.8"') + ) + assert intersection.is_empty() + + def test_single_marker_not_in_python_intersection(): m = parse_marker('python_version not in "2.7, 3.0, 3.1"') @@ -259,6 +277,36 @@ def test_multi_marker_intersect_multi_with_overlapping_constraints(): ) +def test_multi_marker_intersect_with_union_drops_union(): + m = parse_marker('python_version >= "3" and python_version < "4"') + m2 = parse_marker('python_version < "2" or python_version >= "3"') + assert str(m.intersect(m2)) == str(m) + assert str(m2.intersect(m)) == str(m) + + +def test_multi_marker_intersect_with_multi_union_leads_to_empty_in_one_step(): + # empty marker in one step + # py == 2 and (py < 2 or py >= 3) -> empty + m = parse_marker('sys_platform == "darwin" and python_version == "2"') + m2 = parse_marker( + 'sys_platform == "darwin" and (python_version < "2" or python_version >= "3")' + ) + assert m.intersect(m2).is_empty() + assert m2.intersect(m).is_empty() + + +def test_multi_marker_intersect_with_multi_union_leads_to_empty_in_two_steps(): + # empty marker in two steps + # py >= 2 and (py < 2 or py >= 3) -> py >= 3 + # py < 3 and py >= 3 -> empty + m = parse_marker('python_version >= "2" and python_version < "3"') + m2 = parse_marker( + 'sys_platform == "darwin" and (python_version < "2" or python_version >= "3")' + ) + assert m.intersect(m2).is_empty() + assert m2.intersect(m).is_empty() + + def test_multi_marker_union_multi(): m = parse_marker('sys_platform == "darwin" and implementation_name == "cpython"')