-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Allow fancy self-types #7860
Allow fancy self-types #7860
Changes from 7 commits
07de26b
56c5b34
5870ce9
082f4f4
869e15e
a6060da
6dc8126
48bc5e3
f287273
e696834
72b9c8a
c913a7f
3cb098c
52158e7
247ce64
0127c00
56f42bd
ea6376d
eb3b2b7
9a3454e
ab304e5
64c6727
5e5b133
ed58024
b33059e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,13 +9,13 @@ | |
|
||
from mypy.types import ( | ||
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded, | ||
TypeVarType, TypeType, UninhabitedType, FormalArgument, UnionType, NoneType, | ||
TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType, | ||
AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types, | ||
copy_type | ||
) | ||
from mypy.nodes import ( | ||
FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, TypeVar, ARG_STAR, ARG_STAR2, Expression, | ||
StrExpr | ||
StrExpr, ARG_POS | ||
) | ||
from mypy.maptype import map_instance_to_supertype | ||
from mypy.expandtype import expand_type_by_instance, expand_type | ||
|
@@ -50,6 +50,15 @@ def type_object_type_from_function(signature: FunctionLike, | |
# class B(A[List[T]], Generic[T]): pass | ||
# | ||
# We need to first map B's __init__ to the type (List[T]) -> None. | ||
|
||
# We first record all non-trivial (explicit) self types. | ||
if not is_new and def_info == info and not info.is_newtype: | ||
orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_kinds[0] == ARG_POS and | ||
it.arg_types[0] != fill_typevars(def_info) else None) | ||
ilevkivskyi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for it in signature.items()] | ||
else: | ||
orig_self_types = [None] * len(signature.items()) | ||
|
||
signature = bind_self(signature, original_type=fill_typevars(info), is_classmethod=is_new) | ||
signature = cast(FunctionLike, | ||
map_type_from_supertype(signature, info, def_info)) | ||
|
@@ -59,19 +68,19 @@ def type_object_type_from_function(signature: FunctionLike, | |
special_sig = 'dict' | ||
|
||
if isinstance(signature, CallableType): | ||
return class_callable(signature, info, fallback, special_sig, is_new) | ||
return class_callable(signature, info, fallback, special_sig, is_new, orig_self_types[0]) | ||
else: | ||
# Overloaded __init__/__new__. | ||
assert isinstance(signature, Overloaded) | ||
items = [] # type: List[CallableType] | ||
for item in signature.items(): | ||
items.append(class_callable(item, info, fallback, special_sig, is_new)) | ||
for item, orig_self in zip(signature.items(), orig_self_types): | ||
items.append(class_callable(item, info, fallback, special_sig, is_new, orig_self)) | ||
return Overloaded(items) | ||
|
||
|
||
def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, | ||
special_sig: Optional[str], | ||
is_new: bool) -> CallableType: | ||
is_new: bool, orig_self_type: Optional[Type] = None) -> CallableType: | ||
"""Create a type object type based on the signature of __init__.""" | ||
variables = [] # type: List[TypeVarDef] | ||
variables.extend(info.defn.type_vars) | ||
|
@@ -89,6 +98,8 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, | |
and is_subtype(init_ret_type, default_ret_type, ignore_type_params=True) | ||
): | ||
ret_type = init_ret_type # type: Type | ||
elif orig_self_type is not None: | ||
ret_type = orig_self_type | ||
else: | ||
ret_type = default_ret_type | ||
ilevkivskyi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
@@ -130,6 +141,12 @@ def map_type_from_supertype(typ: Type, | |
return expand_type_by_instance(typ, inst_type) | ||
|
||
|
||
def instance_or_var(typ: ProperType) -> bool: | ||
# TODO: use more principled check for non-trivial self-types. | ||
ilevkivskyi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return (isinstance(typ, TypeVarType) or | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to check that there is an instance upper bound? Should there be? Also my inclination would be to throw parens around the and clause. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would say it is safe to keep it as is. We can restrict this later if we will find problems.
OK. |
||
isinstance(typ, Instance) and typ != fill_typevars(typ.type)) | ||
|
||
|
||
F = TypeVar('F', bound=FunctionLike) | ||
|
||
|
||
|
@@ -174,23 +191,25 @@ class B(A): pass | |
# TODO: infer bounds on the type of *args? | ||
return cast(F, func) | ||
self_param_type = get_proper_type(func.arg_types[0]) | ||
if func.variables and (isinstance(self_param_type, TypeVarType) or | ||
if func.variables and (instance_or_var(self_param_type) or | ||
(isinstance(self_param_type, TypeType) and | ||
isinstance(self_param_type.item, TypeVarType))): | ||
instance_or_var(self_param_type.item))): | ||
if original_type is None: | ||
# Type check method override | ||
# XXX value restriction as union? | ||
original_type = erase_to_bound(self_param_type) | ||
original_type = get_proper_type(original_type) | ||
|
||
ids = [x.id for x in func.variables] | ||
typearg = get_proper_type(infer_type_arguments(ids, self_param_type, original_type)[0]) | ||
typearg = get_proper_type(infer_type_arguments(ids, self_param_type, | ||
original_type, is_supertype=True)[0]) | ||
if (is_classmethod and isinstance(typearg, UninhabitedType) | ||
and isinstance(original_type, (Instance, TypeVarType, TupleType))): | ||
# In case we call a classmethod through an instance x, fallback to type(x) | ||
# TODO: handle Union | ||
typearg = get_proper_type(infer_type_arguments(ids, self_param_type, | ||
TypeType(original_type))[0]) | ||
TypeType(original_type), | ||
is_supertype=True)[0]) | ||
|
||
def expand(target: Type) -> Type: | ||
assert typearg is not None | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth documenting the use in
__init__
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed it is mostly useful for typeshed stubs, but didn't find this patter in user code, I will add a short sentence at the end, where I discuss overloads.