Skip to content

Commit fbd4cb6

Browse files
authored
Fix narrowing with final type objects (#20743)
Follow up feature request from here: #20675 (comment) Preserves correct behaviour on this test case: https://github.com/python/mypy/pull/20675/files#diff-e3de7a75a8a107b4f462b164cdf4945d50505c5e9f7092b753c4add0c01530bbR3021-R3037
1 parent 825c0bb commit fbd4cb6

2 files changed

Lines changed: 36 additions & 3 deletions

File tree

mypy/checker.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7936,9 +7936,8 @@ def get_type_range_of_type(self, typ: Type) -> TypeRange | None:
79367936

79377937
if isinstance(typ, UnionType):
79387938
type_ranges = [self.get_type_range_of_type(item) for item in typ.items]
7939-
is_upper_bound = any(t.is_upper_bound for t in type_ranges if t is not None)
79407939
item = make_simplified_union([t.item for t in type_ranges if t is not None])
7941-
return TypeRange(item, is_upper_bound=is_upper_bound)
7940+
return TypeRange(item, is_upper_bound=True)
79427941
if isinstance(typ, FunctionLike) and typ.is_type_obj():
79437942
# If a type is generic, `isinstance` can only narrow its variables to Any.
79447943
any_parameterized = fill_typevars_with_any(typ.type_object())
@@ -7955,6 +7954,8 @@ def get_type_range_of_type(self, typ: Type) -> TypeRange | None:
79557954
if isinstance(typ.item, NoneType):
79567955
# except for Type[None], because "'NoneType' is not an acceptable base type"
79577956
is_upper_bound = False
7957+
if isinstance(typ.item, Instance) and typ.item.type.is_final:
7958+
is_upper_bound = False
79587959
return TypeRange(typ.item, is_upper_bound=is_upper_bound)
79597960
if isinstance(typ, AnyType):
79607961
return TypeRange(typ, is_upper_bound=False)
@@ -8685,7 +8686,7 @@ def flatten_types_if_tuple(t: Type) -> list[Type]:
86858686
"""Flatten a nested sequence of tuples into one list of nodes."""
86868687
t = get_proper_type(t)
86878688
if isinstance(t, UnionType):
8688-
return [b for a in t.items for b in flatten_types_if_tuple(a)]
8689+
return [UnionType.make_union([b for a in t.items for b in flatten_types_if_tuple(a)])]
86898690
if isinstance(t, TupleType):
86908691
return [b for a in t.items for b in flatten_types_if_tuple(a)]
86918692
elif is_named_instance(t, "builtins.tuple"):

test-data/unit/check-isinstance.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3209,3 +3209,35 @@ def foo(x: object, t: T):
32093209
reveal_type(x) # N: Revealed type is "__main__.A"
32103210
reveal_type(x.x) # N: Revealed type is "builtins.int"
32113211
[builtins fixtures/primitives.pyi]
3212+
3213+
3214+
[case testIsInstanceFinalTypeObject]
3215+
# flags: --strict-equality --warn-unreachable
3216+
from __future__ import annotations
3217+
from typing import final
3218+
3219+
@final
3220+
class A: ...
3221+
3222+
def f1(x: A, t: type[A]):
3223+
if isinstance(x, A):
3224+
reveal_type(x) # N: Revealed type is "__main__.A"
3225+
else:
3226+
reveal_type(x) # E: Statement is unreachable
3227+
3228+
if isinstance(x, t):
3229+
reveal_type(x) # N: Revealed type is "__main__.A"
3230+
else:
3231+
reveal_type(x) # E: Statement is unreachable
3232+
3233+
def f2(x: A | None, t: type[A]):
3234+
if isinstance(x, A):
3235+
reveal_type(x) # N: Revealed type is "__main__.A"
3236+
else:
3237+
reveal_type(x) # N: Revealed type is "None"
3238+
3239+
if isinstance(x, t):
3240+
reveal_type(x) # N: Revealed type is "__main__.A"
3241+
else:
3242+
reveal_type(x) # N: Revealed type is "None"
3243+
[builtins fixtures/isinstancelist.pyi]

0 commit comments

Comments
 (0)