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

Make Type[X] compatible with metaclass of X #3346

Merged
merged 6 commits into from
May 31, 2017

Conversation

elazarg
Copy link
Contributor

@elazarg elazarg commented May 9, 2017

Fix #3326.

PR #2837 seems completely broken; the checks were all backwards, so this PR reverses them.

I had to special-case EnumMeta since the bound for the self argument can't be lower than the current class. I fail to see how it can work without something like metaclass=M argument for TypeVar.

Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

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

This mostly looks good. I have an idea about special-casing self-types of the form Type[T].

mypy/subtypes.py Outdated
@@ -155,19 +150,18 @@ def visit_instance(self, left: Instance) -> bool:
for lefta, righta, tvar in
zip(t.args, right.args, right.type.defn.type_vars))
if isinstance(right, TypeType):
item = right.item
if isinstance(item, TupleType):
item = item.fallback
Copy link
Member

Choose a reason for hiding this comment

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

Why did you remove the case of TupleType? A named tuple can appear in Type[].

@@ -263,8 +257,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.
# This is unsound, we don't check the __init__ signature.
Copy link
Member

Choose a reason for hiding this comment

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

I think this comment should stay here, since the __init__ signature should be checked for all items in an overload.

@@ -3066,12 +3066,13 @@ class M(type):
class A(metaclass=M): pass

reveal_type(A[M]) # E: Revealed type is 'builtins.int'
reveal_type(A[M]) # E: Revealed type is 'builtins.int'
Copy link
Member

Choose a reason for hiding this comment

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

This reveal is identical to the previous one. I think you wanted to check something like A[A].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nah I've probably copied it with the test below somehow :)


class M(type):
def g1(cls: 'Type[A]') -> None: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
def g2(cls: Type[TA]) -> None: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
Copy link
Member

Choose a reason for hiding this comment

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

Although Type[A] is lower than M, I think this could be allowed. The special-casing probably should be in checkmember.bind_self (then you will also not need special-casing EnumMeta above). My idea is following:

class M(type):
    def g2(cls: Type[TA]) -> TA:
        ...
    def g4(cls: TM) -> None:
        ...
class A(metaclass=M): ...
class A2(A): ...
class B(metaclass=M): ...
A.g2() # OK, inferred type is 'A'
A2.g2() # OK, inferred type is 'A2'
B.g2() # Error, g2 should be only called on subclasses of A
A.g4(), A2.g4(), B.g4() # All are OK

The situation with one "privileged" class with a given metaclass sometimes appears in frameworks. This will allow users to give more precise types in such situation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So this is probably a more general decision to be made. Instead of requiring a supertype, we can require a related type - either a supertype or a subtype, in which case there's a check at the point of use, just like any other function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although I'm not sure why add g2 in M instead of in A as a classmethod

Copy link
Member

Choose a reason for hiding this comment

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

So this is probably a more general decision to be made. Instead of requiring a supertype, we can require a related type - either a supertype or a subtype, in which case there's a check at the point of use, just like any other function.

I would vote for doing this first only for self types in metaclasses (since this is a situation where we know this is needed).

Although I'm not sure why add g2 in M instead of in A as a classmethod

Yes, but this will not work for dunder methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see.
Adding checks at access seems to have non local effect, which makes this PR significantly more complex. Perhaps it will be better to split it up, and add the check at access only later.

class A(metaclass=M):
def foo(self): pass

# 3 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar:
Copy link
Member

Choose a reason for hiding this comment

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

It looks here are four examples.

cv.x
x3: M = cv

[builtins fixtures/classmethod.pyi]
Copy link
Member

Choose a reason for hiding this comment

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

I would add few reveal_types for methods in these tests, just in case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried, although I'm not sure what information is gained.

Copy link
Member

Choose a reason for hiding this comment

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

Actually I wanted something like reveal_type(A.g1) and reveal_type(A.g1()), in particular, where return types of g2 etc. depend on type variables (like List[TA]). Plus also the situations where name of staticmethod/classmethod in class coincides with a name of a method in metaclass.

Copy link
Member

Choose a reason for hiding this comment

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

Actually I wanted something like reveal_type(A.g1) and reveal_type(A.g1()), in particular, where return types of g2 etc. depend on type variables (like List[TA]). Plus also the situations where name of staticmethod/classmethod in class coincides with a name of a method in metaclass.

@elazarg Have you added these tests? Otherwise I think this PR looks good.
@JukkaL could you please try this PR with the Dropbox internal codebase before it is merged?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm I'm pretty sure I did, but probably forgot to push... :\ I will check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I don't have time at this moment, so I tried pushing kinda blindly. I might have mixed up something. This supposed to be the tests only, not the check-on-access you've asked for. I hope I did not mess up too bad; if I did, I can probably try to fix it later this week. In such case, it can still be useful to check earlier commit against the internal repo.

Sorry about the mess.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@ilevkivskyi I can try this with Dropbox internal code tomorrow (need to go now).

Copy link
Member

Choose a reason for hiding this comment

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

No worries, I have tried this with our internal code, there are no new errors this found (either in our codebase or in the PR). I haven't reviewed the PR though so I can't "Approve" it. But Ivan can.

Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

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

OK, this looks good now. We can discuss whether we need additional errors on access, and whether we can allow cls: Type[A] first argument annotation in metaclass methods (this PR only allows this for EnumMeta) in a separate issue.

@ilevkivskyi ilevkivskyi merged commit f60d874 into python:master May 31, 2017
carljm added a commit to carljm/mypy that referenced this pull request May 31, 2017
* master:
  Support accessing modules imported in class bodies within methods. (python#3450)
  Make Type[X] compatible with metaclass of X (python#3346)
  Sync typeshed (python#3479)
  Handle flags in pytest passthrough (python#3467)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants