def infer_constraints_if_possible(template: Type, actual: Type, direction: int) -> Optional[List[Constraint]]: """Like infer_constraints, but return None if the input relation is known to be unsatisfiable, for example if template=List[T] and actual=int. (In this case infer_constraints would return [], just like it would for an automatically satisfied relation like template=List[T] and actual=object.) """ if (direction == SUBTYPE_OF and not mypy.subtypes.is_subtype(erase_typevars(template), actual)): return None if (direction == SUPERTYPE_OF and not mypy.subtypes.is_subtype(actual, erase_typevars(template))): return None return infer_constraints(template, actual, direction)
def has_no_typevars(typ: Type) -> bool: # We test if a type contains type variables by erasing all type variables # and comparing the result to the original type. We use comparison by equality that # in turn uses `__eq__` defined for types. Note: we can't use `is_same_type` because # it is not safe with unresolved forward references, while this function may be called # before forward references resolution patch pass. Note also that it is not safe to use # `is` comparison because `erase_typevars` doesn't preserve type identity. return typ == erase_typevars(typ)
def infer_constraints_if_possible(template: Type, actual: Type, direction: int) -> Optional[List[Constraint]]: """Like infer_constraints, but return None if the input relation is known to be unsatisfiable, for example if template=List[T] and actual=int. (In this case infer_constraints would return [], just like it would for an automatically satisfied relation like template=List[T] and actual=object.) """ if (direction == SUBTYPE_OF and not mypy.subtypes.is_subtype(erase_typevars(template), actual)): return None if (direction == SUPERTYPE_OF and not mypy.subtypes.is_subtype(actual, erase_typevars(template))): return None if (direction == SUPERTYPE_OF and isinstance(template, TypeVarType) and not mypy.subtypes.is_subtype(actual, erase_typevars(template.upper_bound))): # This is not caught by the above branch because of the erase_typevars() call, # that would return 'Any' for a type variable. return None return infer_constraints(template, actual, direction)
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) elif isinstance(selfarg, ParamSpecType): # TODO: This is not always right. What's the most reasonable thing to do here? 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)
def make_type_object_wrapper(self, tdef: ClassDef) -> FuncDef: """Construct dynamically typed wrapper function for a class. It simple calls the type object and returns the result. """ # TODO keyword args, default args and varargs # TODO overloads type_sig = cast(Callable, type_object_type(tdef.info, None)) type_sig = cast(Callable, erasetype.erase_typevars(type_sig)) init = cast(FuncDef, tdef.info.get_method('__init__')) arg_kinds = type_sig.arg_kinds # The wrapper function has a dynamically typed signature. wrapper_sig = Callable( [AnyType()] * len(arg_kinds), arg_kinds, [None] * len(arg_kinds), AnyType(), False) n = NameExpr(tdef.name) # TODO full name args = self.func_tf.call_args( init.args[1:], type_sig, wrapper_sig, True, False) call = CallExpr(n, args, arg_kinds) ret = ReturnStmt(call) fdef = FuncDef(tdef.name + self.tf.dynamic_suffix(), init.args[1:], arg_kinds, [None] * len(arg_kinds), Block([ret])) fdef.type = wrapper_sig return fdef
def make_type_object_wrapper(self, tdef): """Construct dynamically typed wrapper function for a class. It simple calls the type object and returns the result. """ # TODO keyword args, default args and varargs # TODO overloads type_sig = type_object_type(tdef.info, None) type_sig = erasetype.erase_typevars(type_sig) init = tdef.info.get_method('__init__') arg_kinds = type_sig.arg_kinds # The wrapper function has a dynamically typed signature. wrapper_sig = Callable( [Any()] * len(arg_kinds), arg_kinds, [None] * len(arg_kinds), Any(), False) n = NameExpr(tdef.name) # TODO full name args = self.func_tf.call_args( init.args[1:], type_sig, wrapper_sig, True, False) call = CallExpr(n, args, arg_kinds) ret = ReturnStmt(call) fdef = FuncDef(tdef.name + self.tf.dynamic_suffix(), init.args[1:], arg_kinds, [None] * len(arg_kinds), Block([ret])) fdef.type = wrapper_sig return fdef
def analyze_class_attribute_access( itype: Instance, name: str, mx: MemberContext, override_info: Optional[TypeInfo] = None, original_vars: Optional[List[TypeVarDef]] = None) -> Optional[Type]: """Analyze access to an attribute on a class object. itype is the return type of the class object callable, original_type is the type of E in the expression E.var, original_vars are type variables of the class callable (for generic classes). """ info = itype.type if override_info: info = override_info node = info.get(name) if not node: if info.fallback_to_any: return AnyType(TypeOfAny.special_form) return None is_decorated = isinstance(node.node, Decorator) is_method = is_decorated or isinstance(node.node, FuncBase) if mx.is_lvalue: if is_method: mx.msg.cant_assign_to_method(mx.context) if isinstance(node.node, TypeInfo): mx.msg.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, mx.context) # If a final attribute was declared on `self` in `__init__`, then it # can't be accessed on the class object. if node.implicit and isinstance(node.node, Var) and node.node.is_final: mx.msg.fail( message_registry.CANNOT_ACCESS_FINAL_INSTANCE_ATTR.format( node.node.name), mx.context) # An assignment to final attribute on class object is also always an error, # independently of types. if mx.is_lvalue and not mx.chk.get_final_context(): check_final_member(name, info, mx.msg, mx.context) if info.is_enum and not (mx.is_lvalue or is_decorated or is_method): enum_literal = LiteralType(name, fallback=itype) # When we analyze enums, the corresponding Instance is always considered to be erased # due to how the signature of Enum.__new__ is `(cls: Type[_T], value: object) -> _T` # in typeshed. However, this is really more of an implementation detail of how Enums # are typed, and we really don't want to treat every single Enum value as if it were # from type variable substitution. So we reset the 'erased' field here. return itype.copy_modified(erased=False, last_known_value=enum_literal) t = node.type if t: if isinstance(t, PartialType): symnode = node.node assert isinstance(symnode, Var) return mx.chk.handle_partial_var_type(t, mx.is_lvalue, symnode, mx.context) # Find the class where method/variable was defined. if isinstance(node.node, Decorator): super_info = node.node.var.info # type: Optional[TypeInfo] elif isinstance(node.node, (Var, SYMBOL_FUNCBASE_TYPES)): super_info = node.node.info else: super_info = None # Map the type to how it would look as a defining class. For example: # class C(Generic[T]): ... # class D(C[Tuple[T, S]]): ... # D[int, str].method() # Here itype is D[int, str], isuper is C[Tuple[int, str]]. if not super_info: isuper = None else: isuper = map_instance_to_supertype(itype, super_info) if isinstance(node.node, Var): assert isuper is not None # Check if original variable type has type variables. For example: # class C(Generic[T]): # x: T # C.x # Error, ambiguous access # C[int].x # Also an error, since C[int] is same as C at runtime if isinstance(t, TypeVarType) or has_type_vars(t): # Exception: access on Type[...], including first argument of class methods is OK. if not isinstance(get_proper_type(mx.original_type), TypeType) or node.implicit: if node.node.is_classvar: message = message_registry.GENERIC_CLASS_VAR_ACCESS else: message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS mx.msg.fail(message, mx.context) # Erase non-mapped variables, but keep mapped ones, even if there is an error. # In the above example this means that we infer following types: # C.x -> Any # C[int].x -> int t = erase_typevars(expand_type_by_instance(t, isuper)) is_classmethod = ( (is_decorated and cast(Decorator, node.node).func.is_class) or (isinstance(node.node, FuncBase) and node.node.is_class)) t = get_proper_type(t) if isinstance(t, FunctionLike) and is_classmethod: t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) result = add_class_tvars(t, isuper, is_classmethod, mx.self_type, original_vars=original_vars) if not mx.is_lvalue: result = analyze_descriptor_access(mx.original_type, result, mx.builtin_type, mx.msg, mx.context, chk=mx.chk) return result elif isinstance(node.node, Var): mx.not_ready_callback(name, mx.context) return AnyType(TypeOfAny.special_form) if isinstance(node.node, TypeVarExpr): mx.msg.fail( message_registry.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format( info.name, name), mx.context) return AnyType(TypeOfAny.from_error) if isinstance(node.node, TypeInfo): return type_object_type(node.node, mx.builtin_type) if isinstance(node.node, MypyFile): # Reference to a module object. return mx.builtin_type('types.ModuleType') if (isinstance(node.node, TypeAlias) and isinstance(get_proper_type(node.node.target), Instance)): return instance_alias_type(node.node, mx.builtin_type) if is_decorated: assert isinstance(node.node, Decorator) if node.node.type: return node.node.type else: mx.not_ready_callback(name, mx.context) return AnyType(TypeOfAny.from_error) else: assert isinstance(node.node, FuncBase) typ = function_type(node.node, mx.builtin_type('builtins.function')) # Note: if we are accessing class method on class object, the cls argument is bound. # Annotated and/or explicit class methods go through other code paths above, for # unannotated implicit class methods we do this here. if node.node.is_class: typ = bind_self(typ, is_classmethod=True) return typ
def analyze_class_attribute_access(itype: Instance, name: str, mx: MemberContext) -> Optional[Type]: """original_type is the type of E in the expression E.var""" node = itype.type.get(name) if not node: if itype.type.fallback_to_any: return AnyType(TypeOfAny.special_form) return None is_decorated = isinstance(node.node, Decorator) is_method = is_decorated or isinstance(node.node, FuncBase) if mx.is_lvalue: if is_method: mx.msg.cant_assign_to_method(mx.context) if isinstance(node.node, TypeInfo): mx.msg.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, mx.context) # If a final attribute was declared on `self` in `__init__`, then it # can't be accessed on the class object. if node.implicit and isinstance(node.node, Var) and node.node.is_final: mx.msg.fail( message_registry.CANNOT_ACCESS_FINAL_INSTANCE_ATTR.format( node.node.name()), mx.context) # An assignment to final attribute on class object is also always an error, # independently of types. if mx.is_lvalue and not mx.chk.get_final_context(): check_final_member(name, itype.type, mx.msg, mx.context) if itype.type.is_enum and not (mx.is_lvalue or is_decorated or is_method): return itype t = node.type if t: if isinstance(t, PartialType): symnode = node.node assert isinstance(symnode, Var) return mx.chk.handle_partial_var_type(t, mx.is_lvalue, symnode, mx.context) # Find the class where method/variable was defined. if isinstance(node.node, Decorator): super_info = node.node.var.info # type: Optional[TypeInfo] elif isinstance(node.node, (Var, SYMBOL_FUNCBASE_TYPES)): super_info = node.node.info else: super_info = None # Map the type to how it would look as a defining class. For example: # class C(Generic[T]): ... # class D(C[Tuple[T, S]]): ... # D[int, str].method() # Here itype is D[int, str], isuper is C[Tuple[int, str]]. if not super_info: isuper = None else: isuper = map_instance_to_supertype(itype, super_info) if isinstance(node.node, Var): assert isuper is not None # Check if original variable type has type variables. For example: # class C(Generic[T]): # x: T # C.x # Error, ambiguous access # C[int].x # Also an error, since C[int] is same as C at runtime if isinstance(t, TypeVarType) or get_type_vars(t): # Exception: access on Type[...], including first argument of class methods is OK. if not isinstance(mx.original_type, TypeType): mx.msg.fail( message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS, mx.context) # Erase non-mapped variables, but keep mapped ones, even if there is an error. # In the above example this means that we infer following types: # C.x -> Any # C[int].x -> int t = erase_typevars(expand_type_by_instance(t, isuper)) is_classmethod = ( (is_decorated and cast(Decorator, node.node).func.is_class) or (isinstance(node.node, FuncBase) and node.node.is_class)) result = add_class_tvars(t, itype, isuper, is_classmethod, mx.builtin_type, mx.original_type) if not mx.is_lvalue: result = analyze_descriptor_access(mx.original_type, result, mx.builtin_type, mx.msg, mx.context, chk=mx.chk) return result elif isinstance(node.node, Var): mx.not_ready_callback(name, mx.context) return AnyType(TypeOfAny.special_form) if isinstance(node.node, TypeVarExpr): mx.msg.fail( message_registry.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format( itype.type.name(), name), mx.context) return AnyType(TypeOfAny.from_error) if isinstance(node.node, TypeInfo): return type_object_type(node.node, mx.builtin_type) if isinstance(node.node, MypyFile): # Reference to a module object. return mx.builtin_type('types.ModuleType') if isinstance(node.node, TypeAlias) and isinstance(node.node.target, Instance): return instance_alias_type(node.node, mx.builtin_type) if is_decorated: assert isinstance(node.node, Decorator) if node.node.type: return node.node.type else: mx.not_ready_callback(name, mx.context) return AnyType(TypeOfAny.from_error) else: return function_type(cast(FuncBase, node.node), mx.builtin_type('builtins.function'))
def visit_instance(self, template: Instance) -> List[Constraint]: original_actual = actual = self.actual res = [] # type: List[Constraint] if isinstance( actual, (CallableType, Overloaded)) and template.type.is_protocol: if template.type.protocol_members == ['__call__']: # Special case: a generic callback protocol if not any( mypy.sametypes.is_same_type(template, t) for t in template.type.inferring): template.type.inferring.append(template) call = mypy.subtypes.find_member('__call__', template, actual) assert call is not None if mypy.subtypes.is_subtype(actual, erase_typevars(call)): subres = infer_constraints(call, actual, self.direction) res.extend(subres) template.type.inferring.pop() return res if isinstance(actual, CallableType) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, Overloaded) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, TypedDictType): actual = actual.as_anonymous().fallback if isinstance(actual, Instance): instance = actual erased = erase_typevars(template) assert isinstance(erased, Instance) # We always try nominal inference if possible, # it is much faster than the structural one. if (self.direction == SUBTYPE_OF and template.type.has_base(instance.type.fullname())): mapped = map_instance_to_supertype(template, instance.type) tvars = mapped.type.defn.type_vars for i in range(len(instance.args)): # The constraints for generic type parameters depend on variance. # Include constraints from both directions if invariant. if tvars[i].variance != CONTRAVARIANT: res.extend( infer_constraints(mapped.args[i], instance.args[i], self.direction)) if tvars[i].variance != COVARIANT: res.extend( infer_constraints(mapped.args[i], instance.args[i], neg_op(self.direction))) return res elif (self.direction == SUPERTYPE_OF and instance.type.has_base(template.type.fullname())): mapped = map_instance_to_supertype(instance, template.type) tvars = template.type.defn.type_vars for j in range(len(template.args)): # The constraints for generic type parameters depend on variance. # Include constraints from both directions if invariant. if tvars[j].variance != CONTRAVARIANT: res.extend( infer_constraints(template.args[j], mapped.args[j], self.direction)) if tvars[j].variance != COVARIANT: res.extend( infer_constraints(template.args[j], mapped.args[j], neg_op(self.direction))) return res if (template.type.is_protocol and self.direction == SUPERTYPE_OF and # We avoid infinite recursion for structural subtypes by checking # whether this type already appeared in the inference chain. # This is a conservative way break the inference cycles. # It never produces any "false" constraints but gives up soon # on purely structural inference cycles, see #3829. # Note that we use is_protocol_implementation instead of is_subtype # because some type may be considered a subtype of a protocol # due to _promote, but still not implement the protocol. not any( mypy.sametypes.is_same_type(template, t) for t in template.type.inferring) and mypy.subtypes.is_protocol_implementation( instance, erased)): template.type.inferring.append(template) self.infer_constraints_from_protocol_members( res, instance, template, original_actual, template) template.type.inferring.pop() return res elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and # We avoid infinite recursion for structural subtypes also here. not any( mypy.sametypes.is_same_type(instance, i) for i in instance.type.inferring) and mypy.subtypes.is_protocol_implementation( erased, instance)): instance.type.inferring.append(instance) self.infer_constraints_from_protocol_members( res, instance, template, template, instance) instance.type.inferring.pop() return res if isinstance(actual, AnyType): # IDEA: Include both ways, i.e. add negation as well? return self.infer_against_any(template.args, actual) if (isinstance(actual, TupleType) and (is_named_instance(template, 'typing.Iterable') or is_named_instance(template, 'typing.Container') or is_named_instance(template, 'typing.Sequence') or is_named_instance(template, 'typing.Reversible')) and self.direction == SUPERTYPE_OF): for item in actual.items: cb = infer_constraints(template.args[0], item, SUPERTYPE_OF) res.extend(cb) return res elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: return infer_constraints(template, mypy.typeops.tuple_fallback(actual), self.direction) else: return []
def visit_instance(self, template: Instance) -> List[Constraint]: original_actual = actual = self.actual res: List[Constraint] = [] if isinstance( actual, (CallableType, Overloaded)) and template.type.is_protocol: if template.type.protocol_members == ['__call__']: # Special case: a generic callback protocol if not any( mypy.sametypes.is_same_type(template, t) for t in template.type.inferring): template.type.inferring.append(template) call = mypy.subtypes.find_member('__call__', template, actual, is_operator=True) assert call is not None if mypy.subtypes.is_subtype(actual, erase_typevars(call)): subres = infer_constraints(call, actual, self.direction) res.extend(subres) template.type.inferring.pop() return res if isinstance(actual, CallableType) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, Overloaded) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, TypedDictType): actual = actual.as_anonymous().fallback if isinstance(actual, LiteralType): actual = actual.fallback if isinstance(actual, Instance): instance = actual erased = erase_typevars(template) assert isinstance(erased, Instance) # type: ignore # We always try nominal inference if possible, # it is much faster than the structural one. if (self.direction == SUBTYPE_OF and template.type.has_base(instance.type.fullname)): mapped = map_instance_to_supertype(template, instance.type) tvars = mapped.type.defn.type_vars # N.B: We use zip instead of indexing because the lengths might have # mismatches during daemon reprocessing. for tvar, mapped_arg, instance_arg in zip( tvars, mapped.args, instance.args): # TODO: ParamSpecType if isinstance(tvar, TypeVarType): # The constraints for generic type parameters depend on variance. # Include constraints from both directions if invariant. if tvar.variance != CONTRAVARIANT: res.extend( infer_constraints(mapped_arg, instance_arg, self.direction)) if tvar.variance != COVARIANT: res.extend( infer_constraints(mapped_arg, instance_arg, neg_op(self.direction))) return res elif (self.direction == SUPERTYPE_OF and instance.type.has_base(template.type.fullname)): mapped = map_instance_to_supertype(instance, template.type) tvars = template.type.defn.type_vars # N.B: We use zip instead of indexing because the lengths might have # mismatches during daemon reprocessing. for tvar, mapped_arg, template_arg in zip( tvars, mapped.args, template.args): # TODO: ParamSpecType if isinstance(tvar, TypeVarType): # The constraints for generic type parameters depend on variance. # Include constraints from both directions if invariant. if tvar.variance != CONTRAVARIANT: res.extend( infer_constraints(template_arg, mapped_arg, self.direction)) if tvar.variance != COVARIANT: res.extend( infer_constraints(template_arg, mapped_arg, neg_op(self.direction))) return res if (template.type.is_protocol and self.direction == SUPERTYPE_OF and # We avoid infinite recursion for structural subtypes by checking # whether this type already appeared in the inference chain. # This is a conservative way to break the inference cycles. # It never produces any "false" constraints but gives up soon # on purely structural inference cycles, see #3829. # Note that we use is_protocol_implementation instead of is_subtype # because some type may be considered a subtype of a protocol # due to _promote, but still not implement the protocol. not any( mypy.sametypes.is_same_type(template, t) for t in template.type.inferring) and mypy.subtypes.is_protocol_implementation( instance, erased)): template.type.inferring.append(template) res.extend( self.infer_constraints_from_protocol_members( instance, template, original_actual, template)) template.type.inferring.pop() return res elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and # We avoid infinite recursion for structural subtypes also here. not any( mypy.sametypes.is_same_type(instance, i) for i in instance.type.inferring) and mypy.subtypes.is_protocol_implementation( erased, instance)): instance.type.inferring.append(instance) res.extend( self.infer_constraints_from_protocol_members( instance, template, template, instance)) instance.type.inferring.pop() return res if isinstance(actual, AnyType): return self.infer_against_any(template.args, actual) if (isinstance(actual, TupleType) and is_named_instance(template, TUPLE_LIKE_INSTANCE_NAMES) and self.direction == SUPERTYPE_OF): for item in actual.items: cb = infer_constraints(template.args[0], item, SUPERTYPE_OF) res.extend(cb) return res elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: return infer_constraints(template, mypy.typeops.tuple_fallback(actual), self.direction) elif isinstance(actual, TypeVarType): if not actual.values: return infer_constraints(template, actual.upper_bound, self.direction) return [] else: return []
def has_no_typevars(typ: Type) -> bool: return is_same_type(typ, erase_typevars(typ))
def visit_instance(self, template: Instance) -> List[Constraint]: original_actual = actual = self.actual res = [] # type: List[Constraint] if isinstance(actual, (CallableType, Overloaded)) and template.type.is_protocol: if template.type.protocol_members == ['__call__']: # Special case: a generic callback protocol if not any(is_same_type(template, t) for t in template.type.inferring): template.type.inferring.append(template) call = mypy.subtypes.find_member('__call__', template, actual) assert call is not None if mypy.subtypes.is_subtype(actual, erase_typevars(call)): subres = infer_constraints(call, actual, self.direction) res.extend(subres) template.type.inferring.pop() return res if isinstance(actual, CallableType) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, Overloaded) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, TypedDictType): actual = actual.as_anonymous().fallback if isinstance(actual, Instance): instance = actual erased = erase_typevars(template) assert isinstance(erased, Instance) # We always try nominal inference if possible, # it is much faster than the structural one. if (self.direction == SUBTYPE_OF and template.type.has_base(instance.type.fullname())): mapped = map_instance_to_supertype(template, instance.type) tvars = mapped.type.defn.type_vars for i in range(len(instance.args)): # The constraints for generic type parameters depend on variance. # Include constraints from both directions if invariant. if tvars[i].variance != CONTRAVARIANT: res.extend(infer_constraints( mapped.args[i], instance.args[i], self.direction)) if tvars[i].variance != COVARIANT: res.extend(infer_constraints( mapped.args[i], instance.args[i], neg_op(self.direction))) return res elif (self.direction == SUPERTYPE_OF and instance.type.has_base(template.type.fullname())): mapped = map_instance_to_supertype(instance, template.type) tvars = template.type.defn.type_vars for j in range(len(template.args)): # The constraints for generic type parameters depend on variance. # Include constraints from both directions if invariant. if tvars[j].variance != CONTRAVARIANT: res.extend(infer_constraints( template.args[j], mapped.args[j], self.direction)) if tvars[j].variance != COVARIANT: res.extend(infer_constraints( template.args[j], mapped.args[j], neg_op(self.direction))) return res if (template.type.is_protocol and self.direction == SUPERTYPE_OF and # We avoid infinite recursion for structural subtypes by checking # whether this type already appeared in the inference chain. # This is a conservative way break the inference cycles. # It never produces any "false" constraints but gives up soon # on purely structural inference cycles, see #3829. # Note that we use is_protocol_implementation instead of is_subtype # because some type may be considered a subtype of a protocol # due to _promote, but still not implement the protocol. not any(is_same_type(template, t) for t in template.type.inferring) and mypy.subtypes.is_protocol_implementation(instance, erased)): template.type.inferring.append(template) self.infer_constraints_from_protocol_members(res, instance, template, original_actual, template) template.type.inferring.pop() return res elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and # We avoid infinite recursion for structural subtypes also here. not any(is_same_type(instance, i) for i in instance.type.inferring) and mypy.subtypes.is_protocol_implementation(erased, instance)): instance.type.inferring.append(instance) self.infer_constraints_from_protocol_members(res, instance, template, template, instance) instance.type.inferring.pop() return res if isinstance(actual, AnyType): # IDEA: Include both ways, i.e. add negation as well? return self.infer_against_any(template.args, actual) if (isinstance(actual, TupleType) and (is_named_instance(template, 'typing.Iterable') or is_named_instance(template, 'typing.Container') or is_named_instance(template, 'typing.Sequence') or is_named_instance(template, 'typing.Reversible')) and self.direction == SUPERTYPE_OF): for item in actual.items: cb = infer_constraints(template.args[0], item, SUPERTYPE_OF) res.extend(cb) return res elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: return infer_constraints(template, actual.fallback, self.direction) else: return []
def analyze_class_attribute_access(itype: Instance, name: str, mx: MemberContext) -> Optional[Type]: """original_type is the type of E in the expression E.var""" node = itype.type.get(name) if not node: if itype.type.fallback_to_any: return AnyType(TypeOfAny.special_form) return None is_decorated = isinstance(node.node, Decorator) is_method = is_decorated or isinstance(node.node, FuncBase) if mx.is_lvalue: if is_method: mx.msg.cant_assign_to_method(mx.context) if isinstance(node.node, TypeInfo): mx.msg.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, mx.context) # If a final attribute was declared on `self` in `__init__`, then it # can't be accessed on the class object. if node.implicit and isinstance(node.node, Var) and node.node.is_final: mx.msg.fail(message_registry.CANNOT_ACCESS_FINAL_INSTANCE_ATTR .format(node.node.name()), mx.context) # An assignment to final attribute on class object is also always an error, # independently of types. if mx.is_lvalue and not mx.chk.get_final_context(): check_final_member(name, itype.type, mx.msg, mx.context) if itype.type.is_enum and not (mx.is_lvalue or is_decorated or is_method): return itype t = node.type if t: if isinstance(t, PartialType): symnode = node.node assert isinstance(symnode, Var) return mx.chk.handle_partial_var_type(t, mx.is_lvalue, symnode, mx.context) # Find the class where method/variable was defined. if isinstance(node.node, Decorator): super_info = node.node.var.info # type: Optional[TypeInfo] elif isinstance(node.node, (Var, SYMBOL_FUNCBASE_TYPES)): super_info = node.node.info else: super_info = None # Map the type to how it would look as a defining class. For example: # class C(Generic[T]): ... # class D(C[Tuple[T, S]]): ... # D[int, str].method() # Here itype is D[int, str], isuper is C[Tuple[int, str]]. if not super_info: isuper = None else: isuper = map_instance_to_supertype(itype, super_info) if isinstance(node.node, Var): assert isuper is not None # Check if original variable type has type variables. For example: # class C(Generic[T]): # x: T # C.x # Error, ambiguous access # C[int].x # Also an error, since C[int] is same as C at runtime if isinstance(t, TypeVarType) or get_type_vars(t): # Exception: access on Type[...], including first argument of class methods is OK. if not isinstance(mx.original_type, TypeType): mx.msg.fail(message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS, mx.context) # Erase non-mapped variables, but keep mapped ones, even if there is an error. # In the above example this means that we infer following types: # C.x -> Any # C[int].x -> int t = erase_typevars(expand_type_by_instance(t, isuper)) is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class) or (isinstance(node.node, FuncBase) and node.node.is_class)) result = add_class_tvars(t, itype, isuper, is_classmethod, mx.builtin_type, mx.original_type) if not mx.is_lvalue: result = analyze_descriptor_access(mx.original_type, result, mx.builtin_type, mx.msg, mx.context, chk=mx.chk) return result elif isinstance(node.node, Var): mx.not_ready_callback(name, mx.context) return AnyType(TypeOfAny.special_form) if isinstance(node.node, TypeVarExpr): mx.msg.fail(message_registry.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format( itype.type.name(), name), mx.context) return AnyType(TypeOfAny.from_error) if isinstance(node.node, TypeInfo): return type_object_type(node.node, mx.builtin_type) if isinstance(node.node, MypyFile): # Reference to a module object. return mx.builtin_type('types.ModuleType') if isinstance(node.node, TypeAlias) and isinstance(node.node.target, Instance): return instance_alias_type(node.node, mx.builtin_type) if is_decorated: assert isinstance(node.node, Decorator) if node.node.type: return node.node.type else: mx.not_ready_callback(name, mx.context) return AnyType(TypeOfAny.from_error) else: return function_type(cast(FuncBase, node.node), mx.builtin_type('builtins.function'))
def visit_instance(self, template: Instance) -> List[Constraint]: original_actual = actual = self.actual res = [] # type: List[Constraint] if isinstance(actual, CallableType) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, TypedDictType): actual = actual.as_anonymous().fallback if isinstance(actual, Instance): instance = actual # We always try nominal inference if possible, # it is much faster than the structural one. if (self.direction == SUBTYPE_OF and template.type.has_base(instance.type.fullname())): mapped = map_instance_to_supertype(template, instance.type) for i in range(len(instance.args)): # The constraints for generic type parameters are # invariant. Include constraints from both directions # to achieve the effect. res.extend(infer_constraints( mapped.args[i], instance.args[i], self.direction)) res.extend(infer_constraints( mapped.args[i], instance.args[i], neg_op(self.direction))) return res elif (self.direction == SUPERTYPE_OF and instance.type.has_base(template.type.fullname())): mapped = map_instance_to_supertype(instance, template.type) for j in range(len(template.args)): # The constraints for generic type parameters are # invariant. res.extend(infer_constraints( template.args[j], mapped.args[j], self.direction)) res.extend(infer_constraints( template.args[j], mapped.args[j], neg_op(self.direction))) return res if (template.type.is_protocol and self.direction == SUPERTYPE_OF and # We avoid infinite recursion for structural subtypes by checking # whether this type already appeared in the inference chain. # This is a conservative way break the inference cycles. # It never produces any "false" constraints but gives up soon # on purely structural inference cycles, see #3829. not any(is_same_type(template, t) for t in template.type.inferring) and mypy.subtypes.is_subtype(instance, erase_typevars(template))): template.type.inferring.append(template) self.infer_constraints_from_protocol_members(res, instance, template, original_actual, template) template.type.inferring.pop() return res elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and # We avoid infinite recursion for structural subtypes also here. not any(is_same_type(instance, i) for i in instance.type.inferring) and mypy.subtypes.is_subtype(erase_typevars(template), instance)): instance.type.inferring.append(instance) self.infer_constraints_from_protocol_members(res, instance, template, template, instance) instance.type.inferring.pop() return res if isinstance(actual, AnyType): # IDEA: Include both ways, i.e. add negation as well? return self.infer_against_any(template.args, actual) if (isinstance(actual, TupleType) and (is_named_instance(template, 'typing.Iterable') or is_named_instance(template, 'typing.Container') or is_named_instance(template, 'typing.Sequence') or is_named_instance(template, 'typing.Reversible')) and self.direction == SUPERTYPE_OF): for item in actual.items: cb = infer_constraints(template.args[0], item, SUPERTYPE_OF) res.extend(cb) return res elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: return infer_constraints(template, actual.fallback, self.direction) else: return []
def visit_instance(self, template: Instance) -> List[Constraint]: original_actual = actual = self.actual res = [] # type: List[Constraint] if isinstance(actual, CallableType) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, TypedDictType): actual = actual.as_anonymous().fallback if isinstance(actual, Instance): instance = actual # We always try nominal inference if possible, # it is much faster than the structural one. if (self.direction == SUBTYPE_OF and template.type.has_base(instance.type.fullname())): mapped = map_instance_to_supertype(template, instance.type) for i in range(len(instance.args)): # The constraints for generic type parameters are # invariant. Include constraints from both directions # to achieve the effect. res.extend( infer_constraints(mapped.args[i], instance.args[i], self.direction)) res.extend( infer_constraints(mapped.args[i], instance.args[i], neg_op(self.direction))) return res elif (self.direction == SUPERTYPE_OF and instance.type.has_base(template.type.fullname())): mapped = map_instance_to_supertype(instance, template.type) for j in range(len(template.args)): # The constraints for generic type parameters are # invariant. res.extend( infer_constraints(template.args[j], mapped.args[j], self.direction)) res.extend( infer_constraints(template.args[j], mapped.args[j], neg_op(self.direction))) return res if (template.type.is_protocol and self.direction == SUPERTYPE_OF and # We avoid infinite recursion for structural subtypes by checking # whether this type already appeared in the inference chain. # This is a conservative way break the inference cycles. # It never produces any "false" constraints but gives up soon # on purely structural inference cycles, see #3829. not any( is_same_type(template, t) for t in template.type.inferring) and mypy.subtypes.is_subtype(instance, erase_typevars(template))): template.type.inferring.append(template) self.infer_constraints_from_protocol_members( res, instance, template, original_actual, template) template.type.inferring.pop() return res elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and # We avoid infinite recursion for structural subtypes also here. not any( is_same_type(instance, i) for i in instance.type.inferring) and mypy.subtypes.is_subtype(erase_typevars(template), instance)): instance.type.inferring.append(instance) self.infer_constraints_from_protocol_members( res, instance, template, template, instance) instance.type.inferring.pop() return res if isinstance(actual, AnyType): # IDEA: Include both ways, i.e. add negation as well? return self.infer_against_any(template.args, actual) if (isinstance(actual, TupleType) and (is_named_instance(template, 'typing.Iterable') or is_named_instance(template, 'typing.Container') or is_named_instance(template, 'typing.Sequence') or is_named_instance(template, 'typing.Reversible')) and self.direction == SUPERTYPE_OF): for item in actual.items: cb = infer_constraints(template.args[0], item, SUPERTYPE_OF) res.extend(cb) return res elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: return infer_constraints(template, actual.fallback, self.direction) else: return []
tvar = translate_runtime_type_vars_locally(tvar) # Build the rvalue (initializer) expression return TypeExpr(tvar) FuncDef make_type_object_wrapper(self, TypeDef tdef): """Construct dynamically typed wrapper function for a class. It simple calls the type object and returns the result. """ # TODO keyword args, default args and varargs # TODO overloads type_sig = (Callable)type_object_type(tdef.info, None) type_sig = (Callable)erasetype.erase_typevars(type_sig) init = (FuncDef)tdef.info.get_method('__init__') arg_kinds = type_sig.arg_kinds # The wrapper function has a dynamically typed signature. wrapper_sig = Callable(<Type> [Any()] * len(arg_kinds), arg_kinds, <str> [None] * len(arg_kinds), Any(), False) n = NameExpr(tdef.name) # TODO full name args = self.func_tf.call_args( init.args[1:], type_sig, wrapper_sig,