diff --git a/mypy/checker.py b/mypy/checker.py index fcd334edbcce..e740029ad639 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -667,7 +667,7 @@ def is_implicit_any(t: Type) -> bool: 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(ref_type) + ref_type = mypy.types.TypeType.make_normalized(ref_type) erased = erase_to_bound(arg_type) if not is_subtype_ignoring_tvars(ref_type, erased): note = None @@ -2716,13 +2716,10 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap: if type_map is None: return None for expr, typ in type_map.items(): - if isinstance(typ, UnionType): - converted_type_map[expr] = UnionType([TypeType(t) for t in typ.items]) - elif isinstance(typ, Instance): - converted_type_map[expr] = TypeType(typ) - else: + if not isinstance(typ, (UnionType, Instance)): # unknown type; error was likely reported earlier return {} + converted_type_map[expr] = TypeType.make_normalized(typ) return converted_type_map diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4829b99231cd..9dcb4c322205 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -432,7 +432,7 @@ def check_call(self, callee: Type, args: List[Expression], 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(arg_types[0])) + callee = callee.copy_modified(ret_type=TypeType.make_normalized(arg_types[0])) if callable_node: # Store the inferred callable type. @@ -1108,7 +1108,8 @@ def analyze_descriptor_access(self, instance_type: Type, descriptor_type: Type, owner_type = instance_type _, inferred_dunder_get_type = self.check_call( - dunder_get_type, [TempNode(instance_type), TempNode(TypeType(owner_type))], + dunder_get_type, + [TempNode(instance_type), TempNode(TypeType.make_normalized(owner_type))], [nodes.ARG_POS, nodes.ARG_POS], context) if isinstance(inferred_dunder_get_type, AnyType): diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 4525b1446bda..66949144eed8 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -650,7 +650,7 @@ def expand(target: Type) -> Type: ret_type = func.ret_type variables = func.variables if isinstance(original_type, CallableType) and original_type.is_type_obj(): - original_type = TypeType(original_type.ret_type) + 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:], @@ -665,5 +665,5 @@ def erase_to_bound(t: Type) -> Type: return t.upper_bound if isinstance(t, TypeType): if isinstance(t.item, TypeVarType): - return TypeType(t.item.upper_bound) + return TypeType.make_normalized(t.item.upper_bound) return t diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 49035accf976..910793b6b9d2 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -75,7 +75,7 @@ def visit_union_type(self, t: UnionType) -> Type: return UnionType.make_simplified_union(erased_items) def visit_type_type(self, t: TypeType) -> Type: - return TypeType(t.item.accept(self), line=t.line) + return TypeType.make_normalized(t.item.accept(self), line=t.line) def erase_typevars(t: Type, ids_to_erase: Optional[Container[TypeVarId]] = None) -> Type: diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 18301191948b..937ed8b73a04 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -124,7 +124,7 @@ def visit_type_type(self, t: TypeType) -> Type: # union of instances or Any). Sadly we can't report errors # here yet. item = t.item.accept(self) - return TypeType(item) + return TypeType.make_normalized(item) def expand_types(self, types: Iterable[Type]) -> List[Type]: a = [] # type: List[Type] diff --git a/mypy/join.py b/mypy/join.py index 586d281adbb8..aaaa99fa3798 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -245,7 +245,7 @@ def visit_partial_type(self, t: PartialType) -> Type: def visit_type_type(self, t: TypeType) -> Type: if isinstance(self.s, TypeType): - return TypeType(self.join(t.item, self.s.item), line=t.line) + return TypeType.make_normalized(self.join(t.item, self.s.item), line=t.line) elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type': return self.s else: diff --git a/mypy/meet.py b/mypy/meet.py index e32997fc43c5..62940b08d62b 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -278,7 +278,7 @@ def visit_type_type(self, t: TypeType) -> Type: if isinstance(self.s, TypeType): typ = self.meet(t.item, self.s.item) if not isinstance(typ, NoneTyp): - typ = TypeType(typ, line=t.line) + typ = TypeType.make_normalized(typ, line=t.line) return typ elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type': return t diff --git a/mypy/messages.py b/mypy/messages.py index 8dbf1dbeff95..37d6a93e1b4d 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -192,7 +192,8 @@ def format(self, typ: Type, verbosity: int = 0) -> str: if func.is_type_obj(): # The type of a type object type can be derived from the # return type (this always works). - return self.format(TypeType(erase_type(func.items()[0].ret_type)), verbosity) + return self.format(TypeType.make_normalized(erase_type(func.items()[0].ret_type)), + verbosity) elif isinstance(func, CallableType): return_type = strip_quotes(self.format(func.ret_type)) if func.is_ellipsis_args: diff --git a/mypy/semanal.py b/mypy/semanal.py index a51997f232d8..b130804b4b36 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2168,7 +2168,7 @@ def add_method(funcname: str, is_classmethod: bool = False, ) -> None: if is_classmethod: - first = [Argument(Var('cls'), TypeType(selftype), None, ARG_POS)] + first = [Argument(Var('cls'), TypeType.make_normalized(selftype), None, ARG_POS)] else: first = [Argument(Var('self'), selftype, None, ARG_POS)] args = first + args diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 74a71f4e984b..78fa2e1a2698 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -580,7 +580,7 @@ def test_type_type(self) -> None: self.assert_join(self.fx.type_b, self.fx.type_any, self.fx.type_any) self.assert_join(self.fx.type_b, self.fx.type_type, self.fx.type_type) self.assert_join(self.fx.type_b, self.fx.type_c, self.fx.type_a) - self.assert_join(self.fx.type_c, self.fx.type_d, TypeType(self.fx.o)) + self.assert_join(self.fx.type_c, self.fx.type_d, TypeType.make_normalized(self.fx.o)) self.assert_join(self.fx.type_type, self.fx.type_any, self.fx.type_type) self.assert_join(self.fx.type_b, self.fx.anyt, self.fx.anyt) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 3b90464c3367..ef50d40c46b9 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -189,7 +189,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if len(t.args) != 1: self.fail('Type[...] must have exactly one type argument', t) item = self.anal_type(t.args[0]) - return TypeType(item, 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) @@ -384,7 +384,7 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type: return AnyType() def visit_type_type(self, t: TypeType) -> Type: - return TypeType(self.anal_type(t.item), line=t.line) + return TypeType.make_normalized(self.anal_type(t.item), line=t.line) def analyze_callable_type(self, t: UnboundType) -> Type: fallback = self.builtin_type('builtins.function') diff --git a/mypy/typefixture.py b/mypy/typefixture.py index 04714e9982cb..87ddd06d3ecc 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -145,12 +145,12 @@ def make_type_var(name: str, id: int, values: List[Type], upper_bound: Type, self.lsta = Instance(self.std_listi, [self.a]) # List[A] self.lstb = Instance(self.std_listi, [self.b]) # List[B] - self.type_a = TypeType(self.a) - self.type_b = TypeType(self.b) - self.type_c = TypeType(self.c) - self.type_d = TypeType(self.d) - self.type_t = TypeType(self.t) - self.type_any = TypeType(self.anyt) + self.type_a = TypeType.make_normalized(self.a) + self.type_b = TypeType.make_normalized(self.b) + self.type_c = TypeType.make_normalized(self.c) + self.type_d = TypeType.make_normalized(self.d) + self.type_t = TypeType.make_normalized(self.t) + self.type_any = TypeType.make_normalized(self.anyt) # Helper methods diff --git a/mypy/types.py b/mypy/types.py index c17bb063f5fb..363afde6ffe1 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1183,13 +1183,26 @@ class TypeType(Type): # a generic class instance, a union, Any, a type variable... item = None # type: Type - def __init__(self, item: Type, *, line: int = -1, column: int = -1) -> None: + def __init__(self, item: Union[Instance, AnyType, TypeVarType, TupleType, NoneTyp, + 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) if isinstance(item, CallableType) and item.is_type_obj(): self.item = item.fallback else: self.item = item + @staticmethod + def make_normalized(item: Type, *, line: int = -1, column: int = -1) -> Type: + if isinstance(item, UnionType): + return UnionType.make_union( + [TypeType.make_normalized(union_item) for union_item in item.items], + line=line, column=column + ) + return TypeType(item, line=line, column=column) # type: ignore + def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_type_type(self) @@ -1197,9 +1210,9 @@ def serialize(self) -> JsonDict: return {'.class': 'TypeType', 'item': self.item.serialize()} @classmethod - def deserialize(cls, data: JsonDict) -> 'TypeType': + def deserialize(cls, data: JsonDict) -> Type: assert data['.class'] == 'TypeType' - return TypeType(deserialize_type(data['item'])) + return TypeType.make_normalized(deserialize_type(data['item'])) # @@ -1376,7 +1389,7 @@ def visit_overloaded(self, t: Overloaded) -> Type: return Overloaded(items=items) def visit_type_type(self, t: TypeType) -> Type: - return TypeType(t.item.accept(self), line=t.line, column=t.column) + return TypeType.make_normalized(t.item.accept(self), line=t.line, column=t.column) class TypeStrVisitor(SyntheticTypeVisitor[str]): diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e5abaccef89e..2c7bf1525eb4 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2183,7 +2183,6 @@ def process(cls: Type[User]): [out] [case testTypeUsingTypeCClassMethodUnion] -# Ideally this would work, but not worth the effort; just don't crash from typing import Type, Union class User: @classmethod @@ -2192,11 +2191,11 @@ class User: class ProUser(User): pass class BasicUser(User): pass def process(cls: Type[Union[BasicUser, ProUser]]): - cls.foo() # E: Type[Union[BasicUser, ProUser]] has no attribute "foo" + cls.foo() obj = cls() - cls.bar(obj) # E: Type[Union[BasicUser, ProUser]] has no attribute "bar" + cls.bar(obj) cls.mro() # Defined in class type - cls.error # E: Type[Union[BasicUser, ProUser]] has no attribute "error" + cls.error # E: Item "type" of "Union[Type[BasicUser], Type[ProUser]]" has no attribute "error" [builtins fixtures/classmethod.pyi] [out] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 825a5557fc63..75ebaa081e44 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1443,7 +1443,7 @@ else: [builtins fixtures/isinstancelist.pyi] -[case testIssubclasDestructuringUnions] +[case testIssubclasDestructuringUnions1] from typing import Union, List, Tuple, Dict, Type def f(x: Union[Type[int], Type[str], Type[List]]) -> None: if issubclass(x, (str, (int,))): @@ -1465,6 +1465,51 @@ def f(x: Union[Type[int], Type[str], Type[List]]) -> None: [builtins fixtures/isinstancelist.pyi] +[case testIssubclasDestructuringUnions2] +from typing import Union, List, Tuple, Dict, Type +def f(x: Type[Union[int, str, List]]) -> None: + if issubclass(x, (str, (int,))): + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str]' + x()[1] # E: Value of type "Union[int, str]" is not indexable + else: + reveal_type(x) # E: Revealed type is 'Type[builtins.list]' + reveal_type(x()) # E: Revealed type is 'builtins.list[]' + x()[1] + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' + if issubclass(x, (str, (list,))): + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.str], Type[builtins.list[Any]]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.str, builtins.list[]]' + x()[1] + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' +[builtins fixtures/isinstancelist.pyi] + +[case testIssubclasDestructuringUnions3] +from typing import Union, List, Tuple, Dict, Type +def f(x: Type[Union[int, str, List]]) -> None: + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' + if issubclass(x, (str, (int,))): + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str]' + x()[1] # E: Value of type "Union[int, str]" is not indexable + else: + reveal_type(x) # E: Revealed type is 'Type[builtins.list]' + reveal_type(x()) # E: Revealed type is 'builtins.list[]' + x()[1] + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' + if issubclass(x, (str, (list,))): + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.str], Type[builtins.list[Any]]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.str, builtins.list[]]' + x()[1] + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' +[builtins fixtures/isinstancelist.pyi] + + [case testIssubclass] from typing import Type, ClassVar