From 665e0917031ac4139681a1a3d80f76164ec9fedd Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 26 Oct 2025 19:55:00 +0100 Subject: [PATCH 1/3] Discard partials remaining after inference failure --- mypy/checker.py | 9 ++-- test-data/unit/check-inference.test | 78 ++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2dd7f10e6f354..9ba3dc1d8244f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3294,9 +3294,12 @@ def check_assignment( del partial_types[var] lvalue_type = var.type else: - # Try to infer a partial type. No need to check the return value, as - # an error will be reported elsewhere. - self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type) + # Try to infer a partial type. + if not self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type): + # If that also failed, give up and let the caller know that we + # cannot read their mind. The definition site will be reported later. + rvalue_type = fixup_partial_type(lvalue_type) + self.set_inferred_type(var, lvalue, rvalue_type) elif ( is_literal_none(rvalue) and isinstance(lvalue, NameExpr) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 24ea61f2c7156..25f86e483c5ad 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2522,7 +2522,6 @@ class C: def __init__(self) -> None: self.x = [] # E: Need type annotation for "x" (hint: "x: list[] = ...") [builtins fixtures/list.pyi] -[out] [case testNoCrashOnPartialVariable] from typing import Tuple, TypeVar @@ -2534,7 +2533,6 @@ x = None (x,) = f('') reveal_type(x) # N: Revealed type is "builtins.str" [builtins fixtures/tuple.pyi] -[out] [case testNoCrashOnPartialVariable2] # flags: --no-local-partial-types @@ -2543,11 +2541,10 @@ T = TypeVar('T', bound=str) def f() -> Tuple[T]: ... -x = None +x = None # E: Need type annotation for "x" if int(): (x,) = f() [builtins fixtures/tuple.pyi] -[out] [case testNoCrashOnPartialVariable3] from typing import Tuple, TypeVar @@ -2559,7 +2556,77 @@ x = None (x, x) = f('') reveal_type(x) # N: Revealed type is "builtins.str" [builtins fixtures/tuple.pyi] -[out] + +[case testRejectsPartialWithUninhabited] +from typing import Generic, TypeVar +T = TypeVar('T') + +class Foo(Generic[T]): ... + +def check() -> None: + x = None # E: Need type annotation for "x" + if int(): + x = Foo() + reveal_type(x) # N: Revealed type is "Union[Any, None]" + +[case testRejectsPartialWithUninhabited2] +from typing import Generic, TypeVar +T = TypeVar('T') + +class Foo(Generic[T]): ... + +x = None # E: Need type annotation for "x" + +def check() -> None: + global x + x = Foo() + reveal_type(x) # N: Revealed type is "Union[Any, None]" + +[case testRejectsPartialWithUninhabited3] +# Without force-rejecting Partial, this crashes: +# https://github.com/python/mypy/issues/16573 +from typing import Generic, TypeVar +T = TypeVar('T') + +class Foo(Generic[T]): ... + +def check() -> None: + client = None # E: Need type annotation for "client" + + if client := Foo(): + reveal_type(client) # N: Revealed type is "Any" + pass + + reveal_type(client) # N: Revealed type is "Union[Any, None]" + + client = 0 + reveal_type(client) # N: Revealed type is "builtins.int" + +[case testRejectsPartialWithUninhabitedIndependently] +from typing import Generic, TypeVar +T = TypeVar('T') + +class Foo(Generic[T]): ... + +client = None # E: Need type annotation for "client" + +def bad() -> None: + global client + client = Foo() + reveal_type(client) # N: Revealed type is "Union[Any, None]" + +def good() -> None: + global client + client = 1 + reveal_type(client) # N: Revealed type is "builtins.int" + +def bad2() -> None: + global client + client = Foo() + # This doesn't look optimal, we should keep saying `Any | None` + # But once we store `Any | None` globally, it gives enough context + # to infer Foo[Any] instead of Foo[Never] + reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" [case testInferenceNestedTuplesFromGenericIterable] from typing import Tuple, TypeVar @@ -2574,7 +2641,6 @@ def main() -> None: reveal_type(a) # N: Revealed type is "builtins.int" reveal_type(b) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] -[out] [case testDontMarkUnreachableAfterInferenceUninhabited] from typing import TypeVar From e9a35ad244fe8f6396d8c4908ac47038f7a2d097 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Mon, 27 Oct 2025 18:20:10 +0100 Subject: [PATCH 2/3] Use the same fallback strategy as when `[] -> [something]` inference fails --- mypy/checker.py | 5 ++--- test-data/unit/check-inference.test | 18 +++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9ba3dc1d8244f..131f0c8b794e1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3295,11 +3295,10 @@ def check_assignment( lvalue_type = var.type else: # Try to infer a partial type. - if not self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type): + if not self.infer_partial_type(var, lvalue, rvalue_type): # If that also failed, give up and let the caller know that we # cannot read their mind. The definition site will be reported later. - rvalue_type = fixup_partial_type(lvalue_type) - self.set_inferred_type(var, lvalue, rvalue_type) + self.set_inference_error_fallback_type(var, lvalue, rvalue_type) elif ( is_literal_none(rvalue) and isinstance(lvalue, NameExpr) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 25f86e483c5ad..c2d2fd8d0f434 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2567,7 +2567,7 @@ def check() -> None: x = None # E: Need type annotation for "x" if int(): x = Foo() - reveal_type(x) # N: Revealed type is "Union[Any, None]" + reveal_type(x) # N: Revealed type is "__main__.Foo[Any]" [case testRejectsPartialWithUninhabited2] from typing import Generic, TypeVar @@ -2580,7 +2580,7 @@ x = None # E: Need type annotation for "x" def check() -> None: global x x = Foo() - reveal_type(x) # N: Revealed type is "Union[Any, None]" + reveal_type(x) # N: Revealed type is "__main__.Foo[Any]" [case testRejectsPartialWithUninhabited3] # Without force-rejecting Partial, this crashes: @@ -2594,13 +2594,13 @@ def check() -> None: client = None # E: Need type annotation for "client" if client := Foo(): - reveal_type(client) # N: Revealed type is "Any" + reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" pass - reveal_type(client) # N: Revealed type is "Union[Any, None]" + reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" - client = 0 - reveal_type(client) # N: Revealed type is "builtins.int" + client = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Foo[Any]") + reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" [case testRejectsPartialWithUninhabitedIndependently] from typing import Generic, TypeVar @@ -2613,12 +2613,12 @@ client = None # E: Need type annotation for "client" def bad() -> None: global client client = Foo() - reveal_type(client) # N: Revealed type is "Union[Any, None]" + reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" def good() -> None: global client - client = 1 - reveal_type(client) # N: Revealed type is "builtins.int" + client = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Foo[Any]") + reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" def bad2() -> None: global client From 3cc8725bde4df508f7c4d331d49a1a3630d7ff35 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Mon, 27 Oct 2025 18:56:31 +0100 Subject: [PATCH 3/3] Push this assignment through binder to retain `T | None` as var type --- mypy/checker.py | 10 +++++++++- test-data/unit/check-inference.test | 19 +++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 131f0c8b794e1..7e0d9acb6e3e7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3298,7 +3298,15 @@ def check_assignment( if not self.infer_partial_type(var, lvalue, rvalue_type): # If that also failed, give up and let the caller know that we # cannot read their mind. The definition site will be reported later. - self.set_inference_error_fallback_type(var, lvalue, rvalue_type) + # Calling .put() directly because the newly inferred type is + # not a subtype of None - we are not looking for narrowing + fallback = self.inference_error_fallback_type(rvalue_type) + self.binder.put(lvalue, fallback) + # Same as self.set_inference_error_fallback_type but inlined + # to avoid computing fallback twice. + # We are replacing partial now, so the variable type + # should remain optional. + self.set_inferred_type(var, lvalue, make_optional_type(fallback)) elif ( is_literal_none(rvalue) and isinstance(lvalue, NameExpr) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index c2d2fd8d0f434..9ed9c5e9ec787 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2567,7 +2567,8 @@ def check() -> None: x = None # E: Need type annotation for "x" if int(): x = Foo() - reveal_type(x) # N: Revealed type is "__main__.Foo[Any]" + reveal_type(x) # N: Revealed type is "__main__.Foo[Any]" + reveal_type(x) # N: Revealed type is "Union[__main__.Foo[Any], None]" [case testRejectsPartialWithUninhabited2] from typing import Generic, TypeVar @@ -2582,6 +2583,8 @@ def check() -> None: x = Foo() reveal_type(x) # N: Revealed type is "__main__.Foo[Any]" +reveal_type(x) # N: Revealed type is "Union[__main__.Foo[Any], None]" + [case testRejectsPartialWithUninhabited3] # Without force-rejecting Partial, this crashes: # https://github.com/python/mypy/issues/16573 @@ -2595,12 +2598,11 @@ def check() -> None: if client := Foo(): reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" - pass - reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" + reveal_type(client) # N: Revealed type is "Union[__main__.Foo[Any], None]" - client = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Foo[Any]") - reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" + client = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Optional[Foo[Any]]") + reveal_type(client) # N: Revealed type is "Union[__main__.Foo[Any], None]" [case testRejectsPartialWithUninhabitedIndependently] from typing import Generic, TypeVar @@ -2617,15 +2619,12 @@ def bad() -> None: def good() -> None: global client - client = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Foo[Any]") - reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" + client = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Optional[Foo[Any]]") + reveal_type(client) # N: Revealed type is "Union[__main__.Foo[Any], None]" def bad2() -> None: global client client = Foo() - # This doesn't look optimal, we should keep saying `Any | None` - # But once we store `Any | None` globally, it gives enough context - # to infer Foo[Any] instead of Foo[Never] reveal_type(client) # N: Revealed type is "__main__.Foo[Any]" [case testInferenceNestedTuplesFromGenericIterable]