From 4378fb8360577e09f20b70133a3a169c8f2be631 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 17 Aug 2019 12:05:31 +0100 Subject: [PATCH 01/12] Clean-up typeanal --- misc/proper_plugin.py | 41 ++++++++++++++++++++++++++++------------- mypy/typeanal.py | 21 ++++++++++++++------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/misc/proper_plugin.py b/misc/proper_plugin.py index c38fbc023967..444cf56b7ac4 100644 --- a/misc/proper_plugin.py +++ b/misc/proper_plugin.py @@ -1,5 +1,8 @@ from mypy.plugin import Plugin, FunctionContext -from mypy.types import Type, Instance, CallableType, UnionType, get_proper_type +from mypy.types import ( + Type, Instance, CallableType, UnionType, get_proper_type, ProperType, + get_proper_types, TupleType +) import os.path from typing_extensions import Type as typing_Type @@ -10,8 +13,7 @@ 'checkexpr.py', 'checkmember.py', 'messages.py', - 'semanal.py', - 'typeanal.py' + 'semanal.py' ] @@ -40,21 +42,34 @@ def isinstance_proper_hook(ctx: FunctionContext) -> Type: for arg in ctx.arg_types[0]: if is_improper_type(arg): right = get_proper_type(ctx.arg_types[1][0]) - if isinstance(right, CallableType) and right.is_type_obj(): - if right.type_object().fullname() in ('mypy.types.Type', - 'mypy.types.ProperType', - 'mypy.types.TypeAliasType'): - # Special case: things like assert isinstance(typ, ProperType) are always OK. - return ctx.default_return_type - if right.type_object().fullname() in ('mypy.types.UnboundType', - 'mypy.types.TypeVarType'): - # Special case: these are not valid targets for a type alias and thus safe. - return ctx.default_return_type + if is_special_target(right): + return ctx.default_return_type ctx.api.fail('Never apply isinstance() to unexpanded types;' ' use mypy.types.get_proper_type() first', ctx.context) return ctx.default_return_type +def is_special_target(right: ProperType) -> bool: + if isinstance(right, CallableType) and right.is_type_obj(): + if right.type_object().fullname() in ('mypy.types.Type', + 'mypy.types.ProperType', + 'mypy.types.TypeAliasType'): + # Special case: things like assert isinstance(typ, ProperType) are always OK. + return True + if right.type_object().fullname() in ('mypy.types.UnboundType', + 'mypy.types.TypeVarType', + 'mypy.types.EllipsisType', + 'mypy.types.StarType', + 'mypy.types.TypeList', + 'mypy.types.CallableArgument'): + # Special case: these are not valid targets for a type alias and thus safe. + # TODO: introduce a SyntheticType base to simplify this? + return True + elif isinstance(right, TupleType): + return all(is_special_target(t) for t in get_proper_types(right.items)) + return False + + def is_improper_type(typ: Type) -> bool: """Is this a type that is not a subtype of ProperType?""" typ = get_proper_type(typ) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 49014cf7c449..0d94c611dd9a 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -16,7 +16,7 @@ CallableType, NoneType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType, replace_alias_tvars, CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, - LiteralType, RawExpressionType, PlaceholderType + LiteralType, RawExpressionType, PlaceholderType, get_proper_type ) from mypy.nodes import ( @@ -216,6 +216,9 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) disallow_any=disallow_any) # The only case where expand_type_alias() can return an incorrect instance is # when it is top-level instance, so no need to recurse. + # TODO: this is not really needed, since with the new logic we will not expand + # aliases immediately. + res = get_proper_type(res) if (isinstance(res, Instance) and len(res.args) != len(res.type.type_vars) and not self.defining_alias): fix_instance(res, self.fail, disallow_any=disallow_any, use_generic_error=True, @@ -374,9 +377,11 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl # context. This is slightly problematic as it allows using the type 'Any' # as a base class -- however, this will fail soon at runtime so the problem # is pretty minor. - if isinstance(sym.node, Var) and isinstance(sym.node.type, AnyType): - return AnyType(TypeOfAny.from_unimported_type, - missing_import_name=sym.node.type.missing_import_name) + if isinstance(sym.node, Var): + typ = get_proper_type(sym.node.type) + if isinstance(typ, AnyType): + return AnyType(TypeOfAny.from_unimported_type, + missing_import_name=typ.missing_import_name) # Option 2: # Unbound type variable. Currently these may be still valid, # for example when defining a generic type alias. @@ -694,6 +699,7 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> Optional[L # Literal[...] cannot contain Any. Give up and add an error message # (if we haven't already). + arg = get_proper_type(arg) if isinstance(arg, AnyType): # Note: We can encounter Literals containing 'Any' under three circumstances: # @@ -954,10 +960,10 @@ def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type], unexpanded_type=unexpanded_type) if exp_len == 0 and act_len == 0: if no_args: - assert isinstance(target, Instance) + assert isinstance(target, Instance) # type: ignore return Instance(target.type, [], line=ctx.line, column=ctx.column) return target - if exp_len == 0 and act_len > 0 and isinstance(target, Instance) and no_args: + if exp_len == 0 and act_len > 0 and isinstance(target, Instance) and no_args: # type: ignore tp = Instance(target.type, args) tp.line = ctx.line tp.column = ctx.column @@ -969,7 +975,7 @@ def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type], ctx.line, ctx.column, from_error=True) typ = replace_alias_tvars(target, alias_tvars, args, ctx.line, ctx.column) # HACK: Implement FlexibleAlias[T, typ] by expanding it to typ here. - if (isinstance(typ, Instance) + if (isinstance(typ, Instance) # type: ignore and typ.type.fullname() == 'mypy_extensions.FlexibleAlias'): typ = typ.args[-1] return typ @@ -1151,6 +1157,7 @@ def make_optional_type(t: Type) -> Type: is called during semantic analysis and simplification only works during type checking. """ + t = get_proper_type(t) if isinstance(t, NoneType): return t elif isinstance(t, UnionType): From 56bd1890202b470cc41bb1b1ca2aacdd1ac5f7b8 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 17 Aug 2019 12:57:47 +0100 Subject: [PATCH 02/12] Clean-up semanal --- misc/proper_plugin.py | 8 +----- mypy/fastparse2.py | 4 ++- mypy/messages.py | 10 ++++++-- mypy/semanal.py | 58 +++++++++++++++++++++++++++---------------- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/misc/proper_plugin.py b/misc/proper_plugin.py index 444cf56b7ac4..4c3abc410695 100644 --- a/misc/proper_plugin.py +++ b/misc/proper_plugin.py @@ -8,13 +8,7 @@ from typing_extensions import Type as typing_Type from typing import Optional, Callable -FILE_WHITELIST = [ - 'checker.py', - 'checkexpr.py', - 'checkmember.py', - 'messages.py', - 'semanal.py' -] +FILE_WHITELIST = [] class ProperTypePlugin(Plugin): diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 1d6734afaad3..012b3aa44b89 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -41,6 +41,7 @@ ) from mypy.types import ( Type, CallableType, AnyType, UnboundType, EllipsisType, TypeOfAny, Instance, + ProperType ) from mypy import message_registry, errorcodes as codes from mypy.errors import Errors @@ -223,7 +224,8 @@ def translate_stmt_list(self, res.append(node) return res - def translate_type_comment(self, n: ast27.stmt, type_comment: Optional[str]) -> Optional[Type]: + def translate_type_comment(self, n: ast27.stmt, + type_comment: Optional[str]) -> Optional[ProperType]: if type_comment is None: return None else: diff --git a/mypy/messages.py b/mypy/messages.py index c5c59d469e66..b5c9f8706eef 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -22,7 +22,7 @@ from mypy.types import ( Type, CallableType, Instance, TypeVarType, TupleType, TypedDictType, LiteralType, UnionType, NoneType, AnyType, Overloaded, FunctionLike, DeletedType, TypeType, - UninhabitedType, TypeOfAny, UnboundType, PartialType, + UninhabitedType, TypeOfAny, UnboundType, PartialType, get_proper_type ) from mypy.typetraverser import TypeTraverserVisitor from mypy.nodes import ( @@ -173,6 +173,9 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont If member corresponds to an operator, use the corresponding operator name in the messages. Return type Any. """ + original_type = get_proper_type(original_type) + typ = get_proper_type(typ) + if (isinstance(original_type, Instance) and original_type.type.has_readable_member(member)): self.fail('Member "{}" is not assignable'.format(member), context) @@ -312,6 +315,8 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: that corresponds to an operator, use the corresponding operator name in the messages. """ + arg_type = get_proper_type(arg_type) + target = '' callee_name = callable_name(callee) if callee_name is not None: @@ -449,8 +454,9 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: arg_label, target, quote_type_string(arg_type_str), quote_type_string(expected_type_str)) code = codes.ARG_TYPE + expected_type = get_proper_type(expected_type) if isinstance(expected_type, UnionType): - expected_types = list(expected_type.items) # type: List[Type] + expected_types = list(expected_type.items) else: expected_types = [expected_type] for type in expected_types: diff --git a/mypy/semanal.py b/mypy/semanal.py index 2664d40ddb2f..7dbdb8fbfe01 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -86,7 +86,8 @@ from mypy.types import ( FunctionLike, UnboundType, TypeVarDef, TupleType, UnionType, StarType, function_type, CallableType, Overloaded, Instance, Type, AnyType, LiteralType, LiteralValue, - TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType + TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType, + get_proper_type, get_proper_types ) from mypy.type_visitor import TypeQuery from mypy.nodes import implicit_module_attrs @@ -562,7 +563,7 @@ def analyze_func_def(self, defn: FuncDef) -> None: defn.info = self.type if defn.type is not None and defn.name() in ('__init__', '__init_subclass__'): assert isinstance(defn.type, CallableType) - if isinstance(defn.type.ret_type, AnyType): + if isinstance(get_proper_type(defn.type.ret_type), AnyType): defn.type = defn.type.copy_modified(ret_type=NoneType()) self.prepare_method_signature(defn, self.type) @@ -613,7 +614,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: elif isinstance(functype, CallableType): if func.name() == '__init_subclass__': func.is_class = True - self_type = functype.arg_types[0] + self_type = get_proper_type(functype.arg_types[0]) if isinstance(self_type, AnyType): leading_type = fill_typevars(info) # type: Type if func.is_class or func.name() == '__new__': @@ -907,14 +908,14 @@ def analyze_function_body(self, defn: FuncItem) -> None: self.leave() self.function_stack.pop() - def check_classvar_in_signature(self, typ: Type) -> None: + def check_classvar_in_signature(self, typ: ProperType) -> None: if isinstance(typ, Overloaded): - for t in typ.items(): # type: Type + for t in typ.items(): # type: ProperType self.check_classvar_in_signature(t) return if not isinstance(typ, CallableType): return - for t in typ.arg_types + [typ.ret_type]: + for t in get_proper_types(typ.arg_types) + [get_proper_type(typ.ret_type)]: if self.is_classvar(t): self.fail_invalid_classvar(t) # Show only one error per signature @@ -1384,7 +1385,7 @@ def get_name_repr_of_expr(self, expr: Expression) -> Optional[str]: def analyze_base_classes( self, - base_type_exprs: List[Expression]) -> Optional[Tuple[List[Tuple[Type, Expression]], + base_type_exprs: List[Expression]) -> Optional[Tuple[List[Tuple[ProperType, Expression]], bool]]: """Analyze base class types. @@ -1417,12 +1418,13 @@ def analyze_base_classes( continue if base is None: return None + base = get_proper_type(base) bases.append((base, base_expr)) return bases, is_error def configure_base_classes(self, defn: ClassDef, - bases: List[Tuple[Type, Expression]]) -> None: + bases: List[Tuple[ProperType, Expression]]) -> None: """Set up base classes. This computes several attributes on the corresponding TypeInfo defn.info @@ -1621,7 +1623,7 @@ def analyze_metaclass(self, defn: ClassDef) -> None: if sym is None: # Probably a name error - it is already handled elsewhere return - if isinstance(sym.node, Var) and isinstance(sym.node.type, AnyType): + if isinstance(sym.node, Var) and isinstance(get_proper_type(sym.node.type), AnyType): # 'Any' metaclass -- just ignore it. # # TODO: A better approach would be to record this information @@ -2467,7 +2469,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # When this type alias gets "inlined", the Any is not explicit anymore, # so we need to replace it with non-explicit Anys. res = make_any_non_explicit(res) - no_args = isinstance(res, Instance) and not res.args + no_args = isinstance(res, Instance) and not res.args # type: ignore fix_instance_types(res, self.fail) if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)` s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars, no_args) @@ -2761,6 +2763,7 @@ def store_declared_types(self, lvalue: Lvalue, typ: Type) -> None: var.is_ready = True # If node is not a variable, we'll catch it elsewhere. elif isinstance(lvalue, TupleExpr): + typ = get_proper_type(typ) if isinstance(typ, TupleType): if len(lvalue.items) != len(typ.items): self.fail('Incompatible number of tuple items', lvalue) @@ -2939,7 +2942,7 @@ def process_typevar_parameters(self, args: List[Expression], # class Custom(Generic[T]): # ... analyzed = PlaceholderType(None, [], context.line) - upper_bound = analyzed + upper_bound = get_proper_type(analyzed) if isinstance(upper_bound, AnyType) and upper_bound.is_from_error: self.fail("TypeVar 'bound' must be a type", param_value) # Note: we do not return 'None' here -- we want to continue @@ -3231,6 +3234,7 @@ def visit_with_stmt(self, s: WithStmt) -> None: types = [] # type: List[Type] if s.unanalyzed_type: + assert isinstance(s.unanalyzed_type, ProperType) actual_targets = [t for t in s.target if t is not None] if len(actual_targets) == 0: # We have a type for no targets @@ -3570,6 +3574,8 @@ def visit_member_expr(self, expr: MemberExpr) -> None: if formal_arg and formal_arg.pos == 0: type_info = self.type elif isinstance(base.node, TypeAlias) and base.node.no_args: + assert isinstance(base.node.target, ProperType) + # TODO: support chained aliases. if isinstance(base.node.target, Instance): type_info = base.node.target.type @@ -3632,8 +3638,9 @@ def analyze_type_application(self, expr: IndexExpr) -> None: # subscripted either via type alias... if isinstance(base, RefExpr) and isinstance(base.node, TypeAlias): alias = base.node - if isinstance(alias.target, Instance): - name = alias.target.type.fullname() + target = get_proper_type(alias.target) + if isinstance(target, Instance): + name = target.type.fullname() if (alias.no_args and # this avoids bogus errors for already reported aliases name in nongen_builtins and not alias.normalized): self.fail(no_subscript_builtin_alias(name, propose_alt=False), expr) @@ -3917,9 +3924,11 @@ def lookup_qualified(self, name: str, ctx: Context, elif isinstance(node, PlaceholderNode): return sym else: - if isinstance(node, Var) and isinstance(node.type, AnyType): - # Allow access through Var with Any type without error. - return self.implicit_symbol(sym, name, parts[i:], node.type) + if isinstance(node, Var): + typ = get_proper_type(node.type) + if isinstance(typ, AnyType): + # Allow access through Var with Any type without error. + return self.implicit_symbol(sym, name, parts[i:], typ) # Lookup through invalid node, such as variable or function nextsym = None if not nextsym or nextsym.module_hidden: @@ -4002,8 +4011,9 @@ def create_getattr_var(self, getattr_defn: SymbolTableNode, avoid. """ if isinstance(getattr_defn.node, (FuncDef, Var)): - if isinstance(getattr_defn.node.type, CallableType): - typ = getattr_defn.node.type.ret_type + node_type = get_proper_type(getattr_defn.node.type) + if isinstance(node_type, CallableType): + typ = node_type.ret_type else: typ = AnyType(TypeOfAny.from_error) v = Var(name, type=typ) @@ -4081,7 +4091,7 @@ def named_type_or_none(self, qualified_name: str, return None node = sym.node if isinstance(node, TypeAlias): - assert isinstance(node.target, Instance) + assert isinstance(node.target, Instance) # type: ignore node = node.target.type assert isinstance(node, TypeInfo), node if args is not None: @@ -4776,9 +4786,13 @@ def refers_to_fullname(node: Expression, fullname: str) -> bool: """Is node a name or member expression with the given full name?""" if not isinstance(node, RefExpr): return False - return (node.fullname == fullname or - isinstance(node.node, TypeAlias) and isinstance(node.node.target, Instance) - and node.node.target.type.fullname() == fullname) + if node.fullname == fullname: + return True + if isinstance(node.node, TypeAlias): + target = get_proper_type(node.node.target) + if isinstance(target, Instance) and target.type.fullname() == fullname: + return True + return False def refers_to_class_or_function(node: Expression) -> bool: From 8dae2163bfb7ad3adf3983b754dbd69e7e5febf6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 17 Aug 2019 13:51:19 +0100 Subject: [PATCH 03/12] Clean-up checkmember --- misc/proper_plugin.py | 4 ++- mypy/checkmember.py | 59 ++++++++++++++++++++++++++++--------------- mypy/typeanal.py | 6 ++--- mypy/types.py | 4 +-- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/misc/proper_plugin.py b/misc/proper_plugin.py index 4c3abc410695..73a67a02fc9d 100644 --- a/misc/proper_plugin.py +++ b/misc/proper_plugin.py @@ -55,7 +55,9 @@ def is_special_target(right: ProperType) -> bool: 'mypy.types.EllipsisType', 'mypy.types.StarType', 'mypy.types.TypeList', - 'mypy.types.CallableArgument'): + 'mypy.types.CallableArgument', + 'mypy.types.PartialType', + 'mypy.types.ErasedType'): # Special case: these are not valid targets for a type alias and thus safe. # TODO: introduce a SyntheticType base to simplify this? return True diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 2a9d87564275..ccb7e8d7ad83 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -6,7 +6,7 @@ from mypy.types import ( Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef, Overloaded, TypeVarType, UnionType, PartialType, UninhabitedType, TypeOfAny, LiteralType, - DeletedType, NoneType, TypeType, function_type, get_type_vars, get_proper_type + DeletedType, NoneType, TypeType, function_type, get_type_vars, get_proper_type, ProperType ) from mypy.nodes import ( TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, TypeVarExpr, @@ -114,8 +114,10 @@ def analyze_member_access(name: str, chk=chk, self_type=self_type) result = _analyze_member_access(name, typ, mx, override_info) - if in_literal_context and isinstance(result, Instance) and result.last_known_value is not None: - return result.last_known_value + possible_literal = get_proper_type(result) + if (in_literal_context and isinstance(possible_literal, Instance) and + possible_literal.last_known_value is not None): + return possible_literal.last_known_value else: return result @@ -126,6 +128,7 @@ def _analyze_member_access(name: str, override_info: Optional[TypeInfo] = None) -> Type: # TODO: This and following functions share some logic with subtypes.find_member; # consider refactoring. + typ = get_proper_type(typ) if isinstance(typ, Instance): return analyze_instance_member_access(name, typ, mx, override_info) elif isinstance(typ, AnyType): @@ -212,6 +215,7 @@ def analyze_type_callable_member_access(name: str, # 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): @@ -253,8 +257,9 @@ def analyze_type_type_member_access(name: str, mx = mx.copy_modified(messages=ignore_messages) return _analyze_member_access(name, fallback, mx, override_info) elif isinstance(typ.item, TypeVarType): - if isinstance(typ.item.upper_bound, Instance): - item = typ.item.upper_bound + upper_bound = get_proper_type(typ.item.upper_bound) + if isinstance(upper_bound, Instance): + item = upper_bound elif isinstance(typ.item, TupleType): item = tuple_fallback(typ.item) elif isinstance(typ.item, FunctionLike) and typ.item.is_type_obj(): @@ -267,7 +272,7 @@ def analyze_type_type_member_access(name: str, # See comment above for why operators are skipped result = analyze_class_attribute_access(item, name, mx, override_info) if result: - if not (isinstance(result, AnyType) and item.type.fallback_to_any): + if not (isinstance(get_proper_type(result), AnyType) and item.type.fallback_to_any): return result else: # We don't want errors on metaclass lookup for classes with Any fallback @@ -329,7 +334,7 @@ def analyze_member_var_access(name: str, v = Var(name, type=type_object_type(vv, mx.builtin_type)) v.info = info - if isinstance(vv, TypeAlias) and isinstance(vv.target, Instance): + if isinstance(vv, TypeAlias) and isinstance(get_proper_type(vv.target), Instance): # Similar to the above TypeInfo case, we allow using # qualified type aliases in runtime context if it refers to an # instance type. For example: @@ -423,6 +428,9 @@ def analyze_descriptor_access(instance_type: Type, Return: The return type of the appropriate ``__get__`` overload for the descriptor. """ + instance_type = get_proper_type(instance_type) + descriptor_type = get_proper_type(descriptor_type) + if isinstance(descriptor_type, UnionType): # Map the access over union types return UnionType.make_simplified_union([ @@ -461,6 +469,7 @@ def analyze_descriptor_access(instance_type: Type, [TempNode(instance_type), TempNode(TypeType.make_normalized(owner_type))], [ARG_POS, ARG_POS], context) + inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type) if isinstance(inferred_dunder_get_type, AnyType): # check_call failed, and will have reported an error return inferred_dunder_get_type @@ -478,8 +487,9 @@ def instance_alias_type(alias: TypeAlias, As usual, we first erase any unbound type variables to Any. """ - assert isinstance(alias.target, Instance), "Must be called only with aliases to classes" - target = set_any_tvars(alias.target, alias.alias_tvars, alias.line, alias.column) + target = get_proper_type(alias.target) + assert isinstance(target, Instance), "Must be called only with aliases to classes" + target = set_any_tvars(target, alias.alias_tvars, alias.line, alias.column) assert isinstance(target, Instance) tp = type_object_type(target.type, builtin_type) return expand_type_by_instance(tp, target) @@ -557,7 +567,7 @@ def analyze_var(name: str, return result -def freeze_type_vars(member_type: Type) -> None: +def freeze_type_vars(member_type: ProperType) -> None: if isinstance(member_type, CallableType): for v in member_type.variables: v.id.meta_level = 0 @@ -677,7 +687,7 @@ def analyze_class_attribute_access(itype: Instance, # 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): + if not isinstance(get_proper_type(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. @@ -688,8 +698,8 @@ def analyze_class_attribute_access(itype: Instance, 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) + result = add_class_tvars(get_proper_type(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) @@ -710,7 +720,8 @@ def analyze_class_attribute_access(itype: Instance, # Reference to a module object. return mx.builtin_type('types.ModuleType') - if isinstance(node.node, TypeAlias) and isinstance(node.node.target, Instance): + 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: @@ -724,7 +735,8 @@ def analyze_class_attribute_access(itype: Instance, return function_type(cast(FuncBase, node.node), mx.builtin_type('builtins.function')) -def add_class_tvars(t: Type, itype: Instance, isuper: Optional[Instance], is_classmethod: bool, +def add_class_tvars(t: ProperType, itype: Instance, isuper: Optional[Instance], + is_classmethod: bool, builtin_type: Callable[[str], Instance], original_type: Type) -> Type: """Instantiate type variables during analyze_class_attribute_access, @@ -840,6 +852,7 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> if isinstance(method, FuncBase): t = function_type(method, fallback) else: + assert isinstance(method.type, ProperType) assert isinstance(method.type, FunctionLike) # is_valid_constructor() ensures this t = method.type return type_object_type_from_function(t, info, method.info, fallback, is_new) @@ -854,7 +867,7 @@ def is_valid_constructor(n: Optional[SymbolNode]) -> bool: if isinstance(n, FuncBase): return True if isinstance(n, Decorator): - return isinstance(n.type, FunctionLike) + return isinstance(get_proper_type(n.type), FunctionLike) return False @@ -899,7 +912,8 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, variables.extend(info.defn.type_vars) variables.extend(init_type.variables) - if is_new and isinstance(init_type.ret_type, (Instance, TupleType)): + 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) @@ -982,7 +996,7 @@ class B(A): pass # TODO: infer bounds on the type of *args? return cast(F, func) - self_param_type = func.arg_types[0] + 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))): @@ -990,14 +1004,16 @@ class B(A): pass # 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 = infer_type_arguments(ids, self_param_type, original_type)[0] + 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 = infer_type_arguments(ids, self_param_type, TypeType(original_type))[0] + 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 @@ -1010,6 +1026,8 @@ def expand(target: Type) -> Type: 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, @@ -1022,6 +1040,7 @@ def expand(target: Type) -> Type: def erase_to_bound(t: Type) -> Type: + t = get_proper_type(t) if isinstance(t, TypeVarType): return t.upper_bound if isinstance(t, TypeType): diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 0d94c611dd9a..50e42ef8b1b7 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -16,7 +16,7 @@ CallableType, NoneType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType, replace_alias_tvars, CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, - LiteralType, RawExpressionType, PlaceholderType, get_proper_type + LiteralType, RawExpressionType, PlaceholderType, get_proper_type, ProperType ) from mypy.nodes import ( @@ -973,7 +973,7 @@ def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type], % (exp_len, act_len), ctx) return set_any_tvars(target, alias_tvars or [], ctx.line, ctx.column, from_error=True) - typ = replace_alias_tvars(target, alias_tvars, args, ctx.line, ctx.column) + typ = replace_alias_tvars(target, alias_tvars, args, ctx.line, ctx.column) # type: Type # HACK: Implement FlexibleAlias[T, typ] by expanding it to typ here. if (isinstance(typ, Instance) # type: ignore and typ.type.fullname() == 'mypy_extensions.FlexibleAlias'): @@ -986,7 +986,7 @@ def set_any_tvars(tp: Type, vars: List[str], from_error: bool = False, disallow_any: bool = False, fail: Optional[FailCallback] = None, - unexpanded_type: Optional[Type] = None) -> Type: + unexpanded_type: Optional[Type] = None) -> ProperType: if from_error or disallow_any: type_of_any = TypeOfAny.from_error else: diff --git a/mypy/types.py b/mypy/types.py index a47a45f0949a..c27bb7fefcec 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2288,7 +2288,7 @@ def callable_type(fdef: mypy.nodes.FuncItem, fallback: Instance, def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type], - newline: int, newcolumn: int) -> Type: + newline: int, newcolumn: int) -> ProperType: """Replace type variables in a generic type alias tp with substitutions subs resetting context. Length of subs should be already checked. """ @@ -2321,7 +2321,7 @@ def get_typ_args(tp: Type) -> List[Type]: return cast(List[Type], typ_args) -def set_typ_args(tp: Type, new_args: List[Type], line: int = -1, column: int = -1) -> Type: +def set_typ_args(tp: Type, new_args: List[Type], line: int = -1, column: int = -1) -> ProperType: """Return a copy of a parametrizable Type with arguments set to new_args.""" tp = get_proper_type(tp) # TODO: is this really needed? From f8f674d9550528d7fa337e1111d4f40092e23949 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 17 Aug 2019 15:14:42 +0100 Subject: [PATCH 04/12] Partially clean checker.py and checkexpr.py --- mypy/checker.py | 134 +++++++++++++++++++++++++----------------- mypy/checkexpr.py | 60 ++++++++++++------- mypy/checkmember.py | 2 +- mypy/plugins/attrs.py | 2 +- mypy/types.py | 4 +- 5 files changed, 125 insertions(+), 77 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index deb096c1c479..51eb1054b2d0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -35,7 +35,8 @@ Instance, NoneType, strip_type, TypeType, TypeOfAny, UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef, true_only, false_only, function_type, is_named_instance, union_items, TypeQuery, LiteralType, - is_optional, remove_optional, TypeTranslator, StarType + is_optional, remove_optional, TypeTranslator, StarType, get_proper_type, ProperType, + get_proper_types ) from mypy.sametypes import is_same_type from mypy.messages import ( @@ -621,6 +622,8 @@ def is_async_generator_return_type(self, typ: Type) -> bool: def get_generator_yield_type(self, return_type: Type, is_coroutine: bool) -> Type: """Given the declared return type of a generator (t), return the type it yields (ty).""" + return_type = get_proper_type(return_type) + if isinstance(return_type, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=return_type) elif (not self.is_generator_return_type(return_type, is_coroutine) @@ -648,6 +651,8 @@ def get_generator_yield_type(self, return_type: Type, is_coroutine: bool) -> Typ def get_generator_receive_type(self, return_type: Type, is_coroutine: bool) -> Type: """Given a declared generator return type (t), return the type its yield receives (tc).""" + return_type = get_proper_type(return_type) + if isinstance(return_type, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=return_type) elif (not self.is_generator_return_type(return_type, is_coroutine) @@ -673,6 +678,7 @@ def get_generator_receive_type(self, return_type: Type, is_coroutine: bool) -> T return NoneType() def get_coroutine_return_type(self, return_type: Type) -> Type: + return_type = get_proper_type(return_type) if isinstance(return_type, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=return_type) assert isinstance(return_type, Instance), "Should only be called on coroutine functions." @@ -681,6 +687,8 @@ def get_coroutine_return_type(self, return_type: Type) -> Type: def get_generator_return_type(self, return_type: Type, is_coroutine: bool) -> Type: """Given the declared return type of a generator (t), return the type it returns (tr).""" + return_type = get_proper_type(return_type) + if isinstance(return_type, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=return_type) elif not self.is_generator_return_type(return_type, is_coroutine): @@ -999,6 +1007,8 @@ def is_reverse_op_method(self, method_name: str) -> bool: def check_for_missing_annotations(self, fdef: FuncItem) -> None: # Check for functions with unspecified/not fully specified types. def is_unannotated_any(t: Type) -> bool: + if not isinstance(t, ProperType): + return False return isinstance(t, AnyType) and t.type_of_any == TypeOfAny.unannotated has_explicit_annotation = (isinstance(fdef.type, CallableType) @@ -1131,7 +1141,7 @@ def check_reverse_op_method(self, defn: FuncItem, # With 'Any' or 'object' return type we are happy, since any possible # return value is valid. - ret_type = reverse_type.ret_type + ret_type = get_proper_type(reverse_type.ret_type) if isinstance(ret_type, AnyType): return if isinstance(ret_type, Instance): @@ -1147,9 +1157,9 @@ def check_reverse_op_method(self, defn: FuncItem, forward_name = '__div__' else: forward_name = nodes.normal_from_reverse_op[reverse_name] - forward_inst = reverse_type.arg_types[1] + forward_inst = get_proper_type(reverse_type.arg_types[1]) if isinstance(forward_inst, TypeVarType): - forward_inst = forward_inst.upper_bound + forward_inst = get_proper_type(forward_inst.upper_bound) elif isinstance(forward_inst, TupleType): forward_inst = tuple_fallback(forward_inst) elif isinstance(forward_inst, (FunctionLike, TypedDictType, LiteralType)): @@ -1652,7 +1662,7 @@ def visit_class_def(self, defn: ClassDef) -> None: self.check_multiple_inheritance(typ) if defn.decorators: - sig = type_object_type(defn.info, self.named_type) + sig = type_object_type(defn.info, self.named_type) # type: Type # Decorators are applied in reverse order. for decorator in reversed(defn.decorators): if (isinstance(decorator, CallExpr) @@ -1765,8 +1775,8 @@ class C(B, A[int]): ... # this is unsafe because... return first = base1.names[name] second = base2.names[name] - first_type = self.determine_type_of_class_member(first) - second_type = self.determine_type_of_class_member(second) + first_type = get_proper_type(self.determine_type_of_class_member(first)) + second_type = get_proper_type(self.determine_type_of_class_member(second)) if (isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike)): @@ -1861,8 +1871,8 @@ def is_raising_or_empty(self, s: Statement) -> bool: return True elif isinstance(s.expr, CallExpr): self.expr_checker.msg.disable_errors() - typ = self.expr_checker.accept( - s.expr, allow_none_return=True, always_allow_any=True) + typ = get_proper_type(self.expr_checker.accept( + s.expr, allow_none_return=True, always_allow_any=True)) self.expr_checker.msg.enable_errors() if isinstance(typ, UninhabitedType): @@ -2342,7 +2352,7 @@ def check_multi_assignment(self, lvalues: List[Lvalue], # Infer the type of an ordinary rvalue expression. # TODO: maybe elsewhere; redundant. - rvalue_type = rv_type or self.expr_checker.accept(rvalue) + rvalue_type = get_proper_type(rv_type or self.expr_checker.accept(rvalue)) if isinstance(rvalue_type, UnionType): # If this is an Optional type in non-strict Optional code, unwrap it. @@ -2448,7 +2458,8 @@ def check_multi_assignment_from_tuple(self, lvalues: List[Lvalue], rvalue: Expre if not undefined_rvalue: # Infer rvalue again, now in the correct type context. lvalue_type = self.lvalue_type_for_inference(lvalues, rvalue_type) - reinferred_rvalue_type = self.expr_checker.accept(rvalue, lvalue_type) + reinferred_rvalue_type = get_proper_type(self.expr_checker.accept(rvalue, + lvalue_type)) if isinstance(reinferred_rvalue_type, UnionType): # If this is an Optional type in non-strict Optional code, unwrap it. @@ -2634,6 +2645,7 @@ def infer_variable_type(self, name: Var, lvalue: Lvalue, self.set_inferred_type(name, lvalue, init_type) def infer_partial_type(self, name: Var, lvalue: Lvalue, init_type: Type) -> bool: + init_type = get_proper_type(init_type) if isinstance(init_type, NoneType): partial_type = PartialType(None, name, [init_type]) elif isinstance(init_type, Instance): @@ -2853,6 +2865,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: return_type = self.get_coroutine_return_type(self.return_types[-1]) else: return_type = self.return_types[-1] + return_type = get_proper_type(return_type) if isinstance(return_type, UninhabitedType): self.fail(message_registry.NO_RETURN_EXPECTED, s) @@ -2999,7 +3012,7 @@ def visit_raise_stmt(self, s: RaiseStmt) -> None: def type_check_raise(self, e: Expression, s: RaiseStmt, optional: bool = False) -> None: - typ = self.expr_checker.accept(e) + typ = get_proper_type(self.expr_checker.accept(e)) if isinstance(typ, TypeType): if isinstance(typ.item, AnyType): return @@ -3122,7 +3135,7 @@ def check_except_handler_test(self, n: Expression) -> Type: all_types = [] # type: List[Type] test_types = self.get_types_from_except_handler(typ, n) - for ttype in test_types: + for ttype in get_proper_types(test_types): if isinstance(ttype, AnyType): all_types.append(ttype) continue @@ -3149,6 +3162,7 @@ def check_except_handler_test(self, n: Expression) -> Type: def get_types_from_except_handler(self, typ: Type, n: Expression) -> List[Type]: """Helper for check_except_handler_test to retrieve handler types.""" + typ = get_proper_type(typ) if isinstance(typ, TupleType): return typ.items elif isinstance(typ, UnionType): @@ -3432,6 +3446,8 @@ def partition_by_callable(self, typ: Type, Guaranteed to not return [], []. """ + typ = get_proper_type(typ) + if isinstance(typ, FunctionLike) or isinstance(typ, TypeType): return [typ], [] @@ -3694,44 +3710,48 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context, supertype.""" if is_subtype(subtype, supertype): return True - else: - if self.should_suppress_optional_error([subtype]): - return False - extra_info = [] # type: List[str] - note_msg = '' - notes = [] # type: List[str] - if subtype_label is not None or supertype_label is not None: - subtype_str, supertype_str = format_type_distinctly(subtype, supertype) - if subtype_label is not None: - extra_info.append(subtype_label + ' ' + subtype_str) - if supertype_label is not None: - extra_info.append(supertype_label + ' ' + supertype_str) - note_msg = make_inferred_type_note(context, subtype, - supertype, supertype_str) - if isinstance(subtype, Instance) and isinstance(supertype, Instance): - notes = append_invariance_notes([], subtype, supertype) - if extra_info: - msg += ' (' + ', '.join(extra_info) + ')' - self.fail(msg, context, code=code) - for note in notes: - self.msg.note(note, context) - if note_msg: - self.note(note_msg, context) - if (isinstance(supertype, Instance) and supertype.type.is_protocol and - isinstance(subtype, (Instance, TupleType, TypedDictType))): - self.msg.report_protocol_problems(subtype, supertype, context) - if isinstance(supertype, CallableType) and isinstance(subtype, Instance): - call = find_member('__call__', subtype, subtype) - if call: - self.msg.note_call(subtype, call, context) - if isinstance(subtype, (CallableType, Overloaded)) and isinstance(supertype, Instance): - if supertype.type.is_protocol and supertype.type.protocol_members == ['__call__']: - call = find_member('__call__', supertype, subtype) - assert call is not None - self.msg.note_call(supertype, call, context) + + subtype = get_proper_type(subtype) + supertype = get_proper_type(supertype) + + if self.should_suppress_optional_error([subtype]): return False + extra_info = [] # type: List[str] + note_msg = '' + notes = [] # type: List[str] + if subtype_label is not None or supertype_label is not None: + subtype_str, supertype_str = format_type_distinctly(subtype, supertype) + if subtype_label is not None: + extra_info.append(subtype_label + ' ' + subtype_str) + if supertype_label is not None: + extra_info.append(supertype_label + ' ' + supertype_str) + note_msg = make_inferred_type_note(context, subtype, + supertype, supertype_str) + if isinstance(subtype, Instance) and isinstance(supertype, Instance): + notes = append_invariance_notes([], subtype, supertype) + if extra_info: + msg += ' (' + ', '.join(extra_info) + ')' + self.fail(msg, context, code=code) + for note in notes: + self.msg.note(note, context) + if note_msg: + self.note(note_msg, context) + if (isinstance(supertype, Instance) and supertype.type.is_protocol and + isinstance(subtype, (Instance, TupleType, TypedDictType))): + self.msg.report_protocol_problems(subtype, supertype, context) + if isinstance(supertype, CallableType) and isinstance(subtype, Instance): + call = find_member('__call__', subtype, subtype) + if call: + self.msg.note_call(subtype, call, context) + if isinstance(subtype, (CallableType, Overloaded)) and isinstance(supertype, Instance): + if supertype.type.is_protocol and supertype.type.protocol_members == ['__call__']: + call = find_member('__call__', supertype, subtype) + assert call is not None + self.msg.note_call(supertype, call, context) + return False def contains_none(self, t: Type) -> bool: + t = get_proper_type(t) return ( isinstance(t, NoneType) or (isinstance(t, UnionType) and any(self.contains_none(ut) for ut in t.items)) or @@ -4085,6 +4105,8 @@ def builtin_item_type(tp: Type) -> Optional[Type]: Note: this is only OK for built-in containers, where we know the behavior of __contains__. """ + tp = get_proper_type(tp) + if isinstance(tp, Instance): if tp.type.fullname() in ['builtins.list', 'builtins.tuple', 'builtins.dict', 'builtins.set', 'builtins.frozenset']: @@ -4177,6 +4199,7 @@ def flatten(t: Expression) -> List[Expression]: def flatten_types(t: Type) -> List[Type]: """Flatten a nested sequence of tuples into one list of nodes.""" + t = get_proper_type(t) if isinstance(t, TupleType): return [b for a in t.items for b in flatten_types(a)] else: @@ -4185,14 +4208,14 @@ def flatten_types(t: Type) -> List[Type]: def get_isinstance_type(expr: Expression, type_map: Dict[Expression, Type]) -> Optional[List[TypeRange]]: - all_types = flatten_types(type_map[expr]) + all_types = get_proper_types(flatten_types(type_map[expr])) types = [] # type: List[TypeRange] for typ in all_types: if isinstance(typ, FunctionLike) and typ.is_type_obj(): # Type variables may be present -- erase them, which is the best # we can do (outside disallowing them here). - typ = erase_typevars(typ.items()[0].ret_type) - types.append(TypeRange(typ, is_upper_bound=False)) + erased_type = erase_typevars(typ.items()[0].ret_type) + types.append(TypeRange(erased_type, is_upper_bound=False)) elif isinstance(typ, TypeType): # Type[A] means "any type that is a subtype of A" rather than "precisely type A" # we indicate this by setting is_upper_bound flag @@ -4522,6 +4545,7 @@ def nothing() -> Iterator[None]: def is_typed_callable(c: Optional[Type]) -> bool: + c = get_proper_type(c) if not c or not isinstance(c, CallableType): return False return not all(isinstance(t, AnyType) and t.type_of_any == TypeOfAny.unannotated @@ -4529,6 +4553,7 @@ def is_typed_callable(c: Optional[Type]) -> bool: def is_untyped_decorator(typ: Optional[Type]) -> bool: + typ = get_proper_type(typ) if not typ: return True elif isinstance(typ, CallableType): @@ -4585,7 +4610,7 @@ def is_singleton_type(typ: Type) -> bool: return isinstance(typ, NoneType) or (isinstance(typ, LiteralType) and typ.is_enum_literal()) -def try_expanding_enum_to_union(typ: Type, target_fullname: str) -> Type: +def try_expanding_enum_to_union(typ: Type, target_fullname: str) -> ProperType: """Attempts to recursively expand any enum Instances with the given target_fullname into a Union of all of its component LiteralTypes. @@ -4604,6 +4629,8 @@ class Status(Enum): ...and if we call `try_expanding_enum_to_union(Union[Color, Status], 'module.Color')`, this function will return Literal[Color.RED, Color.BLUE, Color.YELLOW, Status]. """ + typ = get_proper_type(typ) + if isinstance(typ, UnionType): items = [try_expanding_enum_to_union(item, target_fullname) for item in typ.items] return UnionType.make_simplified_union(items) @@ -4626,10 +4653,11 @@ class Status(Enum): return typ -def coerce_to_literal(typ: Type) -> Type: +def coerce_to_literal(typ: Type) -> ProperType: """Recursively converts any Instances that have a last_known_value into the corresponding LiteralType. """ + typ = get_proper_type(typ) if isinstance(typ, UnionType): new_items = [coerce_to_literal(item) for item in typ.items] return UnionType.make_simplified_union(new_items) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 772da98bfbc3..b2124026378d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -18,7 +18,7 @@ TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, LiteralValue, true_only, false_only, is_named_instance, function_type, callable_type, FunctionLike, - StarType, is_optional, remove_optional, is_generic_instance, get_proper_type + StarType, is_optional, remove_optional, is_generic_instance, get_proper_type, ProperType ) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, @@ -342,6 +342,8 @@ def method_fullname(self, object_type: Type, method_name: str) -> Optional[str]: """Convert a method name to a fully qualified name, based on the type of the object that it is invoked on. Return `None` if the name of `object_type` cannot be determined. """ + object_type = get_proper_type(object_type) + if isinstance(object_type, CallableType) and object_type.is_type_obj(): # For class method calls, object_type is a callable representing the class object. # We "unwrap" it to a regular type, as the class/instance method difference doesn't @@ -734,6 +736,7 @@ def check_call(self, on which the method is being called """ arg_messages = arg_messages or self.msg + callee = get_proper_type(callee) if isinstance(callee, CallableType): return self.check_callable_call(callee, args, arg_kinds, context, arg_names, @@ -849,7 +852,7 @@ def check_callable_call(self, callee = callee.copy_modified(ret_type=ret_type) return callee.ret_type, callee - def analyze_type_type_callee(self, item: Type, context: Context) -> Type: + def analyze_type_type_callee(self, item: ProperType, context: Context) -> ProperType: """Analyze the callee X in X(...) where X is Type[item]. Return a Y that we can pass to check_call(Y, ...). @@ -869,15 +872,13 @@ def analyze_type_type_callee(self, item: Type, context: Context) -> Type: # i.e. its constructor (a poor approximation for reality, # but better than AnyType...), but replace the return type # with typevar. - callee = self.analyze_type_type_callee(item.upper_bound, - context) # type: Optional[Type] + callee = self.analyze_type_type_callee(get_proper_type(item.upper_bound), context) if isinstance(callee, CallableType): callee = callee.copy_modified(ret_type=item) elif isinstance(callee, Overloaded): callee = Overloaded([c.copy_modified(ret_type=item) for c in callee.items()]) - if callee: - return callee + return callee # We support Type of namedtuples but not of tuples in general if (isinstance(item, TupleType) and tuple_fallback(item).type.fullname() != 'builtins.tuple'): @@ -1295,6 +1296,10 @@ def check_arg(self, caller_type: Type, original_caller_type: Type, callee_type: Type, n: int, m: int, callee: CallableType, context: Context, messages: MessageBuilder) -> None: """Check the type of a single argument in a call.""" + caller_type = get_proper_type(caller_type) + original_caller_type = get_proper_type(original_caller_type) + callee_type = get_proper_type(callee_type) + if isinstance(caller_type, DeletedType): messages.deleted_as_rvalue(caller_type, context) # Only non-abstract non-protocol class can be given where Type[...] is expected... @@ -1780,6 +1785,7 @@ def apply_generic_arguments(self, callable: CallableType, types: Sequence[Option def check_any_type_call(self, args: List[Expression], callee: Type) -> Tuple[Type, Type]: self.infer_arg_types_in_empty_context(args) + callee = get_proper_type(callee) if isinstance(callee, AnyType): return (AnyType(TypeOfAny.from_another_any, source_any=callee), AnyType(TypeOfAny.from_another_any, source_any=callee)) @@ -2047,6 +2053,10 @@ def dangerous_comparison(self, left: Type, right: Type, """ if not self.chk.options.strict_equality: return False + + left = get_proper_type(left) + right = get_proper_type(right) + if self.chk.binder.is_unreachable_warning_suppressed(): # We are inside a function that contains type variables with value restrictions in # its signature. In this case we just suppress all strict-equality checks to avoid @@ -2228,6 +2238,9 @@ def lookup_definer(typ: Instance, attr_name: str) -> Optional[str]: return cls.fullname() return None + left_type = get_proper_type(left_type) + right_type = get_proper_type(right_type) + # If either the LHS or the RHS are Any, we can't really concluding anything # about the operation since the Any type may or may not define an # __op__ or __rop__ method. So, we punt and return Any instead. @@ -2586,12 +2599,14 @@ def visit_index_expr_helper(self, e: IndexExpr) -> Type: return self.visit_index_with_type(left_type, e) def visit_index_with_type(self, left_type: Type, e: IndexExpr, - original_type: Optional[Type] = None) -> Type: + original_type: Optional[ProperType] = None) -> Type: """Analyze type of an index expression for a given type of base expression. The 'original_type' is used for error messages (currently used for union types). """ index = e.index + left_type = get_proper_type(left_type) + if isinstance(left_type, UnionType): original_type = original_type or left_type return UnionType.make_simplified_union([self.visit_index_with_type(typ, e, @@ -2676,7 +2691,7 @@ def try_getting_int_literals(self, index: Expression) -> Optional[List[int]]: operand = index.expr if isinstance(operand, IntExpr): return [-1 * operand.value] - typ = self.accept(index) + typ = get_proper_type(self.accept(index)) if isinstance(typ, Instance) and typ.last_known_value is not None: typ = typ.last_known_value if isinstance(typ, LiteralType) and isinstance(typ.value, int): @@ -2809,7 +2824,7 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: return AnyType(TypeOfAny.from_error) # Type application of a normal generic class in runtime context. # This is typically used as `x = G[int]()`. - tp = self.accept(tapp.expr) + tp = get_proper_type(self.accept(tapp.expr)) if isinstance(tp, (CallableType, Overloaded)): if not tp.is_type_obj(): self.chk.fail(message_registry.ONLY_CLASS_APPLICATION, tapp) @@ -2850,7 +2865,7 @@ class LongName(Generic[T]): ... x = A() y = cast(A, ...) """ - if isinstance(target, Instance) and target.invalid: + if isinstance(target, Instance) and target.invalid: # type: ignore # An invalid alias, error already has been reported return AnyType(TypeOfAny.from_error) # If this is a generic alias, we set all variables to `Any`. @@ -2886,6 +2901,8 @@ def apply_type_arguments_to_callable(self, tp: Type, args: List[Type], ctx: Cont case this returns Any for non-callable types, because if type object type is not callable, then an error should be already reported. """ + tp = get_proper_type(tp) + if isinstance(tp, CallableType): if len(tp.variables) != len(args): self.msg.incompatible_type_application(len(tp.variables), @@ -2935,7 +2952,7 @@ def check_lst_expr(self, items: List[Expression], fullname: str, def visit_tuple_expr(self, e: TupleExpr) -> Type: """Type check a tuple expression.""" # Try to determine type context for type inference. - type_context = self.type_context[-1] + type_context = get_proper_type(self.type_context[-1]) type_context_items = None if isinstance(type_context, UnionType): tuples_in_context = [t for t in type_context.items @@ -3059,6 +3076,7 @@ def visit_dict_expr(self, e: DictExpr) -> Type: return rv def find_typeddict_context(self, context: Optional[Type]) -> Optional[TypedDictType]: + context = get_proper_type(context) if isinstance(context, TypedDictType): return context elif isinstance(context, UnionType): @@ -3533,6 +3551,8 @@ def has_member(self, typ: Type, member: str) -> bool: """Does type have member with the given name?""" # TODO: refactor this to use checkmember.analyze_member_access, otherwise # these two should be carefully kept in sync. + typ = get_proper_type(typ) + if isinstance(typ, TypeVarType): typ = typ.upper_bound if isinstance(typ, TupleType): @@ -3551,9 +3571,9 @@ def has_member(self, typ: Type, member: str) -> bool: elif isinstance(typ, TypeType): # Type[Union[X, ...]] is always normalized to Union[Type[X], ...], # so we don't need to care about unions here. - item = typ.item # type: Type + item = typ.item if isinstance(item, TypeVarType): - item = item.upper_bound + item = get_proper_type(item.upper_bound) if isinstance(item, TupleType): item = tuple_fallback(item) if isinstance(item, Instance) and item.type.metaclass_type is not None: @@ -3868,17 +3888,17 @@ def arg_approximate_similarity(actual: Type, formal: Type) -> bool: this function to help us identify which alternative the user might have *meant* to match. """ + actual = get_proper_type(actual) + formal = get_proper_type(formal) # Erase typevars: we'll consider them all to have the same "shape". - if isinstance(actual, TypeVarType): actual = actual.erase_to_union_or_bound() if isinstance(formal, TypeVarType): formal = formal.erase_to_union_or_bound() # Callable or Type[...]-ish types - - def is_typetype_like(typ: Type) -> bool: + def is_typetype_like(typ: ProperType) -> bool: return (isinstance(typ, TypeType) or (isinstance(typ, FunctionLike) and typ.is_type_obj()) or (isinstance(typ, Instance) and typ.type.fullname() == "builtins.type")) @@ -3890,14 +3910,12 @@ def is_typetype_like(typ: Type) -> bool: return True # Unions - if isinstance(actual, UnionType): return any(arg_approximate_similarity(item, formal) for item in actual.relevant_items()) if isinstance(formal, UnionType): return any(arg_approximate_similarity(actual, item) for item in formal.relevant_items()) # TypedDicts - if isinstance(actual, TypedDictType): if isinstance(formal, TypedDictType): return True @@ -3905,7 +3923,6 @@ def is_typetype_like(typ: Type) -> bool: # Instances # For instances, we mostly defer to the existing is_subtype check. - if isinstance(formal, Instance): if isinstance(actual, CallableType): actual = actual.fallback @@ -4022,6 +4039,7 @@ def is_literal_type_like(t: Optional[Type]) -> bool: """Returns 'true' if the given type context is potentially either a LiteralType, a Union of LiteralType, or something similar. """ + t = get_proper_type(t) if t is None: return False elif isinstance(t, LiteralType): @@ -4056,6 +4074,7 @@ def is_expr_literal_type(node: Expression) -> bool: def custom_equality_method(typ: Type) -> bool: """Does this type have a custom __eq__() method?""" + typ = get_proper_type(typ) if isinstance(typ, Instance): method = typ.type.get('__eq__') if method and isinstance(method.node, (SYMBOL_FUNCBASE_TYPES, Decorator, Var)): @@ -4078,6 +4097,7 @@ def custom_equality_method(typ: Type) -> bool: def has_bytes_component(typ: Type) -> bool: """Is this the builtin bytes type, or a union that contains it?""" + typ = get_proper_type(typ) if isinstance(typ, UnionType): return any(has_bytes_component(t) for t in typ.items) if isinstance(typ, Instance) and typ.type.fullname() == 'builtins.bytes': @@ -4087,7 +4107,7 @@ def has_bytes_component(typ: Type) -> bool: def type_info_from_type(typ: Type) -> Optional[TypeInfo]: """Gets the TypeInfo for a type, indirecting through things like type variables and tuples.""" - + typ = get_proper_type(typ) if isinstance(typ, FunctionLike) and typ.is_type_obj(): return typ.type_object() if isinstance(typ, TypeType): diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ccb7e8d7ad83..e0b22d12827d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -789,7 +789,7 @@ class B(A[str]): pass return t -def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> Type: +def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> ProperType: """Return the type of a type object. For a generic type G with type variables T and S the type is generally of form diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 796b3d74ee4b..093bcf0edc15 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -84,7 +84,7 @@ def argument(self, ctx: 'mypy.plugin.ClassDefContext') -> Argument: converter = ctx.api.lookup_qualified(self.converter.name, self.info, True) # Get the type of the converter. - converter_type = None + converter_type = None # type: Optional[Type] if converter and isinstance(converter.node, TypeInfo): from mypy.checkmember import type_object_type # To avoid import cycle. converter_type = type_object_type(converter.node, ctx.api.builtin_type) diff --git a/mypy/types.py b/mypy/types.py index c27bb7fefcec..93d39b3ec72e 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -855,11 +855,11 @@ def __init__(self, binder: TypeVarDef, line: int = -1, column: int = -1) -> None def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_type_var(self) - def erase_to_union_or_bound(self) -> Type: + def erase_to_union_or_bound(self) -> ProperType: if self.values: return UnionType.make_simplified_union(self.values) else: - return self.upper_bound + return get_proper_type(self.upper_bound) def __hash__(self) -> int: return hash(self.id) From b19b49f9c4a421f7609b71a2e7b41d09b02b16d5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 17 Aug 2019 15:37:21 +0100 Subject: [PATCH 05/12] Finish cleaning messages.py --- mypy/messages.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index b5c9f8706eef..5c381ce69b84 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -22,7 +22,8 @@ from mypy.types import ( Type, CallableType, Instance, TypeVarType, TupleType, TypedDictType, LiteralType, UnionType, NoneType, AnyType, Overloaded, FunctionLike, DeletedType, TypeType, - UninhabitedType, TypeOfAny, UnboundType, PartialType, get_proper_type + UninhabitedType, TypeOfAny, UnboundType, PartialType, get_proper_type, ProperType, + get_proper_types ) from mypy.typetraverser import TypeTraverserVisitor from mypy.nodes import ( @@ -541,6 +542,7 @@ def duplicate_argument_value(self, callee: CallableType, index: int, def does_not_return_value(self, callee_type: Optional[Type], context: Context) -> None: """Report an error about use of an unusable type.""" name = None # type: Optional[str] + callee_type = get_proper_type(callee_type) if isinstance(callee_type, FunctionLike): name = callable_name(callee_type) if name is not None: @@ -687,7 +689,7 @@ def incompatible_type_application(self, expected_arg_count: int, self.fail('Type application has too few types ({} expected)' .format(expected_arg_count), context) - def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: + def alias_invalid_in_runtime_context(self, item: ProperType, ctx: Context) -> None: kind = (' to Callable' if isinstance(item, CallableType) else ' to Tuple' if isinstance(item, TupleType) else ' to Union' if isinstance(item, UnionType) else @@ -707,6 +709,7 @@ def invalid_var_arg(self, typ: Type, context: Context) -> None: self.fail('List or tuple expected as variable arguments', context) def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) -> None: + typ = get_proper_type(typ) if isinstance(typ, Instance) and is_mapping: self.fail('Keywords must be strings', context) else: @@ -721,6 +724,7 @@ def undefined_in_superclass(self, member: str, context: Context) -> None: self.fail('"{}" undefined in superclass'.format(member), context) def first_argument_for_super_must_be_type(self, actual: Type, context: Context) -> None: + actual = get_proper_type(actual) if isinstance(actual, Instance): # Don't include type of instance, because it can look confusingly like a type # object. @@ -1033,6 +1037,7 @@ def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) def disallowed_any_type(self, typ: Type, context: Context) -> None: + typ = get_proper_type(typ) if isinstance(typ, AnyType): message = 'Expression has type "Any"' else: @@ -1045,6 +1050,7 @@ def incorrectly_returning_any(self, typ: Type, context: Context) -> None: self.fail(message, context, code=codes.NO_ANY_RETURN) def untyped_decorated_function(self, typ: Type, context: Context) -> None: + typ = get_proper_type(typ) if isinstance(typ, AnyType): self.fail("Function is untyped after decorator transformation", context) else: @@ -1135,7 +1141,7 @@ def report_protocol_problems(self, subtype: Union[Instance, TupleType, TypedDict Instance: []} # type: Dict[type, List[str]] if supertype.type.fullname() in exclusions[type(subtype)]: return - if any(isinstance(tp, UninhabitedType) for tp in supertype.args): + if any(isinstance(tp, UninhabitedType) for tp in get_proper_types(supertype.args)): # We don't want to add notes for failed inference (e.g. Iterable[]). # This will be only confusing a user even more. return @@ -1169,6 +1175,8 @@ def report_protocol_problems(self, subtype: Union[Instance, TupleType, TypedDict self.note('Following member(s) of {} have ' 'conflicts:'.format(format_type(subtype)), context) for name, got, exp in conflict_types[:MAX_ITEMS]: + exp = get_proper_type(exp) + got = get_proper_type(got) if (not isinstance(exp, (CallableType, Overloaded)) or not isinstance(got, (CallableType, Overloaded))): self.note('{}: expected {}, got {}'.format(name, @@ -1283,6 +1291,9 @@ def format_type_inner(typ: Type, def format(typ: Type) -> str: return format_type_inner(typ, verbosity, fullnames) + # TODO: show type alias names in errors. + typ = get_proper_type(typ) + if isinstance(typ, Instance): itype = typ # Get the short name of the type. @@ -1563,10 +1574,10 @@ def [T <: int] f(self, x: int, y: T) -> None if tp.variables: tvars = [] for tvar in tp.variables: - if (tvar.upper_bound and isinstance(tvar.upper_bound, Instance) and - tvar.upper_bound.type.fullname() != 'builtins.object'): - tvars.append('{} <: {}'.format(tvar.name, - format_type_bare(tvar.upper_bound))) + upper_bound = get_proper_type(tvar.upper_bound) + if (isinstance(upper_bound, Instance) and + upper_bound.type.fullname() != 'builtins.object'): + tvars.append('{} <: {}'.format(tvar.name, format_type_bare(upper_bound))) elif tvar.values: tvars.append('{} in ({})' .format(tvar.name, ', '.join([format_type_bare(tp) @@ -1790,6 +1801,8 @@ def make_inferred_type_note(context: Context, subtype: Type, of relying on the inferred type. """ from mypy.subtypes import is_subtype + subtype = get_proper_type(subtype) + supertype = get_proper_type(supertype) if (isinstance(subtype, Instance) and isinstance(supertype, Instance) and subtype.type.fullname() == supertype.type.fullname() and From 0783653c1e375093834f3c48542893d90be5cf4f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Aug 2019 13:58:33 +0100 Subject: [PATCH 06/12] Clean-up checker.py --- mypy/checker.py | 107 ++++++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 40 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 51eb1054b2d0..8c8076109005 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -474,6 +474,7 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: # This can happen if we've got an overload with a different # decorator or if the implementation is untyped -- we gave up on the types. + inner_type = get_proper_type(inner_type) if inner_type is not None and not isinstance(inner_type, AnyType): assert isinstance(inner_type, CallableType) impl_type = inner_type @@ -595,6 +596,7 @@ def is_generator_return_type(self, typ: Type, is_coroutine: bool) -> bool: True if `typ` is a *supertype* of Generator or Awaitable. Also true it it's *exactly* AwaitableGenerator (modulo type parameters). """ + typ = get_proper_type(typ) if is_coroutine: # This means we're in Python 3.5 or later. at = self.named_generic_type('typing.Awaitable', [AnyType(TypeOfAny.special_form)]) @@ -810,7 +812,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) fdef = item # Check if __init__ has an invalid, non-None return type. if (fdef.info and fdef.name() in ('__init__', '__init_subclass__') and - not isinstance(typ.ret_type, NoneType) and + not isinstance(get_proper_type(typ.ret_type), NoneType) and not self.dynamic_funcs[-1]): self.fail(message_registry.MUST_HAVE_NONE_RETURN_TYPE.format(fdef.name()), item) @@ -857,10 +859,12 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) self.fail(message_registry.INVALID_RETURN_TYPE_FOR_GENERATOR, typ) # Python 2 generators aren't allowed to return values. + orig_ret_type = get_proper_type(typ.ret_type) if (self.options.python_version[0] == 2 and - isinstance(typ.ret_type, Instance) and - typ.ret_type.type.fullname() == 'typing.Generator'): - if not isinstance(typ.ret_type.args[2], (NoneType, AnyType)): + isinstance(orig_ret_type, Instance) and + orig_ret_type.type.fullname() == 'typing.Generator'): + if not isinstance(get_proper_type(orig_ret_type.args[2]), + (NoneType, AnyType)): self.fail(message_registry.INVALID_GENERATOR_RETURN_ITEM_TYPE, typ) # Fix the type if decorated with `@types.coroutine` or `@asyncio.coroutine`. @@ -961,6 +965,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) else: return_type = self.return_types[-1] + return_type = get_proper_type(return_type) if not isinstance(return_type, (NoneType, AnyType)) and not body_is_trivial: # Control flow fell off the end of a function that was # declared to return a non-None type and is not @@ -1029,7 +1034,7 @@ def is_unannotated_any(t: Type) -> bool: self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef, code=codes.NO_UNTYPED_DEF) elif isinstance(fdef.type, CallableType): - ret_type = fdef.type.ret_type + ret_type = get_proper_type(fdef.type.ret_type) if is_unannotated_any(ret_type): self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef, code=codes.NO_UNTYPED_DEF) @@ -1050,8 +1055,9 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None: self_type = fill_typevars_with_any(fdef.info) bound_type = bind_self(typ, self_type, is_classmethod=True) # Check that __new__ (after binding cls) returns an instance - # type (or any) - if not isinstance(bound_type.ret_type, (AnyType, Instance, TupleType)): + # type (or any). + if not isinstance(get_proper_type(bound_type.ret_type), + (AnyType, Instance, TupleType)): self.fail( message_registry.NON_INSTANCE_NEW_TYPE.format( format_type(bound_type.ret_type)), @@ -1302,8 +1308,8 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: other_method = '__' + method[3:] if cls.has_readable_member(other_method): instance = fill_typevars(cls) - typ2 = self.expr_checker.analyze_external_member_access( - other_method, instance, defn) + typ2 = get_proper_type(self.expr_checker.analyze_external_member_access( + other_method, instance, defn)) fail = False if isinstance(typ2, FunctionLike): if not is_more_general_arg_prefix(typ, typ2): @@ -1454,11 +1460,12 @@ def check_method_override_for_base_with_name( assert defn.var.type is not None typ = defn.var.type override_class_or_static = defn.func.is_class or defn.func.is_static + typ = get_proper_type(typ) if isinstance(typ, FunctionLike) and not is_static(context): typ = bind_self(typ, self.scope.active_self_type()) # Map the overridden method type to subtype context so that # it can be checked for compatibility. - original_type = base_attr.type + original_type = get_proper_type(base_attr.type) original_node = base_attr.node if original_type is None: if self.pass_num < self.last_pass: @@ -1954,7 +1961,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type if isinstance(lvalue_type, PartialType) and lvalue_type.type is None: # Try to infer a proper type for a variable with a partial None type. rvalue_type = self.expr_checker.accept(rvalue) - if isinstance(rvalue_type, NoneType): + if isinstance(get_proper_type(rvalue_type), NoneType): # This doesn't actually provide any additional information -- multiple # None initializers preserve the partial None type. return @@ -2000,6 +2007,8 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type # Special case: only non-abstract non-protocol classes can be assigned to # variables with explicit type Type[A], where A is protocol or abstract. + rvalue_type = get_proper_type(rvalue_type) + lvalue_type = get_proper_type(lvalue_type) if (isinstance(rvalue_type, CallableType) and rvalue_type.is_type_obj() and (rvalue_type.type_object().is_abstract or rvalue_type.type_object().is_protocol) and @@ -2102,6 +2111,8 @@ def check_compatibility_super(self, lvalue: RefExpr, lvalue_type: Optional[Type] if isinstance(compare_node, Decorator): compare_node = compare_node.func + base_type = get_proper_type(base_type) + compare_type = get_proper_type(compare_type) if compare_type: if (isinstance(base_type, CallableType) and isinstance(compare_type, CallableType)): @@ -2161,11 +2172,12 @@ def lvalue_type_from_base(self, expr_node: Var, itype = map_instance_to_supertype(instance, base) base_type = expand_type_by_instance(base_type, itype) + base_type = get_proper_type(base_type) if isinstance(base_type, CallableType) and isinstance(base_node, FuncDef): # If we are a property, return the Type of the return # value, not the Callable if base_node.is_property: - base_type = base_type.ret_type + base_type = get_proper_type(base_type.ret_type) if isinstance(base_type, FunctionLike) and isinstance(base_node, OverloadedFuncDef): # Same for properties with setter @@ -2539,9 +2551,10 @@ def split_around_star(self, items: List[T], star_index: int, left = items[:star_index] star = items[star_index:right_index] right = items[right_index:] - return (left, star, right) + return left, star, right def type_is_iterable(self, type: Type) -> bool: + type = get_proper_type(type) if isinstance(type, CallableType) and type.is_type_obj(): type = type.fallback return is_subtype(type, self.named_generic_type('typing.Iterable', @@ -2550,6 +2563,7 @@ def type_is_iterable(self, type: Type) -> bool: def check_multi_assignment_from_iterable(self, lvalues: List[Lvalue], rvalue_type: Type, context: Context, infer_lvalue_type: bool = True) -> None: + rvalue_type = get_proper_type(rvalue_type) if self.type_is_iterable(rvalue_type) and isinstance(rvalue_type, Instance): item_type = self.iterable_item_type(rvalue_type) for lv in lvalues: @@ -2620,6 +2634,7 @@ def is_definition(self, s: Lvalue) -> bool: def infer_variable_type(self, name: Var, lvalue: Lvalue, init_type: Type, context: Context) -> None: """Infer the type of initialized variables from initializer type.""" + init_type = get_proper_type(init_type) if isinstance(init_type, DeletedType): self.msg.deleted_as_rvalue(init_type, context) elif not is_valid_inferred_type(init_type) and not self.no_partial_types: @@ -2654,7 +2669,8 @@ def infer_partial_type(self, name: Var, lvalue: Lvalue, init_type: Type) -> bool (fullname == 'builtins.list' or fullname == 'builtins.set' or fullname == 'builtins.dict') and - all(isinstance(t, (NoneType, UninhabitedType)) for t in init_type.args)): + all(isinstance(t, (NoneType, UninhabitedType)) + for t in get_proper_types(init_type.args))): partial_type = PartialType(init_type.type, name, init_type.args) else: return False @@ -2705,9 +2721,11 @@ def check_simple_assignment(self, lvalue_type: Optional[Type], rvalue: Expressio # '...' is always a valid initializer in a stub. return AnyType(TypeOfAny.special_form) else: + lvalue_type = get_proper_type(lvalue_type) always_allow_any = lvalue_type is not None and not isinstance(lvalue_type, AnyType) rvalue_type = self.expr_checker.accept(rvalue, lvalue_type, always_allow_any=always_allow_any) + rvalue_type = get_proper_type(rvalue_type) if isinstance(rvalue_type, DeletedType): self.msg.deleted_as_rvalue(rvalue_type, context) if isinstance(lvalue_type, DeletedType): @@ -2731,6 +2749,8 @@ def check_member_assignment(self, instance_type: Type, attribute_type: Type, Note: this method exists here and not in checkmember.py, because we need to take care about interaction between binder and __set__(). """ + instance_type = get_proper_type(instance_type) + attribute_type = get_proper_type(attribute_type) # Descriptors don't participate in class-attribute access if ((isinstance(instance_type, FunctionLike) and instance_type.is_type_obj()) or isinstance(instance_type, TypeType)): @@ -2779,7 +2799,7 @@ def check_member_assignment(self, instance_type: Type, attribute_type: Type, [nodes.ARG_POS, nodes.ARG_POS], context) # should be handled by get_method above - assert isinstance(inferred_dunder_set_type, CallableType) + assert isinstance(inferred_dunder_set_type, CallableType) # type: ignore if len(inferred_dunder_set_type.arg_types) < 2: # A message already will have been recorded in check_call @@ -2801,7 +2821,7 @@ def check_indexed_assignment(self, lvalue: IndexExpr, The lvalue argument is the base[index] expression. """ self.try_infer_partial_type_from_indexed_assignment(lvalue, rvalue) - basetype = self.expr_checker.accept(lvalue.base) + basetype = get_proper_type(self.expr_checker.accept(lvalue.base)) if isinstance(basetype, TypedDictType): item_type = self.expr_checker.visit_typeddict_index_expr(basetype, lvalue.index) method_type = CallableType( @@ -2884,9 +2904,8 @@ def check_return_stmt(self, s: ReturnStmt) -> None: allow_none_func_call = is_lambda or declared_none_return or declared_any_return # Return with a value. - typ = self.expr_checker.accept(s.expr, - return_type, - allow_none_return=allow_none_func_call) + typ = get_proper_type(self.expr_checker.accept( + s.expr, return_type, allow_none_return=allow_none_func_call)) if defn.is_async_generator: self.fail(message_registry.RETURN_IN_ASYNC_GENERATOR, s) @@ -2939,7 +2958,7 @@ def visit_if_stmt(self, s: IfStmt) -> None: # Fall-through to the original frame is handled explicitly in each block. with self.binder.frame_context(can_skip=False, fall_through=0): for e, b in zip(s.expr, s.body): - t = self.expr_checker.accept(e) + t = get_proper_type(self.expr_checker.accept(e)) if isinstance(t, DeletedType): self.msg.deleted_as_rvalue(t, s) @@ -3201,7 +3220,7 @@ def analyze_async_iterable_item_type(self, expr: Expression) -> Tuple[Type, Type def analyze_iterable_item_type(self, expr: Expression) -> Tuple[Type, Type]: """Analyse iterable expression and return iterator and iterator item types.""" echk = self.expr_checker - iterable = echk.accept(expr) + iterable = get_proper_type(echk.accept(expr)) iterator = echk.check_method_call_by_name('__iter__', iterable, [], [], expr)[0] if isinstance(iterable, TupleType): @@ -3222,6 +3241,7 @@ def analyze_container_item_type(self, typ: Type) -> Optional[Type]: Return the corresponding container item type. """ + typ = get_proper_type(typ) if isinstance(typ, UnionType): types = [] # type: List[Type] for item in typ.items: @@ -3296,7 +3316,7 @@ def visit_decorator(self, e: Decorator) -> None: self.check_method_override(e) if e.func.info and e.func.name() in ('__init__', '__new__'): - if e.type and not isinstance(e.type, (FunctionLike, AnyType)): + if e.type and not isinstance(get_proper_type(e.type), (FunctionLike, AnyType)): self.fail(message_registry.BAD_CONSTRUCTOR_TYPE, e) def check_for_untyped_decorator(self, @@ -3365,7 +3385,7 @@ def visit_print_stmt(self, s: PrintStmt) -> None: for arg in s.args: self.expr_checker.accept(arg) if s.target: - target_type = self.expr_checker.accept(s.target) + target_type = get_proper_type(self.expr_checker.accept(s.target)) if not isinstance(target_type, NoneType): # TODO: Also verify the type of 'write'. self.expr_checker.analyze_external_member_access('write', target_type, s.target) @@ -3522,7 +3542,7 @@ def conditional_callable_type_map(self, expr: Expression, if not current_type: return {}, {} - if isinstance(current_type, AnyType): + if isinstance(get_proper_type(current_type), AnyType): return {}, {} callables, uncallables = self.partition_by_callable(current_type, @@ -3571,7 +3591,7 @@ def find_isinstance_check(self, node: Expression return {}, {} expr = node.args[0] if literal(expr) == LITERAL_TYPE: - vartype = type_map[expr] + vartype = get_proper_type(type_map[expr]) type = get_isinstance_type(node.args[1], type_map) if isinstance(vartype, UnionType): union_list = [] @@ -3653,7 +3673,7 @@ def find_isinstance_check(self, node: Expression elif node.operators in [['in'], ['not in']]: expr = node.operands[0] left_type = type_map[expr] - right_type = builtin_item_type(type_map[node.operands[1]]) + right_type = get_proper_type(builtin_item_type(type_map[node.operands[1]])) right_ok = right_type and (not is_optional(right_type) and (not isinstance(right_type, Instance) or right_type.type.fullname() != 'builtins.object')) @@ -3671,8 +3691,11 @@ def find_isinstance_check(self, node: Expression if_type = true_only(vartype) # type: Type else_type = false_only(vartype) # type: Type ref = node # type: Expression - if_map = {ref: if_type} if not isinstance(if_type, UninhabitedType) else None - else_map = {ref: else_type} if not isinstance(else_type, UninhabitedType) else None + if_map = ({ref: if_type} if not isinstance(get_proper_type(if_type), UninhabitedType) + else None) + else_map = ({ref: else_type} if not isinstance(get_proper_type(else_type), + UninhabitedType) + else None) return if_map, else_map elif isinstance(node, OpExpr) and node.op == 'and': left_if_vars, left_else_vars = self.find_isinstance_check(node.left) @@ -3772,7 +3795,7 @@ def named_type(self, name: str) -> Instance: sym = self.lookup_qualified(name) node = sym.node if isinstance(node, TypeAlias): - assert isinstance(node.target, Instance) + assert isinstance(node.target, Instance) # type: ignore node = node.target.type assert isinstance(node, TypeInfo) any_type = AnyType(TypeOfAny.from_omitted_generics) @@ -3997,16 +4020,17 @@ def iterable_item_type(self, instance: Instance) -> Type: instance, self.lookup_typeinfo('typing.Iterable')) item_type = iterable.args[0] - if not isinstance(item_type, AnyType): + if not isinstance(get_proper_type(item_type), AnyType): # This relies on 'map_instance_to_supertype' returning 'Iterable[Any]' # in case there is no explicit base class. return item_type # Try also structural typing. - iter_type = find_member('__iter__', instance, instance) - if (iter_type and isinstance(iter_type, CallableType) and - isinstance(iter_type.ret_type, Instance)): - iterator = map_instance_to_supertype(iter_type.ret_type, - self.lookup_typeinfo('typing.Iterator')) + iter_type = get_proper_type(find_member('__iter__', instance, instance)) + if iter_type and isinstance(iter_type, CallableType): + ret_type = get_proper_type(iter_type.ret_type) + if isinstance(ret_type, Instance): + iterator = map_instance_to_supertype(ret_type, + self.lookup_typeinfo('typing.Iterator')) item_type = iterator.args[0] return item_type @@ -4113,9 +4137,10 @@ def builtin_item_type(tp: Type) -> Optional[Type]: if not tp.args: # TODO: fix tuple in lib-stub/builtins.pyi (it should be generic). return None - if not isinstance(tp.args[0], AnyType): + if not isinstance(get_proper_type(tp.args[0]), AnyType): return tp.args[0] - elif isinstance(tp, TupleType) and all(not isinstance(it, AnyType) for it in tp.items): + elif isinstance(tp, TupleType) and all(not isinstance(it, AnyType) + for it in get_proper_types(tp.items)): return UnionType.make_simplified_union(tp.items) # this type is not externally visible elif isinstance(tp, TypedDictType): # TypedDict always has non-optional string keys. Find the key type from the Mapping @@ -4180,7 +4205,7 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap: if isinstance(t, TypeVarType): t = t.upper_bound # TODO: should we only allow unions of instances as per PEP 484? - if not isinstance(t, (UnionType, Instance)): + if not isinstance(get_proper_type(t), (UnionType, Instance)): # unknown type; error was likely reported earlier return {} converted_type_map[expr] = TypeType.make_normalized(typ) @@ -4420,6 +4445,7 @@ def infer_operator_assignment_method(typ: Type, operator: str) -> Tuple[bool, st For example, if operator is '+', return (True, '__iadd__') or (False, '__add__') depending on which method is supported by the type. """ + typ = get_proper_type(typ) method = nodes.op_methods[operator] if isinstance(typ, Instance): if operator in nodes.ops_with_inplace_method: @@ -4438,7 +4464,7 @@ def is_valid_inferred_type(typ: Type) -> bool: invalid. When doing strict Optional checking, only None and types that are incompletely defined (i.e. contain UninhabitedType) are invalid. """ - if isinstance(typ, (NoneType, UninhabitedType)): + if isinstance(get_proper_type(typ), (NoneType, UninhabitedType)): # With strict Optional checking, we *may* eventually infer NoneType when # the initializer is None, but we only do that if we can't infer a # specific Optional type. This resolution happens in @@ -4549,7 +4575,7 @@ def is_typed_callable(c: Optional[Type]) -> bool: if not c or not isinstance(c, CallableType): return False return not all(isinstance(t, AnyType) and t.type_of_any == TypeOfAny.unannotated - for t in c.arg_types + [c.ret_type]) + for t in get_proper_types(c.arg_types + [c.ret_type])) def is_untyped_decorator(typ: Optional[Type]) -> bool: @@ -4605,6 +4631,7 @@ def is_singleton_type(typ: Type) -> bool: that 'a is b' will always be true -- some implementations of Python will end up constructing two distinct instances of 100001. """ + typ = get_proper_type(typ) # TODO: Also make this return True if the type is a bool LiteralType. # Also make this return True if the type corresponds to ... (ellipsis) or NotImplemented? return isinstance(typ, NoneType) or (isinstance(typ, LiteralType) and typ.is_enum_literal()) From 9dd8c9d143db09fd33a6e96d831773b0709d46fd Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Aug 2019 15:27:56 +0100 Subject: [PATCH 07/12] Finish clean-up --- misc/proper_plugin.py | 4 ++ mypy/checkexpr.py | 142 +++++++++++++++++++++++++----------------- 2 files changed, 90 insertions(+), 56 deletions(-) diff --git a/misc/proper_plugin.py b/misc/proper_plugin.py index 73a67a02fc9d..a61aa1aeac3c 100644 --- a/misc/proper_plugin.py +++ b/misc/proper_plugin.py @@ -3,6 +3,7 @@ Type, Instance, CallableType, UnionType, get_proper_type, ProperType, get_proper_types, TupleType ) +from mypy.subtypes import is_proper_subtype import os.path from typing_extensions import Type as typing_Type @@ -45,6 +46,9 @@ def isinstance_proper_hook(ctx: FunctionContext) -> Type: def is_special_target(right: ProperType) -> bool: if isinstance(right, CallableType) and right.is_type_obj(): + if right.type_object().fullname() == 'builtins.tuple': + # Used with Union[Type, Tuple[Type, ...]]. + return True if right.type_object().fullname() in ('mypy.types.Type', 'mypy.types.ProperType', 'mypy.types.TypeAliasType'): diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index b2124026378d..c86a415d21a1 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -18,7 +18,8 @@ TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, LiteralValue, true_only, false_only, is_named_instance, function_type, callable_type, FunctionLike, - StarType, is_optional, remove_optional, is_generic_instance, get_proper_type, ProperType + StarType, is_optional, remove_optional, is_generic_instance, get_proper_type, ProperType, + get_proper_types ) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, @@ -188,12 +189,13 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: elif isinstance(node, TypeInfo): # Reference to a type object. result = type_object_type(node, self.named_type) - if isinstance(result, CallableType) and isinstance(result.ret_type, Instance): + if (isinstance(result, CallableType) and + isinstance(result.ret_type, Instance)): # type: ignore # We need to set correct line and column # TODO: always do this in type_object_type by passing the original context result.ret_type.line = e.line result.ret_type.column = e.column - if isinstance(self.type_context[-1], TypeType): + if isinstance(get_proper_type(self.type_context[-1]), TypeType): # This is the type in a Type[] expression, so substitute type # variables with Any. result = erasetype.erase_typevars(result) @@ -227,9 +229,10 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: def analyze_var_ref(self, var: Var, context: Context) -> Type: if var.type: - if isinstance(var.type, Instance): - if self.is_literal_context() and var.type.last_known_value is not None: - return var.type.last_known_value + var_type = get_proper_type(var.type) + if isinstance(var_type, Instance): + if self.is_literal_context() and var_type.last_known_value is not None: + return var_type.last_known_value if var.name() in {'True', 'False'}: return self.infer_literal_expr_type(var.name() == 'True', 'builtins.bool') return var.type @@ -293,7 +296,7 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> type_context = CallableType(arg_types, e.callee.arg_kinds, e.callee.arg_names, ret_type=self.object_type(), fallback=self.named_type('builtins.function')) - callee_type = self.accept(e.callee, type_context, always_allow_any=True) + callee_type = get_proper_type(self.accept(e.callee, type_context, always_allow_any=True)) if (self.chk.options.disallow_untyped_calls and self.chk.in_checked_function() and isinstance(callee_type, CallableType) @@ -308,9 +311,10 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> # * A "static" reference/alias to a class or function; # get_function_hook() will be invoked for these. fullname = e.callee.fullname - if (isinstance(e.callee.node, TypeAlias) and - isinstance(e.callee.node.target, Instance)): - fullname = e.callee.node.target.type.fullname() + if isinstance(e.callee.node, TypeAlias): + target = get_proper_type(e.callee.node.target) + if isinstance(target, Instance): + fullname = target.type.fullname() # * Call to a method on object that has a full name (see # method_fullname() for details on supported objects); # get_method_hook() and get_method_signature_hook() will @@ -327,6 +331,7 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> self.check_runtime_protocol_test(e) if e.callee.fullname == 'builtins.issubclass': self.check_protocol_issubclass(e) + ret_type = get_proper_type(ret_type) if isinstance(ret_type, UninhabitedType) and not ret_type.ambiguous: self.chk.binder.unreachable() # Warn on calls to functions that always return None. The check @@ -348,7 +353,7 @@ def method_fullname(self, object_type: Type, method_name: str) -> Optional[str]: # For class method calls, object_type is a callable representing the class object. # We "unwrap" it to a regular type, as the class/instance method difference doesn't # affect the fully qualified name. - object_type = object_type.ret_type + object_type = get_proper_type(object_type.ret_type) type_name = None if isinstance(object_type, Instance): @@ -370,12 +375,15 @@ def always_returns_none(self, node: Expression) -> bool: if self.defn_returns_none(node.node): return True if isinstance(node, MemberExpr) and node.node is None: # instance or class attribute - typ = self.chk.type_map.get(node.expr) + typ = get_proper_type(self.chk.type_map.get(node.expr)) if isinstance(typ, Instance): info = typ.type - elif (isinstance(typ, CallableType) and typ.is_type_obj() and - isinstance(typ.ret_type, Instance)): - info = typ.ret_type.type + elif isinstance(typ, CallableType) and typ.is_type_obj(): + ret_type = get_proper_type(typ.ret_type) + if isinstance(ret_type, Instance): + info = ret_type.type + else: + return False else: return False sym = info.get(node.name) @@ -387,23 +395,23 @@ def defn_returns_none(self, defn: Optional[SymbolNode]) -> bool: """Check if `defn` can _only_ return None.""" if isinstance(defn, FuncDef): return (isinstance(defn.type, CallableType) and - isinstance(defn.type.ret_type, NoneType)) + isinstance(get_proper_type(defn.type.ret_type), NoneType)) if isinstance(defn, OverloadedFuncDef): - return all(isinstance(item.type, CallableType) and - isinstance(item.type.ret_type, NoneType) for item in defn.items) + return all(self.defn_returns_none(item) for item in defn.items) if isinstance(defn, Var): - if (not defn.is_inferred and isinstance(defn.type, CallableType) and - isinstance(defn.type.ret_type, NoneType)): + typ = get_proper_type(defn.type) + if (not defn.is_inferred and isinstance(typ, CallableType) and + isinstance(get_proper_type(typ.ret_type), NoneType)): return True - if isinstance(defn.type, Instance): - sym = defn.type.type.get('__call__') + if isinstance(typ, Instance): + sym = typ.type.get('__call__') if sym and self.defn_returns_none(sym.node): return True return False def check_runtime_protocol_test(self, e: CallExpr) -> None: for expr in mypy.checker.flatten(e.args[1]): - tp = self.chk.type_map[expr] + tp = get_proper_type(self.chk.type_map[expr]) if (isinstance(tp, CallableType) and tp.is_type_obj() and tp.type_object().is_protocol and not tp.type_object().runtime_protocol): @@ -411,7 +419,7 @@ def check_runtime_protocol_test(self, e: CallExpr) -> None: def check_protocol_issubclass(self, e: CallExpr) -> None: for expr in mypy.checker.flatten(e.args[1]): - tp = self.chk.type_map[expr] + tp = get_proper_type(self.chk.type_map[expr]) if (isinstance(tp, CallableType) and tp.is_type_obj() and tp.type_object().is_protocol): attr_members = non_method_protocol_members(tp.type_object()) @@ -528,7 +536,7 @@ def try_infer_partial_type(self, e: CallExpr) -> None: elif (typename in self.container_args and methodname in self.container_args[typename] and e.arg_kinds == [ARG_POS]): - arg_type = self.accept(e.args[0]) + arg_type = get_proper_type(self.accept(e.args[0])) if isinstance(arg_type, Instance): arg_typename = arg_type.type.fullname() if arg_typename in self.container_args[typename][methodname]: @@ -643,6 +651,7 @@ def transform_callee_type( dealing with overloads). Instead, this method needs to be called explicitly (if appropriate) before the signature is passed to check_call. """ + callee = get_proper_type(callee) if (callable_name is not None and object_type is not None and isinstance(callee, FunctionLike)): @@ -673,6 +682,7 @@ def check_call_expr_with_callee_type(self, if callable_name is None and member is not None: assert object_type is not None callable_name = self.method_fullname(object_type, member) + object_type = get_proper_type(object_type) if callable_name: # Try to refine the call signature using plugin hooks before checking the call. callee_type = self.transform_callee_type( @@ -791,8 +801,9 @@ def check_callable_call(self, """ if callable_name is None and callee.name: callable_name = callee.name - if callee.is_type_obj() and isinstance(callee.ret_type, Instance): - callable_name = callee.ret_type.type.fullname() + ret_type = get_proper_type(callee.ret_type) + if callee.is_type_obj() and isinstance(ret_type, Instance): + callable_name = ret_type.type.fullname() if (isinstance(callable_node, RefExpr) and callable_node.fullname in ('enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag')): @@ -846,10 +857,10 @@ def check_callable_call(self, and ((object_type is None and self.plugin.get_function_hook(callable_name)) or (object_type is not None and self.plugin.get_method_hook(callable_name)))): - ret_type = self.apply_function_plugin( + new_ret_type = self.apply_function_plugin( callee, arg_kinds, arg_types, arg_names, formal_to_actual, args, callable_name, object_type, context) - callee = callee.copy_modified(ret_type=ret_type) + callee = callee.copy_modified(ret_type=new_ret_type) return callee.ret_type, callee def analyze_type_type_callee(self, item: ProperType, context: Context) -> ProperType: @@ -1053,7 +1064,7 @@ def infer_function_type_arguments(self, callee_type: CallableType, # if they shuffle type variables around, as we assume that there is a 1-1 # correspondence with dict type variables. This is a marginal issue and # a little tricky to fix so it's left unfixed for now. - first_arg = inferred_args[0] + first_arg = get_proper_type(inferred_args[0]) if isinstance(first_arg, (NoneType, UninhabitedType)): inferred_args[0] = self.named_type('builtins.str') elif not first_arg or not is_subtype(self.named_type('builtins.str'), first_arg): @@ -1088,7 +1099,7 @@ def infer_function_type_arguments_pass2( # information to infer the argument. Replace them with None values so # that they are not applied yet below. inferred_args = list(old_inferred_args) - for i, arg in enumerate(inferred_args): + for i, arg in enumerate(get_proper_types(inferred_args)): if isinstance(arg, (NoneType, UninhabitedType)) or has_erased_component(arg): inferred_args[i] = None callee_type = self.apply_generic_arguments(callee_type, inferred_args, context) @@ -1189,7 +1200,8 @@ def check_argument_count(self, nodes.ARG_NAMED, nodes.ARG_NAMED_OPT] and is_duplicate_mapping( formal_to_actual[i], actual_kinds): if (self.chk.in_checked_function() or - isinstance(actual_types[formal_to_actual[i][0]], TupleType)): + isinstance(get_proper_type(actual_types[formal_to_actual[i][0]]), + TupleType)): if messages: messages.duplicate_argument_value(callee, i, context) ok = False @@ -1236,7 +1248,7 @@ def check_for_extra_actual_arguments(self, is_unexpected_arg_error = True elif ((kind == nodes.ARG_STAR and nodes.ARG_STAR not in callee.arg_kinds) or kind == nodes.ARG_STAR2): - actual_type = actual_types[i] + actual_type = get_proper_type(actual_types[i]) if isinstance(actual_type, (TupleType, TypedDictType)): if all_actuals.count(i) < len(actual_type.items): # Too many tuple/dict items as some did not match. @@ -1378,7 +1390,7 @@ def check_overload_call(self, if inferred_result is not None and unioned_result is not None: # Both unioned and direct checks succeeded, choose the more precise type. if (is_subtype(inferred_result[0], unioned_result[0]) and - not isinstance(inferred_result[0], AnyType)): + not isinstance(get_proper_type(inferred_result[0]), AnyType)): return inferred_result return unioned_result elif unioned_result is not None: @@ -1444,6 +1456,7 @@ def plausible_overload_call_targets(self, def has_shape(typ: Type) -> bool: # TODO: Once https://github.com/python/mypy/issues/5198 is fixed, # add 'isinstance(typ, TypedDictType)' somewhere below. + typ = get_proper_type(typ) return (isinstance(typ, TupleType) or (isinstance(typ, Instance) and typ.type.is_named_tuple)) @@ -1618,13 +1631,14 @@ def union_overload_result(self, direct = self.infer_overload_return_type(plausible_targets, args, arg_types, arg_kinds, arg_names, callable_name, object_type, context, arg_messages) - if direct is not None and not isinstance(direct[0], (UnionType, AnyType)): + if direct is not None and not isinstance(get_proper_type(direct[0]), + (UnionType, AnyType)): # We only return non-unions soon, to avoid greedy match. return [direct] # Step 4: Split the first remaining union type in arguments into items and # try to match each item individually (recursive). - first_union = arg_types[idx] + first_union = get_proper_type(arg_types[idx]) assert isinstance(first_union, UnionType) res_items = [] for item in first_union.relevant_items(): @@ -1650,6 +1664,7 @@ def union_overload_result(self, return result def real_union(self, typ: Type) -> bool: + typ = get_proper_type(typ) return isinstance(typ, UnionType) and len(typ.relevant_items()) > 1 @contextmanager @@ -1673,6 +1688,7 @@ def combine_function_signatures(self, types: Sequence[Type]) -> Union[AnyType, C an ambiguity because of Any in arguments). """ assert types, "Trying to merge no callables" + types = get_proper_types(types) if not all(isinstance(c, CallableType) for c in types): return AnyType(TypeOfAny.special_form) callables = cast(Sequence[CallableType], types) @@ -2114,6 +2130,7 @@ def check_method_call_by_name(self, local_errors = local_errors or self.msg original_type = original_type or base_type # Unions are special-cased to allow plugins to act on each element of the union. + base_type = get_proper_type(base_type) if isinstance(base_type, UnionType): return self.check_union_method_call_by_name(method, base_type, args, arg_kinds, @@ -2384,6 +2401,7 @@ def check_op(self, method: str, base_type: Type, if allow_reverse: left_variants = [base_type] + base_type = get_proper_type(base_type) if isinstance(base_type, UnionType): left_variants = [item for item in base_type.relevant_items()] right_type = self.accept(arg) @@ -2425,6 +2443,7 @@ def check_op(self, method: str, base_type: Type, # type inference errors -- e.g. see 'testOperatorDoubleUnionSum'. # TODO: Can we use `type_overrides_set()` here? right_variants = [(right_type, arg)] + right_type = get_proper_type(right_type) if isinstance(right_type, UnionType): right_variants = [(item, TempNode(item)) for item in right_type.relevant_items()] @@ -2585,7 +2604,7 @@ def visit_index_expr(self, e: IndexExpr) -> Type: It may also represent type application. """ result = self.visit_index_expr_helper(e) - result = self.narrow_type_from_binder(e, result) + result = get_proper_type(self.narrow_type_from_binder(e, result)) if (self.is_literal_context() and isinstance(result, Instance) and result.last_known_value is not None): result = result.last_known_value @@ -2725,14 +2744,14 @@ def visit_typeddict_index_expr(self, td_type: TypedDictType, index: Expression) if isinstance(index, (StrExpr, UnicodeExpr)): key_names = [index.value] else: - typ = self.accept(index) + typ = get_proper_type(self.accept(index)) if isinstance(typ, UnionType): key_types = list(typ.items) # type: List[Type] else: key_types = [typ] key_names = [] - for key_type in key_types: + for key_type in get_proper_types(key_types): if isinstance(key_type, Instance) and key_type.last_known_value is not None: key_type = key_type.last_known_value @@ -2816,6 +2835,7 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: all_vars = tapp.expr.node.alias_tvars item = expand_type_alias(target, all_vars, tapp.types, self.chk.fail, tapp.expr.node.no_args, tapp) + item = get_proper_type(item) if isinstance(item, Instance): tp = type_object_type(item.type, self.named_type) return self.apply_type_arguments_to_callable(tp, item.args, tapp) @@ -2989,6 +3009,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: # context? Counterargument: Why would anyone write # (1, *(2, 3)) instead of (1, 2, 3) except in a test? tt = self.accept(item.expr) + tt = get_proper_type(tt) if isinstance(tt, TupleType): items.extend(tt.items) j += len(tt.items) @@ -3115,7 +3136,7 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type: if e.expr() not in self.chk.type_map: self.accept(e.expr(), allow_none_return=True) ret_type = self.chk.type_map[e.expr()] - if isinstance(ret_type, NoneType): + if isinstance(get_proper_type(ret_type), NoneType): # For "lambda ...: None", just use type from the context. # Important when the context is Callable[..., None] which # really means Void. See #1425. @@ -3132,7 +3153,7 @@ def infer_lambda_type_using_context(self, e: LambdaExpr) -> Tuple[Optional[Calla The second item in the return type is the type_override parameter for check_func_item. """ # TODO also accept 'Any' context - ctx = self.type_context[-1] + ctx = get_proper_type(self.type_context[-1]) if isinstance(ctx, UnionType): callables = [t for t in ctx.relevant_items() if isinstance(t, CallableType)] @@ -3147,7 +3168,7 @@ def infer_lambda_type_using_context(self, e: LambdaExpr) -> Tuple[Optional[Calla # they must be considered as indeterminate. We use ErasedType since it # does not affect type inference results (it is for purposes like this # only). - callable_ctx = replace_meta_vars(ctx, ErasedType()) + callable_ctx = get_proper_type(replace_meta_vars(ctx, ErasedType())) assert isinstance(callable_ctx, CallableType) arg_kinds = [arg.kind for arg in e.arguments] @@ -3247,7 +3268,7 @@ def _super_arg_types(self, e: SuperExpr) -> Union[Type, Tuple[Type, Type]]: # Zero-argument super() is like super(, ) current_type = fill_typevars(e.info) - type_type = TypeType(current_type) # type: Type + type_type = TypeType(current_type) # type: ProperType # Use the type of the self argument, in case it was annotated method = self.chk.scope.top_function() @@ -3267,7 +3288,7 @@ def _super_arg_types(self, e: SuperExpr) -> Union[Type, Tuple[Type, Type]]: self.chk.fail(message_registry.SUPER_WITH_SINGLE_ARG_NOT_SUPPORTED, e) return AnyType(TypeOfAny.from_error) elif len(e.call.args) == 2: - type_type = self.accept(e.call.args[0]) + type_type = get_proper_type(self.accept(e.call.args[0])) instance_type = self.accept(e.call.args[1]) else: self.chk.fail(message_registry.TOO_MANY_ARGS_FOR_SUPER, e) @@ -3293,6 +3314,7 @@ def _super_arg_types(self, e: SuperExpr) -> Union[Type, Tuple[Type, Type]]: return AnyType(TypeOfAny.from_error) # Imprecisely assume that the instance is of the current class + instance_type = get_proper_type(instance_type) if isinstance(instance_type, AnyType): if e.info: instance_type = fill_typevars(e.info) @@ -3450,7 +3472,7 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type: # compatible with older mypy versions where we always did a join. # # TODO: Always create a union or at least in more cases? - if isinstance(self.type_context[-1], UnionType): + if isinstance(get_proper_type(self.type_context[-1]), UnionType): res = UnionType.make_simplified_union([if_type, else_type]) else: res = join.join_types(if_type, else_type) @@ -3524,6 +3546,7 @@ def named_type(self, name: str) -> Instance: def is_valid_var_arg(self, typ: Type) -> bool: """Is a type valid as a *args argument?""" + typ = get_proper_type(typ) return (isinstance(typ, TupleType) or is_subtype(typ, self.chk.named_generic_type('typing.Iterable', [AnyType(TypeOfAny.special_form)])) or @@ -3554,7 +3577,7 @@ def has_member(self, typ: Type, member: str) -> bool: typ = get_proper_type(typ) if isinstance(typ, TypeVarType): - typ = typ.upper_bound + typ = get_proper_type(typ.upper_bound) if isinstance(typ, TupleType): typ = tuple_fallback(typ) if isinstance(typ, LiteralType): @@ -3596,7 +3619,7 @@ def visit_yield_expr(self, e: YieldExpr) -> Type: return_type = self.chk.return_types[-1] expected_item_type = self.chk.get_generator_yield_type(return_type, False) if e.expr is None: - if (not isinstance(expected_item_type, (NoneType, AnyType)) + if (not isinstance(get_proper_type(expected_item_type), (NoneType, AnyType)) and self.chk.in_checked_function()): self.chk.fail(message_registry.YIELD_VALUE_EXPECTED, e) else: @@ -3610,7 +3633,7 @@ def visit_await_expr(self, e: AwaitExpr) -> Type: expected_type = self.type_context[-1] if expected_type is not None: expected_type = self.chk.named_generic_type('typing.Awaitable', [expected_type]) - actual_type = self.accept(e.expr, expected_type) + actual_type = get_proper_type(self.accept(e.expr, expected_type)) if isinstance(actual_type, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=actual_type) return self.check_awaitable_expr(actual_type, e, @@ -3639,7 +3662,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals # If the containing function has type Generator[X, Y, ...], # the context should be Generator[X, Y, T], where T is the # context of the 'yield from' itself (but it isn't known). - subexpr_type = self.accept(e.expr) + subexpr_type = get_proper_type(self.accept(e.expr)) # Check that the expr is an instance of Iterable and get the type of the iterator produced # by __iter__. @@ -3672,19 +3695,21 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals 'actual type', 'expected type') # Determine the type of the entire yield from expression. + iter_type = get_proper_type(iter_type) if (isinstance(iter_type, Instance) and iter_type.type.fullname() == 'typing.Generator'): expr_type = self.chk.get_generator_return_type(iter_type, False) else: # Non-Generators don't return anything from `yield from` expressions. # However special-case Any (which might be produced by an error). + actual_item_type = get_proper_type(actual_item_type) if isinstance(actual_item_type, AnyType): expr_type = AnyType(TypeOfAny.from_another_any, source_any=actual_item_type) else: # Treat `Iterator[X]` as a shorthand for `Generator[X, None, Any]`. expr_type = NoneType() - if not allow_none_return and isinstance(expr_type, NoneType): + if not allow_none_return and isinstance(get_proper_type(expr_type), NoneType): self.chk.msg.does_not_return_value(None, e) return expr_type @@ -3711,7 +3736,7 @@ def visit_enum_call_expr(self, e: EnumCallExpr) -> Type: for name, value in zip(e.items, e.values): if value is not None: typ = self.accept(value) - if not isinstance(typ, AnyType): + if not isinstance(get_proper_type(typ), AnyType): var = e.info.names[name].node if isinstance(var, Var): # Inline TypeChecker.set_inferred_type(), @@ -3758,7 +3783,7 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type, # noqa # If the current node is deferred, some variables may get Any types that they # otherwise wouldn't have. We don't want to narrow down these since it may # produce invalid inferred Optional[Any] types, at least. - if restriction and not (isinstance(known_type, AnyType) + if restriction and not (isinstance(get_proper_type(known_type), AnyType) and self.chk.current_node_deferred): # Note: this call should match the one in narrow_declared_type(). if (skip_non_overlapping and @@ -3784,6 +3809,7 @@ def visit_any(self, t: AnyType) -> bool: def has_coroutine_decorator(t: Type) -> bool: """Whether t came from a function decorated with `@coroutine`.""" + t = get_proper_type(t) return isinstance(t, Instance) and t.type.fullname() == 'typing.AwaitableGenerator' @@ -3801,14 +3827,16 @@ def is_async_def(t: Type) -> bool: # (We really need to see whether the original, undecorated # function was an `async def`, which is orthogonal to its # decorations.) + t = get_proper_type(t) if (isinstance(t, Instance) and t.type.fullname() == 'typing.AwaitableGenerator' and len(t.args) >= 4): - t = t.args[3] + t = get_proper_type(t.args[3]) return isinstance(t, Instance) and t.type.fullname() == 'typing.Coroutine' def is_empty_tuple(t: Type) -> bool: + t = get_proper_type(t) return isinstance(t, TupleType) and not t.items @@ -4053,8 +4081,9 @@ def is_literal_type_like(t: Optional[Type]) -> bool: return False -def try_getting_literal(typ: Type) -> Type: +def try_getting_literal(typ: Type) -> ProperType: """If possible, get a more precise literal type for a given type.""" + typ = get_proper_type(typ) if isinstance(typ, Instance) and typ.last_known_value is not None: return typ.last_known_value return typ @@ -4068,7 +4097,8 @@ def is_expr_literal_type(node: Expression) -> bool: return isinstance(base, RefExpr) and base.fullname in valid if isinstance(node, NameExpr): underlying = node.node - return isinstance(underlying, TypeAlias) and isinstance(underlying.target, LiteralType) + return isinstance(underlying, TypeAlias) and isinstance(get_proper_type(underlying.target), + LiteralType) return False @@ -4113,7 +4143,7 @@ def type_info_from_type(typ: Type) -> Optional[TypeInfo]: if isinstance(typ, TypeType): typ = typ.item if isinstance(typ, TypeVarType): - typ = typ.upper_bound + typ = get_proper_type(typ.upper_bound) if isinstance(typ, TupleType): typ = tuple_fallback(typ) if isinstance(typ, Instance): From 3aa3dbde7347c92c9f98c3176cf955f4450840b0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Aug 2019 17:57:33 +0100 Subject: [PATCH 08/12] Remove some redundant get_proper_type() calls --- misc/proper_plugin.py | 42 +++++++++++++++++++++++++++++++++++++++++- mypy/semanal_shared.py | 2 +- mypy/suggestions.py | 2 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/misc/proper_plugin.py b/misc/proper_plugin.py index a61aa1aeac3c..9c5e3565a882 100644 --- a/misc/proper_plugin.py +++ b/misc/proper_plugin.py @@ -1,8 +1,9 @@ from mypy.plugin import Plugin, FunctionContext from mypy.types import ( Type, Instance, CallableType, UnionType, get_proper_type, ProperType, - get_proper_types, TupleType + get_proper_types, TupleType, NoneTyp ) +from mypy.nodes import TypeInfo from mypy.subtypes import is_proper_subtype import os.path @@ -28,6 +29,10 @@ def get_function_hook(self, fullname: str ) -> Optional[Callable[[FunctionContext], Type]]: if fullname == 'builtins.isinstance': return isinstance_proper_hook + if fullname == 'mypy.types.get_proper_type': + return proper_type_hook + if fullname == 'mypy.types.get_proper_types': + return proper_types_hook return None @@ -45,6 +50,7 @@ def isinstance_proper_hook(ctx: FunctionContext) -> Type: def is_special_target(right: ProperType) -> bool: + """Whitelist some special cases for use in isinstance() with improper types.""" if isinstance(right, CallableType) and right.is_type_obj(): if right.type_object().fullname() == 'builtins.tuple': # Used with Union[Type, Tuple[Type, ...]]. @@ -81,5 +87,39 @@ def is_improper_type(typ: Type) -> bool: return False +def proper_type_hook(ctx: FunctionContext) -> Type: + """Check if this get_proper_type() call is not redundant.""" + arg_types = ctx.arg_types[0] + if arg_types: + arg_type = get_proper_type(arg_types[0]) + proper_type = get_proper_type_instance(ctx) + if is_proper_subtype(arg_type, UnionType.make_union([NoneTyp(), proper_type])): + # Minimize amount of spurious errors from overload machinery. + # TODO: call the hook on the overload as a whole? + if isinstance(arg_type, (UnionType, Instance)): + ctx.api.fail('Redundant call to get_proper_type()', ctx.context) + return ctx.default_return_type + + +def proper_types_hook(ctx: FunctionContext) -> Type: + """Check if this get_proper_types() call is not redundant.""" + arg_types = ctx.arg_types[0] + if arg_types: + arg_type = arg_types[0] + proper_type = get_proper_type_instance(ctx) + item_type = UnionType.make_union([NoneTyp(), proper_type]) + ok_type = ctx.api.named_generic_type('typing.Iterable', [item_type]) + if is_proper_subtype(arg_type, ok_type): + ctx.api.fail('Redundant call to get_proper_types()', ctx.context) + return ctx.default_return_type + + +def get_proper_type_instance(ctx: FunctionContext) -> Instance: + types = ctx.api.modules['mypy.types'] # type: ignore + proper_type_info = types.names['ProperType'] + assert isinstance(proper_type_info.node, TypeInfo) + return Instance(proper_type_info.node, []) + + def plugin(version: str) -> typing_Type[ProperTypePlugin]: return ProperTypePlugin diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 9db4f24271e4..3988635702f8 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -196,7 +196,7 @@ def set_callable_name(sig: Type, fdef: FuncDef) -> ProperType: else: return sig.with_name(fdef.name()) else: - return get_proper_type(sig) + return sig def calculate_tuple_fallback(typ: TupleType) -> None: diff --git a/mypy/suggestions.py b/mypy/suggestions.py index 51143e8b9e98..c0b5606a1089 100644 --- a/mypy/suggestions.py +++ b/mypy/suggestions.py @@ -420,7 +420,7 @@ def extract_from_decorator(self, node: Decorator) -> Optional[FuncDef]: typ = None if (isinstance(dec, RefExpr) and isinstance(dec.node, FuncDef)): - typ = get_proper_type(dec.node.type) + typ = dec.node.type elif (isinstance(dec, CallExpr) and isinstance(dec.callee, RefExpr) and isinstance(dec.callee.node, FuncDef) From 76bddb80f5095f8c3050ede6dae8b92b08e9e8b6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Aug 2019 18:07:26 +0100 Subject: [PATCH 09/12] Remove some unused ignores --- mypy/fastparse.py | 2 +- mypy/fastparse2.py | 2 +- mypy/report.py | 3 +-- mypy/test/testsolve.py | 2 +- mypy/types.py | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 0f999ca4ca07..bdc1e27ae0c0 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -555,7 +555,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], func_type = None if any(arg_types) or return_type: - if len(arg_types) != 1 and any(isinstance(t, EllipsisType) # type: ignore + if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types): self.fail("Ellipses cannot accompany other argument types " "in function type signature", lineno, n.col_offset) diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 012b3aa44b89..e577cee60013 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -411,7 +411,7 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: func_type = None if any(arg_types) or return_type: - if len(arg_types) != 1 and any(isinstance(t, EllipsisType) # type: ignore + if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types): self.fail("Ellipses cannot accompany other argument types " "in function type signature", lineno, n.col_offset) diff --git a/mypy/report.py b/mypy/report.py index e891bbfa423c..ad820f53e514 100644 --- a/mypy/report.py +++ b/mypy/report.py @@ -840,8 +840,7 @@ def on_finish(self) -> None: width = max(4, max(len(info.module) for info in output_files)) titles = ('Lines', 'Precise', 'Imprecise', 'Any', 'Empty', 'Unanalyzed') widths = (width,) + tuple(len(t) for t in titles) - # TODO: Need mypyc mypy pin move - fmt = '{:%d} {:%d} {:%d} {:%d} {:%d} {:%d} {:%d}\n' % widths # type: ignore + fmt = '{:%d} {:%d} {:%d} {:%d} {:%d} {:%d} {:%d}\n' % widths with open(report_file, 'w') as f: f.write( fmt.format('Name', *titles)) diff --git a/mypy/test/testsolve.py b/mypy/test/testsolve.py index 4daebe7811e2..172e4e4743c4 100644 --- a/mypy/test/testsolve.py +++ b/mypy/test/testsolve.py @@ -117,7 +117,7 @@ def assert_solve(self, ) -> None: res = [] # type: List[Optional[Type]] for r in results: - if isinstance(r, tuple): # type: ignore + if isinstance(r, tuple): res.append(r[0]) else: res.append(r) diff --git a/mypy/types.py b/mypy/types.py index 93d39b3ec72e..ad00a18830fd 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2295,7 +2295,7 @@ def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type], typ_args = get_typ_args(tp) new_args = typ_args[:] for i, arg in enumerate(typ_args): - if isinstance(arg, (UnboundType, TypeVarType)): # type: ignore + if isinstance(arg, (UnboundType, TypeVarType)): tvar = arg.name # type: Optional[str] else: tvar = None From 72a9b08eb752e29705c40a4fb9d6f479583d4e7e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Aug 2019 18:10:08 +0100 Subject: [PATCH 10/12] Fix lint --- mypy/semanal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 7dbdb8fbfe01..e0d48432fbac 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1385,7 +1385,8 @@ def get_name_repr_of_expr(self, expr: Expression) -> Optional[str]: def analyze_base_classes( self, - base_type_exprs: List[Expression]) -> Optional[Tuple[List[Tuple[ProperType, Expression]], + base_type_exprs: List[Expression]) -> Optional[Tuple[List[Tuple[ProperType, + Expression]], bool]]: """Analyze base class types. From f26ffc7d247c02597da09fe4fef723bc716e9079 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 18 Aug 2019 21:56:42 +0100 Subject: [PATCH 11/12] Minor formatting --- mypy/typeanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 50e42ef8b1b7..c05a599e878d 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -217,7 +217,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) # The only case where expand_type_alias() can return an incorrect instance is # when it is top-level instance, so no need to recurse. # TODO: this is not really needed, since with the new logic we will not expand - # aliases immediately. + # aliases immediately. res = get_proper_type(res) if (isinstance(res, Instance) and len(res.args) != len(res.type.type_vars) and not self.defining_alias): From 42f0c7e3081d2f88a3a6faa521a8fff9de6ecbb5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 22 Aug 2019 20:10:11 +0100 Subject: [PATCH 12/12] Address CR --- misc/proper_plugin.py | 22 ++++++++++++++-------- mypy/memprofile.py | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/misc/proper_plugin.py b/misc/proper_plugin.py index 9c5e3565a882..d133cf300a87 100644 --- a/misc/proper_plugin.py +++ b/misc/proper_plugin.py @@ -1,17 +1,14 @@ from mypy.plugin import Plugin, FunctionContext from mypy.types import ( Type, Instance, CallableType, UnionType, get_proper_type, ProperType, - get_proper_types, TupleType, NoneTyp + get_proper_types, TupleType, NoneTyp, AnyType ) from mypy.nodes import TypeInfo from mypy.subtypes import is_proper_subtype -import os.path from typing_extensions import Type as typing_Type from typing import Optional, Callable -FILE_WHITELIST = [] - class ProperTypePlugin(Plugin): """ @@ -37,11 +34,10 @@ def get_function_hook(self, fullname: str def isinstance_proper_hook(ctx: FunctionContext) -> Type: - if os.path.split(ctx.api.path)[-1] in FILE_WHITELIST: - return ctx.default_return_type + right = get_proper_type(ctx.arg_types[1][0]) for arg in ctx.arg_types[0]: - if is_improper_type(arg): - right = get_proper_type(ctx.arg_types[1][0]) + if (is_improper_type(arg) or + isinstance(get_proper_type(arg), AnyType) and is_dangerous_target(right)): if is_special_target(right): return ctx.default_return_type ctx.api.fail('Never apply isinstance() to unexpanded types;' @@ -62,6 +58,7 @@ def is_special_target(right: ProperType) -> bool: return True if right.type_object().fullname() in ('mypy.types.UnboundType', 'mypy.types.TypeVarType', + 'mypy.types.RawExpressionType', 'mypy.types.EllipsisType', 'mypy.types.StarType', 'mypy.types.TypeList', @@ -87,6 +84,15 @@ def is_improper_type(typ: Type) -> bool: return False +def is_dangerous_target(typ: ProperType) -> bool: + """Is this a dangerous target (right argument) for an isinstance() check?""" + if isinstance(typ, TupleType): + return any(is_dangerous_target(get_proper_type(t)) for t in typ.items) + if isinstance(typ, CallableType) and typ.is_type_obj(): + return typ.type_object().has_base('mypy.types.Type') + return False + + def proper_type_hook(ctx: FunctionContext) -> Type: """Check if this get_proper_type() call is not redundant.""" arg_types = ctx.arg_types[0] diff --git a/mypy/memprofile.py b/mypy/memprofile.py index 10ab934ca949..4dde1abe588c 100644 --- a/mypy/memprofile.py +++ b/mypy/memprofile.py @@ -34,7 +34,7 @@ def collect_memory_stats() -> Tuple[Dict[str, int], if hasattr(obj, '__dict__'): # Keep track of which class a particular __dict__ is associated with. inferred[id(obj.__dict__)] = '%s (__dict__)' % n - if isinstance(obj, (Node, Type)): + if isinstance(obj, (Node, Type)): # type: ignore if hasattr(obj, '__dict__'): for x in obj.__dict__.values(): if isinstance(x, list):