Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion stdlib/unittest/case.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,17 @@ class TestCase:
def assertFalse(self, expr: Any, msg: Any = None) -> None: ...
def assertIs(self, expr1: object, expr2: object, msg: Any = None) -> None: ...
def assertIsNot(self, expr1: object, expr2: object, msg: Any = None) -> None: ...
def assertIsNone(self, obj: object, msg: Any = None) -> None: ...

@overload
def assertIsNone(self, obj: None, msg: Any = None) -> None: ...
@overload
def assertIsNone(self, obj: object, msg: Any = None) -> Never: ...
Comment on lines +106 to +109

@AlexWaygood AlexWaygood Nov 11, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't think this is correct. None is a subtype of object. If you pass an object of type object, Any, Any | None, or similar into the function, then a type checker will take the second overload and infer Never, but the passed-in object could still easily be None. Here's a demo using ty with your typeshed branch checked out at ../typeshed locally:

% cat foo.py
import unittest

def any_object() -> object:
    return None

reveal_type(unittest.TestCase().assertIsNone(any_object()))
% uvx ty check foo.py --custom-typeshed-dir ../typeshed --ignore=undefined-reveal
info[revealed-type]: Revealed type
 --> foo.py:6:13
  |
6 | reveal_type(unittest.TestCase().assertIsNone(any_object()))
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Never`

Found 1 diagnostic

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note that while it might lead to a type checker making inferences about reachability and control flow if you have an overload returning Never, it won't lead to the type checker successfully narrowing the type. Here, ty infers the type as Never after the call because it deems everything after the call unreachable, rather than narrowing the type to None. Meanwhile, mypy doesn't even see the reveal_type call, because it doesn't emit any diagnostics (including reveal_type diagnostics) in regions of code it deems to be unreachable:

% cat foo.py
import unittest

def any_object() -> object:
    return None

def narrow_please(x: object):
    unittest.TestCase().assertIsNone(x)
    reveal_type(x)
% uvx ty check foo.py --custom-typeshed-dir ../typeshed --ignore=undefined-reveal
info[revealed-type]: Revealed type
 --> foo.py:8:17
  |
8 |     reveal_type(x)
  |                 ^ `Never`

Found 1 diagnostic
% uvx mypy foo.py --custom-typeshed-dir ../typeshed
Success: no issues found in 1 source file


@overload
def assertIsNotNone(self, obj: None, msg: Any = None) -> Never: ...
@overload
def assertIsNotNone(self, obj: object, msg: Any = None) -> None: ...

def assertIn(self, member: Any, container: Iterable[Any] | Container[Any], msg: Any = None) -> None: ...
def assertNotIn(self, member: Any, container: Iterable[Any] | Container[Any], msg: Any = None) -> None: ...
def assertIsInstance(self, obj: object, cls: _ClassInfo, msg: Any = None) -> None: ...
Expand Down
Loading