-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Specify type when type variables are remained in inherited classmethod. #5420
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.
Thanks for the PR!
This may be not the best first issue to fix, but if you are ambitious, then here are few more comments about what to do:
- Add tests for all patterns mentioned in the issues you want to close
- Add more sophisticated tests that test your solution in edge cases (e.g. multiple inheritance bases, multiple inheritance levels, overriding, binding one type variable to another, etc.)
- In
@classmethod
ofGeneric
class #1337 there is also a question what to do with class attributes (in addition to class methods), you should address this. Namely,
class C(Generic[T]):
x: T
class D(C[int]): ...
D.x # should not error and result in 'builtins.int'
- I think a "proper" fix for these issues should also add some more changes. For example, this currently correctly generates an error about ambiguity:
class C(Generic[T]):
x: T
reveal_type(C.x)
but the revealed type should be Any
, type variables should not "leak out" even in case of errors to avoid more spurious errors.
@ilevkivskyi |
ce6fea2
to
e59c7ae
Compare
class C(Generic[T]):
x: T
class D(C[int]): ...
D.x # should not error and result in 'builtins.int' I think this sample should be like this class C(Generic[T]):
x: List[T] = []
class D(C[int]): pass
D.x # should not error and result in 'builtins.int' Because |
Sorry, I've tried and I understood it will not be solved for class attributes. Class attributes are shared with super-class. So subclasses cannot be restricted to narrow type. from typing import Generic, TypeVar, List
T = TypeVar('T')
class A(Generic[T]):
x: List[T] = []
class B(A[int]): pass
class C(A[str]): pass
B.x.append(1)
C.x.append('1')
print(B.x) # => [1, '1']
print(C.x) # => [1, '1'] |
Sorry for a delay. I will try to review this PR next week. In the meantime could you please fix the merge conflict? |
633cd53
to
a9de04c
Compare
@ilevkivskyi I've updated! |
Hi @kitsuyui! Sorry for a delay with review, are you still interested in working on this? |
@ilevkivskyi |
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, let us give this one more chance. The general idea is good, but the implementation requires polishing. I left some comments below.
mypy/expandtype.py
Outdated
if base_instance.type.bases: | ||
typ = expand_type_by_instance_bases(typ, base_instance) | ||
typ = expand_type_by_instance(typ, base_instance) | ||
return typ |
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 algorithm currently looks wrong, and this is why #5664 and similar more complex type variable bindings don't work. You need to map type variables correctly, look for example in maptype.py
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.
The algorithm is still wrong. For example it will not work for something like this:
class B(Generic[T]):
@classmethod
def f(cls) -> T: ...
class C(B[Tuple[T, T]]):
...
class D(C[str]):
...
reveal_type(D.f()) # Revealed type is 'str', but should be 'Tuple[str, str]'
This is not just a small bug there, it is "conceptually" wrong. As I already said twice, you should use something similar to map_instance_to_supertype
from maptype.py
. If this is too complex, then I can overtake this PR, it already takes too long.
I've tried the latest code and found something that may be added to the test cases, here's the code.
I believe there's no type error in the signature, but the output was
Hope this will help! |
Your code works on my local mypy! But I still have not pushed commits yet. |
a9de04c
to
4540a12
Compare
I have pushed commits that rebased from latest master. But this is still insufficient yet. I have been still in the way of this part: #5420 (comment) It might be little more time to solve. |
@kitsuyui what is exactly the problem you have? I might be able to give some hints. |
I don't know what is the true behavior on following case; from typing import Generic, TypeVar
T = TypeVar('T')
class A(Generic[T]):
@classmethod
def f(cls, value: T) -> T: pass
reveal_type(A.f(1)) # : Revealed type is 'builtins.int*' T is solved by arguments. But T is not clear on class. function and type unbounded classmethod's should be checked by different algorithm. But this fix may be too large. So I'm wondering "Does it exist Plan B?" |
4540a12
to
e00f4c5
Compare
I've finally found the answer myself. from typing import Generic, TypeVar
T = TypeVar('T')
S = TypeVar('S')
class A(Generic[T]):
@classmethod
def f(cls) -> T: pass
class B(A[S]): pass
A.f # E: Access to classmethod of generic type remains type variables T is ambiguous.
B.f # E: Access to classmethod of generic type remains type variables S is ambiguous. Would you please review this once more? |
Sorry for another delay. I will try to review this in the next few days. |
Thank you. from typing import Generic, TypeVar
T = TypeVar('T')
class X(Generic[T]):
@classmethod
def a(cls) -> T:
...
@classmethod
def b(cls) -> T:
return cls.a() This should be treated as valid but So I'm planning to add more refactoring and more test cases. Please review after this. |
e00f4c5
to
fbd2a71
Compare
@ilevkivskyi |
fbd2a71
to
9cf96d5
Compare
d0aa3c7
to
9b3a114
Compare
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.
Here is another pass of review. Unfortunately, this is still not ready.
mypy/expandtype.py
Outdated
if base_instance.type.bases: | ||
typ = expand_type_by_instance_bases(typ, base_instance) | ||
typ = expand_type_by_instance(typ, base_instance) | ||
return typ |
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.
The algorithm is still wrong. For example it will not work for something like this:
class B(Generic[T]):
@classmethod
def f(cls) -> T: ...
class C(B[Tuple[T, T]]):
...
class D(C[str]):
...
reveal_type(D.f()) # Revealed type is 'str', but should be 'Tuple[str, str]'
This is not just a small bug there, it is "conceptually" wrong. As I already said twice, you should use something similar to map_instance_to_supertype
from maptype.py
. If this is too complex, then I can overtake this PR, it already takes too long.
I'm going to fix them in a few days. |
84690f7
to
00b1b86
Compare
I did it. Please review this again?
|
You didn't fix the main problem (although you marked it as resolved, why?), as I mentioned in two previous reviews, you algorithm is "conceptually" wrong. It looks like it doesn't make sense to push this forward any more. |
@ilevkivskyi |
cc0084c
to
536225f
Compare
Analyze generic types in classmethod by map_instance_to_supertype and expand_type_by_instance. Add "get_classmethod" method to TypeInfo because get_method cannot treat classmethod.
536225f
to
bbfd94c
Compare
@ilevkivskyi Sorry for taking your time again. I rewrote the code to use The code for analyze ordinary method uses And I added a test for #5420 (comment) How does this? |
@kitsuyui Thank you for your work! It is a useful starting point for me to try making a better fix for this. Don't worry that this PR didn't make it. You just took a bit too hard issue as a first time contribution. |
Thank you @ilevkivskyi ! |
Fix: #3645
- Fix: 1337, 3645