Skip to content

Commit aa52192

Browse files
authored
Model exact narrowing with type(x) checks (#20703)
I have some changes planned to improve final support, which will pair nicely with this Fixes biggest thing in #10302 (original issue in there is not an issue; fixes this comment #10302 (comment) )
1 parent 9542fd4 commit aa52192

File tree

2 files changed

+54
-7
lines changed

2 files changed

+54
-7
lines changed

mypy/checker.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6812,13 +6812,20 @@ def narrow_type_by_identity_equality(
68126812
# We patch this here because it is desirable to widen to any for cases like
68136813
# isinstance(x, (y: Any))
68146814
continue
6815+
6816+
arg_type = self.lookup_type(expr_in_type_expr)
6817+
if_type, else_type = self.conditional_types_with_intersection(
6818+
arg_type, [current_type_range], expr_in_type_expr
6819+
)
6820+
if if_type is not None and (
6821+
not current_type_range.is_upper_bound
6822+
and not is_equivalent(if_type, current_type_range.item)
6823+
):
6824+
# type(x) and x.__class__ checks must exact match
6825+
if_type = UninhabitedType()
6826+
68156827
if_map, else_map = conditional_types_to_typemaps(
6816-
expr_in_type_expr,
6817-
*self.conditional_types_with_intersection(
6818-
self.lookup_type(expr_in_type_expr),
6819-
[current_type_range],
6820-
expr_in_type_expr,
6821-
),
6828+
expr_in_type_expr, if_type, else_type
68226829
)
68236830

68246831
is_final = (

test-data/unit/check-narrowing.test

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3221,7 +3221,7 @@ else:
32213221
reveal_type(y) # N: Revealed type is "builtins.str"
32223222
[builtins fixtures/isinstance.pyi]
32233223

3224-
[case testTypeEqualsCheckUsingIsNonOverlappingChild-xfail]
3224+
[case testTypeEqualsCheckUsingIsNonOverlappingChild]
32253225
# flags: --strict-equality --warn-unreachable
32263226
from typing import Union
32273227

@@ -3237,6 +3237,46 @@ def main(x: Union[B, C]):
32373237
reveal_type(x) # N: Revealed type is "__main__.B | __main__.C"
32383238
[builtins fixtures/isinstance.pyi]
32393239

3240+
[case testTypeEqualsCheckTypeVar]
3241+
# flags: --strict-equality --warn-unreachable
3242+
from typing import TypeVar
3243+
3244+
class A: ...
3245+
class B: ...
3246+
3247+
T = TypeVar("T")
3248+
3249+
def forgets_about_subclasses1(self, obj: T) -> T:
3250+
if isinstance(obj, A):
3251+
return A() # E: Incompatible return value type (got "A", expected "T")
3252+
elif isinstance(obj, B):
3253+
return B() # E: Incompatible return value type (got "B", expected "T")
3254+
raise
3255+
3256+
def correct1(self, obj: T) -> T:
3257+
if type(obj) == A:
3258+
return A() # E: Incompatible return value type (got "A", expected "T")
3259+
elif type(obj) == B:
3260+
return B() # E: Incompatible return value type (got "B", expected "T")
3261+
raise
3262+
3263+
T_value = TypeVar("T_value", A, B)
3264+
3265+
def forgets_about_subclasses2(self, obj: T_value) -> T_value:
3266+
if isinstance(obj, A):
3267+
return A() # E: Incompatible return value type (got "A", expected "B")
3268+
elif isinstance(obj, B):
3269+
return B()
3270+
raise
3271+
3272+
def correct2(self, obj: T_value) -> T_value:
3273+
if type(obj) == A:
3274+
return A()
3275+
elif type(obj) == B:
3276+
return B()
3277+
raise
3278+
[builtins fixtures/primitives.pyi]
3279+
32403280
[case testTypeEqualsCheckUsingDifferentSpecializedTypes]
32413281
# flags: --strict-equality --warn-unreachable
32423282
from collections import defaultdict

0 commit comments

Comments
 (0)