Search code examples
pythonpython-typingpyright

Pylance (VS Code type checking) mistakes the count() infinite iterator as finite


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:

  • Suppressing the warning with # type: ignore, which could swallow unrelated issues.
  • Making the return type Optional[int], which is strictly wrong.
  • Adding a return -1 (or whatever fake value) at the end, which works, but adds a confusing no-op extra line.
  • Or just avoiding the issue and just rewriting this as an explicit infinite loop, which Pylance handles correctly:
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?


Solution

  • 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