Skip to content

Commit c475994

Browse files
committed
Honor return type of __new__
This basically follows the approach Jukka laid out in #1020 four years ago: * If the return type is Any, ignore that and keep the class type as the return type * Otherwise respect `__new__`'s return type * Produce an error if the return type is not a subtype of the class. The main motivation for me in implementing this is to support overloading `__new__` in order to select type variable arguments, which will be useful for subprocess.Popen. Fixes #1020.
1 parent b279f4c commit c475994

11 files changed

+184
-40
lines changed

mypy/checker.py

+25
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,10 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
802802
self.fail(message_registry.MUST_HAVE_NONE_RETURN_TYPE.format(fdef.name()),
803803
item)
804804

805+
# Check validity of __new__ signature
806+
if fdef.info and fdef.name() == '__new__':
807+
self.check___new___signature(fdef, typ)
808+
805809
self.check_for_missing_annotations(fdef)
806810
if self.options.disallow_any_unimported:
807811
if fdef.type and isinstance(fdef.type, CallableType):
@@ -1014,6 +1018,27 @@ def is_unannotated_any(t: Type) -> bool:
10141018
if any(is_unannotated_any(t) for t in fdef.type.arg_types):
10151019
self.fail(message_registry.ARGUMENT_TYPE_EXPECTED, fdef)
10161020

1021+
def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
1022+
self_type = fill_typevars_with_any(fdef.info)
1023+
bound_type = bind_self(typ, self_type, is_classmethod=True)
1024+
# Check that __new__ (after binding cls) returns an instance
1025+
# type (or any)
1026+
if not isinstance(bound_type.ret_type, (AnyType, Instance, TupleType)):
1027+
self.fail(
1028+
message_registry.NON_INSTANCE_NEW_TYPE.format(
1029+
self.msg.format(bound_type.ret_type)),
1030+
fdef)
1031+
else:
1032+
# And that it returns a subtype of the class
1033+
self.check_subtype(
1034+
bound_type.ret_type,
1035+
self_type,
1036+
fdef,
1037+
message_registry.INVALID_NEW_TYPE,
1038+
'returns',
1039+
'but must return a subtype of'
1040+
)
1041+
10171042
def is_trivial_body(self, block: Block) -> bool:
10181043
"""Returns 'true' if the given body is "trivial" -- if it contains just a "pass",
10191044
"..." (ellipsis), or "raise NotImplementedError()". A trivial body may also

mypy/checkmember.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,10 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) ->
787787
fallback = info.metaclass_type or builtin_type('builtins.type')
788788
if init_index < new_index:
789789
method = init_method.node # type: Union[FuncBase, Decorator]
790+
is_new = False
790791
elif init_index > new_index:
791792
method = new_method.node
793+
is_new = True
792794
else:
793795
if init_method.node.info.fullname() == 'builtins.object':
794796
# Both are defined by object. But if we've got a bogus
@@ -807,14 +809,15 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) ->
807809
# is the right thing, but __new__ caused problems with
808810
# typeshed (#5647).
809811
method = init_method.node
812+
is_new = False
810813
# Construct callable type based on signature of __init__. Adjust
811814
# return type and insert type arguments.
812815
if isinstance(method, FuncBase):
813816
t = function_type(method, fallback)
814817
else:
815818
assert isinstance(method.type, FunctionLike) # is_valid_constructor() ensures this
816819
t = method.type
817-
return type_object_type_from_function(t, info, method.info, fallback)
820+
return type_object_type_from_function(t, info, method.info, fallback, is_new)
818821

819822

820823
def is_valid_constructor(n: Optional[SymbolNode]) -> bool:
@@ -833,7 +836,8 @@ def is_valid_constructor(n: Optional[SymbolNode]) -> bool:
833836
def type_object_type_from_function(signature: FunctionLike,
834837
info: TypeInfo,
835838
def_info: TypeInfo,
836-
fallback: Instance) -> FunctionLike:
839+
fallback: Instance,
840+
is_new: bool) -> FunctionLike:
837841
# The __init__ method might come from a generic superclass
838842
# (init_or_new.info) with type variables that do not map
839843
# identically to the type variables of the class being constructed
@@ -843,7 +847,7 @@ def type_object_type_from_function(signature: FunctionLike,
843847
# class B(A[List[T]], Generic[T]): pass
844848
#
845849
# We need to first map B's __init__ to the type (List[T]) -> None.
846-
signature = bind_self(signature)
850+
signature = bind_self(signature, original_type=fill_typevars(info), is_classmethod=is_new)
847851
signature = cast(FunctionLike,
848852
map_type_from_supertype(signature, info, def_info))
849853
special_sig = None # type: Optional[str]
@@ -852,25 +856,32 @@ def type_object_type_from_function(signature: FunctionLike,
852856
special_sig = 'dict'
853857

854858
if isinstance(signature, CallableType):
855-
return class_callable(signature, info, fallback, special_sig)
859+
return class_callable(signature, info, fallback, special_sig, is_new)
856860
else:
857861
# Overloaded __init__/__new__.
858862
assert isinstance(signature, Overloaded)
859863
items = [] # type: List[CallableType]
860864
for item in signature.items():
861-
items.append(class_callable(item, info, fallback, special_sig))
865+
items.append(class_callable(item, info, fallback, special_sig, is_new))
862866
return Overloaded(items)
863867

864868

865869
def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance,
866-
special_sig: Optional[str]) -> CallableType:
870+
special_sig: Optional[str],
871+
is_new: bool = False) -> CallableType:
867872
"""Create a type object type based on the signature of __init__."""
868873
variables = [] # type: List[TypeVarDef]
869874
variables.extend(info.defn.type_vars)
870875
variables.extend(init_type.variables)
871876

877+
is_new = True
878+
if is_new and isinstance(init_type.ret_type, (Instance, TupleType)):
879+
ret_type = init_type.ret_type # type: Type
880+
else:
881+
ret_type = fill_typevars(info)
882+
872883
callable_type = init_type.copy_modified(
873-
ret_type=fill_typevars(info), fallback=type_type, name=None, variables=variables,
884+
ret_type=ret_type, fallback=type_type, name=None, variables=variables,
874885
special_sig=special_sig)
875886
c = callable_type.with_name(info.name())
876887
return c

mypy/interpreted_plugin.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class InterpretedPlugin:
2222
that proxies to this interpreted version.
2323
"""
2424

25-
def __new__(cls, *args: Any, **kwargs: Any) -> 'mypy.plugin.Plugin':
25+
# ... mypy doesn't like these shenanigans so we have to type ignore it!
26+
def __new__(cls, *args: Any, **kwargs: Any) -> 'mypy.plugin.Plugin': # type: ignore
2627
from mypy.plugin import WrapperPlugin
2728
plugin = object.__new__(cls)
2829
plugin.__init__(*args, **kwargs)

mypy/message_registry.py

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
INVALID_SLICE_INDEX = 'Slice index must be an integer or None' # type: Final
5252
CANNOT_INFER_LAMBDA_TYPE = 'Cannot infer type of lambda' # type: Final
5353
CANNOT_ACCESS_INIT = 'Cannot access "__init__" directly' # type: Final
54+
NON_INSTANCE_NEW_TYPE = '"__new__" must return a class instance (got {})' # type: Final
55+
INVALID_NEW_TYPE = 'Incompatible return type for "__new__"' # type: Final
5456
BAD_CONSTRUCTOR_TYPE = 'Unsupported decorated constructor type' # type: Final
5557
CANNOT_ASSIGN_TO_METHOD = 'Cannot assign to a method' # type: Final
5658
CANNOT_ASSIGN_TO_TYPE = 'Cannot assign to a type' # type: Final

test-data/unit/check-class-namedtuple.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ class XMethBad(NamedTuple):
598598
class MagicalFields(NamedTuple):
599599
x: int
600600
def __slots__(self) -> None: pass # E: Cannot overwrite NamedTuple attribute "__slots__"
601-
def __new__(cls) -> None: pass # E: Cannot overwrite NamedTuple attribute "__new__"
601+
def __new__(cls) -> MagicalFields: pass # E: Cannot overwrite NamedTuple attribute "__new__"
602602
def _source(self) -> int: pass # E: Cannot overwrite NamedTuple attribute "_source"
603603
__annotations__ = {'x': float} # E: NamedTuple field name cannot start with an underscore: __annotations__ \
604604
# E: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]" \

test-data/unit/check-classes.test

+108-6
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,12 @@ main:6: error: Return type "A" of "f" incompatible with return type "None" in su
343343

344344
[case testOverride__new__WithDifferentSignature]
345345
class A:
346-
def __new__(cls, x: int) -> str:
347-
return ''
346+
def __new__(cls, x: int) -> A:
347+
pass
348348

349349
class B(A):
350-
def __new__(cls) -> int:
351-
return 1
350+
def __new__(cls) -> B:
351+
pass
352352

353353
[case testOverride__new__AndCallObject]
354354
from typing import TypeVar, Generic
@@ -5359,8 +5359,8 @@ class A:
53595359
pass
53605360

53615361
class B(A):
5362-
def __new__(cls) -> int:
5363-
return 10
5362+
def __new__(cls) -> B:
5363+
pass
53645364

53655365
B()
53665366

@@ -5925,3 +5925,105 @@ class B:
59255925

59265926
class C(A, B): pass
59275927
[out]
5928+
5929+
[case testNewReturnType1]
5930+
class A:
5931+
def __new__(cls) -> B:
5932+
pass
5933+
5934+
class B(A): pass
5935+
5936+
reveal_type(A()) # N: Revealed type is '__main__.B'
5937+
reveal_type(B()) # N: Revealed type is '__main__.B'
5938+
5939+
[case testNewReturnType2]
5940+
from typing import Any
5941+
5942+
# make sure that __new__ method that return Any are ignored when
5943+
# determining the return type
5944+
class A:
5945+
def __new__(cls):
5946+
pass
5947+
5948+
class B:
5949+
def __new__(cls) -> Any:
5950+
pass
5951+
5952+
reveal_type(A()) # N: Revealed type is '__main__.A'
5953+
reveal_type(B()) # N: Revealed type is '__main__.B'
5954+
5955+
[case testNewReturnType3]
5956+
5957+
# Check for invalid __new__ typing
5958+
5959+
class A:
5960+
def __new__(cls) -> int: # E: Incompatible return type for "__new__" (returns "int", but must return a subtype of "A")
5961+
pass
5962+
5963+
reveal_type(A()) # N: Revealed type is 'builtins.int'
5964+
5965+
[case testNewReturnType4]
5966+
from typing import TypeVar, Type
5967+
5968+
# Check for __new__ using type vars
5969+
5970+
TX = TypeVar('TX', bound='X')
5971+
class X:
5972+
def __new__(lol: Type[TX], x: int) -> TX:
5973+
pass
5974+
class Y(X): pass
5975+
5976+
reveal_type(X(20)) # N: Revealed type is '__main__.X*'
5977+
reveal_type(Y(20)) # N: Revealed type is '__main__.Y*'
5978+
5979+
[case testNewReturnType5]
5980+
from typing import Any, TypeVar, Generic, overload
5981+
5982+
T = TypeVar('T')
5983+
class O(Generic[T]):
5984+
@overload
5985+
def __new__(cls) -> O[int]:
5986+
pass
5987+
@overload
5988+
def __new__(cls, x: int) -> O[str]:
5989+
pass
5990+
def __new__(cls, x: int = 0) -> O[Any]:
5991+
pass
5992+
5993+
reveal_type(O()) # N: Revealed type is '__main__.O[builtins.int]'
5994+
reveal_type(O(10)) # N: Revealed type is '__main__.O[builtins.str]'
5995+
5996+
[case testNewReturnType6]
5997+
from typing import Tuple, Optional
5998+
5999+
# Check for some cases that aren't allowed
6000+
6001+
class X:
6002+
def __new__(cls) -> Optional[Y]: # E: "__new__" must return a class instance (got "Optional[Y]")
6003+
pass
6004+
class Y:
6005+
def __new__(cls) -> Optional[int]: # E: "__new__" must return a class instance (got "Optional[int]")
6006+
pass
6007+
6008+
6009+
[case testNewReturnType7]
6010+
from typing import NamedTuple
6011+
6012+
# ... test __new__ returning tuple type
6013+
class A:
6014+
def __new__(cls) -> 'B':
6015+
pass
6016+
6017+
N = NamedTuple('N', [('x', int)])
6018+
class B(A, N): pass
6019+
6020+
reveal_type(A()) # N: Revealed type is 'Tuple[builtins.int, fallback=__main__.B]'
6021+
6022+
[case testNewReturnType8]
6023+
from typing import TypeVar, Any
6024+
6025+
# test type var from a different argument
6026+
TX = TypeVar('TX', bound='X')
6027+
class X:
6028+
def __new__(cls, x: TX) -> TX: # E: "__new__" must return a class instance (got "TX")
6029+
pass

0 commit comments

Comments
 (0)