-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Type[C] #1569
Changes from 1 commit
b7ddf36
eb61f1d
7b52ac6
1fd14f0
371e425
96696ff
aa37595
d4745b0
cf4a0b7
81ee239
3d92049
5ab11d2
afb0482
db30d59
6029c9d
786ef96
ef247f2
4162103
26ff267
256b31a
d224aff
d9c74a0
eaa9e0d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
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
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could also be |
||
callee = callee.copy_modified(ret_type=item) | ||
return callee | ||
|
||
# XXX Do we need to handle other forms? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
return TypeType(item) | ||
|
||
def expand_types(self, types: List[Type]) -> List[Type]: | ||
a = [] # type: List[Type] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mixing of a type object Adding to the confusion, |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add comment about not checking the compatibility of |
||
else: | ||
return False | ||
|
||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add comment about not checking the compatibility of |
||
else: | ||
return False | ||
|
||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we verify that the signature of There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TypeType should also be a subtype of There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Now I'm stuck with one remaining mystery. Why does the reveal_type() call |
||
return False | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test invalid args to |
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
There was a problem hiding this comment.
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 classC
.