Skip to content

Commit

Permalink
Merge branch 'call-type-with-__new__'
Browse files Browse the repository at this point in the history
Addresses much of #982.
  • Loading branch information
JukkaL committed Nov 29, 2015
2 parents 1437bb0 + 2eb5181 commit 2514c48
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 13 deletions.
37 changes: 25 additions & 12 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,28 +269,41 @@ def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool,
def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> Type:
"""Return the type of a type object.
For a generic type G with type variables T and S the type is of form
For a generic type G with type variables T and S the type is generally of form
def [T, S](...) -> G[T, S],
Callable[..., G[T, S]]
where ... are argument types for the __init__ method (without the self argument).
where ... are argument types for the __init__/__new__ method (without the self
argument). Also, the fallback type will be 'type' instead of 'function'.
"""
init_method = info.get_method('__init__')
if not init_method:
# Must be an invalid class definition.
return AnyType()
else:
fallback = builtin_type('builtins.type')
if init_method.info.fullname() == 'builtins.object':
# No non-default __init__ -> look at __new__ instead.
new_method = info.get_method('__new__')
if new_method and new_method.info.fullname() != 'builtins.object':
# Found one! Get signature from __new__.
return type_object_type_from_function(new_method, info, fallback)
# Construct callable type based on signature of __init__. Adjust
# return type and insert type arguments.
init_type = method_type_with_fallback(init_method, builtin_type('builtins.function'))
if isinstance(init_type, CallableType):
return class_callable(init_type, info, builtin_type('builtins.type'))
else:
# Overloaded __init__.
items = [] # type: List[CallableType]
for it in cast(Overloaded, init_type).items():
items.append(class_callable(it, info, builtin_type('builtins.type')))
return Overloaded(items)
return type_object_type_from_function(init_method, info, fallback)


def type_object_type_from_function(init_or_new: FuncBase, info: TypeInfo,
fallback: Instance) -> FunctionLike:
signature = method_type_with_fallback(init_or_new, fallback)
if isinstance(signature, CallableType):
return class_callable(signature, info, fallback)
else:
# Overloaded __init__/__new__.
items = [] # type: List[CallableType]
for item in cast(Overloaded, signature).items():
items.append(class_callable(item, info, fallback))
return Overloaded(items)


def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance) -> CallableType:
Expand Down
4 changes: 3 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1398,10 +1398,12 @@ def make_namedtuple_init(self, info: TypeInfo, items: List[str],
NoneTyp(),
self.named_type('__builtins__.function'),
name=info.name())
return FuncDef('__init__',
func = FuncDef('__init__',
args,
Block([]),
typ=signature)
func.info = info
return func

def analyze_types(self, items: List[Node]) -> List[Type]:
result = [] # type: List[Type]
Expand Down
80 changes: 80 additions & 0 deletions mypy/test/data/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,86 @@ class A:
class B: pass


-- __new__
-- --------


[case testConstructInstanceWith__new__]
class C:
def __new__(cls, foo: int = None) -> 'C':
obj = object.__new__(cls)
return obj

x = C(foo=12)
x.a # E: "C" has no attribute "a"
C(foo='') # E: Argument 1 to "C" has incompatible type "str"; expected "int"
[builtins fixtures/__new__.py]

[case testConstructInstanceWithDynamicallyTyped__new__]
class C:
def __new__(cls, foo):
obj = object.__new__(cls)
return obj

x = C(foo=12)
x = C(foo='x')
x.a # E: "C" has no attribute "a"
C(bar='') # E: Unexpected keyword argument "bar" for "C"
[builtins fixtures/__new__.py]

[case testClassWith__new__AndCompatibilityWithType]
class C:
def __new__(cls, foo: int = None) -> 'C':
obj = object.__new__(cls)
return obj
def f(x: type) -> None: pass
def g(x: int) -> None: pass
f(C)
g(C) # E: Argument 1 to "g" has incompatible type "C"; expected "int"
[builtins fixtures/__new__.py]

[case testClassWith__new__AndCompatibilityWithType2]
class C:
def __new__(cls, foo):
obj = object.__new__(cls)
return obj
def f(x: type) -> None: pass
def g(x: int) -> None: pass
f(C)
g(C) # E: Argument 1 to "g" has incompatible type "C"; expected "int"
[builtins fixtures/__new__.py]

[case testGenericClassWith__new__]
from typing import TypeVar, Generic
T = TypeVar('T')
class C(Generic[T]):
def __new__(cls, foo: T) -> 'C[T]':
obj = object.__new__(cls)
return obj
def set(self, x: T) -> None: pass
c = C('')
c.set('')
c.set(1) # E: Argument 1 to "set" of "C" has incompatible type "int"; expected "str"
[builtins fixtures/__new__.py]

[case testOverloaded__new__]
from typing import overload
class C:
@overload
def __new__(cls, foo: int) -> 'C':
obj = object.__new__(cls)
return obj
@overload
def __new__(cls, x: str, y: str) -> 'C':
obj = object.__new__(cls)
return obj
c = C(1)
c.a # E: "C" has no attribute "a"
C('', '')
C('') # E: No overload variant of "C" matches argument types [builtins.str]
[builtins fixtures/__new__.py]


-- Special cases
-- -------------

Expand Down
14 changes: 14 additions & 0 deletions mypy/test/data/fixtures/__new__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# builtins stub with object.__new__

class object:
def __init__(self) -> None: pass

def __new__(cls): pass

class type:
def __init__(self, x) -> None: pass

class int: pass
class bool: pass
class str: pass
class function: pass

0 comments on commit 2514c48

Please sign in to comment.