From e04dd2e2efb77ba38d0eb61cce322350f8295271 Mon Sep 17 00:00:00 2001 From: ethframe Date: Thu, 17 Jun 2021 00:17:20 +0300 Subject: [PATCH 1/2] Narrow type with typevar when the lower bound of its upper bound and that type is same --- mypy/meet.py | 5 +++++ test-data/unit/check-narrowing.test | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/mypy/meet.py b/mypy/meet.py index 943219d97e24e..8a40d2b83eef3 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -7,6 +7,7 @@ UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType, ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardType ) +from mypy.sametypes import is_same_type from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype from mypy.erasetype import erase_type from mypy.maptype import map_instance_to_supertype @@ -77,6 +78,10 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: and narrowed.type.is_metaclass()): # We'd need intersection types, so give up. return declared + elif isinstance(narrowed, TypeVarType): + if is_same_type(narrowed.upper_bound, meet_types(narrowed.upper_bound, declared)): + return narrowed + return meet_types(declared, narrowed) elif isinstance(declared, (Instance, TupleType, TypeType, LiteralType)): return meet_types(declared, narrowed) elif isinstance(declared, TypedDictType) and isinstance(narrowed, Instance): diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 4fdd8f3b10334..9521929956422 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1073,3 +1073,23 @@ def f(t: Type[C]) -> None: else: reveal_type(t) # N: Revealed type is "Type[__main__.C]" reveal_type(t) # N: Revealed type is "Type[__main__.C]" + +[case testNarrowingUsingTypeVar] +# flags: --strict-optional +from typing import Type, TypeVar + +class A: pass +class B(A): pass + +T = TypeVar("T", bound=A) + +def f(t: Type[T], a: A, b: B) -> None: + if type(a) is t: + reveal_type(a) # N: Revealed type is "T`-1" + else: + reveal_type(a) # N: Revealed type is "__main__.A" + + if type(b) is t: + reveal_type(b) # N: Revealed type is "" + else: + reveal_type(b) # N: Revealed type is "__main__.B" From fafe542fd92a43b117db586b9095f1d7bd7e29ff Mon Sep 17 00:00:00 2001 From: ethframe Date: Sat, 19 Jun 2021 22:47:05 +0300 Subject: [PATCH 2/2] Use is_subtype instead of combination of meet_types and is_same_type --- mypy/meet.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 8a40d2b83eef3..471adc5aa5cac 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -7,7 +7,6 @@ UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType, ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardType ) -from mypy.sametypes import is_same_type from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype from mypy.erasetype import erase_type from mypy.maptype import map_instance_to_supertype @@ -71,6 +70,8 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: for x in narrowed.relevant_items()]) elif isinstance(narrowed, AnyType): return narrowed + elif isinstance(narrowed, TypeVarType) and is_subtype(narrowed.upper_bound, declared): + return narrowed elif isinstance(declared, TypeType) and isinstance(narrowed, TypeType): return TypeType.make_normalized(narrow_declared_type(declared.item, narrowed.item)) elif (isinstance(declared, TypeType) @@ -78,10 +79,6 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: and narrowed.type.is_metaclass()): # We'd need intersection types, so give up. return declared - elif isinstance(narrowed, TypeVarType): - if is_same_type(narrowed.upper_bound, meet_types(narrowed.upper_bound, declared)): - return narrowed - return meet_types(declared, narrowed) elif isinstance(declared, (Instance, TupleType, TypeType, LiteralType)): return meet_types(declared, narrowed) elif isinstance(declared, TypedDictType) and isinstance(narrowed, Instance):