Skip to content

Commit 408f19e

Browse files
committed
Standardize Type[Union...]] -> Union[Type[...]]
* Add more tests for Type[Union[...]] * Force TypeType.make() to be used everywhere instead of TypeType() * Accidentally fixed testTypeUsingTypeCClassMethodUnion
1 parent aca05c7 commit 408f19e

15 files changed

+98
-35
lines changed

mypy/checker.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ def is_implicit_any(t: Type) -> bool:
663663
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]):
664664
isclass = defn.is_class or defn.name() in ('__new__', '__init_subclass__')
665665
if isclass:
666-
ref_type = mypy.types.TypeType(ref_type)
666+
ref_type = mypy.types.TypeType.make(ref_type)
667667
erased = erase_to_bound(arg_type)
668668
if not is_subtype_ignoring_tvars(ref_type, erased):
669669
note = None
@@ -2680,13 +2680,10 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap:
26802680
if type_map is None:
26812681
return None
26822682
for expr, typ in type_map.items():
2683-
if isinstance(typ, UnionType):
2684-
converted_type_map[expr] = UnionType([TypeType(t) for t in typ.items])
2685-
elif isinstance(typ, Instance):
2686-
converted_type_map[expr] = TypeType(typ)
2687-
else:
2683+
if not isinstance(typ, (UnionType, Instance)):
26882684
# unknown type; error was likely reported earlier
26892685
return {}
2686+
converted_type_map[expr] = TypeType.make(typ)
26902687
return converted_type_map
26912688

26922689

mypy/checkexpr.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ def check_call(self, callee: Type, args: List[Expression],
401401

402402
if (callee.is_type_obj() and (len(arg_types) == 1)
403403
and is_equivalent(callee.ret_type, self.named_type('builtins.type'))):
404-
callee = callee.copy_modified(ret_type=TypeType(arg_types[0]))
404+
callee = callee.copy_modified(ret_type=TypeType.make(arg_types[0]))
405405

406406
if callable_node:
407407
# Store the inferred callable type.
@@ -1071,7 +1071,7 @@ def analyze_descriptor_access(self, instance_type: Type, descriptor_type: Type,
10711071
owner_type = instance_type
10721072

10731073
_, inferred_dunder_get_type = self.check_call(
1074-
dunder_get_type, [TempNode(instance_type), TempNode(TypeType(owner_type))],
1074+
dunder_get_type, [TempNode(instance_type), TempNode(TypeType.make(owner_type))],
10751075
[nodes.ARG_POS, nodes.ARG_POS], context)
10761076

10771077
if isinstance(inferred_dunder_get_type, AnyType):

mypy/checkmember.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ def expand(target: Type) -> Type:
633633
ret_type = func.ret_type
634634
variables = func.variables
635635
if isinstance(original_type, CallableType) and original_type.is_type_obj():
636-
original_type = TypeType(original_type.ret_type)
636+
original_type = TypeType.make(original_type.ret_type)
637637
res = func.copy_modified(arg_types=arg_types,
638638
arg_kinds=func.arg_kinds[1:],
639639
arg_names=func.arg_names[1:],
@@ -648,5 +648,5 @@ def erase_to_bound(t: Type) -> Type:
648648
return t.upper_bound
649649
if isinstance(t, TypeType):
650650
if isinstance(t.item, TypeVarType):
651-
return TypeType(t.item.upper_bound)
651+
return TypeType.make(t.item.upper_bound)
652652
return t

mypy/erasetype.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def visit_union_type(self, t: UnionType) -> Type:
7777
return UnionType.make_simplified_union(erased_items)
7878

7979
def visit_type_type(self, t: TypeType) -> Type:
80-
return TypeType(t.item.accept(self), line=t.line)
80+
return TypeType.make(t.item.accept(self), line=t.line)
8181

8282

8383
def erase_typevars(t: Type, ids_to_erase: Optional[Container[TypeVarId]] = None) -> Type:

mypy/expandtype.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def visit_type_type(self, t: TypeType) -> Type:
127127
# union of instances or Any). Sadly we can't report errors
128128
# here yet.
129129
item = t.item.accept(self)
130-
return TypeType(item)
130+
return TypeType.make(item)
131131

132132
def expand_types(self, types: Iterable[Type]) -> List[Type]:
133133
a = [] # type: List[Type]

mypy/join.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def visit_partial_type(self, t: PartialType) -> Type:
248248

249249
def visit_type_type(self, t: TypeType) -> Type:
250250
if isinstance(self.s, TypeType):
251-
return TypeType(self.join(t.item, self.s.item), line=t.line)
251+
return TypeType.make(self.join(t.item, self.s.item), line=t.line)
252252
elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type':
253253
return self.s
254254
else:

mypy/meet.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def visit_type_type(self, t: TypeType) -> Type:
276276
if isinstance(self.s, TypeType):
277277
typ = self.meet(t.item, self.s.item)
278278
if not isinstance(typ, NoneTyp):
279-
typ = TypeType(typ, line=t.line)
279+
typ = TypeType.make(typ, line=t.line)
280280
return typ
281281
elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type':
282282
return t

mypy/semanal.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2212,7 +2212,7 @@ def add_method(funcname: str,
22122212
is_classmethod: bool = False,
22132213
) -> None:
22142214
if is_classmethod:
2215-
first = [Argument(Var('cls'), TypeType(selftype), None, ARG_POS)]
2215+
first = [Argument(Var('cls'), TypeType.make(selftype), None, ARG_POS)]
22162216
else:
22172217
first = [Argument(Var('self'), selftype, None, ARG_POS)]
22182218
args = first + args

mypy/subtypes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ def is_subtype(left: Type, right: Type,
7070
# otherwise, fall through
7171
# Treat builtins.type the same as Type[Any]
7272
elif is_named_instance(left, 'builtins.type'):
73-
return is_subtype(TypeType(AnyType()), right)
73+
return is_subtype(TypeType.make(AnyType()), right)
7474
elif is_named_instance(right, 'builtins.type'):
75-
return is_subtype(left, TypeType(AnyType()))
75+
return is_subtype(left, TypeType.make(AnyType()))
7676
return left.accept(SubtypeVisitor(right, type_parameter_checker,
7777
ignore_pos_arg_names=ignore_pos_arg_names))
7878

mypy/test/testtypes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ def test_type_type(self) -> None:
580580
self.assert_join(self.fx.type_b, self.fx.type_any, self.fx.type_any)
581581
self.assert_join(self.fx.type_b, self.fx.type_type, self.fx.type_type)
582582
self.assert_join(self.fx.type_b, self.fx.type_c, self.fx.type_a)
583-
self.assert_join(self.fx.type_c, self.fx.type_d, TypeType(self.fx.o))
583+
self.assert_join(self.fx.type_c, self.fx.type_d, TypeType.make(self.fx.o))
584584
self.assert_join(self.fx.type_type, self.fx.type_any, self.fx.type_type)
585585
self.assert_join(self.fx.type_b, self.fx.anyt, self.fx.anyt)
586586

mypy/typeanal.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
163163
return self.analyze_callable_type(t)
164164
elif fullname == 'typing.Type':
165165
if len(t.args) == 0:
166-
return TypeType(AnyType(), line=t.line)
166+
return TypeType.make(AnyType(), line=t.line)
167167
if len(t.args) != 1:
168168
self.fail('Type[...] must have exactly one type argument', t)
169169
item = self.anal_type(t.args[0])
170-
return TypeType(item, line=t.line)
170+
return TypeType.make(item, line=t.line)
171171
elif fullname == 'typing.ClassVar':
172172
if self.nesting_level > 0:
173173
self.fail('Invalid type: ClassVar nested inside other type', t)
@@ -364,7 +364,7 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type:
364364
return AnyType()
365365

366366
def visit_type_type(self, t: TypeType) -> Type:
367-
return TypeType(self.anal_type(t.item), line=t.line)
367+
return TypeType.make(self.anal_type(t.item), line=t.line)
368368

369369
def analyze_callable_type(self, t: UnboundType) -> Type:
370370
fallback = self.builtin_type('builtins.function')

mypy/typefixture.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,12 @@ def make_type_var(name: str, id: int, values: List[Type], upper_bound: Type,
145145
self.lsta = Instance(self.std_listi, [self.a]) # List[A]
146146
self.lstb = Instance(self.std_listi, [self.b]) # List[B]
147147

148-
self.type_a = TypeType(self.a)
149-
self.type_b = TypeType(self.b)
150-
self.type_c = TypeType(self.c)
151-
self.type_d = TypeType(self.d)
152-
self.type_t = TypeType(self.t)
153-
self.type_any = TypeType(self.anyt)
148+
self.type_a = TypeType.make(self.a)
149+
self.type_b = TypeType.make(self.b)
150+
self.type_c = TypeType.make(self.c)
151+
self.type_d = TypeType.make(self.d)
152+
self.type_t = TypeType.make(self.t)
153+
self.type_any = TypeType.make(self.anyt)
154154

155155
# Helper methods
156156

mypy/types.py

+25-3
Original file line numberDiff line numberDiff line change
@@ -1159,23 +1159,45 @@ class TypeType(Type):
11591159
# a generic class instance, a union, Any, a type variable...
11601160
item = None # type: Type
11611161

1162+
# this class should not be created directly
1163+
# use make method which may return either TypeType or UnionType
1164+
# this is to ensure Type[Union[A, B]] is always represented as Union[Type[A], Type[B]]
11621165
def __init__(self, item: Type, *, line: int = -1, column: int = -1) -> None:
1166+
raise NotImplementedError
1167+
1168+
def _init(self, item: Type, *, line: int = -1, column: int = -1) -> None:
11631169
super().__init__(line, column)
11641170
if isinstance(item, CallableType) and item.is_type_obj():
11651171
self.item = item.fallback
11661172
else:
11671173
self.item = item
11681174

1175+
def __new__(cls, *args, **kwargs): # type: ignore
1176+
instance = object.__new__(cls)
1177+
instance._init(*args, **kwargs)
1178+
return instance
1179+
1180+
def __copy__(self) -> 'Type':
1181+
return TypeType.make(self.item)
1182+
1183+
@staticmethod
1184+
def make(item: Type, *, line: int = -1, column: int = -1) -> Type:
1185+
if isinstance(item, UnionType):
1186+
return UnionType([TypeType.make(union_item) for union_item in item.items],
1187+
line=line, column=column)
1188+
else:
1189+
return TypeType.__new__(TypeType, item, line=line, column=column)
1190+
11691191
def accept(self, visitor: 'TypeVisitor[T]') -> T:
11701192
return visitor.visit_type_type(self)
11711193

11721194
def serialize(self) -> JsonDict:
11731195
return {'.class': 'TypeType', 'item': self.item.serialize()}
11741196

11751197
@classmethod
1176-
def deserialize(cls, data: JsonDict) -> 'TypeType':
1198+
def deserialize(cls, data: JsonDict) -> Type:
11771199
assert data['.class'] == 'TypeType'
1178-
return TypeType(deserialize_type(data['item']))
1200+
return TypeType.make(deserialize_type(data['item']))
11791201

11801202

11811203
#
@@ -1348,7 +1370,7 @@ def visit_overloaded(self, t: Overloaded) -> Type:
13481370
return Overloaded(items=items)
13491371

13501372
def visit_type_type(self, t: TypeType) -> Type:
1351-
return TypeType(t.item.accept(self), line=t.line, column=t.column)
1373+
return TypeType.make(t.item.accept(self), line=t.line, column=t.column)
13521374

13531375

13541376
class TypeStrVisitor(TypeVisitor[str]):

test-data/unit/check-classes.test

+3-4
Original file line numberDiff line numberDiff line change
@@ -2086,7 +2086,6 @@ def process(cls: Type[User]):
20862086
[out]
20872087

20882088
[case testTypeUsingTypeCClassMethodUnion]
2089-
# Ideally this would work, but not worth the effort; just don't crash
20902089
from typing import Type, Union
20912090
class User:
20922091
@classmethod
@@ -2095,11 +2094,11 @@ class User:
20952094
class ProUser(User): pass
20962095
class BasicUser(User): pass
20972096
def process(cls: Type[Union[BasicUser, ProUser]]):
2098-
cls.foo() # E: Type[Union[BasicUser, ProUser]] has no attribute "foo"
2097+
cls.foo()
20992098
obj = cls()
2100-
cls.bar(obj) # E: Type[Union[BasicUser, ProUser]] has no attribute "bar"
2099+
cls.bar(obj)
21012100
cls.mro() # Defined in class type
2102-
cls.error # E: Type[Union[BasicUser, ProUser]] has no attribute "error"
2101+
cls.error # E: Some element of union has no attribute "error"
21032102
[builtins fixtures/classmethod.pyi]
21042103
[out]
21052104

test-data/unit/check-isinstance.test

+46-1
Original file line numberDiff line numberDiff line change
@@ -1443,7 +1443,7 @@ else:
14431443
[builtins fixtures/isinstancelist.pyi]
14441444

14451445

1446-
[case testIssubclasDestructuringUnions]
1446+
[case testIssubclasDestructuringUnions1]
14471447
from typing import Union, List, Tuple, Dict, Type
14481448
def f(x: Union[Type[int], Type[str], Type[List]]) -> None:
14491449
if issubclass(x, (str, (int,))):
@@ -1465,6 +1465,51 @@ def f(x: Union[Type[int], Type[str], Type[List]]) -> None:
14651465
[builtins fixtures/isinstancelist.pyi]
14661466

14671467

1468+
[case testIssubclasDestructuringUnions2]
1469+
from typing import Union, List, Tuple, Dict, Type
1470+
def f(x: Type[Union[int, str, List]]) -> None:
1471+
if issubclass(x, (str, (int,))):
1472+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str]]'
1473+
reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str]'
1474+
x()[1] # E: Value of type "Union[int, str]" is not indexable
1475+
else:
1476+
reveal_type(x) # E: Revealed type is 'Type[builtins.list]'
1477+
reveal_type(x()) # E: Revealed type is 'builtins.list[<uninhabited>]'
1478+
x()[1]
1479+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]'
1480+
reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[<uninhabited>]]'
1481+
if issubclass(x, (str, (list,))):
1482+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.str], Type[builtins.list[Any]]]'
1483+
reveal_type(x()) # E: Revealed type is 'Union[builtins.str, builtins.list[<uninhabited>]]'
1484+
x()[1]
1485+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]'
1486+
reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[<uninhabited>]]'
1487+
[builtins fixtures/isinstancelist.pyi]
1488+
1489+
[case testIssubclasDestructuringUnions3]
1490+
from typing import Union, List, Tuple, Dict, Type
1491+
def f(x: Type[Union[int, str, List]]) -> None:
1492+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]'
1493+
reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[<uninhabited>]]'
1494+
if issubclass(x, (str, (int,))):
1495+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str]]'
1496+
reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str]'
1497+
x()[1] # E: Value of type "Union[int, str]" is not indexable
1498+
else:
1499+
reveal_type(x) # E: Revealed type is 'Type[builtins.list]'
1500+
reveal_type(x()) # E: Revealed type is 'builtins.list[<uninhabited>]'
1501+
x()[1]
1502+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]'
1503+
reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[<uninhabited>]]'
1504+
if issubclass(x, (str, (list,))):
1505+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.str], Type[builtins.list[Any]]]'
1506+
reveal_type(x()) # E: Revealed type is 'Union[builtins.str, builtins.list[<uninhabited>]]'
1507+
x()[1]
1508+
reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]'
1509+
reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[<uninhabited>]]'
1510+
[builtins fixtures/isinstancelist.pyi]
1511+
1512+
14681513
[case testIssubclass]
14691514
from typing import Type, ClassVar
14701515

0 commit comments

Comments
 (0)