-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Make Type[X] compatible with metaclass of X #3346
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
Conversation
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.
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 |
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.
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. |
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 think this comment should stay here, since the __init__
signature should be checked for all items in an overload.
test-data/unit/check-classes.test
Outdated
@@ -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' |
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.
This reveal is identical to the previous one. I think you wanted to check something like A[A]
.
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.
Nah I've probably copied it with the test below somehow :)
test-data/unit/check-classes.test
Outdated
|
||
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' |
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.
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.
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.
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.
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.
Although I'm not sure why add g2
in M instead of in A
as a classmethod
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.
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 inA
as a classmethod
Yes, but this will not work for dunder methods.
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 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.
test-data/unit/check-classes.test
Outdated
class A(metaclass=M): | ||
def foo(self): pass | ||
|
||
# 3 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar: |
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.
It looks here are four examples.
cv.x | ||
x3: M = cv | ||
|
||
[builtins fixtures/classmethod.pyi] |
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 would add few reveal_type
s for methods in these tests, just in case.
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 tried, although I'm not sure what information is gained.
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.
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.
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.
Actually I wanted something like
reveal_type(A.g1)
andreveal_type(A.g1())
, in particular, where return types ofg2
etc. depend on type variables (likeList[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?
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.
Hmm I'm pretty sure I did, but probably forgot to push... :\ I will check.
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.
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.
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.
@ilevkivskyi I can try this with Dropbox internal code tomorrow (need to go now).
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.
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.
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.
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.
* 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)
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.