Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UndefVarError shadowing function #49917

Closed
apaz-cli opened this issue May 22, 2023 · 5 comments · Fixed by #51979
Closed

UndefVarError shadowing function #49917

apaz-cli opened this issue May 22, 2023 · 5 comments · Fixed by #51979

Comments

@apaz-cli
Copy link
Member

The bug:

function f(s, price)
    @show f
    f = s * price
end

f(2, 3)
ERROR: LoadError: UndefVarError: `f` not defined
Stacktrace:
 [1] macro expansion
   @ Main ./show.jl:1153 [inlined]
 [2] f(s::Int64, price::Int64)
   @ Main ~/git/julia/bug.jl:2
 [3] top-level scope
   @ ~/git/julia/bug.jl:5
in expression starting at /home/apaz/git/julia/bug.jl:5

User's versioninfo():

julia> versioninfo()
Julia Version 1.9.0
Commit 8e630552924 (2023-05-07 11:25 UTC)
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 24 × AMD Ryzen 9 7900X 12-Core Processor
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, znver3)
  Threads: 69 on 24 virtual cores

I also replicated this on my own machine on master.

julia> Meta.parse("function f(s, price)\n  @show f\n  f = s * price\nend")
:(function f(s, price)
      #= none:1 =#
      #= none:2 =#
      #= none:2 =# @show f
      #= none:3 =#
      f = s * price
  end)
julia> @code_lowered f(2, 3)
CodeInfo(
1 ─      value = f
│   %2 = f
│   %3 = Base.repr(%2)
│        Base.println("f = ", %3)
│        value
│   %6 = s * price
│        f = %6
└──      return %6
)
julia> @code_typed f(2, 3)
CodeInfo(
1 ─     $(Expr(:throw_undef_if_not, :f, false))::Any
└──     unreachable
) => Union{}
@apaz-cli
Copy link
Member Author

Pinging @Ellipse0934

@martinholters
Copy link
Member

Seems to work as intended? f is a local variable and you try to @show it before assigning a value. What did you expect to happen?

@apaz-cli
Copy link
Member Author

The expectation is that shadowing works the same way it does in other languages, or that it provides an error message that helps you figure out what the problem is.

@elextr
Copy link

elextr commented May 23, 2023

It doesn't work the same.

As the documentation says "The scope of a variable cannot be an arbitrary set of source lines; instead, it will always line up with one of these blocks.", which means its usable within its scope block before it is declared, but only after it has been given a value, so:

# no s here
for i = 1:5
    if i > 1
        @show s
    end
    s = i
end

is fine.

Aside, I thought that this used to be clearer in the docs, but it seems to have been de-emphasised.

@andrewjradcliffe
Copy link
Contributor

function f(s, price)
    println(f + 1)
    f = s * price
end

Produces the same error, for reasons which are obvious.

(define (f s price)
    (newline)
    (display f)
    (newline)
    (define f (* s price))
    (newline) 
    (display f) 
    (newline))

;; produces (in MIT/GNU Scheme)
1 ]=> (f 2 3)

#[compound-procedure 12 f]

6
;Unspecified return value

But this makes sense given how bindings are resolved in Scheme -- f is found in the enclosing environment (the global environment, in this case).

function f(s, price)
    @show f
    s * price
end

Has the same behavior.

The semantics are little less obvious here since there is no set! (Scheme) equivalent in Julia, which employs = for both define and set! (excluding setfield!, setproperty!, which are not germane to the discussion at hand). I do not know the precise treatment Julia uses, but seemingly, Julia scans out internal definitions, then binds them according to the sequential order indicated by the lines in the source code -- that is, the environment is created with the variables set to '*unassigned* (or some equivalent), then values are bound sequentially; if an unbound variable is used, throw an error.

I suppose one could wish for a more clever name-shadowing.

Rust happily proceeds with the example.

Python returns what is arguably a more informative error message.

def f(s, price):
    print(f)
    f = s * price
    print(f)

# and we get
>>> f(2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: cannot access local variable 'f' where it is not associated with a value

@elextr
The usable-once-defined treatment is similar to one method of handling internal definitions. Consider the example:

Given that Julia's let is actually let*, it is preferable to use the following for clarity:

let u
    # u exists in environment, but is unassigned                    
    let v
        # v exists in environment, but is unassigned             
        let a = 2        
            let b = 3
                # now we assign values to u and v. If this were Scheme, we'd use `set!`  
                u = a + b
                v = a * b
            end          
        end              
    println(u + v) # prints 11               
    end
    # println(v) # this will error as v is now out of scope   
    println(u) # prints 5           
end                      

vtjnash added a commit that referenced this issue Oct 31, 2023
Record the 'scope' of the variable that was undefined (the Module, or a
descriptive word such as :local or :static_parameter). Add that scope to
the error message, and expand the hint suggestions added by the REPL to
include more specific advice on common mistakes:

  - forgetting to set an initial value
  - forgetting to import a global
  - creating a local of the same name as a global
  - not matching a static parameter in a signature subtype

Fixes #17062 (although more could probably be done to search for typos using REPL.string_distance and getting the method from stacktrace)
Fixes #18877
Fixes #25263
Fixes #35126
Fixes #39280
Fixes #41728
Fixes #48731
Fixes #49917
Fixes #50369
vtjnash added a commit that referenced this issue Nov 8, 2023
Record the 'scope' of the variable that was undefined (the Module, or a
descriptive word such as :local or :static_parameter). Add that scope to
the error message, and expand the hint suggestions added by the REPL to
include more specific advice on common mistakes:

  - forgetting to set an initial value
  - forgetting to import a global
  - creating a local of the same name as a global
  - not matching a static parameter in a signature subtype

Fixes #17062 (although more could probably be done to search for typos using REPL.string_distance and getting the method from stacktrace)
Fixes #18877
Fixes #25263
Fixes #35126
Fixes #39280
Fixes #41728
Fixes #48731
Fixes #49917
Fixes #50369
@vtjnash vtjnash closed this as completed in 449c7a2 Nov 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants