From 80bf852232534b9295eb44f5ccf14a4db2a14e56 Mon Sep 17 00:00:00 2001 From: cjc0013 Date: Tue, 30 Jun 2026 14:47:20 -0400 Subject: [PATCH] Fix TypeIs narrowing for covariant generic Any --- mypy/subtypes.py | 38 ++++++++++++++++++++++++++++++++ test-data/unit/check-typeis.test | 22 ++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 259bb3791deaf..59d5cf4550b60 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -2203,9 +2203,47 @@ def restrict_subtype_away(t: Type, s: Type, *, consider_runtime_isinstance: bool return UninhabitedType() if is_proper_subtype(t, s, ignore_promotions=True, erase_instances=True): return UninhabitedType() + if covers_via_declared_variance(t, s): + return UninhabitedType() return t +def covers_via_declared_variance(item: Type, supertype: Type) -> bool: + """Is item covered by supertype when generic arguments follow declared variance?""" + item = get_proper_type(item) + supertype = get_proper_type(supertype) + if not isinstance(item, Instance) or not isinstance(supertype, Instance): + return False + if not item.type.has_base(supertype.type.fullname): + return False + + item = map_instance_to_supertype(item, supertype.type) + if len(item.args) != len(supertype.args): + return False + + tried_infer = False + type_params = zip(item.args, supertype.args, supertype.type.defn.type_vars) + for item_arg, supertype_arg, tvar in type_params: + if isinstance(tvar, TypeVarType): + if tvar.variance == VARIANCE_NOT_READY and not tried_infer: + infer_class_variances(supertype.type) + tried_infer = True + variance = tvar.variance + else: + variance = COVARIANT + + if variance == COVARIANT or variance == VARIANCE_NOT_READY: + if not is_subtype(item_arg, supertype_arg, ignore_promotions=True): + return False + elif variance == CONTRAVARIANT: + if not is_subtype(supertype_arg, item_arg, ignore_promotions=True): + return False + elif not is_same_type(item_arg, supertype_arg): + return False + + return True + + def covers_at_runtime(item: Type, supertype: Type) -> bool: """Will isinstance(item, supertype) always return True at runtime?""" item = get_proper_type(item) diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test index 65ee837452f5b..aa3df7d988e81 100644 --- a/test-data/unit/check-typeis.test +++ b/test-data/unit/check-typeis.test @@ -541,6 +541,28 @@ def test(x: List[Any]) -> None: g(reveal_type(x)) # N: Revealed type is "builtins.list[builtins.str] | __main__." [builtins fixtures/tuple.pyi] +[case testTypeIsNegativeNarrowCovariantGenericAny] +from typing_extensions import TypeIs +from typing import Any, Generic, TypeVar + +T = TypeVar("T") +T_co = TypeVar("T_co", covariant=True) + +class Box(Generic[T]): ... +class Source(Generic[T_co]): ... + +def is_str_box(x: object) -> TypeIs[Box[str]]: ... +def is_source(x: object) -> TypeIs[Source[object]]: ... + +def keep_invariant(x: Box[Any]) -> None: + assert not is_str_box(x) + reveal_type(x) # N: Revealed type is "__main__.Box[Any]" + +def narrow_covariant(x: int | Source[Any]) -> None: + assert not is_source(x) + reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + [case testTypeIsMultipleCondition] from typing_extensions import TypeIs from typing import Any, List