wemake-python-styleguide: Forbid to use variables from conditional blocks

Rule request

Thesis

This code does not raise a single violation that x might not be defined:

try:
    function_that_raises()
    x = 1
except:
    ...
print(x)

One more example:

if False:
    x = 1
print(x)

With for:

for i in []:
    x = 0
print(x)

With while:

while False:
    x = 0
print(x)

Reasoning

We need to forbid to use variables that are only defined in try, if, for body. It might not be defined at all. mypy also does not complain about it.

Cases like:

x = 0

try:
    function_that_raises()
    x = 1
except:
    ...
print(x)

are fine.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Comments: 24 (21 by maintainers)

Most upvoted comments

@sobolevn I’ll be able to continue soon. That’s what I have to do:

  • implement the collection of assigned variables in lambda and comprehension scopes. It doesn’t collect them now, so access to the variables inside the corresponding expression is considered unsafe
  • decompose the SafeVars class to reduce the complexity of the code
  • edit the code to pass linters

I have a few questions, but I’ll ask them when I get back. Sorry for long pauses.

I am almost done with SafeVars class that collects variable assignments for a given node and reduces them to a set of safe variables. But there are cases I still haven’t covered.

The next step will be implementation of visitor with this logic:

  1. Collect safe vars
    • Collect local safe vars to check vars from local scopes
    • Collect safe vars for module, classes and functions to check vars from outer scopes
  2. Check that the used variables in safe vars from 1)

e.g.

if ...                      # 1.1) collect outer safe vars: {outer_x, outer_fn}
    outer_x = ...           
else ...:                   
    outer_x = ...           

def outer_fn(outer_z):      # 1.2) collect outer safe vars: {outer_z, outer_y, local_fn}
    outer_y = ...           
    
    def local_fn(z):        # 1.3) collect outer safe vars: {z, y, somevar}
        y = ...              
        if ...:                
            if ...:         
                w = ...     
            else:           
                w = ...      

            # 1.4) collect local safe vars for each variable use below: {w, y, z}

            print(y)        # 2) local safe var from 1.4
            print(w)        # 2) local safe var from 1.4
            print(outer_x)  # 2) outer safe var from 1.1
            print(outer_z)  # 2) outer safe var from 1.2
            print(outer_y)  # 2) outer safe var from 1.2
            print(unknown)  # 2) unsafe
            print(somevar)  # 2) unsafe because var not in local vars and not in {outer vars} - {`local_fn` vars}
        else:
            ...
        somevar = ...

@sobolevn Sorry, I had to pause due lack of time. Fortunately, I will be able to continue during this week.

I am working on prototype for the issue, and I already have implemented safe_vars collector for For, AyncFor, If, While, FunctionDef, AsyncFunctionDef, ClassDef, Module scopes. Progress can be found here

And


undefined-loop-variable (W0631):
--
  | Using possibly undefined loop variable %r Used when 
an loop variable (i.e. defined by a for loop or a list 
comprehension or a generator expression) is used outside the loop.