def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: MemberContext) -> Type: # Class attribute. # TODO super? ret_type = typ.items()[0].ret_type assert isinstance(ret_type, ProperType) if isinstance(ret_type, TupleType): ret_type = tuple_fallback(ret_type) if isinstance(ret_type, Instance): if not mx.is_operator: # When Python sees an operator (eg `3 == 4`), it automatically translates that # into something like `int.__eq__(3, 4)` instead of `(3).__eq__(4)` as an # optimization. # # While it normally it doesn't matter which of the two versions are used, it # does cause inconsistencies when working with classes. For example, translating # `int == int` to `int.__eq__(int)` would not work since `int.__eq__` is meant to # compare two int _instances_. What we really want is `type(int).__eq__`, which # is meant to compare two types or classes. # # This check makes sure that when we encounter an operator, we skip looking up # the corresponding method in the current instance to avoid this edge case. # See https://github.com/python/mypy/pull/1787 for more info. # TODO: do not rely on same type variables being present in all constructor overloads. result = analyze_class_attribute_access( ret_type, name, mx, original_vars=typ.items()[0].variables) if result: return result # Look up from the 'type' type. return _analyze_member_access(name, typ.fallback, mx) else: assert False, 'Unexpected type {}'.format(repr(ret_type))
def type_object_type_from_function(signature: FunctionLike, info: TypeInfo, def_info: TypeInfo, fallback: Instance, is_new: bool) -> FunctionLike: # We first need to record all non-trivial (explicit) self types in __init__, # since they will not be available after we bind them. Note, we use explicit # self-types only in the defining class, similar to __new__ (but not exactly the same, # see comment in class_callable below). This is mostly useful for annotating library # classes such as subprocess.Popen. default_self = fill_typevars(info) if not is_new and not info.is_newtype: orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_types[0] != default_self and it.arg_kinds[0] == ARG_POS else None) for it in signature.items()] else: orig_self_types = [None] * len(signature.items()) # The __init__ method might come from a generic superclass 'def_info' # with type variables that do not map identically to the type variables of # the class 'info' being constructed. For example: # # class A(Generic[T]): # def __init__(self, x: T) -> None: ... # class B(A[List[T]]): # ... # # We need to map B's __init__ to the type (List[T]) -> None. signature = bind_self(signature, original_type=default_self, 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, orig_self_types[0]) else: # Overloaded __init__/__new__. assert isinstance(signature, Overloaded) items = [] # type: List[CallableType] 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 check_self_arg(functype: FunctionLike, dispatched_arg_type: Type, is_classmethod: bool, context: Context, name: str, msg: MessageBuilder) -> None: """For x.f where A.f: A1 -> T, check that meet(type(x), A) <: A1 for each overload. dispatched_arg_type is meet(B, A) in the following example def g(x: B): x.f class A: f: Callable[[A1], None] """ # TODO: this is too strict. We can return filtered overloads for matching definitions for item in functype.items(): if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). msg.no_formal_self(name, item, context) else: selfarg = item.arg_types[0] if is_classmethod: dispatched_arg_type = TypeType.make_normalized( dispatched_arg_type) if not subtypes.is_subtype(dispatched_arg_type, erase_to_bound(selfarg)): msg.incompatible_self_argument(name, dispatched_arg_type, item, is_classmethod, context)
def check_method_type(functype: FunctionLike, itype: Instance, is_classmethod: bool, context: Context, msg: MessageBuilder) -> None: for item in functype.items(): if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). msg.invalid_method_type(item, context) elif not is_classmethod: # Check that self argument has type 'Any' or valid instance type. selfarg = item.arg_types[0] # If this is a method of a tuple class, correct for the fact that # we passed to typ.fallback in analyze_member_access. See #1432. if isinstance(selfarg, TupleType): selfarg = selfarg.fallback if not subtypes.is_subtype(selfarg, itype): msg.invalid_method_type(item, context) else: # Check that cls argument has type 'Any' or valid class type. # (This is sufficient for the current treatment of @classmethod, # but probably needs to be revisited when we implement Type[C] # or advanced variants of it like Type[<args>, C].) clsarg = item.arg_types[0] if isinstance(clsarg, CallableType) and clsarg.is_type_obj(): if not subtypes.is_equivalent(clsarg.ret_type, itype): msg.invalid_class_method_type(item, context) else: if not subtypes.is_equivalent(clsarg, AnyType()): msg.invalid_class_method_type(item, context)
def check_method_type( functype: FunctionLike, itype: Instance, is_classmethod: bool, context: Context, msg: MessageBuilder ) -> None: for item in functype.items(): if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). msg.invalid_method_type(item, context) elif not is_classmethod: # Check that self argument has type 'Any' or valid instance type. selfarg = item.arg_types[0] # If this is a method of a tuple class, correct for the fact that # we passed to typ.fallback in analyze_member_access. See #1432. if isinstance(selfarg, TupleType): selfarg = selfarg.fallback if not subtypes.is_equivalent(selfarg, itype): msg.invalid_method_type(item, context) else: # Check that cls argument has type 'Any' or valid class type. # (This is sufficient for the current treatment of @classmethod, # but probably needs to be revisited when we implement Type[C] # or advanced variants of it like Type[<args>, C].) clsarg = item.arg_types[0] if isinstance(clsarg, CallableType) and clsarg.is_type_obj(): if not subtypes.is_equivalent(clsarg.ret_type, itype): msg.invalid_class_method_type(item, context) else: if not subtypes.is_equivalent(clsarg, AnyType()): msg.invalid_class_method_type(item, context)
def check_self_arg(functype: FunctionLike, dispatched_arg_type: Type, is_classmethod: bool, context: Context, name: str, msg: MessageBuilder) -> None: """For x.f where A.f: A1 -> T, check that meet(type(x), A) <: A1 for each overload. dispatched_arg_type is meet(B, A) in the following example def g(x: B): x.f class A: f: Callable[[A1], None] """ # TODO: this is too strict. We can return filtered overloads for matching definitions for item in functype.items(): if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). msg.no_formal_self(name, item, context) else: selfarg = item.arg_types[0] if is_classmethod: dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type) if not subtypes.is_subtype(dispatched_arg_type, erase_to_bound(selfarg)): msg.incompatible_self_argument(name, dispatched_arg_type, item, is_classmethod, context)
def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: MemberContext) -> Type: # Class attribute. # TODO super? ret_type = typ.items()[0].ret_type if isinstance(ret_type, TupleType): ret_type = tuple_fallback(ret_type) if isinstance(ret_type, Instance): if not mx.is_operator: # When Python sees an operator (eg `3 == 4`), it automatically translates that # into something like `int.__eq__(3, 4)` instead of `(3).__eq__(4)` as an # optimization. # # While it normally it doesn't matter which of the two versions are used, it # does cause inconsistencies when working with classes. For example, translating # `int == int` to `int.__eq__(int)` would not work since `int.__eq__` is meant to # compare two int _instances_. What we really want is `type(int).__eq__`, which # is meant to compare two types or classes. # # This check makes sure that when we encounter an operator, we skip looking up # the corresponding method in the current instance to avoid this edge case. # See https://github.com/python/mypy/pull/1787 for more info. result = analyze_class_attribute_access(ret_type, name, mx) if result: return result # Look up from the 'type' type. return _analyze_member_access(name, typ.fallback, mx) else: assert False, 'Unexpected type {}'.format(repr(ret_type))
def type_object_type_from_function(signature: FunctionLike, info: TypeInfo, def_info: TypeInfo, fallback: Instance) -> 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) 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) 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)) return Overloaded(items)
def _unify_type( function: FunctionLike, fetch_type: Callable[[CallableType], MypyType], ) -> MypyType: return UnionType.make_union([ fetch_type(case) for case in function.items() ])
def check_method_type(functype: FunctionLike, itype: Instance, context: Context, msg: MessageBuilder) -> None: for item in functype.items(): if not item.arg_types or item.arg_kinds[0] != ARG_POS: # No positional first (self) argument. msg.invalid_method_type(item, context) else: # Check that self argument has type 'Any' or valid instance type. selfarg = item.arg_types[0] if not subtypes.is_equivalent(selfarg, itype): msg.invalid_method_type(item, context)
def check_self_arg(functype: FunctionLike, dispatched_arg_type: Type, is_classmethod: bool, context: Context, name: str, msg: MessageBuilder) -> FunctionLike: """Check that an instance has a valid type for a method with annotated 'self'. For example if the method is defined as: class A: def f(self: S) -> T: ... then for 'x.f' we check that meet(type(x), A) <: S. If the method is overloaded, we select only overloads items that satisfy this requirement. If there are no matching overloads, an error is generated. Note: dispatched_arg_type uses a meet to select a relevant item in case if the original type of 'x' is a union. This is done because several special methods treat union types in ad-hoc manner, so we can't use MemberContext.self_type yet. """ items = functype.items() if not items: return functype new_items = [] if is_classmethod: dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type) for item in items: if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). msg.no_formal_self(name, item, context) # This is pretty bad, so just return the original signature if # there is at least one such error. return functype else: selfarg = item.arg_types[0] if subtypes.is_subtype(dispatched_arg_type, erase_typevars(erase_to_bound(selfarg))): new_items.append(item) if not new_items: # Choose first item for the message (it may be not very helpful for overloads). msg.incompatible_self_argument(name, dispatched_arg_type, items[0], is_classmethod, context) return functype if len(new_items) == 1: return new_items[0] return Overloaded(new_items)