Skip to content

Commit 62c0399

Browse files
committed
fix
1 parent f02c757 commit 62c0399

File tree

2 files changed

+28
-14
lines changed

2 files changed

+28
-14
lines changed

mypy/join.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from collections.abc import Sequence
6-
from typing import overload
6+
from typing import Iterable, overload
77

88
import mypy.typeops
99
from mypy.expandtype import expand_type

mypy/solve.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint, infer_constraints, neg_op
1010
from mypy.expandtype import expand_type
1111
from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort
12-
from mypy.join import join_types
12+
from mypy.join import join_type_list
1313
from mypy.meet import meet_type_list, meet_types
1414
from mypy.subtypes import is_subtype
1515
from mypy.typeops import get_all_type_vars
@@ -247,10 +247,16 @@ def solve_iteratively(
247247
return solutions
248248

249249

250+
def _join_sorted_key(t: Type) -> int:
251+
t = get_proper_type(t)
252+
if isinstance(t, UnionType):
253+
return -1
254+
return 0
255+
256+
250257
def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None:
251258
"""Solve constraints by finding by using meets of upper bounds, and joins of lower bounds."""
252-
bottom: Type | None = None
253-
top: Type | None = None
259+
254260
candidate: Type | None = None
255261

256262
# Filter out previous results of failed inference, they will only spoil the current pass...
@@ -267,19 +273,27 @@ def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None:
267273
candidate.ambiguous = True
268274
return candidate
269275

276+
bottom: Type | None = None
277+
top: Type | None = None
278+
270279
# Process each bound separately, and calculate the lower and upper
271280
# bounds based on constraints. Note that we assume that the constraint
272281
# targets do not have constraint references.
273-
for target in lowers:
274-
if bottom is None:
275-
bottom = target
276-
else:
277-
if type_state.infer_unions:
278-
# This deviates from the general mypy semantics because
279-
# recursive types are union-heavy in 95% of cases.
280-
bottom = UnionType.make_union([bottom, target])
281-
else:
282-
bottom = join_types(bottom, target)
282+
if type_state.infer_unions:
283+
# This deviates from the general mypy semantics because
284+
# recursive types are union-heavy in 95% of cases.
285+
bottom = UnionType.make_union(list(lowers))
286+
else:
287+
# The order of lowers is non-deterministic.
288+
# We attempt to sort lowers because joins are non-associative. For instance:
289+
# join(join(int, str), int | str) == join(object, int | str) == object
290+
# join(int, join(str, int | str)) == join(int, int | str) == int | str
291+
# Note that joins in theory should be commutative, but in practice some bugs mean this is
292+
# also a source of non-deterministic type checking results.
293+
sorted_lowers = sorted(lowers, key=_join_sorted_key)
294+
bottom = join_type_list(sorted_lowers)
295+
if isinstance(bottom, UninhabitedType):
296+
bottom = None
283297

284298
for target in uppers:
285299
if top is None:

0 commit comments

Comments
 (0)