Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Allow returning Literals in __new__ #15687

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1475,7 +1475,8 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
"but must return a subtype of",
)
elif not isinstance(
get_proper_type(bound_type.ret_type), (AnyType, Instance, TupleType, UninhabitedType)
get_proper_type(bound_type.ret_type),
(AnyType, Instance, TupleType, UninhabitedType, LiteralType),
):
self.fail(
message_registry.NON_INSTANCE_NEW_TYPE.format(
Expand Down
2 changes: 2 additions & 0 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: Member
ret_type = tuple_fallback(ret_type)
if isinstance(ret_type, TypedDictType):
ret_type = ret_type.fallback
if isinstance(ret_type, LiteralType):
ret_type = ret_type.fallback
if isinstance(ret_type, Instance):
if not mx.is_operator:
# When Python sees an operator (eg `3 == 4`), it automatically translates that
Expand Down
2 changes: 1 addition & 1 deletion mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def class_callable(
default_ret_type = fill_typevars(info)
explicit_type = init_ret_type if is_new else orig_self_type
if (
isinstance(explicit_type, (Instance, TupleType, UninhabitedType))
isinstance(explicit_type, (Instance, TupleType, UninhabitedType, LiteralType))
# We have to skip protocols, because it can be a subtype of a return type
# by accident. Like `Hashable` is a subtype of `object`. See #11799
and isinstance(default_ret_type, Instance)
Expand Down
2 changes: 2 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,8 @@ def type_object(self) -> mypy.nodes.TypeInfo:
ret = ret.partial_fallback
if isinstance(ret, TypedDictType):
ret = ret.fallback
if isinstance(ret, LiteralType):
ret = ret.fallback
assert isinstance(ret, Instance)
return ret.type

Expand Down
21 changes: 21 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,27 @@ class B(A):
def __new__(cls) -> B:
pass

[case testOverride__new__WithLiteralReturnPassing]
from typing import Literal

class Falsy:
def __bool__(self) -> Literal[False]: pass

reveal_type(bool(Falsy())) # N: Revealed type is "Literal[False]"
reveal_type(int()) # N: Revealed type is "Literal[0]"

[builtins fixtures/literal__new__.pyi]
[typing fixtures/typing-medium.pyi]

[case testOverride__new__WithLiteralReturnFailing]
from typing import Literal

class Foo:
def __new__(cls) -> Literal[1]: pass # E: Incompatible return type for "__new__" (returns "Literal[1]", but must return a subtype of "Foo")

[builtins fixtures/__new__.pyi]
[typing fixtures/typing-medium.pyi]

[case testOverride__new__AndCallObject]
from typing import TypeVar, Generic

Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/__new__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class object:
class type:
def __init__(self, x) -> None: pass

class float: pass
class int: pass
class bool: pass
class str: pass
Expand Down
19 changes: 19 additions & 0 deletions test-data/unit/fixtures/literal__new__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Literal, Protocol, overload

class str: pass
class dict: pass
class float: pass
class int:
def __new__(cls) -> Literal[0]: pass

class _Truthy(Protocol):
def __bool__(self) -> Literal[True]: pass

class _Falsy(Protocol):
def __bool__(self) -> Literal[False]: pass

class bool(int):
@overload
def __new__(cls, __o: _Truthy) -> Literal[True]: pass
@overload
def __new__(cls, __o: _Falsy) -> Literal[False]: pass