You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
And of isinstances / typeguards containing unions #14434
When taking the and of multiple isinstances/typeguards, it seems like only the most recent is used to constrain the variable whose type is being checked.
I think the code reveal_type(x) should return int in each of these examples.
I'm not sure about the failing_in_both example: I vaguely understand that user-defined type guards do not constrain the negative case of an if (but I haven't yet understood the rationale) -- does similar reasoning explain why the type guards on the LHS and RHS of the and do not combine?
I mean, technically, I could write a TypeGuard that modifies the argument, and so I guess the LHS of the and could cease to hold when the RHS is called; is this why? Would @erictraut's StrictTypeGuard python/typing#1013 be able to resolve this?
Actual Behavior
main.py:14: note: Revealed type is "Union[builtins.int, builtins.str, builtins.list[Any]]"
main.py:21: note: Revealed type is "Union[builtins.int, builtins.str, builtins.list[Any]]"
Success: no issues found in 1 source file
Mypy appears to be implementing TypeGuard support correctly here. In failing_in_both, the revealed type should be int | list because that's what the int_or_list type guard function specifies.
The working_in_pyright function doesn't use TypeGuard but rather uses built-in isinstance type narrowing logic. Narrowing for isinstance is extremely complex and not standardized, so it's likely that there will be some differences between type checkers here. And indeed, as you show, pyright and mypy produce slightly different results in this particular case. I don't consider that a bug in either type checker, just an implementation difference.
Pyright contains provisional support for the StrictTypeGuard proposal if you want to try it out. It does produce the results you're looking for if you switch the type of x from Any to object. (In the current provisional StrictTypeGuard implementation, pyright does not attempt to narrow Any.)
I see, thanks for the explanation! In that case, would it be possible to relabel this issue from a bug report to a feature request? In the case of isinstance type narrowing, while int | list is still a valid revealed type in the working_in_pyright function, it'd be nice to have the narrower revealed type of int.
Activity
erictraut commentedon Jan 12, 2023
Mypy appears to be implementing
TypeGuardsupport correctly here. Infailing_in_both, the revealed type should beint | listbecause that's what theint_or_listtype guard function specifies.The
working_in_pyrightfunction doesn't useTypeGuardbut rather uses built-inisinstancetype narrowing logic. Narrowing forisinstanceis extremely complex and not standardized, so it's likely that there will be some differences between type checkers here. And indeed, as you show, pyright and mypy produce slightly different results in this particular case. I don't consider that a bug in either type checker, just an implementation difference.Pyright contains provisional support for the
StrictTypeGuardproposal if you want to try it out. It does produce the results you're looking for if you switch the type ofxfromAnytoobject. (In the current provisionalStrictTypeGuardimplementation, pyright does not attempt to narrowAny.)Anonymous-Stranger commentedon Jan 13, 2023
I see, thanks for the explanation! In that case, would it be possible to relabel this issue from a bug report to a feature request? In the case of isinstance type narrowing, while
int | listis still a valid revealed type in theworking_in_pyrightfunction, it'd be nice to have the narrower revealed type ofint.hauntsaninja commentedon May 11, 2026
This was fixed in #19517