Skip to content

Commit 2514c48

Browse files
committed
Merge branch 'call-type-with-__new__'
Addresses much of #982.
2 parents 1437bb0 + 2eb5181 commit 2514c48

File tree

4 files changed

+122
-13
lines changed

4 files changed

+122
-13
lines changed

mypy/checkmember.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -269,28 +269,41 @@ def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool,
269269
def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> Type:
270270
"""Return the type of a type object.
271271
272-
For a generic type G with type variables T and S the type is of form
272+
For a generic type G with type variables T and S the type is generally of form
273273
274-
def [T, S](...) -> G[T, S],
274+
Callable[..., G[T, S]]
275275
276-
where ... are argument types for the __init__ method (without the self argument).
276+
where ... are argument types for the __init__/__new__ method (without the self
277+
argument). Also, the fallback type will be 'type' instead of 'function'.
277278
"""
278279
init_method = info.get_method('__init__')
279280
if not init_method:
280281
# Must be an invalid class definition.
281282
return AnyType()
282283
else:
284+
fallback = builtin_type('builtins.type')
285+
if init_method.info.fullname() == 'builtins.object':
286+
# No non-default __init__ -> look at __new__ instead.
287+
new_method = info.get_method('__new__')
288+
if new_method and new_method.info.fullname() != 'builtins.object':
289+
# Found one! Get signature from __new__.
290+
return type_object_type_from_function(new_method, info, fallback)
283291
# Construct callable type based on signature of __init__. Adjust
284292
# return type and insert type arguments.
285-
init_type = method_type_with_fallback(init_method, builtin_type('builtins.function'))
286-
if isinstance(init_type, CallableType):
287-
return class_callable(init_type, info, builtin_type('builtins.type'))
288-
else:
289-
# Overloaded __init__.
290-
items = [] # type: List[CallableType]
291-
for it in cast(Overloaded, init_type).items():
292-
items.append(class_callable(it, info, builtin_type('builtins.type')))
293-
return Overloaded(items)
293+
return type_object_type_from_function(init_method, info, fallback)
294+
295+
296+
def type_object_type_from_function(init_or_new: FuncBase, info: TypeInfo,
297+
fallback: Instance) -> FunctionLike:
298+
signature = method_type_with_fallback(init_or_new, fallback)
299+
if isinstance(signature, CallableType):
300+
return class_callable(signature, info, fallback)
301+
else:
302+
# Overloaded __init__/__new__.
303+
items = [] # type: List[CallableType]
304+
for item in cast(Overloaded, signature).items():
305+
items.append(class_callable(item, info, fallback))
306+
return Overloaded(items)
294307

295308

296309
def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance) -> CallableType:

mypy/semanal.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1398,10 +1398,12 @@ def make_namedtuple_init(self, info: TypeInfo, items: List[str],
13981398
NoneTyp(),
13991399
self.named_type('__builtins__.function'),
14001400
name=info.name())
1401-
return FuncDef('__init__',
1401+
func = FuncDef('__init__',
14021402
args,
14031403
Block([]),
14041404
typ=signature)
1405+
func.info = info
1406+
return func
14051407

14061408
def analyze_types(self, items: List[Node]) -> List[Type]:
14071409
result = [] # type: List[Type]

mypy/test/data/check-classes.test

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,86 @@ class A:
11931193
class B: pass
11941194

11951195

1196+
-- __new__
1197+
-- --------
1198+
1199+
1200+
[case testConstructInstanceWith__new__]
1201+
class C:
1202+
def __new__(cls, foo: int = None) -> 'C':
1203+
obj = object.__new__(cls)
1204+
return obj
1205+
1206+
x = C(foo=12)
1207+
x.a # E: "C" has no attribute "a"
1208+
C(foo='') # E: Argument 1 to "C" has incompatible type "str"; expected "int"
1209+
[builtins fixtures/__new__.py]
1210+
1211+
[case testConstructInstanceWithDynamicallyTyped__new__]
1212+
class C:
1213+
def __new__(cls, foo):
1214+
obj = object.__new__(cls)
1215+
return obj
1216+
1217+
x = C(foo=12)
1218+
x = C(foo='x')
1219+
x.a # E: "C" has no attribute "a"
1220+
C(bar='') # E: Unexpected keyword argument "bar" for "C"
1221+
[builtins fixtures/__new__.py]
1222+
1223+
[case testClassWith__new__AndCompatibilityWithType]
1224+
class C:
1225+
def __new__(cls, foo: int = None) -> 'C':
1226+
obj = object.__new__(cls)
1227+
return obj
1228+
def f(x: type) -> None: pass
1229+
def g(x: int) -> None: pass
1230+
f(C)
1231+
g(C) # E: Argument 1 to "g" has incompatible type "C"; expected "int"
1232+
[builtins fixtures/__new__.py]
1233+
1234+
[case testClassWith__new__AndCompatibilityWithType2]
1235+
class C:
1236+
def __new__(cls, foo):
1237+
obj = object.__new__(cls)
1238+
return obj
1239+
def f(x: type) -> None: pass
1240+
def g(x: int) -> None: pass
1241+
f(C)
1242+
g(C) # E: Argument 1 to "g" has incompatible type "C"; expected "int"
1243+
[builtins fixtures/__new__.py]
1244+
1245+
[case testGenericClassWith__new__]
1246+
from typing import TypeVar, Generic
1247+
T = TypeVar('T')
1248+
class C(Generic[T]):
1249+
def __new__(cls, foo: T) -> 'C[T]':
1250+
obj = object.__new__(cls)
1251+
return obj
1252+
def set(self, x: T) -> None: pass
1253+
c = C('')
1254+
c.set('')
1255+
c.set(1) # E: Argument 1 to "set" of "C" has incompatible type "int"; expected "str"
1256+
[builtins fixtures/__new__.py]
1257+
1258+
[case testOverloaded__new__]
1259+
from typing import overload
1260+
class C:
1261+
@overload
1262+
def __new__(cls, foo: int) -> 'C':
1263+
obj = object.__new__(cls)
1264+
return obj
1265+
@overload
1266+
def __new__(cls, x: str, y: str) -> 'C':
1267+
obj = object.__new__(cls)
1268+
return obj
1269+
c = C(1)
1270+
c.a # E: "C" has no attribute "a"
1271+
C('', '')
1272+
C('') # E: No overload variant of "C" matches argument types [builtins.str]
1273+
[builtins fixtures/__new__.py]
1274+
1275+
11961276
-- Special cases
11971277
-- -------------
11981278

mypy/test/data/fixtures/__new__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# builtins stub with object.__new__
2+
3+
class object:
4+
def __init__(self) -> None: pass
5+
6+
def __new__(cls): pass
7+
8+
class type:
9+
def __init__(self, x) -> None: pass
10+
11+
class int: pass
12+
class bool: pass
13+
class str: pass
14+
class function: pass

0 commit comments

Comments
 (0)