julia icon indicating copy to clipboard operation
julia copied to clipboard

Module inside a `begin ... end` leads to false-positive parsing error

Open vchuravy opened this issue 1 year ago • 3 comments

module Test
    begin
        module Inner
        end
    end
end
julia> include("Test.jl")
ERROR: LoadError: syntax: "module" expression not at top level
Stacktrace:
 [1] top-level scope
   @ ~/Test.jl:2
 [2] include(fname::String)
   @ Base.MainInclude ./client.jl:444
 [3] top-level scope
   @ REPL[1]:1
in expression starting at /home/vchuravy/Test.jl:1

Two issues:

  1. The line-number is the outer module not the inner module
  2. I believe that begin...end should be neutral here and we are still in top-level

If we move the inner module into it's own file. Inner.jl and include it instead this works without issue, Encountered while parsing and re-evaluating some test-code

cc: @gbaraldi

vchuravy avatar Apr 15 '24 14:04 vchuravy

There's also the following lowering error:

julia> :(begin
           module Inner
           end
       end)
quote
    #= REPL[12]:2 =#
    module Inner
    #= REPL[12]:2 =#
    #= REPL[12]:2 =#
    end
end

julia> Meta.lower(Main, :(begin
           module Inner
           end
       end))
:($(Expr(:error, "\"module\" expression not at top level")))

We'd run into this lowering error if the code above parsed. Making the desired expression by hand, we have:

julia> :(module Test
           begin
               $(Expr(:module, true, :Inner, Expr(:block)))
           end
       end)
:(module Test
  #= REPL[22]:1 =#
  #= REPL[22]:2 =#
  begin
      #= REPL[22]:3 =#
      module Inner
      end
  end
  end)

julia> eval(:(module Test
           begin
               $(Expr(:module, true, :Inner, Expr(:block)))
           end
       end))
WARNING: replacing module Test.
ERROR: syntax: "module" expression not at top level
Stacktrace:
 [1] top-level scope
   @ REPL[23]:2
 [2] eval
   @ ./boot.jl:432 [inlined]
 [3] eval(x::Expr)
   @ Main ./sysimg.jl:48
 [4] top-level scope
   @ REPL[23]:1

I think these are bugs in the existing system? module should be able to be nested inside top level begin ... end blocks because every statement inside such a block is evaluated in the latest world, due to being inside a top level thunk.

But that "everything in the latest world" notion of top-level isn't enough - modules shouldn't be able to exist inside nontrivial scopes like let blocks. Otherwise users might expect the following to work

let
   x = 100
   module B
       import ..x
       y = x
   end
end

by analogy to the case of nested modules

module A
   x = 100
   module B
       import ..x
       y = x
   end
end

Or even this

let
   x = 100
   module B
       y = x
   end
end

Perhaps there's no fundamental reason this couldn't work, but it changes/generalizes several nontrivial things about the way lowering and evaluation of modules currently works. And is probably a bunch of work for fairly marginal utility.

c42f avatar Aug 26 '24 02:08 c42f

But that "everything in the latest world" notion of top-level isn't enough - modules shouldn't be able to exist inside nontrivial scopes like let blocks.

Actually we've got at least one other construct which is allowed at top level - including inside begin ... end blocks - but not inside local scopes

julia> let
           macro foo()
               10
           end
      end
ERROR: syntax: macro definition not allowed inside a local scope
Stacktrace:
 [1] top-level scope
   @ REPL[39]:1

julia> begin
           macro foo()
               10
           end
       end
@foo (macro with 1 method)

Presumably the rules for modules would be the same.

c42f avatar Aug 26 '24 02:08 c42f

I implemented the "modules are only allowed at global toplevel" rule in JuliaLowering.jl, and used the same mechanism for macros.

https://github.com/c42f/JuliaLowering.jl/commit/658a7c9d022c8ff296ef18f7a28146946c158838

c42f avatar Aug 26 '24 04:08 c42f