From a59ee23f02af134dbc9627a94fb38d33d05adb2d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 5 Nov 2021 18:35:43 +0300 Subject: [PATCH 1/9] Enhance `is_subtype` for `TypeType`, refs #11470 --- mypy/subtypes.py | 1 + test-data/unit/check-generic-subtyping.test | 26 +++++++++++++++++++++ test-data/unit/fixtures/type.pyi | 1 + 3 files changed, 28 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b145745c3f32f..b1a9ce36bdff5 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -510,6 +510,7 @@ def visit_type_type(self, left: TypeType) -> bool: if isinstance(item, Instance): metaclass = item.type.metaclass_type return metaclass is not None and self._is_subtype(metaclass, right) + return self._is_subtype(item, left) return False def visit_type_alias_type(self, left: TypeAliasType) -> bool: diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index f97e3015fa326..d4005b5ac3977 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -1033,3 +1033,29 @@ x2: X2[str, int] reveal_type(iter(x2)) # N: Revealed type is "typing.Iterator[builtins.int*]" reveal_type([*x2]) # N: Revealed type is "builtins.list[builtins.int*]" [builtins fixtures/dict.pyi] + +[case testTypeSubtypingWithDifferentGenericVars] +# flags: --python-version 3.10 +from typing import Any, Protocol + +class Hashable(Protocol): + def __hash__(self) -> int: pass + +t1: type = type +h1: Hashable = t1 + +t2: type[type] = type +h2: Hashable = t2 + +t3: type[Any] = type +h3: Hashable = t3 + +class NotHashable: + __hash__: None + +t4: type[NotHashable] = NotHashable +h4: Hashable = t4 # E: Incompatible types in assignment (expression has type "Type[NotHashable]", variable has type "Hashable") + +class DifferentMRO(type): + def mro(self) -> int: pass # E: Return type "int" of "mro" incompatible with return type "List[type[Any]]" in supertype "type" +[builtins fixtures/type.pyi] diff --git a/test-data/unit/fixtures/type.pyi b/test-data/unit/fixtures/type.pyi index 755b45ff0bb56..456eb0a29c464 100644 --- a/test-data/unit/fixtures/type.pyi +++ b/test-data/unit/fixtures/type.pyi @@ -14,6 +14,7 @@ class type(Generic[T]): __name__: str def __or__(self, other: Union[type, None]) -> type: pass def mro(self) -> List['type']: pass + def __hash__(self) -> int: pass class tuple(Generic[T]): pass class function: pass From 7a393005d5eebc0f79ece60ad372503a2dde27c8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 5 Nov 2021 19:17:46 +0300 Subject: [PATCH 2/9] Simplifies test case --- mypy/subtypes.py | 3 ++- test-data/unit/check-generic-subtyping.test | 9 --------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b1a9ce36bdff5..c538dc1db742b 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -507,10 +507,11 @@ def visit_type_type(self, left: TypeType) -> bool: item = left.item if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) + if isinstance(item, AnyType): + return True if isinstance(item, Instance): metaclass = item.type.metaclass_type return metaclass is not None and self._is_subtype(metaclass, right) - return self._is_subtype(item, left) return False def visit_type_alias_type(self, left: TypeAliasType) -> bool: diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index d4005b5ac3977..fc5c7737a4eb9 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -1049,13 +1049,4 @@ h2: Hashable = t2 t3: type[Any] = type h3: Hashable = t3 - -class NotHashable: - __hash__: None - -t4: type[NotHashable] = NotHashable -h4: Hashable = t4 # E: Incompatible types in assignment (expression has type "Type[NotHashable]", variable has type "Hashable") - -class DifferentMRO(type): - def mro(self) -> int: pass # E: Return type "int" of "mro" incompatible with return type "List[type[Any]]" in supertype "type" [builtins fixtures/type.pyi] From b57562ac7a5c596df9380d2db2a0f2118d9986ab Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 5 Nov 2021 19:19:36 +0300 Subject: [PATCH 3/9] Fixes fixture --- test-data/unit/fixtures/type.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/fixtures/type.pyi b/test-data/unit/fixtures/type.pyi index 456eb0a29c464..755b45ff0bb56 100644 --- a/test-data/unit/fixtures/type.pyi +++ b/test-data/unit/fixtures/type.pyi @@ -14,7 +14,6 @@ class type(Generic[T]): __name__: str def __or__(self, other: Union[type, None]) -> type: pass def mro(self) -> List['type']: pass - def __hash__(self) -> int: pass class tuple(Generic[T]): pass class function: pass From 102219f3d10d3e48b87871473437353c1ce9cc54 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 5 Nov 2021 19:22:03 +0300 Subject: [PATCH 4/9] oups --- test-data/unit/fixtures/type.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/fixtures/type.pyi b/test-data/unit/fixtures/type.pyi index 755b45ff0bb56..456eb0a29c464 100644 --- a/test-data/unit/fixtures/type.pyi +++ b/test-data/unit/fixtures/type.pyi @@ -14,6 +14,7 @@ class type(Generic[T]): __name__: str def __or__(self, other: Union[type, None]) -> type: pass def mro(self) -> List['type']: pass + def __hash__(self) -> int: pass class tuple(Generic[T]): pass class function: pass From 302b27c7acce3bdbac68bfb6d3a7140da4bbbefc Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 5 Nov 2021 23:48:01 +0300 Subject: [PATCH 5/9] Adds `fallback: Instance` to `TypeType` --- mypy/checker.py | 24 ++++++++++++++++-------- mypy/checkexpr.py | 13 ++++++++----- mypy/checkmember.py | 34 ++++++++++++++++++++-------------- mypy/semanal.py | 2 +- mypy/subtypes.py | 10 +++++++--- mypy/typeanal.py | 13 ++++++++++--- mypy/types.py | 32 +++++++++++++++++++++++++------- 7 files changed, 87 insertions(+), 41 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5aa1741a03f68..3fc53a54dfa9e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -938,7 +938,8 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]): isclass = defn.is_class or defn.name in ('__new__', '__init_subclass__') if isclass: - ref_type = mypy.types.TypeType.make_normalized(ref_type) + ref_type = mypy.types.TypeType.make_normalized( + ref_type, fallback=self.type_type()) erased = get_proper_type(erase_to_bound(arg_type)) if not is_subtype_ignoring_tvars(ref_type, erased): note = None @@ -3487,7 +3488,7 @@ def type_check_raise(self, e: Expression, s: RaiseStmt, # Python3 case: exc_type = self.named_type('builtins.BaseException') - expected_type_items = [exc_type, TypeType(exc_type)] + expected_type_items = [exc_type, TypeType(exc_type, fallback=self.type_type())] if optional: # This is used for `x` part in a case like `raise e from x`, # where we allow `raise e from None`. @@ -3522,7 +3523,9 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType # `raise (exc, ...)` case: item = typ.items[0] if isinstance(typ, TupleType) else typ.args[0] self.check_subtype( - item, UnionType([exc_type, TypeType(exc_type)]), s, + item, + UnionType([exc_type, TypeType(exc_type, fallback=self.type_type())]), + s, 'When raising a tuple, first element must by derived from BaseException', ) return @@ -3532,7 +3535,9 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType # https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement assert isinstance(typ, TupleType) # Is set in fastparse2.py self.check_subtype( - typ.items[0], TypeType(exc_type), s, + typ.items[0], + TypeType(exc_type, fallback=self.type_type()), + s, 'First argument must be BaseException subtype', ) @@ -3553,7 +3558,7 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType else: expected_type_items = [ # `raise Exception` and `raise Exception()` cases: - exc_type, TypeType(exc_type), + exc_type, TypeType(exc_type, fallback=self.type_type()), ] self.check_subtype( typ, UnionType.make_union(expected_type_items), @@ -5316,7 +5321,10 @@ def infer_issubclass_maps(self, node: CallExpr, # for example, Any or a custom metaclass. return {}, {} # unknown type yes_map, no_map = self.conditional_type_map_with_intersection(expr, vartype, type) - yes_map, no_map = map(convert_to_typetype, (yes_map, no_map)) + yes_map, no_map = map( + lambda x: convert_to_typetype(x, self.type_type()), + (yes_map, no_map), + ) return yes_map, no_map def conditional_type_map_with_intersection(self, @@ -5607,7 +5615,7 @@ def reduce_conditional_maps(type_maps: List[Tuple[TypeMap, TypeMap]], return final_if_map, final_else_map -def convert_to_typetype(type_map: TypeMap) -> TypeMap: +def convert_to_typetype(type_map: TypeMap, type_type: Instance) -> TypeMap: converted_type_map: Dict[Expression, Type] = {} if type_map is None: return None @@ -5619,7 +5627,7 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap: 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) + converted_type_map[expr] = TypeType.make_normalized(typ, fallback=type_type) return converted_type_map diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 623776f8e48e0..cbbbce9d7e546 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1039,7 +1039,8 @@ def check_callable_call(self, if (callee.is_type_obj() and (len(arg_types) == 1) and is_equivalent(callee.ret_type, self.named_type('builtins.type'))): - callee = callee.copy_modified(ret_type=TypeType.make_normalized(arg_types[0])) + callee = callee.copy_modified(ret_type=TypeType.make_normalized( + arg_types[0], fallback=self.chk.type_type())) if callable_node: # Store the inferred callable type. @@ -3683,7 +3684,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: ProperType = TypeType(current_type) + type_type: ProperType = TypeType(current_type, fallback=self.chk.type_type()) # Use the type of the self argument, in case it was annotated method = self.chk.scope.top_function() @@ -3712,14 +3713,15 @@ def _super_arg_types(self, e: SuperExpr) -> Union[Type, Tuple[Type, Type]]: # Imprecisely assume that the type is the current class if isinstance(type_type, AnyType): if e.info: - type_type = TypeType(fill_typevars(e.info)) + type_type = TypeType(fill_typevars(e.info), fallback=self.chk.type_type()) else: return AnyType(TypeOfAny.from_another_any, source_any=type_type) elif isinstance(type_type, TypeType): type_item = type_type.item if isinstance(type_item, AnyType): if e.info: - type_type = TypeType(fill_typevars(e.info)) + type_type = TypeType(fill_typevars(e.info), + fallback=self.chk.type_type()) else: return AnyType(TypeOfAny.from_another_any, source_any=type_item) @@ -3739,7 +3741,8 @@ def _super_arg_types(self, e: SuperExpr) -> Union[Type, Tuple[Type, Type]]: instance_item = instance_type.item if isinstance(instance_item, AnyType): if e.info: - instance_type = TypeType(fill_typevars(e.info)) + instance_type = TypeType(fill_typevars(e.info), + fallback=self.chk.type_type()) else: return AnyType(TypeOfAny.from_another_any, source_any=instance_item) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 3efc39753627e..e945c0f353ddb 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -214,7 +214,7 @@ def analyze_instance_member_access(name: str, # of this hack here and below (i.e. mx.self_type). dispatched_type = meet.meet_types(mx.original_type, typ) signature = check_self_arg(signature, dispatched_type, method.is_class, - mx.context, name, mx.msg) + name, mx) signature = bind_self(signature, mx.self_type, is_classmethod=method.is_class) typ = map_instance_to_supertype(typ, method.info) member_type = expand_type_by_instance(signature, typ) @@ -493,15 +493,21 @@ def analyze_descriptor_access(instance_type: Type, callable_name = chk.expr_checker.method_fullname(descriptor_type, "__get__") dunder_get_type = chk.expr_checker.transform_callee_type( callable_name, dunder_get_type, - [TempNode(instance_type, context=context), - TempNode(TypeType.make_normalized(owner_type), context=context)], + [ + TempNode(instance_type, context=context), + TempNode(TypeType.make_normalized( + owner_type, fallback=chk.type_type()), context=context), + ], [ARG_POS, ARG_POS], context, object_type=descriptor_type, ) _, inferred_dunder_get_type = chk.expr_checker.check_call( dunder_get_type, - [TempNode(instance_type, context=context), - TempNode(TypeType.make_normalized(owner_type), context=context)], + [ + TempNode(instance_type, context=context), + TempNode(TypeType.make_normalized( + owner_type, fallback=chk.type_type()), context=context), + ], [ARG_POS, ARG_POS], context, object_type=descriptor_type, callable_name=callable_name) @@ -597,8 +603,7 @@ def analyze_var(name: str, # and similarly for B1 when checking against B dispatched_type = meet.meet_types(mx.original_type, itype) signature = freshen_function_type_vars(functype) - signature = check_self_arg(signature, dispatched_type, var.is_classmethod, - mx.context, name, mx.msg) + signature = check_self_arg(signature, dispatched_type, var.is_classmethod, name, mx) signature = bind_self(signature, mx.self_type, var.is_classmethod) expanded_signature = get_proper_type(expand_type_by_instance(signature, itype)) freeze_type_vars(expanded_signature) @@ -650,8 +655,8 @@ def lookup_member_var_or_accessor(info: TypeInfo, name: str, def check_self_arg(functype: FunctionLike, dispatched_arg_type: Type, is_classmethod: bool, - context: Context, name: str, - msg: MessageBuilder) -> FunctionLike: + name: str, + mx: MemberContext) -> FunctionLike: """Check that an instance has a valid type for a method with annotated 'self'. For example if the method is defined as: @@ -670,11 +675,12 @@ def f(self: S) -> T: ... return functype new_items = [] if is_classmethod: - dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type) + dispatched_arg_type = TypeType.make_normalized( + dispatched_arg_type, mx.chk.type_type()) for item in items: if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). - msg.no_formal_self(name, item, context) + mx.msg.no_formal_self(name, item, mx.context) # This is pretty bad, so just return the original signature if # there is at least one such error. return functype @@ -684,8 +690,8 @@ def f(self: S) -> T: ... new_items.append(item) if not new_items: # Choose first item for the message (it may be not very helpful for overloads). - msg.incompatible_self_argument(name, dispatched_arg_type, items[0], - is_classmethod, context) + mx.msg.incompatible_self_argument(name, dispatched_arg_type, items[0], + is_classmethod, mx.context) return functype if len(new_items) == 1: return new_items[0] @@ -789,7 +795,7 @@ def analyze_class_attribute_access(itype: Instance, or (isinstance(node.node, FuncBase) and node.node.is_class)) t = get_proper_type(t) if isinstance(t, FunctionLike) and is_classmethod: - t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) + t = check_self_arg(t, mx.self_type, False, name, mx) result = add_class_tvars(t, isuper, is_classmethod, mx.self_type, original_vars=original_vars) if not mx.is_lvalue: diff --git a/mypy/semanal.py b/mypy/semanal.py index 76985d4ab4583..9bb3ffa715866 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5121,7 +5121,7 @@ def anal_type(self, return typ def class_type(self, self_type: Type) -> Type: - return TypeType.make_normalized(self_type) + return TypeType.make_normalized(self_type, fallback=self.named_type('builtins.type')) def schedule_patch(self, priority: int, patch: Callable[[], None]) -> None: self.patches.append((priority, patch)) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c538dc1db742b..c67cc10eadd1c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -281,7 +281,10 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(item, TupleType): item = mypy.typeops.tuple_fallback(item) if is_named_instance(left, 'builtins.type'): - return self._is_subtype(TypeType(AnyType(TypeOfAny.special_form)), right) + return self._is_subtype( + TypeType(AnyType(TypeOfAny.special_form), fallback=left), + right, + ) if left.type.is_metaclass(): if isinstance(item, AnyType): return True @@ -507,11 +510,12 @@ def visit_type_type(self, left: TypeType) -> bool: item = left.item if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) - if isinstance(item, AnyType): - return True if isinstance(item, Instance): metaclass = item.type.metaclass_type return metaclass is not None and self._is_subtype(metaclass, right) + if isinstance(left.fallback, Instance): + print('a') + return self._is_subtype(left.fallback, right) return False def visit_type_alias_type(self, left: TypeAliasType) -> bool: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index e4a3d8b439622..43f7e043eab86 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -327,7 +327,8 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt if len(t.args) == 0: if fullname == 'typing.Type': any_type = self.get_omitted_any(t) - return TypeType(any_type, line=t.line, column=t.column) + return TypeType(any_type, fallback=self.named_type('builtins.type'), + line=t.line, column=t.column) else: # To prevent assignment of 'builtins.type' inferred as 'builtins.object' # See https://github.com/python/mypy/issues/9476 for more information @@ -336,7 +337,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt if len(t.args) != 1: self.fail(type_str + ' must have exactly one type argument', t) item = self.anal_type(t.args[0]) - return TypeType.make_normalized(item, line=t.line) + return TypeType.make_normalized(item, + fallback=self.named_type('builtins.type'), + line=t.line) elif fullname == 'typing.ClassVar': if self.nesting_level > 0: self.fail('Invalid type: ClassVar nested inside other type', t) @@ -666,7 +669,11 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type: return AnyType(TypeOfAny.from_error) def visit_type_type(self, t: TypeType) -> Type: - return TypeType.make_normalized(self.anal_type(t.item), line=t.line) + return TypeType.make_normalized( + self.anal_type(t.item), + fallback=self.named_type('builtins.type'), + line=t.line, + ) def visit_placeholder_type(self, t: PlaceholderType) -> Type: n = None if t.fullname is None else self.api.lookup_fully_qualified(t.fullname) diff --git a/mypy/types.py b/mypy/types.py index 49a25ef4504c5..fa9161ec8470c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1882,6 +1882,12 @@ def serialize(self) -> JsonDict: assert False, "Synthetic types don't serialize" +PossibleTypeItem: _TypeAlias = Union[ + Instance, AnyType, TypeVarType, TupleType, NoneType, + CallableType, +] + + class TypeType(ProperType): """For types like Type[User]. @@ -1910,30 +1916,42 @@ class TypeType(ProperType): assumption). """ - __slots__ = ('item',) + __slots__ = ('item', 'fallback') # This can't be everything, but it can be a class reference, # a generic class instance, a union, Any, a type variable... item: ProperType + # Fallback to `builtins.type` instance, + # might be unset due to compatinility reasons. + # TODO: make sure it is always set. + fallback: Optional[Instance] - def __init__(self, item: Bogus[Union[Instance, AnyType, TypeVarType, TupleType, NoneType, - CallableType]], *, + def __init__(self, + item: Bogus[PossibleTypeItem], + fallback: Optional[Instance] = None, + *, line: int = -1, column: int = -1) -> None: """To ensure Type[Union[A, B]] is always represented as Union[Type[A], Type[B]], item of type UnionType must be handled through make_normalized static method. """ super().__init__(line, column) self.item = item + self.fallback = fallback @staticmethod - def make_normalized(item: Type, *, line: int = -1, column: int = -1) -> ProperType: + def make_normalized(item: Type, fallback: Optional[Instance] = None, + *, + line: int = -1, column: int = -1) -> ProperType: item = get_proper_type(item) if isinstance(item, UnionType): return UnionType.make_union( - [TypeType.make_normalized(union_item) for union_item in item.items], - line=line, column=column + [ + TypeType.make_normalized(union_item, fallback) + for union_item in item.items + ], + line=line, column=column, ) - return TypeType(item, line=line, column=column) # type: ignore[arg-type] + return TypeType(item, fallback, line=line, column=column) # type: ignore[arg-type] def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_type_type(self) From 7c04bba0203c54e44e5fd198f3c9abbfd43e3016 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 6 Nov 2021 09:58:58 +0300 Subject: [PATCH 6/9] Revert "Adds `fallback: Instance` to `TypeType`" This reverts commit 302b27c7acce3bdbac68bfb6d3a7140da4bbbefc. --- mypy/checker.py | 24 ++++++++---------------- mypy/checkexpr.py | 13 +++++-------- mypy/checkmember.py | 34 ++++++++++++++-------------------- mypy/semanal.py | 2 +- mypy/subtypes.py | 10 +++------- mypy/typeanal.py | 13 +++---------- mypy/types.py | 32 +++++++------------------------- 7 files changed, 41 insertions(+), 87 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 3fc53a54dfa9e..5aa1741a03f68 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -938,8 +938,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]): isclass = defn.is_class or defn.name in ('__new__', '__init_subclass__') if isclass: - ref_type = mypy.types.TypeType.make_normalized( - ref_type, fallback=self.type_type()) + ref_type = mypy.types.TypeType.make_normalized(ref_type) erased = get_proper_type(erase_to_bound(arg_type)) if not is_subtype_ignoring_tvars(ref_type, erased): note = None @@ -3488,7 +3487,7 @@ def type_check_raise(self, e: Expression, s: RaiseStmt, # Python3 case: exc_type = self.named_type('builtins.BaseException') - expected_type_items = [exc_type, TypeType(exc_type, fallback=self.type_type())] + expected_type_items = [exc_type, TypeType(exc_type)] if optional: # This is used for `x` part in a case like `raise e from x`, # where we allow `raise e from None`. @@ -3523,9 +3522,7 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType # `raise (exc, ...)` case: item = typ.items[0] if isinstance(typ, TupleType) else typ.args[0] self.check_subtype( - item, - UnionType([exc_type, TypeType(exc_type, fallback=self.type_type())]), - s, + item, UnionType([exc_type, TypeType(exc_type)]), s, 'When raising a tuple, first element must by derived from BaseException', ) return @@ -3535,9 +3532,7 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType # https://docs.python.org/2/reference/simple_stmts.html#the-raise-statement assert isinstance(typ, TupleType) # Is set in fastparse2.py self.check_subtype( - typ.items[0], - TypeType(exc_type, fallback=self.type_type()), - s, + typ.items[0], TypeType(exc_type), s, 'First argument must be BaseException subtype', ) @@ -3558,7 +3553,7 @@ def _type_check_raise_python2(self, e: Expression, s: RaiseStmt, typ: ProperType else: expected_type_items = [ # `raise Exception` and `raise Exception()` cases: - exc_type, TypeType(exc_type, fallback=self.type_type()), + exc_type, TypeType(exc_type), ] self.check_subtype( typ, UnionType.make_union(expected_type_items), @@ -5321,10 +5316,7 @@ def infer_issubclass_maps(self, node: CallExpr, # for example, Any or a custom metaclass. return {}, {} # unknown type yes_map, no_map = self.conditional_type_map_with_intersection(expr, vartype, type) - yes_map, no_map = map( - lambda x: convert_to_typetype(x, self.type_type()), - (yes_map, no_map), - ) + yes_map, no_map = map(convert_to_typetype, (yes_map, no_map)) return yes_map, no_map def conditional_type_map_with_intersection(self, @@ -5615,7 +5607,7 @@ def reduce_conditional_maps(type_maps: List[Tuple[TypeMap, TypeMap]], return final_if_map, final_else_map -def convert_to_typetype(type_map: TypeMap, type_type: Instance) -> TypeMap: +def convert_to_typetype(type_map: TypeMap) -> TypeMap: converted_type_map: Dict[Expression, Type] = {} if type_map is None: return None @@ -5627,7 +5619,7 @@ def convert_to_typetype(type_map: TypeMap, type_type: Instance) -> TypeMap: 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, fallback=type_type) + converted_type_map[expr] = TypeType.make_normalized(typ) return converted_type_map diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index cbbbce9d7e546..623776f8e48e0 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1039,8 +1039,7 @@ def check_callable_call(self, if (callee.is_type_obj() and (len(arg_types) == 1) and is_equivalent(callee.ret_type, self.named_type('builtins.type'))): - callee = callee.copy_modified(ret_type=TypeType.make_normalized( - arg_types[0], fallback=self.chk.type_type())) + callee = callee.copy_modified(ret_type=TypeType.make_normalized(arg_types[0])) if callable_node: # Store the inferred callable type. @@ -3684,7 +3683,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: ProperType = TypeType(current_type, fallback=self.chk.type_type()) + type_type: ProperType = TypeType(current_type) # Use the type of the self argument, in case it was annotated method = self.chk.scope.top_function() @@ -3713,15 +3712,14 @@ def _super_arg_types(self, e: SuperExpr) -> Union[Type, Tuple[Type, Type]]: # Imprecisely assume that the type is the current class if isinstance(type_type, AnyType): if e.info: - type_type = TypeType(fill_typevars(e.info), fallback=self.chk.type_type()) + type_type = TypeType(fill_typevars(e.info)) else: return AnyType(TypeOfAny.from_another_any, source_any=type_type) elif isinstance(type_type, TypeType): type_item = type_type.item if isinstance(type_item, AnyType): if e.info: - type_type = TypeType(fill_typevars(e.info), - fallback=self.chk.type_type()) + type_type = TypeType(fill_typevars(e.info)) else: return AnyType(TypeOfAny.from_another_any, source_any=type_item) @@ -3741,8 +3739,7 @@ def _super_arg_types(self, e: SuperExpr) -> Union[Type, Tuple[Type, Type]]: instance_item = instance_type.item if isinstance(instance_item, AnyType): if e.info: - instance_type = TypeType(fill_typevars(e.info), - fallback=self.chk.type_type()) + instance_type = TypeType(fill_typevars(e.info)) else: return AnyType(TypeOfAny.from_another_any, source_any=instance_item) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index e945c0f353ddb..3efc39753627e 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -214,7 +214,7 @@ def analyze_instance_member_access(name: str, # of this hack here and below (i.e. mx.self_type). dispatched_type = meet.meet_types(mx.original_type, typ) signature = check_self_arg(signature, dispatched_type, method.is_class, - name, mx) + mx.context, name, mx.msg) signature = bind_self(signature, mx.self_type, is_classmethod=method.is_class) typ = map_instance_to_supertype(typ, method.info) member_type = expand_type_by_instance(signature, typ) @@ -493,21 +493,15 @@ def analyze_descriptor_access(instance_type: Type, callable_name = chk.expr_checker.method_fullname(descriptor_type, "__get__") dunder_get_type = chk.expr_checker.transform_callee_type( callable_name, dunder_get_type, - [ - TempNode(instance_type, context=context), - TempNode(TypeType.make_normalized( - owner_type, fallback=chk.type_type()), context=context), - ], + [TempNode(instance_type, context=context), + TempNode(TypeType.make_normalized(owner_type), context=context)], [ARG_POS, ARG_POS], context, object_type=descriptor_type, ) _, inferred_dunder_get_type = chk.expr_checker.check_call( dunder_get_type, - [ - TempNode(instance_type, context=context), - TempNode(TypeType.make_normalized( - owner_type, fallback=chk.type_type()), context=context), - ], + [TempNode(instance_type, context=context), + TempNode(TypeType.make_normalized(owner_type), context=context)], [ARG_POS, ARG_POS], context, object_type=descriptor_type, callable_name=callable_name) @@ -603,7 +597,8 @@ def analyze_var(name: str, # and similarly for B1 when checking against B dispatched_type = meet.meet_types(mx.original_type, itype) signature = freshen_function_type_vars(functype) - signature = check_self_arg(signature, dispatched_type, var.is_classmethod, name, mx) + signature = check_self_arg(signature, dispatched_type, var.is_classmethod, + mx.context, name, mx.msg) signature = bind_self(signature, mx.self_type, var.is_classmethod) expanded_signature = get_proper_type(expand_type_by_instance(signature, itype)) freeze_type_vars(expanded_signature) @@ -655,8 +650,8 @@ def lookup_member_var_or_accessor(info: TypeInfo, name: str, def check_self_arg(functype: FunctionLike, dispatched_arg_type: Type, is_classmethod: bool, - name: str, - mx: MemberContext) -> FunctionLike: + context: Context, name: str, + msg: MessageBuilder) -> FunctionLike: """Check that an instance has a valid type for a method with annotated 'self'. For example if the method is defined as: @@ -675,12 +670,11 @@ def f(self: S) -> T: ... return functype new_items = [] if is_classmethod: - dispatched_arg_type = TypeType.make_normalized( - dispatched_arg_type, mx.chk.type_type()) + dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type) for item in items: if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). - mx.msg.no_formal_self(name, item, mx.context) + msg.no_formal_self(name, item, context) # This is pretty bad, so just return the original signature if # there is at least one such error. return functype @@ -690,8 +684,8 @@ def f(self: S) -> T: ... new_items.append(item) if not new_items: # Choose first item for the message (it may be not very helpful for overloads). - mx.msg.incompatible_self_argument(name, dispatched_arg_type, items[0], - is_classmethod, mx.context) + msg.incompatible_self_argument(name, dispatched_arg_type, items[0], + is_classmethod, context) return functype if len(new_items) == 1: return new_items[0] @@ -795,7 +789,7 @@ def analyze_class_attribute_access(itype: Instance, or (isinstance(node.node, FuncBase) and node.node.is_class)) t = get_proper_type(t) if isinstance(t, FunctionLike) and is_classmethod: - t = check_self_arg(t, mx.self_type, False, name, mx) + t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) result = add_class_tvars(t, isuper, is_classmethod, mx.self_type, original_vars=original_vars) if not mx.is_lvalue: diff --git a/mypy/semanal.py b/mypy/semanal.py index 9bb3ffa715866..76985d4ab4583 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5121,7 +5121,7 @@ def anal_type(self, return typ def class_type(self, self_type: Type) -> Type: - return TypeType.make_normalized(self_type, fallback=self.named_type('builtins.type')) + return TypeType.make_normalized(self_type) def schedule_patch(self, priority: int, patch: Callable[[], None]) -> None: self.patches.append((priority, patch)) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c67cc10eadd1c..c538dc1db742b 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -281,10 +281,7 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(item, TupleType): item = mypy.typeops.tuple_fallback(item) if is_named_instance(left, 'builtins.type'): - return self._is_subtype( - TypeType(AnyType(TypeOfAny.special_form), fallback=left), - right, - ) + return self._is_subtype(TypeType(AnyType(TypeOfAny.special_form)), right) if left.type.is_metaclass(): if isinstance(item, AnyType): return True @@ -510,12 +507,11 @@ def visit_type_type(self, left: TypeType) -> bool: item = left.item if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) + if isinstance(item, AnyType): + return True if isinstance(item, Instance): metaclass = item.type.metaclass_type return metaclass is not None and self._is_subtype(metaclass, right) - if isinstance(left.fallback, Instance): - print('a') - return self._is_subtype(left.fallback, right) return False def visit_type_alias_type(self, left: TypeAliasType) -> bool: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 43f7e043eab86..e4a3d8b439622 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -327,8 +327,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt if len(t.args) == 0: if fullname == 'typing.Type': any_type = self.get_omitted_any(t) - return TypeType(any_type, fallback=self.named_type('builtins.type'), - line=t.line, column=t.column) + return TypeType(any_type, line=t.line, column=t.column) else: # To prevent assignment of 'builtins.type' inferred as 'builtins.object' # See https://github.com/python/mypy/issues/9476 for more information @@ -337,9 +336,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt if len(t.args) != 1: self.fail(type_str + ' must have exactly one type argument', t) item = self.anal_type(t.args[0]) - return TypeType.make_normalized(item, - fallback=self.named_type('builtins.type'), - line=t.line) + return TypeType.make_normalized(item, line=t.line) elif fullname == 'typing.ClassVar': if self.nesting_level > 0: self.fail('Invalid type: ClassVar nested inside other type', t) @@ -669,11 +666,7 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type: return AnyType(TypeOfAny.from_error) def visit_type_type(self, t: TypeType) -> Type: - return TypeType.make_normalized( - self.anal_type(t.item), - fallback=self.named_type('builtins.type'), - line=t.line, - ) + return TypeType.make_normalized(self.anal_type(t.item), line=t.line) def visit_placeholder_type(self, t: PlaceholderType) -> Type: n = None if t.fullname is None else self.api.lookup_fully_qualified(t.fullname) diff --git a/mypy/types.py b/mypy/types.py index fa9161ec8470c..49a25ef4504c5 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1882,12 +1882,6 @@ def serialize(self) -> JsonDict: assert False, "Synthetic types don't serialize" -PossibleTypeItem: _TypeAlias = Union[ - Instance, AnyType, TypeVarType, TupleType, NoneType, - CallableType, -] - - class TypeType(ProperType): """For types like Type[User]. @@ -1916,42 +1910,30 @@ class TypeType(ProperType): assumption). """ - __slots__ = ('item', 'fallback') + __slots__ = ('item',) # This can't be everything, but it can be a class reference, # a generic class instance, a union, Any, a type variable... item: ProperType - # Fallback to `builtins.type` instance, - # might be unset due to compatinility reasons. - # TODO: make sure it is always set. - fallback: Optional[Instance] - def __init__(self, - item: Bogus[PossibleTypeItem], - fallback: Optional[Instance] = None, - *, + def __init__(self, item: Bogus[Union[Instance, AnyType, TypeVarType, TupleType, NoneType, + CallableType]], *, line: int = -1, column: int = -1) -> None: """To ensure Type[Union[A, B]] is always represented as Union[Type[A], Type[B]], item of type UnionType must be handled through make_normalized static method. """ super().__init__(line, column) self.item = item - self.fallback = fallback @staticmethod - def make_normalized(item: Type, fallback: Optional[Instance] = None, - *, - line: int = -1, column: int = -1) -> ProperType: + def make_normalized(item: Type, *, line: int = -1, column: int = -1) -> ProperType: item = get_proper_type(item) if isinstance(item, UnionType): return UnionType.make_union( - [ - TypeType.make_normalized(union_item, fallback) - for union_item in item.items - ], - line=line, column=column, + [TypeType.make_normalized(union_item) for union_item in item.items], + line=line, column=column ) - return TypeType(item, fallback, line=line, column=column) # type: ignore[arg-type] + return TypeType(item, line=line, column=column) # type: ignore[arg-type] def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_type_type(self) From 43c214529c12e8e594075e62250ee6dd53f91ac7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 6 Nov 2021 10:23:40 +0300 Subject: [PATCH 7/9] New attempt: now `TypeType.fallback` is a class var --- mypy/checker.py | 6 ++++++ mypy/subtypes.py | 7 ++++--- mypy/typeanal.py | 7 ++++--- mypy/types.py | 3 +++ test-data/unit/check-generic-subtyping.test | 9 ++++++++- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5aa1741a03f68..ad6a8ae317ef8 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -303,6 +303,7 @@ def check_first_pass(self) -> None: Deferred functions will be processed by check_second_pass(). """ self.recurse_into_functions = True + self.prepare_types() with state.strict_optional_set(self.options.strict_optional): self.errors.set_file(self.path, self.tree.fullname, scope=self.tscope) with self.tscope.module_scope(self.tree.fullname): @@ -366,6 +367,11 @@ def check_second_pass(self, self.check_partial(node) return True + def prepare_types(self) -> None: + """Additional preparations before actual type checking.""" + # We need to set `TypeType` fallback, since it is now unset: + TypeType.fallback = self.type_type() + def check_partial(self, node: Union[DeferredNodeType, FineGrainedDeferredNodeType]) -> None: if isinstance(node, MypyFile): self.check_top_level(node) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c538dc1db742b..e50cfad0ec001 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -507,11 +507,12 @@ def visit_type_type(self, left: TypeType) -> bool: item = left.item if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) - if isinstance(item, AnyType): - return True if isinstance(item, Instance): metaclass = item.type.metaclass_type - return metaclass is not None and self._is_subtype(metaclass, right) + if metaclass is not None and self._is_subtype(metaclass, right): + return True + if isinstance(TypeType.fallback, Instance): + return self._is_subtype(TypeType.fallback, right) return False def visit_type_alias_type(self, left: TypeAliasType) -> bool: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index e4a3d8b439622..659d238285b6b 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -321,9 +321,10 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt return make_optional_type(item) elif fullname == 'typing.Callable': return self.analyze_callable_type(t) - elif (fullname == 'typing.Type' or - (fullname == 'builtins.type' and (self.options.python_version >= (3, 9) or - self.api.is_future_flag_set('annotations')))): + elif (fullname == 'typing.Type' + or (fullname == 'builtins.type' + and (self.options.python_version >= (3, 9) + or self.api.is_future_flag_set('annotations')))): if len(t.args) == 0: if fullname == 'typing.Type': any_type = self.get_omitted_any(t) diff --git a/mypy/types.py b/mypy/types.py index 49a25ef4504c5..489e82af32892 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1915,6 +1915,9 @@ class TypeType(ProperType): # This can't be everything, but it can be a class reference, # a generic class instance, a union, Any, a type variable... item: ProperType + # Fallback to `builtins.type`, used for better type checking. + # Can be unset untill `typechecker` phase. + fallback: ClassVar[Optional[Instance]] = None def __init__(self, item: Bogus[Union[Instance, AnyType, TypeVarType, TupleType, NoneType, CallableType]], *, diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index fc5c7737a4eb9..2597eee8917f1 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -1036,7 +1036,7 @@ reveal_type([*x2]) # N: Revealed type is "builtins.list[builtins.int*]" [case testTypeSubtypingWithDifferentGenericVars] # flags: --python-version 3.10 -from typing import Any, Protocol +from typing import Any, Protocol, Iterable class Hashable(Protocol): def __hash__(self) -> int: pass @@ -1049,4 +1049,11 @@ h2: Hashable = t2 t3: type[Any] = type h3: Hashable = t3 + +err1: Iterable = t1 # E: Incompatible types in assignment (expression has type "type[Any]", variable has type "Iterable[Any]") +err2: Iterable = t2 # E: Incompatible types in assignment (expression has type "Type[type[Any]]", variable has type "Iterable[Any]") +err3: Iterable = t3 # E: Incompatible types in assignment (expression has type "Type[Any]", variable has type "Iterable[Any]") +err4: int = t1 # E: Incompatible types in assignment (expression has type "type[Any]", variable has type "int") +err5: int = t2 # E: Incompatible types in assignment (expression has type "Type[type[Any]]", variable has type "int") +err6: int = t3 # E: Incompatible types in assignment (expression has type "Type[Any]", variable has type "int") [builtins fixtures/type.pyi] From a931f17a6b5a0bfdf34e02a7c9485bcc611844d0 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 6 Nov 2021 11:07:16 +0300 Subject: [PATCH 8/9] Adds test cases from #11469 --- test-data/unit/check-generic-subtyping.test | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index 2597eee8917f1..d0c2f9f488b22 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -1050,6 +1050,33 @@ h2: Hashable = t2 t3: type[Any] = type h3: Hashable = t3 +# Metaclass magic: + +class NotHashable: + __hash__: None + +h_i: Hashable = NotHashable + +class NotHashableMeta(type): + __hash__: None # E: Incompatible types in assignment (expression has type "None", base class "type" defined the type as "Callable[[type[Any]], int]") + +class NotHashableType(metaclass=NotHashableMeta): + pass + +h_t: Hashable = NotHashableType # E: Incompatible types in assignment (expression has type "Type[NotHashableType]", variable has type "Hashable") + +# Exact types: + +o1: type[object] = object +o2: type[int] = int +o3 = object + +h4: Hashable = o1 +h5: Hashable = o2 +h6: Hashable = o3 + +# Errors: + err1: Iterable = t1 # E: Incompatible types in assignment (expression has type "type[Any]", variable has type "Iterable[Any]") err2: Iterable = t2 # E: Incompatible types in assignment (expression has type "Type[type[Any]]", variable has type "Iterable[Any]") err3: Iterable = t3 # E: Incompatible types in assignment (expression has type "Type[Any]", variable has type "Iterable[Any]") From d37a43f8a3ef6eebc972b576db3df16ece93e0fa Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 30 Nov 2021 10:25:09 +0300 Subject: [PATCH 9/9] Update mypy/types.py Co-authored-by: pranavrajpal <78008260+pranavrajpal@users.noreply.github.com> --- mypy/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/types.py b/mypy/types.py index 489e82af32892..34228315b1549 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1916,7 +1916,7 @@ class TypeType(ProperType): # a generic class instance, a union, Any, a type variable... item: ProperType # Fallback to `builtins.type`, used for better type checking. - # Can be unset untill `typechecker` phase. + # Can be unset until `typechecker` phase. fallback: ClassVar[Optional[Instance]] = None def __init__(self, item: Bogus[Union[Instance, AnyType, TypeVarType, TupleType, NoneType,