I'm relatively new to Pylance (VS Code's static type checker, based on Pyright) and just stumbled upon this type checking error with the count()
infinite iterator. I have some code I've been playing with, which I reduced to the following basic version which reproduces the problem:
from itertools import count
def foo() -> int:
for i in count():
if i == 42:
return i
print(foo())
I'm using this loop over count()
as essentially a variation of while True
, to manage an index for me while I'm waiting for a condition to be satisfied. Given it's an infinite loop, the only code path out of this function is via that condition inside the loop which returns an int, but Pylance/Pyright is giving me the following type error:
Function with declared return type "int" must return value on all code paths
Type "None" cannot be assigned to type "int" Pylance(reportGeneralTypeIssues)
I saw that I can silence the issue by either of the following:
# type: ignore
, which could swallow unrelated issues.Optional[int]
, which is strictly wrong.return -1
(or whatever fake value) at the end, which works, but adds a confusing no-op extra line.def foo() -> int:
i = 0
while True:
if i == 42:
return i
i += 1
But I'm wondering if there's a cleaner solution here to somehow correctly mark this loop as infinite.
I would also appreciate insight as to the cause - is it just a Pylance/Pyright bug (missing feature), or is there a deeper technical limitation with marking count()
as an infinite iterator?
The type system doesn't know whether a generator is infinite or not. There is an issue in mypy
related to this (#7374), but it was created in 2019 and it is labeled as low priority.
There is also issue #5992, where Guido van Rossum (yes, the creator of Python himself) commented the following:
Honestly, such code seems really rare, and apart from toy examples like this, it's also hard to analyze for a human. The convention for an unreachable point in the code is
assert False
(which it seems mypy understands).
So I would go for the approach of adding an assert False
after the loop. Alternatively you could also do something like this (mentioned in #5992):
from itertools import count
def foo() -> int:
while True:
for i in count():
if i == 42:
return i