Skip to content

Commit edc47c6

Browse files
authored
Avoid narrowing type[T] (#20662)
Maybe a little ad hoc, but avoids some of the undesirable primer regression from #20492 Closes #20636
1 parent 596a288 commit edc47c6

File tree

4 files changed

+79
-0
lines changed

4 files changed

+79
-0
lines changed

mypy/checker.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6492,10 +6492,13 @@ def comparison_type_narrowing_helper(self, node: ComparisonExpr) -> tuple[TypeMa
64926492
and not is_false_literal(expr)
64936493
and not is_true_literal(expr)
64946494
and not self.is_literal_enum(expr)
6495+
# CallableType type objects are usually already maximally specific
64956496
and not (
64966497
isinstance(p_expr := get_proper_type(expr_type), CallableType)
64976498
and p_expr.is_type_obj()
64986499
)
6500+
# This is a little ad hoc, in the absence of intersection types
6501+
and not (isinstance(p_expr, TypeType) and isinstance(p_expr.item, TypeVarType))
64996502
):
65006503
h = literal_hash(expr)
65016504
if h is not None:

test-data/unit/check-narrowing.test

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3018,6 +3018,60 @@ def f2(x: Any) -> None:
30183018
reveal_type(x) # N: Revealed type is "Any"
30193019
[builtins fixtures/tuple.pyi]
30203020

3021+
[case testNarrowTypeObject]
3022+
# flags: --strict-equality --warn-unreachable
3023+
from typing import Any
3024+
3025+
# https://github.com/python/mypy/issues/13704
3026+
3027+
def f1(cls: type):
3028+
if cls is str:
3029+
reveal_type(cls) # N: Revealed type is "def (o: builtins.object =) -> builtins.str"
3030+
reveal_type(cls(5)) # N: Revealed type is "builtins.str"
3031+
3032+
if issubclass(cls, int):
3033+
reveal_type(cls) # N: Revealed type is "type[builtins.int]"
3034+
elif cls is str:
3035+
reveal_type(cls) # N: Revealed type is "def (o: builtins.object =) -> builtins.str"
3036+
reveal_type(cls(5)) # N: Revealed type is "builtins.str"
3037+
3038+
def f2(cls: type[object]):
3039+
if cls is str:
3040+
reveal_type(cls) # N: Revealed type is "def (o: builtins.object =) -> builtins.str"
3041+
reveal_type(cls(5)) # N: Revealed type is "builtins.str"
3042+
3043+
if issubclass(cls, int):
3044+
reveal_type(cls) # N: Revealed type is "type[builtins.int]"
3045+
elif cls is str:
3046+
reveal_type(cls) # N: Revealed type is "def (o: builtins.object =) -> builtins.str"
3047+
reveal_type(cls(5)) # N: Revealed type is "builtins.str"
3048+
3049+
def f3(cls: type[Any]):
3050+
if cls is str:
3051+
reveal_type(cls) # N: Revealed type is "type[Any]"
3052+
reveal_type(cls(5)) # N: Revealed type is "Any"
3053+
3054+
if issubclass(cls, int):
3055+
reveal_type(cls) # N: Revealed type is "type[builtins.int]"
3056+
elif cls is str:
3057+
reveal_type(cls) # N: Revealed type is "type[Any]"
3058+
reveal_type(cls(5)) # N: Revealed type is "Any"
3059+
[builtins fixtures/isinstance.pyi]
3060+
3061+
[case testNarrowTypeObjectUnion]
3062+
# flags: --strict-equality --warn-unreachable
3063+
from __future__ import annotations
3064+
3065+
def f4(cls: type[str | int]):
3066+
reveal_type(cls) # N: Revealed type is "type[builtins.str] | type[builtins.int]"
3067+
3068+
if cls is int:
3069+
reveal_type(cls) # N: Revealed type is "type[builtins.int]"
3070+
if cls == int:
3071+
reveal_type(cls) # N: Revealed type is "type[builtins.int]"
3072+
[builtins fixtures/primitives.pyi]
3073+
3074+
30213075
[case testTypeEqualsCheck]
30223076
# flags: --strict-equality --warn-unreachable
30233077
from typing import Any
@@ -3261,3 +3315,23 @@ def bar(y: Any):
32613315
else:
32623316
reveal_type(y) # N: Revealed type is "Any"
32633317
[builtins fixtures/dict-full.pyi]
3318+
3319+
[case testNarrowTypeVarType]
3320+
from typing import TypeVar
3321+
3322+
T = TypeVar("T")
3323+
3324+
class A: ...
3325+
3326+
def foo(X: type[T]) -> T:
3327+
if X == A:
3328+
return X()
3329+
raise
3330+
3331+
# It could be nice to make these two test cases consistent, but it would be tricky
3332+
# The above case is much more common in real world code
3333+
def bar(X: type[T]) -> T:
3334+
if X == A:
3335+
return A() # E: Incompatible return value type (got "A", expected "T")
3336+
raise
3337+
[builtins fixtures/type.pyi]

test-data/unit/fixtures/isinstance.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class int:
2222
class float: pass
2323
class bool(int): pass
2424
class str:
25+
def __new__(cls, o: object = ...) -> str: pass
2526
def __add__(self, other: 'str') -> 'str': pass
2627
class ellipsis: pass
2728

test-data/unit/fixtures/type.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ S = TypeVar("S")
1010
class object:
1111
def __init__(self) -> None: pass
1212
def __str__(self) -> 'str': pass
13+
def __eq__(self, value: object, /) -> bool: ...
1314

1415
class list(Generic[T]): pass
1516

0 commit comments

Comments
 (0)