From b02cd86007102e02357ba5edcef1d70e3eaef24e Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 26 Aug 2019 14:19:39 -0700 Subject: [PATCH 1/8] Move bind_self and some friends into typeops.py --- mypy/checker.py | 6 +- mypy/checkmember.py | 182 +---------------------------------------- mypy/subtypes.py | 3 +- mypy/typeops.py | 194 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 202 insertions(+), 183 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 561e5fcd24397..93f7f48bf7c9f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -45,8 +45,10 @@ ) import mypy.checkexpr from mypy.checkmember import ( - map_type_from_supertype, bind_self, erase_to_bound, type_object_type, - analyze_descriptor_access, + analyze_descriptor_access, type_object_type, +) +from mypy.typeops import ( + map_type_from_supertype, bind_self, erase_to_bound, ) from mypy import message_registry from mypy.subtypes import ( diff --git a/mypy/checkmember.py b/mypy/checkmember.py index e0b22d12827d2..d027978390ce2 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -24,7 +24,9 @@ from mypy import message_registry from mypy import subtypes from mypy import meet -from mypy.typeops import tuple_fallback +from mypy.typeops import ( + tuple_fallback, bind_self, erase_to_bound, class_callable, type_object_type_from_function +) if TYPE_CHECKING: # import for forward declaration only import mypy.checker @@ -869,181 +871,3 @@ def is_valid_constructor(n: Optional[SymbolNode]) -> bool: if isinstance(n, Decorator): return isinstance(get_proper_type(n.type), FunctionLike) return False - - -def type_object_type_from_function(signature: FunctionLike, - info: TypeInfo, - def_info: TypeInfo, - fallback: Instance, - is_new: bool) -> FunctionLike: - # The __init__ method might come from a generic superclass - # (init_or_new.info) with type variables that do not map - # identically to the type variables of the class being constructed - # (info). For example - # - # class A(Generic[T]): def __init__(self, x: T) -> None: pass - # class B(A[List[T]], Generic[T]): pass - # - # We need to first map B's __init__ to the type (List[T]) -> None. - signature = bind_self(signature, original_type=fill_typevars(info), is_classmethod=is_new) - signature = cast(FunctionLike, - map_type_from_supertype(signature, info, def_info)) - special_sig = None # type: Optional[str] - if def_info.fullname() == 'builtins.dict': - # Special signature! - special_sig = 'dict' - - if isinstance(signature, CallableType): - return class_callable(signature, info, fallback, special_sig, is_new) - else: - # Overloaded __init__/__new__. - assert isinstance(signature, Overloaded) - items = [] # type: List[CallableType] - for item in signature.items(): - items.append(class_callable(item, info, fallback, special_sig, is_new)) - return Overloaded(items) - - -def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, - special_sig: Optional[str], - is_new: bool) -> CallableType: - """Create a type object type based on the signature of __init__.""" - variables = [] # type: List[TypeVarDef] - variables.extend(info.defn.type_vars) - variables.extend(init_type.variables) - - init_ret_type = get_proper_type(init_type.ret_type) - if is_new and isinstance(init_ret_type, (Instance, TupleType)): - ret_type = init_type.ret_type # type: Type - else: - ret_type = fill_typevars(info) - - callable_type = init_type.copy_modified( - ret_type=ret_type, fallback=type_type, name=None, variables=variables, - special_sig=special_sig) - c = callable_type.with_name(info.name()) - return c - - -def map_type_from_supertype(typ: Type, - sub_info: TypeInfo, - super_info: TypeInfo) -> Type: - """Map type variables in a type defined in a supertype context to be valid - in the subtype context. Assume that the result is unique; if more than - one type is possible, return one of the alternatives. - - For example, assume - - . class D(Generic[S]) ... - . class C(D[E[T]], Generic[T]) ... - - Now S in the context of D would be mapped to E[T] in the context of C. - """ - # Create the type of self in subtype, of form t[a1, ...]. - inst_type = fill_typevars(sub_info) - if isinstance(inst_type, TupleType): - inst_type = tuple_fallback(inst_type) - # Map the type of self to supertype. This gets us a description of the - # supertype type variables in terms of subtype variables, i.e. t[t1, ...] - # so that any type variables in tN are to be interpreted in subtype - # context. - inst_type = map_instance_to_supertype(inst_type, super_info) - # Finally expand the type variables in type with those in the previously - # constructed type. Note that both type and inst_type may have type - # variables, but in type they are interpreted in supertype context while - # in inst_type they are interpreted in subtype context. This works even if - # the names of type variables in supertype and subtype overlap. - return expand_type_by_instance(typ, inst_type) - - -F = TypeVar('F', bound=FunctionLike) - - -def bind_self(method: F, original_type: Optional[Type] = None, is_classmethod: bool = False) -> F: - """Return a copy of `method`, with the type of its first parameter (usually - self or cls) bound to original_type. - - If the type of `self` is a generic type (T, or Type[T] for classmethods), - instantiate every occurrence of type with original_type in the rest of the - signature and in the return type. - - original_type is the type of E in the expression E.copy(). It is None in - compatibility checks. In this case we treat it as the erasure of the - declared type of self. - - This way we can express "the type of self". For example: - - T = TypeVar('T', bound='A') - class A: - def copy(self: T) -> T: ... - - class B(A): pass - - b = B().copy() # type: B - - """ - if isinstance(method, Overloaded): - return cast(F, Overloaded([bind_self(c, original_type) for c in method.items()])) - assert isinstance(method, CallableType) - func = method - if not func.arg_types: - # invalid method. return something - return cast(F, func) - if func.arg_kinds[0] == ARG_STAR: - # The signature is of the form 'def foo(*args, ...)'. - # In this case we shouldn't drop the first arg, - # since func will be absorbed by the *args. - - # TODO: infer bounds on the type of *args? - return cast(F, func) - self_param_type = get_proper_type(func.arg_types[0]) - if func.variables and (isinstance(self_param_type, TypeVarType) or - (isinstance(self_param_type, TypeType) and - isinstance(self_param_type.item, TypeVarType))): - if original_type is None: - # Type check method override - # XXX value restriction as union? - original_type = erase_to_bound(self_param_type) - original_type = get_proper_type(original_type) - - ids = [x.id for x in func.variables] - typearg = get_proper_type(infer_type_arguments(ids, self_param_type, original_type)[0]) - if (is_classmethod and isinstance(typearg, UninhabitedType) - and isinstance(original_type, (Instance, TypeVarType, TupleType))): - # In case we call a classmethod through an instance x, fallback to type(x) - # TODO: handle Union - typearg = get_proper_type(infer_type_arguments(ids, self_param_type, - TypeType(original_type))[0]) - - def expand(target: Type) -> Type: - assert typearg is not None - return expand_type(target, {func.variables[0].id: typearg}) - - arg_types = [expand(x) for x in func.arg_types[1:]] - ret_type = expand(func.ret_type) - variables = func.variables[1:] - else: - arg_types = func.arg_types[1:] - ret_type = func.ret_type - variables = func.variables - - original_type = get_proper_type(original_type) - if isinstance(original_type, CallableType) and original_type.is_type_obj(): - original_type = TypeType.make_normalized(original_type.ret_type) - res = func.copy_modified(arg_types=arg_types, - arg_kinds=func.arg_kinds[1:], - arg_names=func.arg_names[1:], - variables=variables, - ret_type=ret_type, - bound_args=[original_type]) - return cast(F, res) - - -def erase_to_bound(t: Type) -> Type: - t = get_proper_type(t) - if isinstance(t, TypeVarType): - return t.upper_bound - if isinstance(t, TypeType): - if isinstance(t.item, TypeVarType): - return TypeType.make_normalized(t.item.upper_bound) - return t diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 0a40b09fff327..c10a8e1032a9c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -609,7 +609,8 @@ def find_node_type(node: Union[Var, FuncBase], itype: Instance, subtype: Type) - """Find type of a variable or method 'node' (maybe also a decorated method). Apply type arguments from 'itype', and bind 'self' to 'subtype'. """ - from mypy.checkmember import bind_self + from mypy.typeops import bind_self + if isinstance(node, FuncBase): typ = function_type(node, fallback=Instance(itype.type.mro[-1], [])) # type: Optional[Type] diff --git a/mypy/typeops.py b/mypy/typeops.py index 67135af550e5f..f60ce2c939cd7 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -7,8 +7,20 @@ # TODO: Move more type operations here -from mypy.types import TupleType, Instance +from typing import cast, Optional, List +from mypy.types import ( + TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded, + TypeVarType, TypeType, UninhabitedType, + get_proper_type, +) +from mypy.nodes import ( + TypeInfo, TypeVar, ARG_STAR, +) +from mypy.maptype import map_instance_to_supertype +from mypy.expandtype import expand_type_by_instance, expand_type + +from mypy.typevars import fill_typevars def tuple_fallback(typ: TupleType) -> Instance: """Return fallback type for a tuple.""" @@ -18,3 +30,183 @@ def tuple_fallback(typ: TupleType) -> Instance: if info.fullname() != 'builtins.tuple': return typ.partial_fallback return Instance(info, [join_type_list(typ.items)]) + + +def type_object_type_from_function(signature: FunctionLike, + info: TypeInfo, + def_info: TypeInfo, + fallback: Instance, + is_new: bool) -> FunctionLike: + # The __init__ method might come from a generic superclass + # (init_or_new.info) with type variables that do not map + # identically to the type variables of the class being constructed + # (info). For example + # + # class A(Generic[T]): def __init__(self, x: T) -> None: pass + # class B(A[List[T]], Generic[T]): pass + # + # We need to first map B's __init__ to the type (List[T]) -> None. + signature = bind_self(signature, original_type=fill_typevars(info), is_classmethod=is_new) + signature = cast(FunctionLike, + map_type_from_supertype(signature, info, def_info)) + special_sig = None # type: Optional[str] + if def_info.fullname() == 'builtins.dict': + # Special signature! + special_sig = 'dict' + + if isinstance(signature, CallableType): + return class_callable(signature, info, fallback, special_sig, is_new) + else: + # Overloaded __init__/__new__. + assert isinstance(signature, Overloaded) + items = [] # type: List[CallableType] + for item in signature.items(): + items.append(class_callable(item, info, fallback, special_sig, is_new)) + return Overloaded(items) + + +def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, + special_sig: Optional[str], + is_new: bool) -> CallableType: + """Create a type object type based on the signature of __init__.""" + variables = [] # type: List[TypeVarDef] + variables.extend(info.defn.type_vars) + variables.extend(init_type.variables) + + init_ret_type = get_proper_type(init_type.ret_type) + if is_new and isinstance(init_ret_type, (Instance, TupleType)): + ret_type = init_type.ret_type # type: Type + else: + ret_type = fill_typevars(info) + + callable_type = init_type.copy_modified( + ret_type=ret_type, fallback=type_type, name=None, variables=variables, + special_sig=special_sig) + c = callable_type.with_name(info.name()) + return c + + +def map_type_from_supertype(typ: Type, + sub_info: TypeInfo, + super_info: TypeInfo) -> Type: + """Map type variables in a type defined in a supertype context to be valid + in the subtype context. Assume that the result is unique; if more than + one type is possible, return one of the alternatives. + + For example, assume + + . class D(Generic[S]) ... + . class C(D[E[T]], Generic[T]) ... + + Now S in the context of D would be mapped to E[T] in the context of C. + """ + # Create the type of self in subtype, of form t[a1, ...]. + inst_type = fill_typevars(sub_info) + if isinstance(inst_type, TupleType): + inst_type = tuple_fallback(inst_type) + # Map the type of self to supertype. This gets us a description of the + # supertype type variables in terms of subtype variables, i.e. t[t1, ...] + # so that any type variables in tN are to be interpreted in subtype + # context. + inst_type = map_instance_to_supertype(inst_type, super_info) + # Finally expand the type variables in type with those in the previously + # constructed type. Note that both type and inst_type may have type + # variables, but in type they are interpreted in supertype context while + # in inst_type they are interpreted in subtype context. This works even if + # the names of type variables in supertype and subtype overlap. + return expand_type_by_instance(typ, inst_type) + + +F = TypeVar('F', bound=FunctionLike) + + +def bind_self(method: F, original_type: Optional[Type] = None, is_classmethod: bool = False) -> F: + """Return a copy of `method`, with the type of its first parameter (usually + self or cls) bound to original_type. + + If the type of `self` is a generic type (T, or Type[T] for classmethods), + instantiate every occurrence of type with original_type in the rest of the + signature and in the return type. + + original_type is the type of E in the expression E.copy(). It is None in + compatibility checks. In this case we treat it as the erasure of the + declared type of self. + + This way we can express "the type of self". For example: + + T = TypeVar('T', bound='A') + class A: + def copy(self: T) -> T: ... + + class B(A): pass + + b = B().copy() # type: B + + """ + from mypy.infer import infer_type_arguments + + if isinstance(method, Overloaded): + return cast(F, Overloaded([bind_self(c, original_type) for c in method.items()])) + assert isinstance(method, CallableType) + func = method + if not func.arg_types: + # invalid method. return something + return cast(F, func) + if func.arg_kinds[0] == ARG_STAR: + # The signature is of the form 'def foo(*args, ...)'. + # In this case we shouldn't drop the first arg, + # since func will be absorbed by the *args. + + # TODO: infer bounds on the type of *args? + return cast(F, func) + self_param_type = get_proper_type(func.arg_types[0]) + if func.variables and (isinstance(self_param_type, TypeVarType) or + (isinstance(self_param_type, TypeType) and + isinstance(self_param_type.item, TypeVarType))): + if original_type is None: + # Type check method override + # XXX value restriction as union? + original_type = erase_to_bound(self_param_type) + original_type = get_proper_type(original_type) + + ids = [x.id for x in func.variables] + typearg = get_proper_type(infer_type_arguments(ids, self_param_type, original_type)[0]) + if (is_classmethod and isinstance(typearg, UninhabitedType) + and isinstance(original_type, (Instance, TypeVarType, TupleType))): + # In case we call a classmethod through an instance x, fallback to type(x) + # TODO: handle Union + typearg = get_proper_type(infer_type_arguments(ids, self_param_type, + TypeType(original_type))[0]) + + def expand(target: Type) -> Type: + assert typearg is not None + return expand_type(target, {func.variables[0].id: typearg}) + + arg_types = [expand(x) for x in func.arg_types[1:]] + ret_type = expand(func.ret_type) + variables = func.variables[1:] + else: + arg_types = func.arg_types[1:] + ret_type = func.ret_type + variables = func.variables + + original_type = get_proper_type(original_type) + if isinstance(original_type, CallableType) and original_type.is_type_obj(): + original_type = TypeType.make_normalized(original_type.ret_type) + res = func.copy_modified(arg_types=arg_types, + arg_kinds=func.arg_kinds[1:], + arg_names=func.arg_names[1:], + variables=variables, + ret_type=ret_type, + bound_args=[original_type]) + return cast(F, res) + + +def erase_to_bound(t: Type) -> Type: + t = get_proper_type(t) + if isinstance(t, TypeVarType): + return t.upper_bound + if isinstance(t, TypeType): + if isinstance(t.item, TypeVarType): + return TypeType.make_normalized(t.item.upper_bound) + return t From 24c1d1a92481648ac729315df0a75b7de2fa369d Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 26 Aug 2019 14:24:54 -0700 Subject: [PATCH 2/8] Move corresponding_argument to typeops --- mypy/subtypes.py | 2 +- mypy/typeops.py | 29 ++++++++++++++++++++++++++++- mypy/types.py | 27 ++------------------------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c10a8e1032a9c..35ca26843dbb3 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -842,7 +842,7 @@ def _incompatible(left_arg: Optional[FormalArgument], # also accept. The only exception is if we are allowing partial # partial overlaps: in that case, we ignore optional args on the right. for right_arg in right.formal_arguments(): - left_arg = left.corresponding_argument(right_arg) + left_arg = mypy.typeops.callable_corresponding_argument(left, right_arg) if left_arg is None: if allow_partial_overlap and not right_arg.required: continue diff --git a/mypy/typeops.py b/mypy/typeops.py index f60ce2c939cd7..3ef5976592eee 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -11,7 +11,7 @@ from mypy.types import ( TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded, - TypeVarType, TypeType, UninhabitedType, + TypeVarType, TypeType, UninhabitedType, FormalArgument, get_proper_type, ) from mypy.nodes import ( @@ -210,3 +210,30 @@ def erase_to_bound(t: Type) -> Type: if isinstance(t.item, TypeVarType): return TypeType.make_normalized(t.item.upper_bound) return t + + +def callable_corresponding_argument(typ: CallableType, + model: FormalArgument) -> Optional[FormalArgument]: + """Return the argument a function that corresponds to `model`""" + + by_name = typ.argument_by_name(model.name) + by_pos = typ.argument_by_position(model.pos) + if by_name is None and by_pos is None: + return None + if by_name is not None and by_pos is not None: + if by_name == by_pos: + return by_name + # If we're dealing with an optional pos-only and an optional + # name-only arg, merge them. This is the case for all functions + # taking both *args and **args, or a pair of functions like so: + + # def right(a: int = ...) -> None: ... + # def left(__a: int = ..., *, a: int = ...) -> None: ... + from mypy.subtypes import is_equivalent + + if (not (by_name.required or by_pos.required) + and by_pos.name is None + and by_name.pos is None + and is_equivalent(by_name.typ, by_pos.typ)): + return FormalArgument(by_name.name, by_pos.pos, by_name.typ, False) + return by_name if by_name is not None else by_pos diff --git a/mypy/types.py b/mypy/types.py index bd96d539ef596..42e0d723d104e 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1123,30 +1123,6 @@ def formal_arguments(self, include_star_args: bool = False) -> Iterator[FormalAr self.arg_types[i], required) - def corresponding_argument(self, model: FormalArgument) -> Optional[FormalArgument]: - """Return the argument in this function that corresponds to `model`""" - - by_name = self.argument_by_name(model.name) - by_pos = self.argument_by_position(model.pos) - if by_name is None and by_pos is None: - return None - if by_name is not None and by_pos is not None: - if by_name == by_pos: - return by_name - # If we're dealing with an optional pos-only and an optional - # name-only arg, merge them. This is the case for all functions - # taking both *args and **args, or a pair of functions like so: - - # def right(a: int = ...) -> None: ... - # def left(__a: int = ..., *, a: int = ...) -> None: ... - from mypy.subtypes import is_equivalent - if (not (by_name.required or by_pos.required) - and by_pos.name is None - and by_name.pos is None - and is_equivalent(by_name.typ, by_pos.typ)): - return FormalArgument(by_name.name, by_pos.pos, by_name.typ, False) - return by_name if by_name is not None else by_pos - def argument_by_name(self, name: Optional[str]) -> Optional[FormalArgument]: if name is None: return None @@ -1749,7 +1725,8 @@ def make_simplified_union(items: Sequence[Type], all_items.append(typ) items = all_items - from mypy.subtypes import is_proper_subtype + # from mypy.subtypes import is_proper_subtype + is_proper_subtype = __import__('mypy.subtypes').subtypes.is_proper_subtype # type: ignore removed = set() # type: Set[int] for i, ti in enumerate(items): From 93511400cd6c27d15c29da2679a9dab97eeb5a1f Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 26 Aug 2019 14:47:56 -0700 Subject: [PATCH 3/8] Move make_simplified_union to typeops --- mypy/checker.py | 30 +++++++++--------- mypy/checkexpr.py | 38 +++++++++++------------ mypy/checkmember.py | 7 +++-- mypy/erasetype.py | 5 ++- mypy/expandtype.py | 4 ++- mypy/join.py | 6 ++-- mypy/meet.py | 8 ++--- mypy/plugins/attrs.py | 3 +- mypy/plugins/ctypes.py | 9 +++--- mypy/plugins/default.py | 13 ++++---- mypy/sametypes.py | 4 +-- mypy/subtypes.py | 7 +++-- mypy/suggestions.py | 3 +- mypy/typeops.py | 58 ++++++++++++++++++++++++++++++++-- mypy/types.py | 69 +++++++---------------------------------- 15 files changed, 140 insertions(+), 124 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 93f7f48bf7c9f..267c0979ceb75 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -48,7 +48,7 @@ analyze_descriptor_access, type_object_type, ) from mypy.typeops import ( - map_type_from_supertype, bind_self, erase_to_bound, + map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union, ) from mypy import message_registry from mypy.subtypes import ( @@ -1541,7 +1541,7 @@ def get_op_other_domain(self, tp: FunctionLike) -> Optional[Type]: raw_items = [self.get_op_other_domain(it) for it in tp.items()] items = [it for it in raw_items if it] if items: - return UnionType.make_simplified_union(items) + return make_simplified_union(items) return None else: assert False, "Need to check all FunctionLike subtypes here" @@ -1975,7 +1975,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type if not self.current_node_deferred: # Partial type can't be final, so strip any literal values. rvalue_type = remove_instance_last_known_values(rvalue_type) - inferred_type = UnionType.make_simplified_union( + inferred_type = make_simplified_union( [rvalue_type, NoneType()]) self.set_inferred_type(var, lvalue, inferred_type) else: @@ -2419,7 +2419,7 @@ def check_multi_assignment_from_union(self, lvalues: List[Expression], rvalue: E rv_type=item, undefined_rvalue=True) for t, lv in zip(transposed, self.flatten_lvalues(lvalues)): t.append(self.type_map.pop(lv, AnyType(TypeOfAny.special_form))) - union_types = tuple(UnionType.make_simplified_union(col) for col in transposed) + union_types = tuple(make_simplified_union(col) for col in transposed) for expr, items in assignments.items(): # Bind a union of types collected in 'assignments' to every expression. if isinstance(expr, StarExpr): @@ -2434,8 +2434,8 @@ def check_multi_assignment_from_union(self, lvalues: List[Expression], rvalue: E types, declared_types = zip(*clean_items) self.binder.assign_type(expr, - UnionType.make_simplified_union(list(types)), - UnionType.make_simplified_union(list(declared_types)), + make_simplified_union(list(types)), + make_simplified_union(list(declared_types)), False) for union, lv in zip(union_types, self.flatten_lvalues(lvalues)): # Properly store the inferred types. @@ -2858,9 +2858,9 @@ def try_infer_partial_type_from_indexed_assignment( # TODO: Don't infer things twice. key_type = self.expr_checker.accept(lvalue.index) value_type = self.expr_checker.accept(rvalue) - full_key_type = UnionType.make_simplified_union( + full_key_type = make_simplified_union( [key_type, var.type.inner_types[0]]) - full_value_type = UnionType.make_simplified_union( + full_value_type = make_simplified_union( [value_type, var.type.inner_types[1]]) if (is_valid_inferred_type(full_key_type) and is_valid_inferred_type(full_value_type)): @@ -3178,7 +3178,7 @@ def check_except_handler_test(self, n: Expression) -> Type: all_types.append(exc_type) - return UnionType.make_simplified_union(all_types) + return make_simplified_union(all_types) def get_types_from_except_handler(self, typ: Type, n: Expression) -> List[Type]: """Helper for check_except_handler_test to retrieve handler types.""" @@ -4089,7 +4089,7 @@ def conditional_type_map(expr: Expression, if it was not the proposed type, if any. None means bot, {} means top""" if proposed_type_ranges: proposed_items = [type_range.item for type_range in proposed_type_ranges] - proposed_type = UnionType.make_simplified_union(proposed_items) + proposed_type = make_simplified_union(proposed_items) if current_type: if isinstance(proposed_type, AnyType): # We don't really know much about the proposed type, so we shouldn't @@ -4172,7 +4172,7 @@ def builtin_item_type(tp: Type) -> Optional[Type]: return tp.args[0] 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 + return 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 # base class. @@ -4223,7 +4223,7 @@ def or_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: for n1 in m1: for n2 in m2: if literal_hash(n1) == literal_hash(n2): - result[n1] = UnionType.make_simplified_union([m1[n1], m2[n2]]) + result[n1] = make_simplified_union([m1[n1], m2[n2]]) return result @@ -4691,7 +4691,7 @@ class Status(Enum): if isinstance(typ, UnionType): items = [try_expanding_enum_to_union(item, target_fullname) for item in typ.items] - return UnionType.make_simplified_union(items) + return make_simplified_union(items) elif isinstance(typ, Instance) and typ.type.is_enum and typ.type.fullname() == target_fullname: new_items = [] for name, symbol in typ.type.names.items(): @@ -4706,7 +4706,7 @@ class Status(Enum): # only using CPython, but we might as well for the sake of full correctness. if sys.version_info < (3, 7): new_items.sort(key=lambda lit: lit.value) - return UnionType.make_simplified_union(new_items) + return make_simplified_union(new_items) else: return typ @@ -4718,7 +4718,7 @@ def coerce_to_literal(typ: Type) -> ProperType: 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) + return make_simplified_union(new_items) elif isinstance(typ, Instance) and typ.last_known_value: return typ.last_known_value else: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 135f796d479e4..0d510f13959c7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -59,7 +59,7 @@ from mypy.typevars import fill_typevars from mypy.visitor import ExpressionVisitor from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext -from mypy.typeops import tuple_fallback +from mypy.typeops import tuple_fallback, make_simplified_union import mypy.errorcodes as codes # Type of callback user for checking individual function arguments. See @@ -528,7 +528,7 @@ def try_infer_partial_type(self, e: CallExpr) -> None: if (typename in self.item_args and methodname in self.item_args[typename] and e.arg_kinds == [ARG_POS]): item_type = self.accept(e.args[0]) - full_item_type = UnionType.make_simplified_union( + full_item_type = make_simplified_union( [item_type, partial_type.inner_types[0]]) if mypy.checker.is_valid_inferred_type(full_item_type): var.type = self.chk.named_generic_type(typename, [full_item_type]) @@ -541,7 +541,7 @@ def try_infer_partial_type(self, e: CallExpr) -> None: arg_typename = arg_type.type.fullname() if arg_typename in self.container_args[typename][methodname]: full_item_types = [ - UnionType.make_simplified_union([item_type, prev_type]) + make_simplified_union([item_type, prev_type]) for item_type, prev_type in zip(arg_type.args, partial_type.inner_types) ] @@ -713,7 +713,7 @@ def check_union_call_expr(self, e: CallExpr, object_type: UnionType, member: str item_object_type = typ if callable_name else None res.append(self.check_call_expr_with_callee_type(narrowed, e, callable_name, item_object_type)) - return UnionType.make_simplified_union(res) + return make_simplified_union(res) def check_call(self, callee: Type, @@ -1377,7 +1377,7 @@ def check_overload_call(self, # a union of inferred callables because for example a call # Union[int -> int, str -> str](Union[int, str]) is invalid and # we don't want to introduce internal inconsistencies. - unioned_result = (UnionType.make_simplified_union(list(returns), + unioned_result = (make_simplified_union(list(returns), context.line, context.column), self.combine_function_signatures(inferred_types)) @@ -1736,7 +1736,7 @@ def combine_function_signatures(self, types: Sequence[Type]) -> Union[AnyType, C new_args[i].append(arg) new_returns.append(target.ret_type) - union_return = UnionType.make_simplified_union(new_returns) + union_return = make_simplified_union(new_returns) if too_complex: any = AnyType(TypeOfAny.special_form) return callables[0].copy_modified( @@ -1749,7 +1749,7 @@ def combine_function_signatures(self, types: Sequence[Type]) -> Union[AnyType, C final_args = [] for args_list in new_args: - new_type = UnionType.make_simplified_union(args_list) + new_type = make_simplified_union(args_list) final_args.append(new_type) return callables[0].copy_modified( @@ -1818,7 +1818,7 @@ def check_union_call(self, arg_messages=arg_messages) for subtype in callee.relevant_items()] self.msg.disable_type_names -= 1 - return (UnionType.make_simplified_union([res[0] for res in results]), + return (make_simplified_union([res[0] for res in results]), callee) def visit_member_expr(self, e: MemberExpr, is_lvalue: bool = False) -> Type: @@ -2168,7 +2168,7 @@ def check_union_method_call_by_name(self, local_errors.disable_type_names -= 1 res.append(item) meth_res.append(meth_item) - return UnionType.make_simplified_union(res), UnionType.make_simplified_union(meth_res) + return make_simplified_union(res), make_simplified_union(meth_res) def check_method_call(self, method_name: str, @@ -2425,8 +2425,8 @@ def check_op(self, method: str, base_type: Type, all_inferred.append(inferred) if not msg.is_errors(): - results_final = UnionType.make_simplified_union(all_results) - inferred_final = UnionType.make_simplified_union(all_inferred) + results_final = make_simplified_union(all_results) + inferred_final = make_simplified_union(all_inferred) return results_final, inferred_final # Step 2: If that fails, we try again but also destructure the right argument. @@ -2475,7 +2475,7 @@ def check_op(self, method: str, base_type: Type, # See the comment in 'check_overload_call' for more details on why # we call 'combine_function_signature' instead of just unioning the inferred # callable types. - results_final = UnionType.make_simplified_union(all_results) + results_final = make_simplified_union(all_results) inferred_final = self.combine_function_signatures(all_inferred) return results_final, inferred_final else: @@ -2560,7 +2560,7 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: # The left operand is always the result return left_type else: - return UnionType.make_simplified_union([restricted_left_type, right_type]) + return make_simplified_union([restricted_left_type, right_type]) def check_list_multiply(self, e: OpExpr) -> Type: """Type check an expression of form '[...] * e'. @@ -2626,7 +2626,7 @@ def visit_index_with_type(self, left_type: Type, e: IndexExpr, if isinstance(left_type, UnionType): original_type = original_type or left_type - return UnionType.make_simplified_union([self.visit_index_with_type(typ, e, + return make_simplified_union([self.visit_index_with_type(typ, e, original_type) for typ in left_type.relevant_items()]) elif isinstance(left_type, TupleType) and self.chk.in_checked_function(): @@ -2646,7 +2646,7 @@ def visit_index_with_type(self, left_type: Type, e: IndexExpr, else: self.chk.fail(message_registry.TUPLE_INDEX_OUT_OF_RANGE, e) return AnyType(TypeOfAny.from_error) - return UnionType.make_simplified_union(out) + return make_simplified_union(out) else: return self.nonliteral_tuple_index_helper(left_type, index) elif isinstance(left_type, TypedDictType): @@ -2687,7 +2687,7 @@ def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr) -> Typ items = [] # type: List[Type] for b, e, s in itertools.product(begin, end, stride): items.append(left_type.slice(b, e, s)) - return UnionType.make_simplified_union(items) + return make_simplified_union(items) def try_getting_int_literals(self, index: Expression) -> Optional[List[int]]: """If the given expression or type corresponds to an int literal @@ -2732,7 +2732,7 @@ def nonliteral_tuple_index_helper(self, left_type: TupleType, index: Expression) 'actual type', 'expected type'): return AnyType(TypeOfAny.from_error) else: - union = UnionType.make_simplified_union(left_type.items) + union = make_simplified_union(left_type.items) if isinstance(index, SliceExpr): return self.chk.named_generic_type('builtins.tuple', [union]) else: @@ -2767,7 +2767,7 @@ def visit_typeddict_index_expr(self, td_type: TypedDictType, index: Expression) return AnyType(TypeOfAny.from_error) else: value_types.append(value_type) - return UnionType.make_simplified_union(value_types) + return make_simplified_union(value_types) def visit_enum_index_expr(self, enum_type: TypeInfo, index: Expression, context: Context) -> Type: @@ -3471,7 +3471,7 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type: # # TODO: Always create a union or at least in more cases? if isinstance(get_proper_type(self.type_context[-1]), UnionType): - res = UnionType.make_simplified_union([if_type, else_type]) + res = make_simplified_union([if_type, else_type]) else: res = join.join_types(if_type, else_type) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d027978390ce2..c8bc381fd9108 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -25,7 +25,8 @@ from mypy import subtypes from mypy import meet from mypy.typeops import ( - tuple_fallback, bind_self, erase_to_bound, class_callable, type_object_type_from_function + tuple_fallback, bind_self, erase_to_bound, class_callable, type_object_type_from_function, + make_simplified_union, ) if TYPE_CHECKING: # import for forward declaration only @@ -292,7 +293,7 @@ def analyze_union_member_access(name: str, typ: UnionType, mx: MemberContext) -> item_mx = mx.copy_modified(self_type=subtype) results.append(_analyze_member_access(name, subtype, item_mx)) mx.msg.disable_type_names -= 1 - return UnionType.make_simplified_union(results) + return make_simplified_union(results) def analyze_none_member_access(name: str, typ: NoneType, mx: MemberContext) -> Type: @@ -435,7 +436,7 @@ def analyze_descriptor_access(instance_type: Type, if isinstance(descriptor_type, UnionType): # Map the access over union types - return UnionType.make_simplified_union([ + return make_simplified_union([ analyze_descriptor_access(instance_type, typ, builtin_type, msg, context, chk=chk) for typ in descriptor_type.items diff --git a/mypy/erasetype.py b/mypy/erasetype.py index ee4a2d9f5bdc1..b438d62ec7d2e 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -9,6 +9,7 @@ from mypy.nodes import ARG_STAR, ARG_STAR2 + def erase_type(typ: Type) -> ProperType: """Erase any type variables from a type. @@ -87,7 +88,9 @@ def visit_literal_type(self, t: LiteralType) -> ProperType: def visit_union_type(self, t: UnionType) -> ProperType: erased_items = [erase_type(item) for item in t.items] - return UnionType.make_simplified_union(erased_items) + from mypy.typeops import make_simplified_union # asdf + # XXX: does this need to be simplified? + return make_simplified_union(erased_items) def visit_type_type(self, t: TypeType) -> ProperType: return TypeType.make_normalized(t.item.accept(self), line=t.line) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 0f04fe43f1c0e..d43c87ea065cc 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -119,7 +119,9 @@ def visit_literal_type(self, t: LiteralType) -> ProperType: def visit_union_type(self, t: UnionType) -> ProperType: # After substituting for type variables in t.items, # some of the resulting types might be subtypes of others. - return UnionType.make_simplified_union(self.expand_types(t.items), t.line, t.column) + from mypy.typeops import make_simplified_union # asdf + # XXX: does this need to be simplified? + return make_simplified_union(self.expand_types(t.items), t.line, t.column) def visit_partial_type(self, t: PartialType) -> ProperType: return t diff --git a/mypy/join.py b/mypy/join.py index a64ed62cbdb44..44b5a95e7f0f2 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -43,7 +43,7 @@ def join_simple(declaration: Optional[Type], s: Type, t: Type) -> ProperType: return s if isinstance(declaration, UnionType): - return UnionType.make_simplified_union([s, t]) + return mypy.typeops.make_simplified_union([s, t]) if isinstance(s, NoneType) and not isinstance(t, NoneType): s, t = t, s @@ -107,7 +107,7 @@ def visit_union_type(self, t: UnionType) -> ProperType: if is_subtype(self.s, t): return t else: - return UnionType.make_simplified_union([self.s, t]) + return mypy.typeops.make_simplified_union([self.s, t]) def visit_any(self, t: AnyType) -> ProperType: return t @@ -119,7 +119,7 @@ def visit_none_type(self, t: NoneType) -> ProperType: elif isinstance(self.s, UnboundType): return AnyType(TypeOfAny.special_form) else: - return UnionType.make_simplified_union([self.s, t]) + return mypy.typeops.make_simplified_union([self.s, t]) else: return self.s diff --git a/mypy/meet.py b/mypy/meet.py index b15dbd0ef3a8c..7bc2205879808 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -13,7 +13,7 @@ from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype from mypy.erasetype import erase_type from mypy.maptype import map_instance_to_supertype -from mypy.typeops import tuple_fallback +from mypy.typeops import tuple_fallback, make_simplified_union from mypy import state # TODO Describe this module. @@ -41,7 +41,7 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: if declared == narrowed: return declared if isinstance(declared, UnionType): - return UnionType.make_simplified_union([narrow_declared_type(x, narrowed) + return make_simplified_union([narrow_declared_type(x, narrowed) for x in declared.relevant_items()]) elif not is_overlapping_types(declared, narrowed, prohibit_none_typevar_overlap=True): @@ -50,7 +50,7 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: else: return NoneType() elif isinstance(narrowed, UnionType): - return UnionType.make_simplified_union([narrow_declared_type(declared, x) + return make_simplified_union([narrow_declared_type(declared, x) for x in narrowed.relevant_items()]) elif isinstance(narrowed, AnyType): return narrowed @@ -425,7 +425,7 @@ def visit_union_type(self, t: UnionType) -> ProperType: else: meets = [meet_types(x, self.s) for x in t.items] - return UnionType.make_simplified_union(meets) + return make_simplified_union(meets) def visit_none_type(self, t: NoneType) -> ProperType: if state.strict_optional: diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 093bcf0edc150..305b7592e562c 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -22,6 +22,7 @@ Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarDef, TypeVarType, Overloaded, UnionType, FunctionLike, get_proper_type ) +from mypy.typeops import make_simplified_union from mypy.typevars import fill_typevars from mypy.util import unmangle from mypy.server.trigger import make_wildcard_trigger @@ -109,7 +110,7 @@ def argument(self, ctx: 'mypy.plugin.ClassDefContext') -> Argument: types.append(item.arg_types[0]) # Make a union of all the valid types. if types: - args = UnionType.make_simplified_union(types) + args = make_simplified_union(types) init_type = ctx.api.anal_type(args) if self.converter.is_attr_converters_optional and init_type: diff --git a/mypy/plugins/ctypes.py b/mypy/plugins/ctypes.py index 446fb2cac8add..ce943cec88504 100644 --- a/mypy/plugins/ctypes.py +++ b/mypy/plugins/ctypes.py @@ -11,6 +11,7 @@ AnyType, CallableType, Instance, NoneType, Type, TypeOfAny, UnionType, union_items, ProperType, get_proper_type ) +from mypy.typeops import make_simplified_union def _get_bytes_type(api: 'mypy.plugin.CheckerPluginInterface') -> Instance: @@ -76,7 +77,7 @@ def _autoconvertible_to_cdata(tp: Type, api: 'mypy.plugin.CheckerPluginInterface allowed_types.append(api.named_generic_type('builtins.int', [])) allowed_types.append(NoneType()) - return UnionType.make_simplified_union(allowed_types) + return make_simplified_union(allowed_types) def _autounboxed_cdata(tp: Type) -> ProperType: @@ -89,7 +90,7 @@ def _autounboxed_cdata(tp: Type) -> ProperType: tp = get_proper_type(tp) if isinstance(tp, UnionType): - return UnionType.make_simplified_union([_autounboxed_cdata(t) for t in tp.items]) + return make_simplified_union([_autounboxed_cdata(t) for t in tp.items]) elif isinstance(tp, Instance): for base in tp.type.bases: if base.type.fullname() == 'ctypes._SimpleCData': @@ -207,7 +208,7 @@ def array_value_callback(ctx: 'mypy.plugin.AttributeContext') -> Type: ' with element type c_char or c_wchar, not "{}"' .format(et), ctx.context) - return UnionType.make_simplified_union(types) + return make_simplified_union(types) return ctx.default_attr_type @@ -226,5 +227,5 @@ def array_raw_callback(ctx: 'mypy.plugin.AttributeContext') -> Type: ' with element type c_char, not "{}"' .format(et), ctx.context) - return UnionType.make_simplified_union(types) + return make_simplified_union(types) return ctx.default_attr_type diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index e67cd1fa3aa02..5dbbd106d0cfe 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -12,6 +12,7 @@ TypeVarType, TPDICT_FB_NAMES, get_proper_type ) from mypy.subtypes import is_subtype +from mypy.typeops import make_simplified_union class DefaultPlugin(Plugin): @@ -168,7 +169,7 @@ def typed_dict_get_signature_callback(ctx: MethodSigContext) -> CallableType: tv = TypeVarType(signature.variables[0]) return signature.copy_modified( arg_types=[signature.arg_types[0], - UnionType.make_simplified_union([value_type, tv])], + make_simplified_union([value_type, tv])], ret_type=ret_type) return signature @@ -205,7 +206,7 @@ def typed_dict_get_callback(ctx: MethodContext) -> Type: if len(ctx.arg_types) == 1: output_types.append(NoneType()) - return UnionType.make_simplified_union(output_types) + return make_simplified_union(output_types) return ctx.default_return_type @@ -231,7 +232,7 @@ def typed_dict_pop_signature_callback(ctx: MethodSigContext) -> CallableType: # only needed for type inference since there's a union with a type # variable that accepts everything. tv = TypeVarType(signature.variables[0]) - typ = UnionType.make_simplified_union([value_type, tv]) + typ = make_simplified_union([value_type, tv]) return signature.copy_modified( arg_types=[str_type, typ], ret_type=typ) @@ -261,10 +262,10 @@ def typed_dict_pop_callback(ctx: MethodContext) -> Type: return AnyType(TypeOfAny.from_error) if len(ctx.args[1]) == 0: - return UnionType.make_simplified_union(value_types) + return make_simplified_union(value_types) elif (len(ctx.arg_types) == 2 and len(ctx.arg_types[1]) == 1 and len(ctx.args[1]) == 1): - return UnionType.make_simplified_union([*value_types, ctx.arg_types[1][0]]) + return make_simplified_union([*value_types, ctx.arg_types[1][0]]) return ctx.default_return_type @@ -321,7 +322,7 @@ def typed_dict_setdefault_callback(ctx: MethodContext) -> Type: value_types.append(value_type) - return UnionType.make_simplified_union(value_types) + return make_simplified_union(value_types) return ctx.default_return_type diff --git a/mypy/sametypes.py b/mypy/sametypes.py index cc0884ac42e7b..f09de9c18e15f 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -6,7 +6,7 @@ Overloaded, PartialType, DeletedType, UninhabitedType, TypeType, LiteralType, ProperType, get_proper_type ) -from mypy.typeops import tuple_fallback +from mypy.typeops import tuple_fallback, make_simplified_union def is_same_type(left: Type, right: Type) -> bool: @@ -35,7 +35,7 @@ def is_same_type(left: Type, right: Type) -> bool: def simplify_union(t: Type) -> ProperType: t = get_proper_type(t) if isinstance(t, UnionType): - return UnionType.make_simplified_union(t.items) + return make_simplified_union(t.items) return t diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 35ca26843dbb3..6fe6b1ae65dc6 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -241,7 +241,8 @@ def visit_type_var(self, left: TypeVarType) -> bool: right = self.right if isinstance(right, TypeVarType) and left.id == right.id: return True - if left.values and self._is_subtype(UnionType.make_simplified_union(left.values), right): + if left.values and self._is_subtype( + mypy.typeops.make_simplified_union(left.values), right): return True return self._is_subtype(left.upper_bound, self.right) @@ -1167,8 +1168,8 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool: def visit_type_var(self, left: TypeVarType) -> bool: if isinstance(self.right, TypeVarType) and left.id == self.right.id: return True - if left.values and self._is_proper_subtype(UnionType.make_simplified_union(left.values), - self.right): + if left.values and self._is_proper_subtype( + mypy.typeops.make_simplified_union(left.values), self.right): return True return self._is_proper_subtype(left.upper_bound, self.right) diff --git a/mypy/suggestions.py b/mypy/suggestions.py index c0b5606a1089e..1b100d50e9494 100644 --- a/mypy/suggestions.py +++ b/mypy/suggestions.py @@ -48,6 +48,7 @@ from mypy.join import join_type_list from mypy.sametypes import is_same_type +from mypy.typeops import make_simplified_union from contextlib import contextmanager @@ -598,7 +599,7 @@ def generate_type_combinations(types: List[Type]) -> List[Type]: and unioning the types. We try both. """ joined_type = join_type_list(types) - union_type = UnionType.make_simplified_union(types) + union_type = make_simplified_union(types) if is_same_type(joined_type, union_type): return [joined_type] else: diff --git a/mypy/typeops.py b/mypy/typeops.py index 3ef5976592eee..55f6191d3d6bd 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -7,12 +7,12 @@ # TODO: Move more type operations here -from typing import cast, Optional, List +from typing import cast, Optional, List, Sequence, Set from mypy.types import ( TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded, - TypeVarType, TypeType, UninhabitedType, FormalArgument, - get_proper_type, + TypeVarType, TypeType, UninhabitedType, FormalArgument, UnionType, true_or_false, + ProperType, get_proper_type, get_proper_types, ) from mypy.nodes import ( TypeInfo, TypeVar, ARG_STAR, @@ -237,3 +237,55 @@ def callable_corresponding_argument(typ: CallableType, and is_equivalent(by_name.typ, by_pos.typ)): return FormalArgument(by_name.name, by_pos.pos, by_name.typ, False) return by_name if by_name is not None else by_pos + + +def make_simplified_union(items: Sequence[Type], + line: int = -1, column: int = -1) -> ProperType: + """Build union type with redundant union items removed. + + If only a single item remains, this may return a non-union type. + + Examples: + + * [int, str] -> Union[int, str] + * [int, object] -> object + * [int, int] -> int + * [int, Any] -> Union[int, Any] (Any types are not simplified away!) + * [Any, Any] -> Any + + Note: This must NOT be used during semantic analysis, since TypeInfos may not + be fully initialized. + """ + # TODO: Make this a function living somewhere outside mypy.types. Most other non-trivial + # type operations are not static methods, so this is inconsistent. + items = get_proper_types(items) + while any(isinstance(typ, UnionType) for typ in items): + all_items = [] # type: List[ProperType] + for typ in items: + if isinstance(typ, UnionType): + all_items.extend(typ.items) + else: + all_items.append(typ) + items = all_items + + from mypy.subtypes import is_proper_subtype + + removed = set() # type: Set[int] + for i, ti in enumerate(items): + if i in removed: continue + # Keep track of the truishness info for deleted subtypes which can be relevant + cbt = cbf = False + for j, tj in enumerate(items): + if i != j and is_proper_subtype(tj, ti): + # We found a redundant item in the union. + removed.add(j) + cbt = cbt or tj.can_be_true + cbf = cbf or tj.can_be_false + # if deleted subtypes had more general truthiness, use that + if not ti.can_be_true and cbt: + items[i] = true_or_false(ti) + elif not ti.can_be_false and cbf: + items[i] = true_or_false(ti) + + simplified_set = [items[i] for i in range(len(items)) if i not in removed] + return UnionType.make_union(simplified_set, line, column) diff --git a/mypy/types.py b/mypy/types.py index 42e0d723d104e..75ae78b2d42ca 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -334,8 +334,10 @@ def new_unification_variable(old: 'TypeVarDef') -> 'TypeVarDef': old.upper_bound, old.variance, old.line, old.column) def erase_to_union_or_bound(self) -> Type: + from mypy.typeops import make_simplified_union + if self.values: - return UnionType.make_simplified_union(self.values) + return make_simplified_union(self.values) else: return self.upper_bound @@ -856,8 +858,9 @@ def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_type_var(self) def erase_to_union_or_bound(self) -> ProperType: + from mypy.typeops import make_simplified_union if self.values: - return UnionType.make_simplified_union(self.values) + return make_simplified_union(self.values) else: return get_proper_type(self.upper_bound) @@ -1695,59 +1698,6 @@ def make_union(items: Sequence[Type], line: int = -1, column: int = -1) -> Type: else: return UninhabitedType() - @staticmethod - def make_simplified_union(items: Sequence[Type], - line: int = -1, column: int = -1) -> ProperType: - """Build union type with redundant union items removed. - - If only a single item remains, this may return a non-union type. - - Examples: - - * [int, str] -> Union[int, str] - * [int, object] -> object - * [int, int] -> int - * [int, Any] -> Union[int, Any] (Any types are not simplified away!) - * [Any, Any] -> Any - - Note: This must NOT be used during semantic analysis, since TypeInfos may not - be fully initialized. - """ - # TODO: Make this a function living somewhere outside mypy.types. Most other non-trivial - # type operations are not static methods, so this is inconsistent. - items = get_proper_types(items) - while any(isinstance(typ, UnionType) for typ in items): - all_items = [] # type: List[ProperType] - for typ in items: - if isinstance(typ, UnionType): - all_items.extend(typ.items) - else: - all_items.append(typ) - items = all_items - - # from mypy.subtypes import is_proper_subtype - is_proper_subtype = __import__('mypy.subtypes').subtypes.is_proper_subtype # type: ignore - - removed = set() # type: Set[int] - for i, ti in enumerate(items): - if i in removed: continue - # Keep track of the truishness info for deleted subtypes which can be relevant - cbt = cbf = False - for j, tj in enumerate(items): - if i != j and is_proper_subtype(tj, ti): - # We found a redundant item in the union. - removed.add(j) - cbt = cbt or tj.can_be_true - cbf = cbf or tj.can_be_false - # if deleted subtypes had more general truthiness, use that - if not ti.can_be_true and cbt: - items[i] = true_or_false(ti) - elif not ti.can_be_false and cbf: - items[i] = true_or_false(ti) - - simplified_set = [items[i] for i in range(len(items)) if i not in removed] - return UnionType.make_union(simplified_set, line, column) - def length(self) -> int: return len(self.items) @@ -2177,7 +2127,8 @@ def true_only(t: Type) -> ProperType: elif isinstance(t, UnionType): # The true version of a union type is the union of the true versions of its components new_items = [true_only(item) for item in t.items] - return UnionType.make_simplified_union(new_items, line=t.line, column=t.column) + from mypy.typeops import make_simplified_union + return make_simplified_union(new_items, line=t.line, column=t.column) else: new_t = copy_type(t) new_t.can_be_false = False @@ -2204,7 +2155,8 @@ def false_only(t: Type) -> ProperType: elif isinstance(t, UnionType): # The false version of a union type is the union of the false versions of its components new_items = [false_only(item) for item in t.items] - return UnionType.make_simplified_union(new_items, line=t.line, column=t.column) + from mypy.typeops import make_simplified_union + return make_simplified_union(new_items, line=t.line, column=t.column) else: new_t = copy_type(t) new_t.can_be_true = False @@ -2219,7 +2171,8 @@ def true_or_false(t: Type) -> ProperType: if isinstance(t, UnionType): new_items = [true_or_false(item) for item in t.items] - return UnionType.make_simplified_union(new_items, line=t.line, column=t.column) + from mypy.typeops import make_simplified_union + return make_simplified_union(new_items, line=t.line, column=t.column) new_t = copy_type(t) new_t.can_be_true = new_t.can_be_true_default() From ed2bdad3c78a82d984f37668fb402f7022ee6afe Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 26 Aug 2019 14:52:21 -0700 Subject: [PATCH 4/8] Move the true/false ones to typeops --- mypy/checker.py | 3 +- mypy/checkexpr.py | 4 +-- mypy/join.py | 10 +++--- mypy/test/testtypes.py | 4 +-- mypy/typeops.py | 75 ++++++++++++++++++++++++++++++++++++++++-- mypy/types.py | 68 -------------------------------------- 6 files changed, 84 insertions(+), 80 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 267c0979ceb75..8162398587d4d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -34,7 +34,7 @@ Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType, 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, + function_type, is_named_instance, union_items, TypeQuery, LiteralType, is_optional, remove_optional, TypeTranslator, StarType, get_proper_type, ProperType, get_proper_types, is_literal_type ) @@ -49,6 +49,7 @@ ) from mypy.typeops import ( map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union, + true_only, false_only, ) from mypy import message_registry from mypy.subtypes import ( diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0d510f13959c7..0b573a9246b12 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -17,7 +17,7 @@ Type, AnyType, CallableType, Overloaded, NoneType, TypeVarDef, 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, + is_named_instance, function_type, callable_type, FunctionLike, StarType, is_optional, remove_optional, is_generic_instance, get_proper_type, ProperType, get_proper_types ) @@ -59,7 +59,7 @@ from mypy.typevars import fill_typevars from mypy.visitor import ExpressionVisitor from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext -from mypy.typeops import tuple_fallback, make_simplified_union +from mypy.typeops import tuple_fallback, make_simplified_union, true_only, false_only import mypy.errorcodes as codes # Type of callback user for checking individual function arguments. See diff --git a/mypy/join.py b/mypy/join.py index 44b5a95e7f0f2..20d18d96d16b1 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -6,7 +6,7 @@ from mypy.types import ( Type, AnyType, NoneType, TypeVisitor, Instance, UnboundType, TypeVarType, CallableType, TupleType, TypedDictType, ErasedType, UnionType, FunctionLike, Overloaded, LiteralType, - PartialType, DeletedType, UninhabitedType, TypeType, true_or_false, TypeOfAny, get_proper_type, + PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, get_proper_type, ProperType, get_proper_types ) from mypy.maptype import map_instance_to_supertype @@ -27,8 +27,8 @@ def join_simple(declaration: Optional[Type], s: Type, t: Type) -> ProperType: if (s.can_be_true, s.can_be_false) != (t.can_be_true, t.can_be_false): # if types are restricted in different ways, use the more general versions - s = true_or_false(s) - t = true_or_false(t) + s = mypy.typeops.true_or_false(s) + t = mypy.typeops.true_or_false(t) if isinstance(s, AnyType): return s @@ -68,8 +68,8 @@ def join_types(s: Type, t: Type) -> ProperType: if (s.can_be_true, s.can_be_false) != (t.can_be_true, t.can_be_false): # if types are restricted in different ways, use the more general versions - s = true_or_false(s) - t = true_or_false(t) + s = mypy.typeops.true_or_false(s) + t = mypy.typeops.true_or_false(t) if isinstance(s, AnyType): return s diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index fbf9e1601d63f..0959481498672 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -10,14 +10,14 @@ from mypy.sametypes import is_same_type from mypy.types import ( UnboundType, AnyType, CallableType, TupleType, TypeVarDef, Type, Instance, NoneType, - Overloaded, TypeType, UnionType, UninhabitedType, true_only, false_only, TypeVarId, TypeOfAny, + Overloaded, TypeType, UnionType, UninhabitedType, TypeVarId, TypeOfAny, LiteralType, ) from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, CONTRAVARIANT, INVARIANT, COVARIANT from mypy.subtypes import is_subtype, is_more_precise, is_proper_subtype from mypy.test.typefixture import TypeFixture, InterfaceTypeFixture from mypy.state import strict_optional_set - +from mypy.typeops import true_only, false_only class TypesSuite(Suite): def setUp(self) -> None: diff --git a/mypy/typeops.py b/mypy/typeops.py index 55f6191d3d6bd..7d1ea317e4d37 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -11,8 +11,8 @@ from mypy.types import ( TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded, - TypeVarType, TypeType, UninhabitedType, FormalArgument, UnionType, true_or_false, - ProperType, get_proper_type, get_proper_types, + TypeVarType, TypeType, UninhabitedType, FormalArgument, UnionType, NoneType, + ProperType, get_proper_type, get_proper_types, copy_type ) from mypy.nodes import ( TypeInfo, TypeVar, ARG_STAR, @@ -22,6 +22,9 @@ from mypy.typevars import fill_typevars +from mypy import state + + def tuple_fallback(typ: TupleType) -> Instance: """Return fallback type for a tuple.""" from mypy.join import join_type_list @@ -289,3 +292,71 @@ def make_simplified_union(items: Sequence[Type], simplified_set = [items[i] for i in range(len(items)) if i not in removed] return UnionType.make_union(simplified_set, line, column) + + +def true_only(t: Type) -> ProperType: + """ + Restricted version of t with only True-ish values + """ + t = get_proper_type(t) + + if not t.can_be_true: + # All values of t are False-ish, so there are no true values in it + return UninhabitedType(line=t.line, column=t.column) + elif not t.can_be_false: + # All values of t are already True-ish, so true_only is idempotent in this case + return t + elif isinstance(t, UnionType): + # The true version of a union type is the union of the true versions of its components + new_items = [true_only(item) for item in t.items] + from mypy.typeops import make_simplified_union + return make_simplified_union(new_items, line=t.line, column=t.column) + else: + new_t = copy_type(t) + new_t.can_be_false = False + return new_t + + +def false_only(t: Type) -> ProperType: + """ + Restricted version of t with only False-ish values + """ + t = get_proper_type(t) + + if not t.can_be_false: + if state.strict_optional: + # All values of t are True-ish, so there are no false values in it + return UninhabitedType(line=t.line) + else: + # When strict optional checking is disabled, everything can be + # False-ish since anything can be None + return NoneType(line=t.line) + elif not t.can_be_true: + # All values of t are already False-ish, so false_only is idempotent in this case + return t + elif isinstance(t, UnionType): + # The false version of a union type is the union of the false versions of its components + new_items = [false_only(item) for item in t.items] + from mypy.typeops import make_simplified_union + return make_simplified_union(new_items, line=t.line, column=t.column) + else: + new_t = copy_type(t) + new_t.can_be_true = False + return new_t + + +def true_or_false(t: Type) -> ProperType: + """ + Unrestricted version of t with both True-ish and False-ish values + """ + t = get_proper_type(t) + + if isinstance(t, UnionType): + new_items = [true_or_false(item) for item in t.items] + from mypy.typeops import make_simplified_union + return make_simplified_union(new_items, line=t.line, column=t.column) + + new_t = copy_type(t) + new_t.can_be_true = new_t.can_be_true_default() + new_t.can_be_false = new_t.can_be_false_default() + return new_t diff --git a/mypy/types.py b/mypy/types.py index 75ae78b2d42ca..ff6bba5f90556 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2112,74 +2112,6 @@ def copy_type(t: TP) -> TP: return nt -def true_only(t: Type) -> ProperType: - """ - Restricted version of t with only True-ish values - """ - t = get_proper_type(t) - - if not t.can_be_true: - # All values of t are False-ish, so there are no true values in it - return UninhabitedType(line=t.line, column=t.column) - elif not t.can_be_false: - # All values of t are already True-ish, so true_only is idempotent in this case - return t - elif isinstance(t, UnionType): - # The true version of a union type is the union of the true versions of its components - new_items = [true_only(item) for item in t.items] - from mypy.typeops import make_simplified_union - return make_simplified_union(new_items, line=t.line, column=t.column) - else: - new_t = copy_type(t) - new_t.can_be_false = False - return new_t - - -def false_only(t: Type) -> ProperType: - """ - Restricted version of t with only False-ish values - """ - t = get_proper_type(t) - - if not t.can_be_false: - if state.strict_optional: - # All values of t are True-ish, so there are no false values in it - return UninhabitedType(line=t.line) - else: - # When strict optional checking is disabled, everything can be - # False-ish since anything can be None - return NoneType(line=t.line) - elif not t.can_be_true: - # All values of t are already False-ish, so false_only is idempotent in this case - return t - elif isinstance(t, UnionType): - # The false version of a union type is the union of the false versions of its components - new_items = [false_only(item) for item in t.items] - from mypy.typeops import make_simplified_union - return make_simplified_union(new_items, line=t.line, column=t.column) - else: - new_t = copy_type(t) - new_t.can_be_true = False - return new_t - - -def true_or_false(t: Type) -> ProperType: - """ - Unrestricted version of t with both True-ish and False-ish values - """ - t = get_proper_type(t) - - if isinstance(t, UnionType): - new_items = [true_or_false(item) for item in t.items] - from mypy.typeops import make_simplified_union - return make_simplified_union(new_items, line=t.line, column=t.column) - - new_t = copy_type(t) - new_t.can_be_true = new_t.can_be_true_default() - new_t.can_be_false = new_t.can_be_false_default() - return new_t - - def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike: if func.type: assert isinstance(func.type, FunctionLike) From 54a501b541753078d39d1cb1fafcb460c0a9d3ec Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 26 Aug 2019 14:58:59 -0700 Subject: [PATCH 5/8] last bit of untangling --- mypy/checker.py | 5 +++-- mypy/checkexpr.py | 8 +++++--- mypy/typeops.py | 21 +++++++++++++++------ mypy/types.py | 15 --------------- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8162398587d4d..e3f7e066e5c20 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -49,6 +49,7 @@ ) from mypy.typeops import ( map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union, + erase_def_to_union_or_bound, erase_to_union_or_bound, true_only, false_only, ) from mypy import message_registry @@ -3527,7 +3528,7 @@ def partition_by_callable(self, typ: Type, # do better. # If it is possible for the false branch to execute, return the original # type to avoid losing type information. - callables, uncallables = self.partition_by_callable(typ.erase_to_union_or_bound(), + callables, uncallables = self.partition_by_callable(erase_to_union_or_bound(typ), unsound_partition) uncallables = [typ] if len(uncallables) else [] return callables, uncallables @@ -4438,7 +4439,7 @@ def overload_can_never_match(signature: CallableType, other: CallableType) -> bo # the below subtype check and (surprisingly?) `is_proper_subtype(Any, Any)` # returns `True`. # TODO: find a cleaner solution instead of this ad-hoc erasure. - exp_signature = expand_type(signature, {tvar.id: tvar.erase_to_union_or_bound() + exp_signature = expand_type(signature, {tvar.id: erase_def_to_union_or_bound(tvar) for tvar in signature.variables}) assert isinstance(exp_signature, CallableType) return is_callable_compatible(exp_signature, other, diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0b573a9246b12..245fb12e5abd9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -59,7 +59,9 @@ from mypy.typevars import fill_typevars from mypy.visitor import ExpressionVisitor from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext -from mypy.typeops import tuple_fallback, make_simplified_union, true_only, false_only +from mypy.typeops import ( + tuple_fallback, make_simplified_union, true_only, false_only, erase_to_union_or_bound, +) import mypy.errorcodes as codes # Type of callback user for checking individual function arguments. See @@ -3919,9 +3921,9 @@ def arg_approximate_similarity(actual: Type, formal: Type) -> bool: # Erase typevars: we'll consider them all to have the same "shape". if isinstance(actual, TypeVarType): - actual = actual.erase_to_union_or_bound() + actual = erase_to_union_or_bound(actual) if isinstance(formal, TypeVarType): - formal = formal.erase_to_union_or_bound() + formal = erase_to_union_or_bound(formal) # Callable or Type[...]-ish types def is_typetype_like(typ: ProperType) -> bool: diff --git a/mypy/typeops.py b/mypy/typeops.py index 7d1ea317e4d37..91548fb77608b 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -1,12 +1,10 @@ -"""Miscellaneuus type operations and helpers for use during type checking. +"""Miscellaneous type operations and helpers for use during type checking. NOTE: These must not be accessed from mypy.nodes or mypy.types to avoid import cycles. These must not be called from the semantic analysis main pass since these may assume that MROs are ready. """ -# TODO: Move more type operations here - from typing import cast, Optional, List, Sequence, Set from mypy.types import ( @@ -309,7 +307,6 @@ def true_only(t: Type) -> ProperType: elif isinstance(t, UnionType): # The true version of a union type is the union of the true versions of its components new_items = [true_only(item) for item in t.items] - from mypy.typeops import make_simplified_union return make_simplified_union(new_items, line=t.line, column=t.column) else: new_t = copy_type(t) @@ -337,7 +334,6 @@ def false_only(t: Type) -> ProperType: elif isinstance(t, UnionType): # The false version of a union type is the union of the false versions of its components new_items = [false_only(item) for item in t.items] - from mypy.typeops import make_simplified_union return make_simplified_union(new_items, line=t.line, column=t.column) else: new_t = copy_type(t) @@ -353,10 +349,23 @@ def true_or_false(t: Type) -> ProperType: if isinstance(t, UnionType): new_items = [true_or_false(item) for item in t.items] - from mypy.typeops import make_simplified_union return make_simplified_union(new_items, line=t.line, column=t.column) new_t = copy_type(t) new_t.can_be_true = new_t.can_be_true_default() new_t.can_be_false = new_t.can_be_false_default() return new_t + + +def erase_def_to_union_or_bound(tdef: TypeVarDef) -> Type: + if tdef.values: + return make_simplified_union(tdef.values) + else: + return tdef.upper_bound + + +def erase_to_union_or_bound(typ: TypeVarType) -> ProperType: + if typ.values: + return make_simplified_union(typ.values) + else: + return get_proper_type(typ.upper_bound) diff --git a/mypy/types.py b/mypy/types.py index ff6bba5f90556..e4b5918d9c31a 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -333,14 +333,6 @@ def new_unification_variable(old: 'TypeVarDef') -> 'TypeVarDef': return TypeVarDef(old.name, old.fullname, new_id, old.values, old.upper_bound, old.variance, old.line, old.column) - def erase_to_union_or_bound(self) -> Type: - from mypy.typeops import make_simplified_union - - if self.values: - return make_simplified_union(self.values) - else: - return self.upper_bound - def __repr__(self) -> str: if self.values: return '{} in {}'.format(self.name, tuple(self.values)) @@ -857,13 +849,6 @@ 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) -> ProperType: - from mypy.typeops import make_simplified_union - if self.values: - return make_simplified_union(self.values) - else: - return get_proper_type(self.upper_bound) - def __hash__(self) -> int: return hash(self.id) From cea19ed3d864438d1d6fda1cc3dfdc81616237a9 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 26 Aug 2019 17:42:00 -0700 Subject: [PATCH 6/8] lint fixes --- mypy/checkexpr.py | 8 ++++---- mypy/checkmember.py | 8 +++----- mypy/erasetype.py | 1 - mypy/meet.py | 4 ++-- mypy/plugins/default.py | 2 +- mypy/test/testtypes.py | 1 + 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 245fb12e5abd9..b49c73bed8468 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1380,8 +1380,8 @@ def check_overload_call(self, # Union[int -> int, str -> str](Union[int, str]) is invalid and # we don't want to introduce internal inconsistencies. unioned_result = (make_simplified_union(list(returns), - context.line, - context.column), + context.line, + context.column), self.combine_function_signatures(inferred_types)) # Step 3: We try checking each branch one-by-one. @@ -2629,8 +2629,8 @@ def visit_index_with_type(self, left_type: Type, e: IndexExpr, if isinstance(left_type, UnionType): original_type = original_type or left_type return make_simplified_union([self.visit_index_with_type(typ, e, - original_type) - for typ in left_type.relevant_items()]) + original_type) + for typ in left_type.relevant_items()]) elif isinstance(left_type, TupleType) and self.chk.in_checked_function(): # Special case for tuples. They return a more specific type when # indexed by an integer literal. diff --git a/mypy/checkmember.py b/mypy/checkmember.py index c8bc381fd9108..f42eaa0d87db1 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -1,11 +1,11 @@ """Type checking of attribute access""" -from typing import cast, Callable, List, Optional, TypeVar, Union +from typing import cast, Callable, Optional, Union from typing_extensions import TYPE_CHECKING from mypy.types import ( Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef, - Overloaded, TypeVarType, UnionType, PartialType, UninhabitedType, TypeOfAny, LiteralType, + Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType, DeletedType, NoneType, TypeType, function_type, get_type_vars, get_proper_type, ProperType ) from mypy.nodes import ( @@ -15,10 +15,8 @@ ) from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype -from mypy.expandtype import expand_type_by_instance, expand_type, freshen_function_type_vars +from mypy.expandtype import expand_type_by_instance, freshen_function_type_vars from mypy.erasetype import erase_typevars -from mypy.infer import infer_type_arguments -from mypy.typevars import fill_typevars from mypy.plugin import AttributeContext from mypy.typeanal import set_any_tvars from mypy import message_registry diff --git a/mypy/erasetype.py b/mypy/erasetype.py index b438d62ec7d2e..25f4db64cb417 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -9,7 +9,6 @@ from mypy.nodes import ARG_STAR, ARG_STAR2 - def erase_type(typ: Type) -> ProperType: """Erase any type variables from a type. diff --git a/mypy/meet.py b/mypy/meet.py index 7bc2205879808..c5d25652d7b3a 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -42,7 +42,7 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: return declared if isinstance(declared, UnionType): return make_simplified_union([narrow_declared_type(x, narrowed) - for x in declared.relevant_items()]) + for x in declared.relevant_items()]) elif not is_overlapping_types(declared, narrowed, prohibit_none_typevar_overlap=True): if state.strict_optional: @@ -51,7 +51,7 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: return NoneType() elif isinstance(narrowed, UnionType): return make_simplified_union([narrow_declared_type(declared, x) - for x in narrowed.relevant_items()]) + for x in narrowed.relevant_items()]) elif isinstance(narrowed, AnyType): return narrowed elif isinstance(declared, TypeType) and isinstance(narrowed, TypeType): diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 5dbbd106d0cfe..3476b930307b0 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -8,7 +8,7 @@ ) from mypy.plugins.common import try_getting_str_literals from mypy.types import ( - Type, Instance, AnyType, TypeOfAny, CallableType, NoneType, UnionType, TypedDictType, + Type, Instance, AnyType, TypeOfAny, CallableType, NoneType, TypedDictType, TypeVarType, TPDICT_FB_NAMES, get_proper_type ) from mypy.subtypes import is_subtype diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 0959481498672..604ce941e5e88 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -19,6 +19,7 @@ from mypy.state import strict_optional_set from mypy.typeops import true_only, false_only + class TypesSuite(Suite): def setUp(self) -> None: self.x = UnboundType('X') # Helpers From e2d4d63d7ea3c37775e57a22b1f27292e25503c0 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 27 Aug 2019 11:20:17 -0700 Subject: [PATCH 7/8] Remove some comments --- mypy/erasetype.py | 1 - mypy/expandtype.py | 1 - 2 files changed, 2 deletions(-) diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 25f4db64cb417..62580dfb0f12e 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -88,7 +88,6 @@ def visit_literal_type(self, t: LiteralType) -> ProperType: def visit_union_type(self, t: UnionType) -> ProperType: erased_items = [erase_type(item) for item in t.items] from mypy.typeops import make_simplified_union # asdf - # XXX: does this need to be simplified? return make_simplified_union(erased_items) def visit_type_type(self, t: TypeType) -> ProperType: diff --git a/mypy/expandtype.py b/mypy/expandtype.py index d43c87ea065cc..d92275b684bf9 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -120,7 +120,6 @@ def visit_union_type(self, t: UnionType) -> ProperType: # After substituting for type variables in t.items, # some of the resulting types might be subtypes of others. from mypy.typeops import make_simplified_union # asdf - # XXX: does this need to be simplified? return make_simplified_union(self.expand_types(t.items), t.line, t.column) def visit_partial_type(self, t: PartialType) -> ProperType: From 68ab6d427804ce71e17278ef7e45b8888ed0925d Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 27 Aug 2019 11:41:56 -0700 Subject: [PATCH 8/8] Fix most import order dependence (except type_visitor) --- mypy/build.py | 4 ++-- mypy/subtypes.py | 6 +++++- mypyc/genops_for.py | 5 ++++- mypyc/ops.py | 9 ++------- mypyc/ops_primitive.py | 9 +++++++++ 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index e3b51e5dd0fc6..f69488a943b97 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -31,7 +31,7 @@ from mypy.nodes import MypyFile, ImportBase, Import, ImportFrom, ImportAll, SymbolTable from mypy.semanal_pass1 import SemanticAnalyzerPreAnalysis from mypy.semanal import SemanticAnalyzer -from mypy.semanal_main import semantic_analysis_for_scc +import mypy.semanal_main from mypy.checker import TypeChecker from mypy.indirection import TypeIndirectionVisitor from mypy.errors import Errors, CompileError, ErrorInfo, report_internal_error @@ -2961,7 +2961,7 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No # SemanticAnalyzerPass2.add_builtin_aliases for details. typing_mod = graph['typing'].tree assert typing_mod, "The typing module was not parsed" - semantic_analysis_for_scc(graph, scc, manager.errors) + mypy.semanal_main.semantic_analysis_for_scc(graph, scc, manager.errors) # Track what modules aren't yet done so we can finish them as soon # as possible, saving memory. diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 6fe6b1ae65dc6..13276f0d4f60e 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -983,13 +983,17 @@ def new_is_compat(left: Type, right: Type) -> bool: def unify_generic_callable(type: CallableType, target: CallableType, ignore_return: bool, - return_constraint_direction: int = mypy.constraints.SUBTYPE_OF, + return_constraint_direction: Optional[int] = None, ) -> Optional[CallableType]: """Try to unify a generic callable type with another callable type. Return unified CallableType if successful; otherwise, return None. """ import mypy.solve + + if return_constraint_direction is None: + return_constraint_direction = mypy.constraints.SUBTYPE_OF + constraints = [] # type: List[mypy.constraints.Constraint] for arg_type, target_arg_type in zip(type.arg_types, target.arg_types): c = mypy.constraints.infer_constraints( diff --git a/mypyc/genops_for.py b/mypyc/genops_for.py index 1aafc0a180a4d..03d4094b960c4 100644 --- a/mypyc/genops_for.py +++ b/mypyc/genops_for.py @@ -6,6 +6,7 @@ """ from typing import Union, List +from typing_extensions import TYPE_CHECKING from mypy.nodes import Lvalue, Expression from mypyc.ops import ( @@ -16,7 +17,9 @@ from mypyc.ops_list import list_len_op, list_get_item_unsafe_op from mypyc.ops_misc import iter_op, next_op from mypyc.ops_exc import no_err_occurred_op -import mypyc.genops + +if TYPE_CHECKING: + import mypyc.genops class ForGenerator: diff --git a/mypyc/ops.py b/mypyc/ops.py index b72c75bce236e..3005d96a49ccc 100644 --- a/mypyc/ops.py +++ b/mypyc/ops.py @@ -1866,10 +1866,5 @@ def all_concrete_classes(class_ir: ClassIR) -> List[ClassIR]: return concrete -# Import various modules that set up global state. -import mypyc.ops_int # noqa -import mypyc.ops_str # noqa -import mypyc.ops_list # noqa -import mypyc.ops_dict # noqa -import mypyc.ops_tuple # noqa -import mypyc.ops_misc # noqa +# Import ops_primitive that will set up set up global primitives tables. +import mypyc.ops_primitive # noqa diff --git a/mypyc/ops_primitive.py b/mypyc/ops_primitive.py index 497963d1496ae..e2fe875547778 100644 --- a/mypyc/ops_primitive.py +++ b/mypyc/ops_primitive.py @@ -214,3 +214,12 @@ def custom_op(arg_types: List[RType], assert format_str is not None return OpDescription('', arg_types, result_type, is_var_arg, error_kind, format_str, emit, steals, is_borrowed, 0) + + +# Import various modules that set up global state. +import mypyc.ops_int # noqa +import mypyc.ops_str # noqa +import mypyc.ops_list # noqa +import mypyc.ops_dict # noqa +import mypyc.ops_tuple # noqa +import mypyc.ops_misc # noqa