8000 Type[C] by gvanrossum · Pull Request #1569 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Type[C] #1569

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

Merged
merged 23 commits into from
Jun 8, 2016
Merged

Type[C] #1569

Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b7ddf36
Tentative tests for Type[C].
May 18, 2016
eb61f1d
Pave the way for implementing Type[C].
May 20, 2016
7b52ac6
WIP: Introducing TypeType: Type[x] becomes TypeType(x).
May 20, 2016
1fd14f0
WIP: Fix/new tests; implement more subtype checks.
May 23, 2016
371e425
WIP: Towards supporting Type[T].
May 23, 2016
96696ff
Fix things so all tests pass!
May 24, 2016
aa37595
Some cleanup and fixup (not done).
May 24, 2016
d4745b0
Improve equivalence between type and Type.
May 25, 2016
cf4a0b7
Add support for method lookup from Type[].
May 25, 2016
81ee239
Print a proper error for unsupported Type[] args.
May 26, 2016
3d92049
Reject Type[U] where U's bound is a generic class.
May 26, 2016
5ab11d2
Support classes with @overload-ed __init__.
May 26, 2016
afb0482
Update our copies of typing.py to the latest from the python/typing r…
May 26, 2016
db30d59
Make Type[A] erase to Type[A], but Type[T] erases to Type[Any].
Jun 6, 2016
6029c9d
Special-case joining Type[] with builtins.type.
Jun 7, 2016
786ef96
Special-case joining Type[] with builtins.type.
Jun 7, 2016
ef247f2
Delete outdated XXX comment.
Jun 7, 2016
4162103
Finishing touch -- fix and test TypeAnalyser.visit_type_type().
Jun 7, 2016
26ff267
Added tests as requested.
Jun 7, 2016
256b31a
Remove comment asking for name for analyze_type_type_callee().
Jun 7, 2016
d224aff
Use self.default(self.s) instead of AnyType()/NonType() in join/meet.
Jun 7, 2016
d9c74a0
Add tests for overloaded __init__.
Jun 7, 2016
eaa9e0d
Add more tests. Fixed a bug in join() this found.
Jun 7, 2016
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
Prev Previous commit
Next Next commit
WIP: Towards supporting Type[T].
The TypeC tests no pass except for one ProUser* that should be
ProUser; but something's seriously wrong because many other tests now
fail.
  • Loading branch information
Guido van Rossum committed Jun 6, 2016
commit 371e42545fa188d4a94609f871623fb1e8c3391a
4 changes: 4 additions & 0 deletions mypy/applytype.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ def apply_generic_arguments(callable: CallableType, types: List[Type],
else:
msg.incompatible_typevar_value(callable, i + 1, type, context)

# Translate constructor fn back to class.
if isinstance(type, CallableType) and type.is_type_obj():
types[i] = type = type.ret_type

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is right, though somehow it never made a difference in my tests. The value C (the class) is not the same sort of thing as an instance of the class C.

upper_bound = callable.variables[i].upper_bound
if type and not mypy.subtypes.satisfies_upper_bound(type, upper_bound):
msg.incompatible_typevar_value(callable, i + 1, type, context)
Expand Down
31 changes: 23 additions & 8 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,20 +278,35 @@ def check_call(self, callee: Type, args: List[Node],
return self.check_call(callee.upper_bound, args, arg_kinds, context, arg_names,
callable_node, arg_messages)
elif isinstance(callee, TypeType):
item = self.analyze_type_type(callee.item, context)
item = self.analyze_type_type_callee(callee.item, context)
return self.check_call(item, args, arg_kinds, context, arg_names,
callable_node, arg_messages)
else:
return self.msg.not_callable(callee, context), AnyType()

def analyze_type_type(self, arg: Type, context: Context) -> Type:
"""Given the arg from (x: Type[arg]), return the type for x."""
if isinstance(arg, AnyType):
# What's a good name for this method?
def analyze_type_type_callee(self, item: Type, context: Context) -> Type:
"""Analyze the callee X in X(...) where X is Type[item].

Return a Y that we can pass to check_call(Y, ...).
"""
if isinstance(item, AnyType):
return AnyType()
if isinstance(arg, Instance):
return type_object_type(arg.type, self.named_type)
if isinstance(arg, UnionType):
return UnionType([self.analyze_type_type(item, context) for item in arg.items], arg.line)
if isinstance(item, Instance):
return type_object_type(item.type, self.named_type)
if isinstance(item, UnionType):
return UnionType([self.analyze_type_type_callee(item, context)
for item in item.items], item.line)
if isinstance(item, TypeVarType):
# Pretend we're calling the typevar's upper bound,
# i.e. its constructor (a poor approximation for reality,
# but better than AnyType...), but replace the return type
# with typevar.
callee = self.analyze_type_type_callee(item.upper_bound, context)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the class is generic things get harder. Not sure what we should then, though. Maybe just reject calls to generic type objects for now.

if isinstance(callee, CallableType):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be Overloaded.

callee = callee.copy_modified(ret_type=item)
return callee

# XXX Do we need to handle other forms?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that other forms should be rejected during semantic analysis.

# XXX Make a nicely formatted error message.
self.msg.fail("XXX Bad arg to Type[]", context)
Expand Down
8 changes: 4 additions & 4 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,14 +348,14 @@ def infer_against_any(self, types: List[Type]) -> List[Constraint]:
res.extend(infer_constraints(t, AnyType(), self.direction))
return res

def visit_overloaded(self, type: Overloaded) -> List[Constraint]:
def visit_overloaded(self, template: Overloaded) -> List[Constraint]:
res = [] # type: List[Constraint]
for t in type.items():
for t in template.items():
res.extend(infer_constraints(t, self.actual, self.direction))
return res

def visit_type_type(self, type: TypeType) -> List[Constraint]:
return [] # XXX What else to do?
def visit_type_type(self, template: TypeType) -> List[Constraint]:
return infer_constraints(template.item, self.actual, self.direction)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mixing up the class and its instances, it should be something like

    def visit_type_type(self, template: TypeType) -> List[Constraint]:
        actual = self.actual
        if isinstance(self.actual, TypeType):
            return infer_constraints(template.item, self.actual.item, self.direction)
        if isinstance(self.actual, CallableType) and self.actual.is_type_obj():
            return infer_constraints(template.item, self.actual.ret_type, self.direction)
        return []



def neg_op(op: int) -> int:
Expand Down
3 changes: 2 additions & 1 deletion mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ def visit_partial_type(self, t: PartialType) -> Type:
return t

def visit_type_type(self, t: TypeType) -> Type:
return TypeType(t.item.accept(self))
item = t.item.accept(self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that we should verify here that the new item type is valid (instance or union of instances, or Any). However, expand_type currently can't report an error, which is unfortunate. You could just write a TODO comment about this and not worry about it for now.

return TypeType(item)

def expand_types(self, types: List[Type]) -> List[Type]:
a = [] # type: List[Type]
Expand Down
11 changes: 5 additions & 6 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,11 @@ def visit_callable_type(self, left: CallableType) -> bool:
return all(is_subtype(left, item, self.check_type_parameter)
for item in right.items())
elif isinstance(right, Instance):
if left.is_type_obj():
return is_subtype(left.ret_type, right)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing of a type object C with instances of the class C. These two lines can just be deleted. This is why other tests are failing, since the value C's type was no longer a subtype of type, so isinstance(..., C) would fail.

Adding to the confusion, messages.py usually prints the type of C as simply C. Now that we have the Type[C] syntax, it would be clearer to use it in messages.

return is_subtype(left.fallback, right)
elif isinstance(right, TypeType):
# XXX Or left.is_type_obj()?
return left.is_concrete_type_obj() and is_subtype(left.ret_type, right.item)
return left.is_type_obj() and is_subtype(left.ret_type, right.item)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment about not checking the compatibility of __init__ signatures.

else:
return False

Expand Down Expand Up @@ -204,8 +205,7 @@ def visit_overloaded(self, left: Overloaded) -> bool:
elif isinstance(right, TypeType):
# All the items must have the same type object status, so
# it's sufficient to query only (any) one of them.
# XXX Or left.is_type_obj()?
return left.is_concrete_type_obj() and is_subtype(left.items()[0].ret_type, right.item)
return left.is_type_obj() and is_subtype(left.items()[0].ret_type, right.item)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment about not checking the compatibility of __init__ signatures.

else:
return False

Expand All @@ -222,8 +222,7 @@ def visit_type_type(self, left: TypeType) -> bool:
if isinstance(right, TypeType):
return is_subtype(left.item, right.item)
if isinstance(right, CallableType):
# XXX Or is_type_obj()?
return right.is_concrete_type_obj() and is_subtype(left.item, right.ret_type)
return right.is_type_obj() and is_subtype(left.item, right.ret_type)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we verify that the signature of __init__ of the left type is compatible with the callable on the right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, we aren't checking the signature anywhere so it would be consistent to not have to check it here either. Maybe add a comment about the unsoundness, though.

# XXX Others? Union, Any, TypeVar
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeType should also be a subtype of builtins.type so that you can use isinstance with an argument of type Type[C].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! You hit the nail right on the head with all three passages of
dubious code. Independently I realized the same and came up with nearly the
same fixes (your fix for constraints.py is better).

Now I'm stuck with one remaining mystery. Why does the reveal_type() call
produce ProUser* rather than plain ProUser? The '*' is appended by
types.py:1020 when t.erased -- what exactly does that mean and should we
expect it here?

return False

Expand Down
13 changes: 9 additions & 4 deletions mypy/test/data/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1443,11 +1443,16 @@ class ProUser(User): pass
class BasicUser(User): pass
U = TypeVar('U', bound=User)
def new_user(user_class: Type[U]) -> U:
return user_class()
reveal_type(new_user(ProUser))
[out]
user = user_class()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test invalid args to __init__. Test TypeVar with non-default __init__, and try calling with compatible and incompatible constructor arguments.

reveal_type(user)
return user
pro_user = new_user(ProUser)
reveal_type(pro_user)
[out]
main: note: In function "new_user":
main:8: error: Revealed type is 'U`-1'
main: note: At top level:
main:8: error: Revealed type is '__main__.ProUser'
main:11: error: Revealed type is '__main__.ProUser'

[case testTypeUsingTypeCCovariance]
from typing import Type, TypeVar
Expand Down
3 changes: 3 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,9 @@ def visit_overloaded(self, t: Overloaded) -> Type:
raise RuntimeError('CallableType expectected, but got {}'.format(type(new)))
return Overloaded(items=items)

def visit_type_type(self, t: TypeType) -> Type:
return t
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Translate the item type recursively.



class TypeStrVisitor(TypeVisitor[str]):
"""Visitor for pretty-printing types into strings.
Expand Down
0