JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.73k stars 5.49k forks source link

`for x in X` syntax is not available on `Expr` #52560

Open hyrodium opened 11 months ago

hyrodium commented 11 months ago

MWE

The following functions foo and bar should be identical, but bar throws an error.

julia> @generated function foo()
           Expr(
               :block,
               :(i = 0),
               Expr(
                   :for,
                   :(j = 1:5),
                   :(i += j),
               ),
               :(return i)
           )
       end
foo (generic function with 1 method)

julia> foo()
15

julia> @generated function bar()
           Expr(
               :block,
               :(i = 0),
               Expr(
                   :for,
                   :(j in 1:5),
                   :(i += j),
               ),
               :(return i)
           )
       end
bar (generic function with 1 method)

julia> bar()
ERROR: UndefVarError: `j` not defined
[...]

julia> j = 42
42

julia> bar()  # The returned value `i` was calculated by referring to the value `Main.j`. (`i += j`)
42

Environment

julia> versioninfo()
Julia Version 1.9.4
Commit 8e5136fa297 (2023-11-14 08:46 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 16 × AMD Ryzen 7 2700X Eight-Core Processor
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, znver1)
  Threads: 1 on 16 virtual cores

I confirmed the above error was also reproduced on v1.6.7 and v1.10.0-rc2.

Note

The for x in X syntax seems fine not in @generated macro.

julia> Expr(
           :block,
           :(i = 0),
           Expr(
               :for,
               :(j in 1:5),
               :(i += j),
           ),
           :(return i)
       )
quote
    i = 0
    for j in 1:5
        i += j
    end
    return i
end
ararslan commented 10 months ago

This behavior is expected since the use of in there is not a valid for expression, even though it prints like one. Amending the expression slightly to make it evaluable at the top level, consider the following:

julia> ex1 = Expr(:block, Expr(:let, :(i = 0), Expr(:block, Expr(:for, :(j = 1:5), :(i += j)), :i)))
quote
    let i = 0
        for j = 1:5
            i += j
        end
        i
    end
end

julia> ex2 = Expr(:block, Expr(:let, :(i = 0), Expr(:block, Expr(:for, :(j in 1:5), :(i += j)), :i)))
quote
    let i = 0
        for j in 1:5
            i += j
        end
        i
    end
end

julia> ex3 = quote
           let i = 0
               for j in 1:5
                   i += j
               end
               i
           end
       end;

julia> Base.remove_linenums!(ex3)
quote
    let i = 0
        for j = 1:5
            i += j
        end
        i
    end
end

Note in particular how ex3 is printed: the loop specification uses = even though ex3 was defined using in. This is further exemplified by

julia> Meta.parse("for j in 1:5 end")
:(for j = 1:5
      #= none:1 =#
      #= none:1 =#
  end)

And if we go to evaluate the expressions, we get

julia> eval(ex1)
15

julia> eval(ex3)
15

julia> eval(ex2)
ERROR: UndefVarError: `j` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ ./none:1
 [2] eval
   @ Core ./boot.jl:428 [inlined]
 [3] eval(x::Expr)
   @ Main ./sysimg.jl:48
 [4] top-level scope
   @ REPL[32]:1

This is because ex2 is defined incorrectly. Using in is syntactic sugar that the parser turns into the correct expression, but when constructing an Expr manually, you're bypassing that correction made by the parser.