Skip to content

Commit 054f721

Browse files
authored
Discard partials remaining after inference failure (#20126)
Fixes #16573. Fixes #3031. When we infer something with Never as a replacement for a partial type, we cannot ignore that result entirely: if we do that, any use of that variable down the road will still refer to `partial<None>`, causing reachability issues and unexpected partials leaks.
1 parent 841db1f commit 054f721

File tree

2 files changed

+84
-9
lines changed

2 files changed

+84
-9
lines changed

mypy/checker.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3299,9 +3299,19 @@ def check_assignment(
32993299
del partial_types[var]
33003300
lvalue_type = var.type
33013301
else:
3302-
# Try to infer a partial type. No need to check the return value, as
3303-
# an error will be reported elsewhere.
3304-
self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type)
3302+
# Try to infer a partial type.
3303+
if not self.infer_partial_type(var, lvalue, rvalue_type):
3304+
# If that also failed, give up and let the caller know that we
3305+
# cannot read their mind. The definition site will be reported later.
3306+
# Calling .put() directly because the newly inferred type is
3307+
# not a subtype of None - we are not looking for narrowing
3308+
fallback = self.inference_error_fallback_type(rvalue_type)
3309+
self.binder.put(lvalue, fallback)
3310+
# Same as self.set_inference_error_fallback_type but inlined
3311+
# to avoid computing fallback twice.
3312+
# We are replacing partial<None> now, so the variable type
3313+
# should remain optional.
3314+
self.set_inferred_type(var, lvalue, make_optional_type(fallback))
33053315
elif (
33063316
is_literal_none(rvalue)
33073317
and isinstance(lvalue, NameExpr)

test-data/unit/check-inference.test

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2522,7 +2522,6 @@ class C:
25222522
def __init__(self) -> None:
25232523
self.x = [] # E: Need type annotation for "x" (hint: "x: list[<type>] = ...")
25242524
[builtins fixtures/list.pyi]
2525-
[out]
25262525

25272526
[case testNoCrashOnPartialVariable]
25282527
from typing import Tuple, TypeVar
@@ -2534,7 +2533,6 @@ x = None
25342533
(x,) = f('')
25352534
reveal_type(x) # N: Revealed type is "builtins.str"
25362535
[builtins fixtures/tuple.pyi]
2537-
[out]
25382536

25392537
[case testNoCrashOnPartialVariable2]
25402538
# flags: --no-local-partial-types
@@ -2543,11 +2541,10 @@ T = TypeVar('T', bound=str)
25432541

25442542
def f() -> Tuple[T]:
25452543
...
2546-
x = None
2544+
x = None # E: Need type annotation for "x"
25472545
if int():
25482546
(x,) = f()
25492547
[builtins fixtures/tuple.pyi]
2550-
[out]
25512548

25522549
[case testNoCrashOnPartialVariable3]
25532550
from typing import Tuple, TypeVar
@@ -2559,7 +2556,76 @@ x = None
25592556
(x, x) = f('')
25602557
reveal_type(x) # N: Revealed type is "builtins.str"
25612558
[builtins fixtures/tuple.pyi]
2562-
[out]
2559+
2560+
[case testRejectsPartialWithUninhabited]
2561+
from typing import Generic, TypeVar
2562+
T = TypeVar('T')
2563+
2564+
class Foo(Generic[T]): ...
2565+
2566+
def check() -> None:
2567+
x = None # E: Need type annotation for "x"
2568+
if int():
2569+
x = Foo()
2570+
reveal_type(x) # N: Revealed type is "__main__.Foo[Any]"
2571+
reveal_type(x) # N: Revealed type is "Union[__main__.Foo[Any], None]"
2572+
2573+
[case testRejectsPartialWithUninhabited2]
2574+
from typing import Generic, TypeVar
2575+
T = TypeVar('T')
2576+
2577+
class Foo(Generic[T]): ...
2578+
2579+
x = None # E: Need type annotation for "x"
2580+
2581+
def check() -> None:
2582+
global x
2583+
x = Foo()
2584+
reveal_type(x) # N: Revealed type is "__main__.Foo[Any]"
2585+
2586+
reveal_type(x) # N: Revealed type is "Union[__main__.Foo[Any], None]"
2587+
2588+
[case testRejectsPartialWithUninhabited3]
2589+
# Without force-rejecting Partial<None>, this crashes:
2590+
# https://github.com/python/mypy/issues/16573
2591+
from typing import Generic, TypeVar
2592+
T = TypeVar('T')
2593+
2594+
class Foo(Generic[T]): ...
2595+
2596+
def check() -> None:
2597+
client = None # E: Need type annotation for "client"
2598+
2599+
if client := Foo():
2600+
reveal_type(client) # N: Revealed type is "__main__.Foo[Any]"
2601+
2602+
reveal_type(client) # N: Revealed type is "Union[__main__.Foo[Any], None]"
2603+
2604+
client = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Optional[Foo[Any]]")
2605+
reveal_type(client) # N: Revealed type is "Union[__main__.Foo[Any], None]"
2606+
2607+
[case testRejectsPartialWithUninhabitedIndependently]
2608+
from typing import Generic, TypeVar
2609+
T = TypeVar('T')
2610+
2611+
class Foo(Generic[T]): ...
2612+
2613+
client = None # E: Need type annotation for "client"
2614+
2615+
def bad() -> None:
2616+
global client
2617+
client = Foo()
2618+
reveal_type(client) # N: Revealed type is "__main__.Foo[Any]"
2619+
2620+
def good() -> None:
2621+
global client
2622+
client = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Optional[Foo[Any]]")
2623+
reveal_type(client) # N: Revealed type is "Union[__main__.Foo[Any], None]"
2624+
2625+
def bad2() -> None:
2626+
global client
2627+
client = Foo()
2628+
reveal_type(client) # N: Revealed type is "__main__.Foo[Any]"
25632629

25642630
[case testInferenceNestedTuplesFromGenericIterable]
25652631
from typing import Tuple, TypeVar
@@ -2574,7 +2640,6 @@ def main() -> None:
25742640
reveal_type(a) # N: Revealed type is "builtins.int"
25752641
reveal_type(b) # N: Revealed type is "builtins.int"
25762642
[builtins fixtures/tuple.pyi]
2577-
[out]
25782643

25792644
[case testDontMarkUnreachableAfterInferenceUninhabited]
25802645
from typing import TypeVar

0 commit comments

Comments
 (0)