Pylance-release: Code is marked as unreachable, even though it isn't

Created on 2 Jul 2020  Â·  15Comments  Â·  Source: microsoft/pylance-release

Environment data

  • Language Server version: Pylance language server 2020.6.1
  • OS and version: macOS 10.15.5
  • Python version (& distribution if applicable, e.g. Anaconda): CPython 3.8.3

Expected behaviour

Code should not be marked as unreachable

Actual behaviour

Code is marked as unreachable

Code Snippet / Additional information

import os
import sys

print("Hello Python")
info = os.getenv("URL") or sys.exit("Missing url")

print("Now everything is greyed out and marked as unreachable")
print("But it's not actually unreachable. Only if the env var URL is not defined")
bug fixed in next version

Most helpful comment

The print statement in this example also shows up as unreachable:

class Base:

    def get_stuff(self):
        raise NotImplementedError

    def do_stuff(self):
        stuff = self.get_stuff()
        print("Is this unreachable?")

class Child(Base):

    def get_stuff(self):
        return "stuff"

c = Child()
c.do_stuff()

All 15 comments

Thanks for the bug report. I've fixed the issue, and it will be addressed in a future release of Pylance.

Awesome thanks :+1:

Just FYI: The same happens with Django serializers and calling the save method.

The print statement in this example also shows up as unreachable:

class Base:

    def get_stuff(self):
        raise NotImplementedError

    def do_stuff(self):
        stuff = self.get_stuff()
        print("Is this unreachable?")

class Child(Base):

    def get_stuff(self):
        return "stuff"

c = Child()
c.do_stuff()

This is a different situation. This behavior is expected in this case.

The method get_stuff has no return type annotation, so Pylance needs to infer its return type. Since the method always raises an exception and it is not marked as an abstract method, Pylance infers that its return type is NoReturn.

There are two ways you can address this in your code. (I recommend doing both.)

  1. Declare Base as an abstract base class (i.e. derive from abc.ABC) and use the @abstractmethod decorator on the get_stuff method. This tells the type checker (and anyone who is reading your code) that this method must be overridden by a subclass.
  2. Provide a return type annotation for method do_stuff.
from abc import ABC

class Base(ABC):
   @abstractmethod
   def get_stuff(self) -> str:
       raise NotImplementedError

This issue has been fixed in version 2020.7.0, which we've just released. You can find the changelog here: https://github.com/microsoft/pylance-release/blob/master/CHANGELOG.md#202070-9-july-2020

I'm still facing this with asyncio statements:

Screenshot from 2020-11-23 16-35-22
Screenshot from 2020-11-23 16-36-22

Version 2020.8.2

If you hover over Heartbeat, what type does Pylance indicate? I'm guessing that it will display a function with a return type of NoReturn, which means that Pylance believes that the function always raises an exception.

@erictraut yes, you're correct. But Heartbeat is function which has an endless loop. But since it will run as a task and has asyncio.sleep statements in it, it won't block the execution. Does Pylance support asyncio?

EDIT:
Never mind, this issue is related to mine and it seem's that this has been fixed with version 2020.9.4.

Same after self.fail() in a unittest TestCase

Getting this with start_transaction() from mysql-connector

I think we'd appreciate a new issue for that; this one is old and likely not fully related. My expectation is that start_transaction() is not typed, and ends up having an inferred type of NoReturn or is a context manager missing a specific annotation, etc. It's hard to say without a full issue with a code example.

Hi @jakebailey,

I am having this issue with the current version of Pylance:
Pylance 2021.2.4
Python 3.7.10 (it was the same before the 3.7.10 update, I want to say all 3.7.X have not worked)
VS code: 1.53.2
Ubuntu: 20.04.2 LTS

I boiled it down to this code (see screenshot for Pylance reporting unreachable code in the for loop)

from typing import NoReturn


def my_func(input_data: int) -> NoReturn:
    assert input_data >= 0
    print(input_data)


def my_other_func(input_data: int) -> NoReturn:
    assert input_data >= 0
    print(input_data * 2)


print("Hello")

for idx in range(10):

    assert idx >= 0

    if (idx % 2) == 0:
        print("looking fiiine")
        my_func(idx)
    else:
        print("looking good")

        my_other_func(idx)

    print("I'm running!")
    print("I'm reachable!")

print("I'm done!")

image

if I remove the asserts, it is the same:

image

The 2 prints in the for loop are claimed to be unreachable.

I hope this helps move this forward! :-)

Thank you!

This is intended behavior. You have annotated my_func and my_other_func with a return type of NoReturn, which indicates to callers that the call to those functions will never return control back to the caller — i.e. they will always raise an exception or terminate the process. I think your intent was to specify None as the return type for these two functions.

Hi @erictraut

Thank you! Interpreted the python doc differently.

Was this page helpful?
0 / 5 - 0 ratings