-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
TypeGuard on self does not narrow associated TypeVars #14425
Comments
In the event that this complaint is actually desired behaviour because in the general case TypeVars interact with variance, inheritance, and all sorts of "But what if it is actually both types?" questions, it may be worth knowing that the following (ugly) variation passes without errors by bouncing the variable through a member variable on the class. _res_return_type: AB_T
def res(self) -> AB_T:
# This should return A() for a Gen[A] and B() for a Gen[B]
if Gen.gives_A(self):
reveal_type(self) # Again it now correctly understands that self is a Gen[A]
self._res_return_type= A() # So the type of self._res_return_type is A, so this is a legal assignment
... # Case for B skipped for brevity
return self._res_return_type # And here the type of self._res_return_type is understood to be AB_T |
The TypeGuard PEP explicitly excludes narrowing self. I have a PR open that should warn on this behavior. |
Thanks for the reply, @A5rocks. The only thing that I can see in PeP 647 is this point which near as I can tell explains why My question, however, is whether when I have successfully (using the explicit argument approach) narrowed Self[T] to Self[A], other things in T like the return type of the current method should also be understood to be A. It seems that either the answer is yes, and the [return-value] and [assignment] errors in my first post are false positives, or the answer is no and the indirection in my follow up post is a false negative. |
Ah I see what you mean now. (FWIW I recommend using I think yours might be the same issue as: from typing import TypeGuard, TypeVar, Generic, Any
T = TypeVar("T", int, str)
class A(Generic[T]):
pass
def tp_int(a: A[Any]) -> TypeGuard[A[int]]:
return False
def tp_str(a: A[Any]) -> TypeGuard[A[str]]:
return False
def f(a: A[T]) -> T:
if tp_int(a):
return 42
elif tp_str(a):
return "hi"
else:
assert False No FWIW this case would probably (by my guess) have to be fixed first: from typing import TypeVar
T = TypeVar("T")
def f(x: T) -> T:
if isinstance(x, int):
return 42
else:
return x I looked for an issue and found #1539
Actually nevermind, I think a big part of me being confused here is because while |
If you write a user-defined type guard function as an instance method, the If your intent is to test the value of a @staticmethod
def gives_A(obj: "Gen[Any]") -> "TypeGuard[Gen[A]]":
return obj._should_be_A
@staticmethod
def gives_B(obj: "Gen[Any]") -> "TypeGuard[Gen[B]]":
return not obj._should_be_A If, for some reason, you really don't want to use a static method and want to stick with an instance method, then you will need to define a second parameter. def gives_A(self, obj: "Gen[Any]") -> "TypeGuard[Gen[A]]":
return obj._should_be_A And you would need to call it like this: self.gives_A(self) @A5rocks, you said "the typeguard will say that x's type is T". That's an incorrect interpretation. The type specified as the return type of a |
Ah, my interpretation was based on how typeguards don't narrow to an intersection between the type it's guarded against and the type passed in, unlike normal type narrowing. I see now how that was a wrong interpretation. I was confused at the time at how to handle the case described (assuming the typeguard is valid). Maybe this will become obvious once I think about it. |
Thanks for the reply, @erictraut. I understand the point about instance methods not narrowing self if used conventionally, which is why my code was written as The question is whether, having narrowed self as Gen[T] to Gen[A], MyPy should understand T to necessarily refer to A. If yes, then there is a false positive in complaining about returning A from a method which promises a T. If no, then there is a false negative in being able to return an A via the indirection of packing it into self, as illustrated in my second comment. |
I would quite like for this particular example to be fixed, it will make a lot of GADT-style code a lot simpler (today, I have to cast the return type back to T in order to get this to type check). |
@ezyang the example you post is not type safe:
|
Well, how about the more precise:
|
That is safe (for non-generics)! |
I am too used to type systems that don't have subtyping 👹 |
I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyangmeta.com> [ghstack-poisoned]
I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyangmeta.com> [ghstack-poisoned]
I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyang@meta.com> Pull Request resolved: #118529 Approved by: https://github.com/Skylion007
Summary: I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyang@meta.com> X-link: pytorch/pytorch#118529 Approved by: https://github.com/Skylion007 Reviewed By: clee2000 Differential Revision: D53296779 Pulled By: ezyang fbshipit-source-id: 95799914350e50aeedf1acad93d71b79cad827c8
I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyangmeta.com> cc voznesenskym penguinwu EikanWang jgong5 Guobing-Chen XiaobingSuper zhuhaozhe blzheng wenzhe-nrv jiayisunx peterbell10 ipiszy yf225 chenyang78 kadeng muchulee8 aakhundov ColinPeppler [ghstack-poisoned]
I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyangmeta.com> cc voznesenskym penguinwu EikanWang jgong5 Guobing-Chen XiaobingSuper zhuhaozhe blzheng wenzhe-nrv jiayisunx peterbell10 ipiszy yf225 chenyang78 kadeng muchulee8 aakhundov ColinPeppler [ghstack-poisoned]
I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyang@meta.com> Pull Request resolved: #118529 Approved by: https://github.com/Skylion007
Summary: I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyang@meta.com> X-link: pytorch/pytorch#118529 Approved by: https://github.com/Skylion007 Reviewed By: atalman Differential Revision: D53436982 Pulled By: ezyang fbshipit-source-id: a31a94137df69eae5ddc86396580655ff478b8a2
I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyang@meta.com> Pull Request resolved: #118529 Approved by: https://github.com/Skylion007
I was just playing around with improving the typing of symbolic_shapes. The PR is not "complete" but I in particular wanted to get feedback on whether or not people liked making ValueRanges Generic; it seems that distinguishing if you have an Expr ValueRange or a SympyBoolean ValueRange is a lot of trouble for downstream. Using TypeGuard, we can perform refinements on the generic parameter inside methods, although we still have to cast back to ValueRange[T] due to python/mypy#14425 (comment) Signed-off-by: Edward Z. Yang <ezyang@meta.com> Pull Request resolved: #118529 Approved by: https://github.com/Skylion007
I just came across this issue and was indeed very puzzled by the mypy errors. It would be really great if the idea can be expressed safely using type annotations. I first thought that the type narrowing on
mypy's output is:
Version:
|
I have a class which is generic and has some runtime marker for determining the underlying type. I would like to use that marker to guide MyPy in validating the implementation of the class. So if I have a TypeGuard which confirms the current object is generic in A (that is, TypeVar AB_T is bound to A), then it should be safe for the method which is annotated as returning an AB_T to return an A in that context.
What I'm finding instead is that the TypeGuard correctly tells MyPy that the object which knows itself to be Gen[AB_T] is specifically a Gen[A], but it does not make the link that AB_T is A. As such, it refuses to return an A.
I've tested the following example with MyPy 0.991 on Python 3.10
Actual Behavior
The text was updated successfully, but these errors were encountered: