Skip to content

Commit 051bba0

Browse files
pkchJukkaL
authored andcommitted
Standardize Type[Union[...]] -> Union[Type[...]] (#3209)
Also in this PR: * Add tests for Type[Union[...]] * Update testTypeUsingTypeCClassMethodUnion, since it can now use the correct expect output
1 parent 9941b61 commit 051bba0

15 files changed

+90
-34
lines changed

mypy/checker.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ def is_implicit_any(t: Type) -> bool:
682682
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]):
683683
isclass = defn.is_class or defn.name() in ('__new__', '__init_subclass__')
684684
if isclass:
685-
ref_type = mypy.types.TypeType(ref_type)
685+
ref_type = mypy.types.TypeType.make_normalized(ref_type)
686686
erased = erase_to_bound(arg_type)
687687
if not is_subtype_ignoring_tvars(ref_type, erased):
688688
note = None
@@ -2741,13 +2741,10 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap:
27412741
if type_map is None:
27422742
return None
27432743
for expr, typ in type_map.items():
2744-
if isinstance(typ, UnionType):
2745-
converted_type_map[expr] = UnionType([TypeType(t) for t in typ.items])
2746-
elif isinstance(typ, Instance):
2747-
converted_type_map[expr] = TypeType(typ)
2748-
else:
2744+
if not isinstance(typ, (UnionType, Instance)):
27492745
# unknown type; error was likely reported earlier
27502746
return {}
2747+
converted_type_map[expr] = TypeType.make_normalized(typ)
27512748
return converted_type_map
27522749

27532750

mypy/checkexpr.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ def check_call(self, callee: Type, args: List[Expression],
513513

514514
if (callee.is_type_obj() and (len(arg_types) == 1)
515515
and is_equivalent(callee.ret_type, self.named_type('builtins.type'))):
516-
callee = callee.copy_modified(ret_type=TypeType(arg_types[0]))
516+
callee = callee.copy_modified(ret_type=TypeType.make_normalized(arg_types[0]))
517517

518518
if callable_node:
519519
# Store the inferred callable type.
@@ -1193,7 +1193,8 @@ def analyze_descriptor_access(self, instance_type: Type, descriptor_type: Type,
11931193
owner_type = instance_type
11941194

11951195
_, inferred_dunder_get_type = self.check_call(
1196-
dunder_get_type, [TempNode(instance_type), TempNode(TypeType(owner_type))],
1196+
dunder_get_type,
1197+
[TempNode(instance_type), TempNode(TypeType.make_normalized(owner_type))],
11971198
[nodes.ARG_POS, nodes.ARG_POS], context)
11981199

11991200
if isinstance(inferred_dunder_get_type, AnyType):

mypy/checkmember.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ def expand(target: Type) -> Type:
650650
ret_type = func.ret_type
651651
variables = func.variables
652652
if isinstance(original_type, CallableType) and original_type.is_type_obj():
653-
original_type = TypeType(original_type.ret_type)
653+
original_type = TypeType.make_normalized(original_type.ret_type)
654654
res = func.copy_modified(arg_types=arg_types,
655655
arg_kinds=func.arg_kinds[1:],
656656
arg_names=func.arg_names[1:],
@@ -665,5 +665,5 @@ def erase_to_bound(t: Type) -> Type:
665665
return t.upper_bound
666666
if isinstance(t, TypeType):
667667
if isinstance(t.item, TypeVarType):
668-
return TypeType(t.item.upper_bound)
668+
return TypeType.make_normalized(t.item.upper_bound)
669669
return t

mypy/erasetype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def visit_union_type(self, t: UnionType) -> Type:
7575
return UnionType.make_simplified_union(erased_items)
7676

7777
def visit_type_type(self, t: TypeType) -> Type:
78-
return TypeType(t.item.accept(self), line=t.line)
78+
return TypeType.make_normalized(t.item.accept(self), line=t.line)
7979

8080

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

mypy/expandtype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def visit_type_type(self, t: TypeType) -> Type:
124124
# union of instances or Any). Sadly we can't report errors
125125
# here yet.
126126
item = t.item.accept(self)
127-
return TypeType(item)
127+
return TypeType.make_normalized(item)
128128

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

mypy/join.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def visit_partial_type(self, t: PartialType) -> Type:
245245

246246
def visit_type_type(self, t: TypeType) -> Type:
247247
if isinstance(self.s, TypeType):
248-
return TypeType(self.join(t.item, self.s.item), line=t.line)
248+
return TypeType.make_normalized(self.join(t.item, self.s.item), line=t.line)
249249
elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type':
250250
return self.s
251251
else:

mypy/meet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ def visit_type_type(self, t: TypeType) -> Type:
278278
if isinstance(self.s, TypeType):
279279
typ = self.meet(t.item, self.s.item)
280280
if not isinstance(typ, NoneTyp):
281-
typ = TypeType(typ, line=t.line)
281+
typ = TypeType.make_normalized(typ, line=t.line)
282282
return typ
283283
elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type':
284284
return t

mypy/messages.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ def format(self, typ: Type, verbosity: int = 0) -> str:
191191
if func.is_type_obj():
192192
# The type of a type object type can be derived from the
193193
# return type (this always works).
194-
return self.format(TypeType(erase_type(func.items()[0].ret_type)), verbosity)
194+
return self.format(TypeType.make_normalized(erase_type(func.items()[0].ret_type)),
195+
verbosity)
195196
elif isinstance(func, CallableType):
196197
return_type = strip_quotes(self.format(func.ret_type))
197198
if func.is_ellipsis_args:

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2214,7 +2214,7 @@ def add_method(funcname: str,
22142214
is_classmethod: bool = False,
22152215
) -> None:
22162216
if is_classmethod:
2217-
first = [Argument(Var('cls'), TypeType(selftype), None, ARG_POS)]
2217+
first = [Argument(Var('cls'), TypeType.make_normalized(selftype), None, ARG_POS)]
22182218
else:
22192219
first = [Argument(Var('self'), selftype, None, ARG_POS)]
22202220
args = first + args

mypy/test/testtypes.py

Lines changed: 1 addition & 1 deletion
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_normalized(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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
189189
if len(t.args) != 1:
190190
self.fail('Type[...] must have exactly one type argument', t)
191191
item = self.anal_type(t.args[0])
192-
return TypeType(item, line=t.line)
192+
return TypeType.make_normalized(item, line=t.line)
193193
elif fullname == 'typing.ClassVar':
194194
if self.nesting_level > 0:
195195
self.fail('Invalid type: ClassVar nested inside other type', t)
@@ -384,7 +384,7 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type:
384384
return AnyType()
385385

386386
def visit_type_type(self, t: TypeType) -> Type:
387-
return TypeType(self.anal_type(t.item), line=t.line)
387+
return TypeType.make_normalized(self.anal_type(t.item), line=t.line)
388388

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

mypy/typefixture.py

Lines changed: 6 additions & 6 deletions
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_normalized(self.a)
149+
self.type_b = TypeType.make_normalized(self.b)
150+
self.type_c = TypeType.make_normalized(self.c)
151+
self.type_d = TypeType.make_normalized(self.d)
152+
self.type_t = TypeType.make_normalized(self.t)
153+
self.type_any = TypeType.make_normalized(self.anyt)
154154

155155
# Helper methods
156156

mypy/types.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,23 +1190,36 @@ class TypeType(Type):
11901190
# a generic class instance, a union, Any, a type variable...
11911191
item = None # type: Type
11921192

1193-
def __init__(self, item: Type, *, line: int = -1, column: int = -1) -> None:
1193+
def __init__(self, item: Union[Instance, AnyType, TypeVarType, TupleType, NoneTyp,
1194+
CallableType], *, line: int = -1, column: int = -1) -> None:
1195+
"""To ensure Type[Union[A, B]] is always represented as Union[Type[A], Type[B]], item of
1196+
type UnionType must be handled through make_normalized static method.
1197+
"""
11941198
super().__init__(line, column)
11951199
if isinstance(item, CallableType) and item.is_type_obj():
11961200
self.item = item.fallback
11971201
else:
11981202
self.item = item
11991203

1204+
@staticmethod
1205+
def make_normalized(item: Type, *, line: int = -1, column: int = -1) -> Type:
1206+
if isinstance(item, UnionType):
1207+
return UnionType.make_union(
1208+
[TypeType.make_normalized(union_item) for union_item in item.items],
1209+
line=line, column=column
1210+
)
1211+
return TypeType(item, line=line, column=column) # type: ignore
1212+
12001213
def accept(self, visitor: 'TypeVisitor[T]') -> T:
12011214
return visitor.visit_type_type(self)
12021215

12031216
def serialize(self) -> JsonDict:
12041217
return {'.class': 'TypeType', 'item': self.item.serialize()}
12051218

12061219
@classmethod
1207-
def deserialize(cls, data: JsonDict) -> 'TypeType':
1220+
def deserialize(cls, data: JsonDict) -> Type:
12081221
assert data['.class'] == 'TypeType'
1209-
return TypeType(deserialize_type(data['item']))
1222+
return TypeType.make_normalized(deserialize_type(data['item']))
12101223

12111224

12121225
#
@@ -1383,7 +1396,7 @@ def visit_overloaded(self, t: Overloaded) -> Type:
13831396
return Overloaded(items=items)
13841397

13851398
def visit_type_type(self, t: TypeType) -> Type:
1386-
return TypeType(t.item.accept(self), line=t.line, column=t.column)
1399+
return TypeType.make_normalized(t.item.accept(self), line=t.line, column=t.column)
13871400

13881401

13891402
class TypeStrVisitor(SyntheticTypeVisitor[str]):

test-data/unit/check-classes.test

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,7 +2184,6 @@ def process(cls: Type[User]):
21842184
[out]
21852185

21862186
[case testTypeUsingTypeCClassMethodUnion]
2187-
# Ideally this would work, but not worth the effort; just don't crash
21882187
from typing import Type, Union
21892188
class User:
21902189
@classmethod
@@ -2193,11 +2192,11 @@ class User:
21932192
class ProUser(User): pass
21942193
class BasicUser(User): pass
21952194
def process(cls: Type[Union[BasicUser, ProUser]]):
2196-
cls.foo() # E: Type[Union[BasicUser, ProUser]] has no attribute "foo"
2195+
cls.foo()
21972196
obj = cls()
2198-
cls.bar(obj) # E: Type[Union[BasicUser, ProUser]] has no attribute "bar"
2197+
cls.bar(obj)
21992198
cls.mro() # Defined in class type
2200-
cls.error # E: Type[Union[BasicUser, ProUser]] has no attribute "error"
2199+
cls.error # E: Item "type" of "Union[Type[BasicUser], Type[ProUser]]" has no attribute "error"
22012200
[builtins fixtures/classmethod.pyi]
22022201
[out]
22032202

test-data/unit/check-isinstance.test

Lines changed: 46 additions & 1 deletion
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[<nothing>]'
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[<nothing>]]'
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[<nothing>]]'
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[<nothing>]]'
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[<nothing>]]'
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[<nothing>]'
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[<nothing>]]'
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[<nothing>]]'
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[<nothing>]]'
1510+
[builtins fixtures/isinstancelist.pyi]
1511+
1512+
14681513
[case testIssubclass]
14691514
from typing import Type, ClassVar
14701515

0 commit comments

Comments
 (0)