Pylance-release: How to disable "code is not reachable" warning and greying out the code? I find this feature to be unreliable quite often.

Created on 16 Aug 2020  路  23Comments  路  Source: microsoft/pylance-release

bug fixed in next version

Most helpful comment

After thinking about this further, I think it's reasonable for Pyright to assume that if a function meets all of the criteria below that it should be considered an abstract method for purposes of type inference:

  1. Is a method (as opposed to a function declared outside of a class)
  2. Has no return statements
  3. Has no yield statements
  4. Contains one or more raise statements
  5. All raise statements raise NotImplementedError exception types

I've changed Pyright to infer the return type of such methods as "Unknown" rather than "NoReturn". This should address most of the cases that have been cited.

This change will be in the next version of Pyright and Pylance.

All 23 comments

We don't have any way to disable that, no. Can you provide some examples of how this feature is unreliable?

For auto-imports, see: https://github.com/microsoft/pylance-release/issues/64#issuecomment-669604900

Thanks for the reply! Unfortunately, I cannot provide details, since I'm working on a proprietary code. I'm pretty sure "code is unreachable" warning is generated due to how the code is structured, and, unfortunately, pylance analyzer doesn't dig deep enough.

Here is one example I'm hitting. We have a base class with a method that's expected to be overridden by children, and the base class also has another method that calls the (presumably overridden) base method. Herein lies the issue: PyLance seems to see our NotImplementedError on the base/non-overridden version of the method and doesn't realize that what we're referencing is in fact the overridden version. See the comment in function() for where I would expect things to properly work -- or to be able to disable the check so I don't have to see the greyed out code.

class MyBaseClass:
    @classmethod
    def get(cls):
         raise NotImplementedError

    @classmethod
    def get2(cls):
        blat = cls.get()
        # First hint: Code below here is "unreachable"
        # ... technically true out of context, but ...
        return blat

class MyClass(MyBaseClass):
    @classmethod
    def get(cls):
        return None

def function():
    r = MyClass.get2()
    # Anything from here down is "unreachable"
    return r

Thanks for providing the example. This is the most common cause of code being marked as unreachable. In your example, the get method in the MyBaseClass has no return type annotation, so Pylance is forced to infer its type based on code analysis. The method always raises an exception, and it's not properly marked as abstract (using an @abstractmethod decorator). So Pylance concludes that the return type is NoReturn.

There are two ways to work around this issue:

  1. Add a return type annotation to the get method so Pylance doesn't need to infer the return type.
  2. Mark MyBaseClass as abstract by deriving from abc.ABC and marking get as abstract using the @abc.abstractmethod decorator.

Thanks for a constructive reply!

We have a lot of similar to @amachanic 's situations, and it would be a pain to rework everything to accommodate PyLance reachability analyzer. I'd definitely prefer to just disable it for these kind of projects. I'm sure a lot of people would welcome an additional PyLance option to control this setting. :)

The reachability analyzer doesn't ever produce errors or warnings, so the worst thing that happens if you decide not to change your code is that you will see some text grayed out in your editor.

Thanks @erictraut. I went with the return annotation and it did the trick. Probably should have had one on there anyway.

@yury-tokpanov, it took me less than five minutes to change a few impacted base classes, and it's a nearly zero-risk modification. I highly recommend giving it a shot if the gray text bothers you.

@erictraut Sometimes not just some text, but all the text, if Pylance didn't correctly analyze beginning portion of the code.

@amachanic There are too many cases in my project, and I don't feel saying "adding support for Pylance" is a good justification to other people working on it.

Happens when you are using

if typing.TYPE_CHECKING:
    annotation = str
# below code is marked as "unreachable"
else:
    annotation = ... # some function, these are used for validating data in pydantic as 

I am forced to do like this as MyPy throws invalid annotation

Yes, this is as intended. Pylance is showing you the code that is unreachable from the perspective of its code analysis. The "else" clause in your example above should appear as gray. This gives you a visual clue that it is not being analyzed, and errors within this block will not be reported because it's conditionalized on TYPE_CHECKING.

Out of curiosity, why do you need to conditionalize this code? It should be fine to execute the statement annotation = str at runtime.

Out of curiosity, why do you need to conditionalize this code? It should be fine to execute the statement annotation = str at runtime.

Pydantic uses annotations for validating data, and if you want to use custom validation for the field without making type-checkers screaming ;)

If the library you are using doesn't use type hints then you are reduced to 2 options:

  1. use type hints in your code and deal with the greyed out text
  2. don't use the type hints in your code when referencing the library code

Seems a bit odd to me that Pylance would make an opinionated decision on this sort of thing.
Interested to know how Typescript handles this situation.

EDIT: There is actually a 3rd option

  1. try/except around the calling code

Library code doesn't need to contain type annotations to avoid this condition. If it makes proper use of @abstractmethod decorators, that will suffice. There are advantages to using both type annotations and @abstractmethod decorators, so I would encourage library authors to use both. But one or the other is sufficient in this case. Please submit bug reports or PRs to fix these issues in your favorite libraries. The entire Python community will benefit from such improvements.

It is important for Pylance to infer when a method or function is a "NoReturn" type. Failure to do this will result in false-positive errors because it will analyze code that is not meant to be executed. It's a tradeoff between false-positive errors (something that we try hard to avoid) versus grayed-out text when using a library that is missing annotations and proper use of @abstractmethod.

@erictraut I understand the motives, but it's infeasible to do what you suggest in all of the Python code out there. Why not just give a user an option whether to show results of reachability analysis in UI or not (meaning disabling graying out)? Otherwise, Pylance is great, it's fast and gives useful suggestions, but having sometimes significant portion of the code greyed out is annoying at least.

As I mentioned, disabling reachability analysis will produce more problems than it solves.

One potential solution is to provide a setting that allows you to disable the _reporting of unreachable code_ (i.e. don't show any gray text), but you can already effectively do that by adjusting your theme settings if the gray text really bothers you.

That was an really clear explanation :)

It seems that the main issue is that NotImplementedError is a visual marker to developers that some class will implement the method but Pylance only considers @abstractmethod to be the marker.
Fair enough, they can have different semantic meanings, the just usually don't, which makes the grey text somewhat confusing to the developer.

If there was a setting, it would be to make NotImplementedError work the same as @abstractmethod.
But I don't think it's worth the extra maintainence burden.

After thinking about this further, I think it's reasonable for Pyright to assume that if a function meets all of the criteria below that it should be considered an abstract method for purposes of type inference:

  1. Is a method (as opposed to a function declared outside of a class)
  2. Has no return statements
  3. Has no yield statements
  4. Contains one or more raise statements
  5. All raise statements raise NotImplementedError exception types

I've changed Pyright to infer the return type of such methods as "Unknown" rather than "NoReturn". This should address most of the cases that have been cited.

This change will be in the next version of Pyright and Pylance.

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

Another example is when using contextlib.suppress:

with suppress(AttributeError):
    return something.a 
return something_else

pylance will not understand that the suppress context manager is basically doing a try/catch pass and mark return something_else as unreachable.

After thinking about this further, I think it's reasonable for Pyright to assume that if a function meets all of the criteria below that it should be considered an abstract method for purposes of type inference:

1. Is a method (as opposed to a function declared outside of a class)

2. Has no return statements

3. Has no yield statements

4. Contains one or more raise statements

5. All raise statements raise NotImplementedError exception types

I've changed Pyright to infer the return type of such methods as "Unknown" rather than "NoReturn". This should address most of the cases that have been cited.

This change will be in the next version of Pyright and Pylance.

Not sure if this is by design, or a bug, but this doesn't work for methods marked as @classmethod.

Example:
```python
class Test(object):
@classmethod
def do_something(cls):
val = cls.get_value()
print(val)

@classmethod
def get_value(cls):
    raise NotImplementedError

`` print(val)` is still grayed out, if decorator is removed, then it works fine.

I'm able to repro the problem using the above code snippet, but it only happens with Pylance (not with Pyright), and it seems to happen intermittently. In other words, I can make it go away by making a small edit in the file.

These symptoms indicate there's a bug related to type evaluation ordering. Pylance's semantic token highlighting feature tends to cause the types of identifiers to be evaluated in a different order than they otherwise would. In theory, the order of evaluation shouldn't affect the type evaluation results, but we have had bugs in the past where this is not the case. I'll need to look into the problem more deeply.

As I suspected, there was a bug that resulted in different results depending on the order of evaluation. The fix will be in the next release. Thanks for reporting the problem.

This issue has been fixed in version 2021.2.1, which we've just released. You can find the changelog here: https://github.com/microsoft/pylance-release/blob/main/CHANGELOG.md#202121-10-february-2021

Was this page helpful?
0 / 5 - 0 ratings