Open sterliakov opened 7 months ago
I think the issue is probably a general type narrowing problem of the TypeIs
implementation (so not directly an issue with the reachability analysis itself). At least I think that it is a problem (otherwise it would be confusing).
I had a quick look into the implementation and it seems that the code of the isinstance
runtime check is reused. From the comments I found for the implementations it seems that this is desired:
def covers_at_runtime(item: Type, supertype: Type) -> bool:
"""Will isinstance(item, supertype) always return True at runtime?"""
....
# Since runtime type checks will ignore type arguments, erase the types.
supertype = erase_type(supertype)
if is_proper_subtype(
erase_type(item), supertype, ignore_promotions=True, erase_instances=True
):
return True
...
Erasing the type argument is fine for isinstance
(e.g. isinstance(x, list)
, but here it leads to the following issue (slightly simplified from above by using is_list_int
instead of is_iterable_int
):
bar: list[int] | list[str]
if is_list_int(bar): <- here both Union members are erased to list[Any] (which is subtype of the erased TypeIs argument: list[Any])
reveal_type(bar)
else: <- thus here is nothing left in the Union for this branch and we get the type Never
reveal_type(bar)
Thanks for the analysis! I'd be happy to review a PR addressing this.
I was already thinking about submitting one, but I thought it is best to wait until it is confirmed (which you did just now ;) )
I will have a look :+1:
This isn't a problem in basedmypy: playground
I think it involved updating conditional_types_with_intersection
/conditional_types
/restrict_subtype_away
/covers_at_runtime
to not erase generics.
I think the problem arises in covers_at_runtime
, we can see:
# Since runtime type checks will ignore type arguments, erase the types.
I checked the code in basedmypy and it seems that covers_at_runtime
is using "only" is_proper_subtype
(without erasing the generics). I think there are a few cases where this is not sufficient (because it does not handle Any
as expected) for example:
(This PR should handle these cases.)
Bug Report
Applying
TypeIs
to narrow type of an iterable results in too optimistic reachability analysis.To Reproduce
playground has more code, including
TypeGuard
comparison and non-iterable case that works correctly.Expected Behavior
I'd expected both branches to be reachable and to narrow type according to the spec:
list[int]
inif
andlist[str]
inelse
, ideally.Actual Behavior
Your Environment
--warn-unreachable
mypy.ini
(and other config files): N/A