8000 Untangle most of the big import cycle by msullivan · Pull Request #7397 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Untangle most of the big import cycle #7397

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

Merged
merged 8 commits into from
Aug 27, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Move bind_self and some friends into typeops.py
  • Loading branch information
msullivan committed Aug 26, 2019
commit b02cd86007102e02357ba5edcef1d70e3eaef24e
6 changes: 4 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@
)
import mypy.checkexpr
from mypy.checkmember import (
map_type_from_supertype, bind_self, erase_to_bound, type_object_type,
analyze_descriptor_access,
analyze_descriptor_access, type_object_type,
)
from mypy.typeops import (
map_type_from_supertype, bind_self, erase_to_bound,
)
from mypy import message_registry
from mypy.subtypes import (
Expand Down
182 changes: 3 additions & 179 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
from mypy import message_registry
from mypy import subtypes
from mypy import meet
from mypy.typeops import tuple_fallback
from mypy.typeops import (
tuple_fallback, bind_self, erase_to_bound, class_callable, type_object_type_from_function
)

if TYPE_CHECKING: # import for forward declaration only
import mypy.checker
Expand Down Expand Up @@ -869,181 +871,3 @@ def is_valid_constructor(n: Optional[SymbolNode]) -> bool:
if isinstance(n, Decorator):
return isinstance(get_proper_type(n.type), FunctionLike)
return False


def type_object_type_from_function(signature: FunctionLike,
info: TypeInfo,
def_info: TypeInfo,
fallback: Instance,
is_new: bool) -> FunctionLike:
# The __init__ method might come from a generic superclass
# (init_or_new.info) with type variables that do not map
# identically to the type variables of the class being constructed
# (info). For example
#
# class A(Generic[T]): def __init__(self, x: T) -> None: pass
# class B(A[List[T]], Generic[T]): pass
#
# We need to first map B's __init__ to the type (List[T]) -> None.
signature = bind_self(signature, original_type=fill_typevars(info), is_classmethod=is_new)
signature = cast(FunctionLike,
map_type_from_supertype(signature, info, def_info))
special_sig = None # type: Optional[str]
if def_info.fullname() == 'builtins.dict':
# Special signature!
special_sig = 'dict'

if isinstance(signature, CallableType):
return class_callable(signature, info, fallback, special_sig, is_new)
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))
return Overloaded(items)


def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance,
special_sig: Optional[str],
is_new: bool) -> CallableType:
"""Create a type object type based on the signature of __init__."""
variables = [] # type: List[TypeVarDef]
variables.extend(info.defn.type_vars)
variables.extend(init_type.variables)

init_ret_type = get_proper_type(init_type.ret_type)
if is_new and isinstance(init_ret_type, (Instance, TupleType)):
ret_type = init_type.ret_type # type: Type
else:
ret_type = fill_typevars(info)

callable_type = init_type.copy_modified(
ret_type=ret_type, fallback=type_type, name=None, variables=variables,
special_sig=special_sig)
c = callable_type.with_name(info.name())
return c


def map_type_from_supertype(typ: Type,
sub_info: TypeInfo,
10000 super_info: TypeInfo) -> Type:
"""Map type variables in a type defined in a supertype context to be valid
in the subtype context. Assume that the result is unique; if more than
one type is possible, return one of the alternatives.

For example, assume

. class D(Generic[S]) ...
. class C(D[E[T]], Generic[T]) ...

Now S in the context of D would be mapped to E[T] in the context of C.
"""
# Create the type of self in subtype, of form t[a1, ...].
inst_type = fill_typevars(sub_info)
if isinstance(inst_type, TupleType):
inst_type = tuple_fallback(inst_type)
# Map the type of self to supertype. This gets us a description of the
# supertype type variables in terms of subtype variables, i.e. t[t1, ...]
# so that any type variables in tN are to be interpreted in subtype
# context.
inst_type = map_instance_to_supertype(inst_type, super_info)
# Finally expand the type variables in type with those in the previously
# constructed type. Note that both type and inst_type may have type
# variables, but in type they are interpreted in supertype context while
# in inst_type they are interpreted in subtype context. This works even if
# the names of type variables in supertype and subtype overlap.
return expand_type_by_instance(typ, inst_type)


F = TypeVar('F', bound=FunctionLike)


def bind_self(method: F, original_type: Optional[Type] = None, is_classmethod: bool = False) -> F:
"""Return a copy of `method`, with the type of its first parameter (usually
self or cls) bound to original_type.

If the type of `self` is a generic type (T, or Type[T] for classmethods),
instantiate every occurrence of type with original_type in the rest of the
signature and in the return type.

original_type is the type of E in the expression E.copy(). It is None in
compatibility checks. In this case we treat it as the erasure of the
declared type of self.

This way we can express "the type of self". For example:

T = TypeVar('T', bound='A')
class A:
def copy(self: T) -> T: ...

class B(A): pass

b = B().copy() # type: B

"""
if isinstance(method, Overloaded):
return cast(F, Overloaded([bind_self(c, original_type) for c in method.items()]))
assert isinstance(method, CallableType)
func = method
if not func.arg_types:
# invalid method. return something
return cast(F, func)
if func.arg_kinds[0] == ARG_STAR:
# The signature is of the form 'def foo(*args, ...)'.
# In this case we shouldn't drop the first arg,
# since func will be absorbed by the *args.

# 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
(isinstance(self_param_type, TypeType) and
isinstance(self_param_type.item, TypeVarType))):
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])
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])

def expand(target: Type) -> Type:
assert typearg is not None
return expand_type(target, {func.variables[0].id: typearg})

arg_types = [expand(x) for x in func.arg_types[1:]]
ret_type = expand(func.ret_type)
variables = func.variables[1:]
else:
arg_types = func.arg_types[1:]
ret_type = func.ret_type
variables = func.variables

original_type = get_proper_type(original_type)
if isinstance(original_type, CallableType) and original_type.is_type_obj():
original_type = TypeType.make_normalized(original_type.ret_type)
res = func.copy_modified(arg_types=arg_types,
arg_kinds=func.arg_kinds[1:],
arg_names=func.arg_names[1:],
variables=variables,
ret_type=ret_type,
bound_args=[original_type])
return cast(F, res)


def erase_to_bound(t: Type) -> Type:
t = get_proper_type(t)
if isinstance(t, TypeVarType):
return t.upper_bound
if isinstance(t, TypeType):
if isinstance(t.item, TypeVarType):
return TypeType.make_normalized(t.item.upper_bound)
return t
3 changes: 2 additions & 1 deletion mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,8 @@ def find_node_type(node: Union[Var, FuncBase], itype: Instance, subtype: Type) -
"""Find type of a variable or method 'node' (maybe also a decorated method).
Apply type arguments from 'itype', and bind 'self' to 'subtype'.
"""
from mypy.checkmember import bind_self
from mypy.typeops import bind_self

if isinstance(node, FuncBase):
typ = function_type(node,
fallback=Instance(itype.type.mro[-1], [])) # type: Optional[Type]
Expand Down
Loading
0