Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standardize Type[Union[...]] -> Union[Type[...]] #3209

Merged
merged 7 commits into from
Jun 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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


Expand Down
5 changes: 3 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:],
Expand All @@ -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
2 changes: 1 addition & 1 deletion mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion mypy/test/testtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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')
Expand Down
12 changes: 6 additions & 6 deletions mypy/typefixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 17 additions & 4 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1183,23 +1183,36 @@ 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)

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']))


#
Expand Down Expand Up @@ -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]):
Expand Down
7 changes: 3 additions & 4 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]

Expand Down
47 changes: 46 additions & 1 deletion test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -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,))):
Expand All @@ -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[<nothing>]'
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[<nothing>]]'
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[<nothing>]]'
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[<nothing>]]'
[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[<nothing>]]'
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[<nothing>]'
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[<nothing>]]'
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[<nothing>]]'
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[<nothing>]]'
[builtins fixtures/isinstancelist.pyi]


[case testIssubclass]
from typing import Type, ClassVar

Expand Down