StaticLint.jl icon indicating copy to clipboard operation
StaticLint.jl copied to clipboard

New variable bindings are created for existing local variables

Open Neelo16 opened this issue 5 years ago • 0 comments

Currently, StaticLint handles any assignment as a new declaration. If we assign a different value to an existing variable, it is treated as a new variable with the same name, and any new references can only be traced back to this second assignment.

As an example, consider the following code excerpt:

function foo()
    a = 0
    for x in [1, 2, 3]
        a = x
    end
    a
end

If we run this code and call the function, we can see that it returns the value 3. This is expected, as it is described in Julia's documentation. When we assign to an existing local variable, as is the case here, the existing variable is assigned. As a result, all 3 references of the variable a should have the same Binding. As we will see, this is not currently the case with StaticLint.

I saved this function to a file named test.jl, loaded it, and called scopepass on it, as follows:

using StaticLint, CSTParser

function loadfile(path::AbstractString)
    server = StaticLint.FileServer();
    source = read(path, String)
    cst = CSTParser.parse(source, true)
    file = StaticLint.File(path, source, cst, nothing, server)
    StaticLint.setroot(file, file)
    StaticLint.setfile(server, path, file)
    StaticLint.scopepass(file)
    return file.cst
end

cst = loadfile("test.jl")
println(cst)

This outputs:

  1:96  FileH(  new scope)
  1:81   FunctionDef( Binding(foo:: (2 refs)) new scope)
  1:9     FUNCTION
 10:19    Call( )
 10:12     foo * 
 13:13     (
 14:19     )
 20:76    Block( )
 20:29     BinaryOpCall( )
 20:21      aBinding(a:: (2 refs)) * 
 22:23      OP: EQ
 24:29      INTEGER: 0
 30:74     For(  new scope)
 30:33      FOR
 34:56      BinaryOpCall( )
 34:35       xBinding(x(2 refs)) * 
 36:38       OP: IN
 39:56       Vect( )
 39:39        [
 40:40        INTEGER: 1
 41:42        ,
 43:43        INTEGER: 2
 44:45        ,
 46:46        INTEGER: 3
 47:56        ]
 57:66      Block( )
 57:66       BinaryOpCall( )
 57:58        aBinding(a(1 refs)) * 
 59:60        OP: EQ
 61:66        x * 
 67:74      END
 75:76     a * 
 77:81    END
 82:96   Call( )
 82:88    println * 
 89:89    (
 90:94    Call( )
 90:92     foo * 
 93:93     (
 94:94     )
 95:96    )

We can see that the a variable within the for loop creates a new bindings, which should not be the case. It should be a reference to the original variable, and added to the Binding's refs. This seems like a bug, unless I misunderstood something (which is not impossible, given that my usage of this package is based on experimentation).

Neelo16 avatar Sep 12 '20 21:09 Neelo16