Skip to content

Commit f42c936

Browse files
authored
Retain None as constraints bottom if no bottoms were provided (#19485)
Current version replaces `None` (which indicates "no items") with an empty union (=`Uninhabited`). This breaks inference in some cases. Fixes #19483.
1 parent a0665e1 commit f42c936

File tree

3 files changed

+24
-4
lines changed

3 files changed

+24
-4
lines changed

mypy/solve.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None:
270270
uppers = new_uppers
271271

272272
# ...unless this is the only information we have, then we just pass it on.
273+
lowers = list(lowers)
273274
if not uppers and not lowers:
274275
candidate = UninhabitedType()
275276
candidate.ambiguous = True
@@ -281,10 +282,11 @@ def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None:
281282
# Process each bound separately, and calculate the lower and upper
282283
# bounds based on constraints. Note that we assume that the constraint
283284
# targets do not have constraint references.
284-
if type_state.infer_unions:
285+
if type_state.infer_unions and lowers:
285286
# This deviates from the general mypy semantics because
286287
# recursive types are union-heavy in 95% of cases.
287-
bottom = UnionType.make_union(list(lowers))
288+
# Retain `None` when no bottoms were provided to avoid bogus `Never` inference.
289+
bottom = UnionType.make_union(lowers)
288290
else:
289291
# The order of lowers is non-deterministic.
290292
# We attempt to sort lowers because joins are non-associative. For instance:

test-data/unit/check-recursive-types.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ reveal_type(flatten([1, [2, [3]]])) # N: Revealed type is "builtins.list[builti
5454

5555
class Bad: ...
5656
x: Nested[int] = [1, [2, [3]]]
57-
x = [1, [Bad()]] # E: List item 1 has incompatible type "list[Bad]"; expected "Union[int, Nested[int]]"
57+
x = [1, [Bad()]] # E: List item 0 has incompatible type "Bad"; expected "Union[int, Nested[int]]"
5858
[builtins fixtures/isinstancelist.pyi]
5959

6060
[case testRecursiveAliasGenericInferenceNested]
@@ -605,7 +605,7 @@ class NT(NamedTuple, Generic[T]):
605605
class A: ...
606606
class B(A): ...
607607

608-
nti: NT[int] = NT(key=0, value=NT(key=1, value=A())) # E: Argument "value" to "NT" has incompatible type "NT[A]"; expected "Union[int, NT[int]]"
608+
nti: NT[int] = NT(key=0, value=NT(key=1, value=A())) # E: Argument "value" to "NT" has incompatible type "A"; expected "Union[int, NT[int]]"
609609
reveal_type(nti) # N: Revealed type is "tuple[builtins.int, Union[builtins.int, ...], fallback=__main__.NT[builtins.int]]"
610610

611611
nta: NT[A]

test-data/unit/check-typeddict.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4271,3 +4271,21 @@ reveal_type(dicts.TF) # N: Revealed type is "def (*, user_id: builtins.int =) -
42714271
reveal_type(dicts.TotalFalse) # N: Revealed type is "def (*, user_id: builtins.int =) -> TypedDict('__main__.Dicts.TF', {'user_id'?: builtins.int})"
42724272
[builtins fixtures/dict.pyi]
42734273
[typing fixtures/typing-typeddict.pyi]
4274+
4275+
[case testRecursiveNestedTypedDictInference]
4276+
from typing import TypedDict, Sequence
4277+
from typing_extensions import NotRequired
4278+
4279+
class Component(TypedDict):
4280+
type: str
4281+
components: NotRequired[Sequence['Component']]
4282+
4283+
inputs: Sequence[Component] = [{
4284+
'type': 'tuple',
4285+
'components': [
4286+
{'type': 'uint256'},
4287+
{'type': 'address'},
4288+
]
4289+
}]
4290+
[builtins fixtures/dict.pyi]
4291+
[typing fixtures/typing-typeddict.pyi]

0 commit comments

Comments
 (0)