From 27afa42093d9cef71eed56b0971ed63e9e735a74 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 19 Jul 2023 07:40:42 +0900 Subject: [PATCH] Narrow TypeVars in the negative case + narrow them through upper bounds While the second part of this (upper bound narrowing) could be another PR, I believe it's the better state for the initial PR and it's not much extra work. --- mypy/checker.py | 4 ++++ mypy/subtypes.py | 7 ++++++- test-data/unit/check-classes.test | 4 ++-- test-data/unit/check-isinstance.test | 22 ++++++++++++++++++++-- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f2873c7d58e4..9bdc07bbf35f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6899,6 +6899,10 @@ def conditional_types( # Expression is never of any type in proposed_type_ranges return UninhabitedType(), default else: + if isinstance(current_type, TypeVarType): + # narrowing T`-1 to int should *really* return T`-1 with a narrowed upper bound! + proposed_type = current_type.copy_modified(upper_bound=proposed_type) + # we can only restrict when the type is precise, not bounded proposed_precise_type = UnionType.make_union( [ diff --git a/mypy/subtypes.py b/mypy/subtypes.py index a6dc071f92b0..ef8ab92df7c1 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1756,7 +1756,7 @@ def restrict_subtype_away(t: Type, s: Type) -> Type: """Return t minus s for runtime type assertions. If we can't determine a precise result, return a supertype of the - ideal result (just t is a valid result). + ideal result (i.e. just t is a valid result). This is used for type inference of runtime type checks such as isinstance(). Currently, this just removes elements of a union type. @@ -1771,6 +1771,11 @@ def restrict_subtype_away(t: Type, s: Type) -> Type: if (isinstance(get_proper_type(item), AnyType) or not covers_at_runtime(item, s)) ] return UnionType.make_union(new_items) + elif isinstance(p_t, TypeVarType): + # TODO: should this check if `p_t.upper_bound` is covered at runtime? + # NOTE: I don't think I'm allowed to return proper types like this. But + # I don't know what else I could do. + return p_t.copy_modified(upper_bound=restrict_subtype_away(p_t.upper_bound, s)) elif covers_at_runtime(t, s): return UninhabitedType() else: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index b9e65ef4ad20..8d304756f577 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6682,11 +6682,11 @@ class C(Generic[T]): def meth(self, cls: Type[T]) -> None: if not issubclass(cls, Sub): return - reveal_type(cls) # N: Revealed type is "Type[__main__.Sub]" + reveal_type(cls) # N: Revealed type is "Type[T`1]" def other(self, cls: Type[T]) -> None: if not issubclass(cls, Sub): return - reveal_type(cls) # N: Revealed type is "Type[__main__.Sub]" + reveal_type(cls) # N: Revealed type is "Type[T`1]" [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 361d4db78752..39cff6dc8874 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1821,13 +1821,15 @@ if issubclass(fm, Baz): from typing import TypeVar class A: pass -class B(A): pass +class B(A): + z: int T = TypeVar('T', bound=A) def f(x: T) -> None: if isinstance(x, B): - reveal_type(x) # N: Revealed type is "__main__.B" + reveal_type(x.z) # N: Revealed type is "builtins.int" + reveal_type(x) # N: Revealed type is "T`-1" else: reveal_type(x) # N: Revealed type is "T`-1" reveal_type(x) # N: Revealed type is "T`-1" @@ -2892,3 +2894,19 @@ if hasattr(mod, "y"): [file mod.py] def __getattr__(attr: str) -> str: ... [builtins fixtures/module.pyi] + +[case testFunctionCanReturnNarrowedTypeVar] +from typing import TypeVar + +T = TypeVar("T") + +def f(x: T) -> T: + if isinstance(x, int): + reveal_type(x) # N: Revealed type is "T`-1" + x + 42 + return x + else: + reveal_type(x) # N: Revealed type is "T`-1" + x + 42 # E: Unsupported left operand type for + ("T") + return x +[builtins fixtures/isinstancelist.pyi]