Skip to content

Commit a35e84b

Browse files
Allow returning Literals in __new__ (#15687)
Unblocks python/typeshed#10465 --------- Co-authored-by: Ivan Levkivskyi <[email protected]>
1 parent 1b8841b commit a35e84b

File tree

7 files changed

+54
-2
lines changed

7 files changed

+54
-2
lines changed

mypy/checker.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1801,7 +1801,8 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
18011801
"but must return a subtype of",
18021802
)
18031803
elif not isinstance(
1804-
get_proper_type(bound_type.ret_type), (AnyType, Instance, TupleType, UninhabitedType)
1804+
get_proper_type(bound_type.ret_type),
1805+
(AnyType, Instance, TupleType, UninhabitedType, LiteralType),
18051806
):
18061807
self.fail(
18071808
message_registry.NON_INSTANCE_NEW_TYPE.format(

mypy/checkmember.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: Member
410410
ret_type = tuple_fallback(ret_type)
411411
if isinstance(ret_type, TypedDictType):
412412
ret_type = ret_type.fallback
413+
if isinstance(ret_type, LiteralType):
414+
ret_type = ret_type.fallback
413415
if isinstance(ret_type, Instance):
414416
if not mx.is_operator:
415417
# When Python sees an operator (eg `3 == 4`), it automatically translates that

mypy/typeops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def class_callable(
319319
default_ret_type = fill_typevars(info)
320320
explicit_type = init_ret_type if is_new else orig_self_type
321321
if (
322-
isinstance(explicit_type, (Instance, TupleType, UninhabitedType))
322+
isinstance(explicit_type, (Instance, TupleType, UninhabitedType, LiteralType))
323323
# We have to skip protocols, because it can be a subtype of a return type
324324
# by accident. Like `Hashable` is a subtype of `object`. See #11799
325325
and isinstance(default_ret_type, Instance)

mypy/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2278,6 +2278,8 @@ def type_object(self) -> mypy.nodes.TypeInfo:
22782278
ret = ret.partial_fallback
22792279
if isinstance(ret, TypedDictType):
22802280
ret = ret.fallback
2281+
if isinstance(ret, LiteralType):
2282+
ret = ret.fallback
22812283
assert isinstance(ret, Instance)
22822284
return ret.type
22832285

test-data/unit/check-classes.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,27 @@ class B(A):
472472
def __new__(cls) -> B:
473473
pass
474474

475+
[case testOverride__new__WithLiteralReturnPassing]
476+
from typing import Literal
477+
478+
class Falsy:
479+
def __bool__(self) -> Literal[False]: pass
480+
481+
reveal_type(bool(Falsy())) # N: Revealed type is "Literal[False]"
482+
reveal_type(int()) # N: Revealed type is "Literal[0]"
483+
484+
[builtins fixtures/literal__new__.pyi]
485+
[typing fixtures/typing-medium.pyi]
486+
487+
[case testOverride__new__WithLiteralReturnFailing]
488+
from typing import Literal
489+
490+
class Foo:
491+
def __new__(cls) -> Literal[1]: pass # E: Incompatible return type for "__new__" (returns "Literal[1]", but must return a subtype of "Foo")
492+
493+
[builtins fixtures/__new__.pyi]
494+
[typing fixtures/typing-medium.pyi]
495+
475496
[case testOverride__new__AndCallObject]
476497
from typing import TypeVar, Generic
477498

test-data/unit/fixtures/__new__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class object:
1212
class type:
1313
def __init__(self, x) -> None: pass
1414

15+
class float: pass
1516
class int: pass
1617
class bool: pass
1718
class str: pass
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import Literal, Protocol, overload
2+
3+
class object:
4+
def __init__(self) -> None: pass
5+
6+
class type:
7+
def __init__(self, x) -> None: pass
8+
9+
class str: pass
10+
class dict: pass
11+
class float: pass
12+
class int:
13+
def __new__(cls) -> Literal[0]: pass
14+
15+
class _Truthy(Protocol):
16+
def __bool__(self) -> Literal[True]: pass
17+
18+
class _Falsy(Protocol):
19+
def __bool__(self) -> Literal[False]: pass
20+
21+
class bool(int):
22+
@overload
23+
def __new__(cls, __o: _Truthy) -> Literal[True]: pass
24+
@overload
25+
def __new__(cls, __o: _Falsy) -> Literal[False]: pass

0 commit comments

Comments
 (0)