Olical / aniseed

Neovim configuration and plugins in Fennel (Lisp compiled to Lua)
https://discord.gg/wXAMr8F
The Unlicense
606 stars 28 forks source link

require(module) does not always return module #90

Closed harrygallagher4 closed 2 years ago

harrygallagher4 commented 2 years ago

Hi, I've noticed this bug a couple times but I finally managed to figure out how to reliably recreate it. The way aniseed uses package.loaded['module'] to set the value of require('module') seems to be unreliable. In cases where the compiled lua file has an actual return statement at the end the returned value overrides the value set in package.loaded.

Here's a small example

; ---------------
; fnl/modtest.fnl
; ---------------
(module modtest
  {require {a aniseed.core}})

(def x 1337)

(defn- do-something [z] z)

(do-something {:x 420})
-- ---------------
-- lua/modtest.lua
-- ---------------
local _2afile_2a = "fnl/modtest.fnl"
local _2amodule_name_2a = "modtest"
local _2amodule_2a
do
  package.loaded[_2amodule_name_2a] = {}
  _2amodule_2a = package.loaded[_2amodule_name_2a]
end
local _2amodule_locals_2a
do
  _2amodule_2a["aniseed/locals"] = {}
  _2amodule_locals_2a = (_2amodule_2a)["aniseed/locals"]
end
local a = require("aniseed.core")
do end (_2amodule_locals_2a)["a"] = a
local x = 1337
_2amodule_2a["x"] = x
local function do_something(z)
  return z
end
_2amodule_locals_2a["do-something"] = do_something
return do_something({x = 420})
require('modtest').x --> 420 !! (expected value is 1337)
require('modtest') --> { x = 420 }

This can be fixed by using an aniseed def(n)(-) at the end of the module or just putting *module* after everything.

(module modtest
  {require {a aniseed.core}})

(defn- do-something [z] z)

(do-something {:x 420})

(def x 1337)
require('modtest').x --> 1337 :)

This is most likely to cause issues when the last call in the module is a side-effecting function that happens to have a return value

Olical commented 2 years ago

I like the idea of putting a *module* at the end of the file 🤔 I'll have a look at that! Sorry for the issue! I've run into it once I think then I just put a nil at the end of the file which will also fix it. This bug is basically if your module ends with a table, if you put nil or anything else it should be fine.

harrygallagher4 commented 2 years ago

Wrapping the entire file in this macro (after the (require-macros ...) that the compiler inserts) could be a solution. It should allow users to compile files that aren't aniseed modules while just appending *module* to everything would break that. I had a little trouble modifying the compiler but I'll see if I can get something working and open a PR. The macro implementation might also need a few safety checks, let me know what you think.

(macro wrap-module [...]
  (let [body# [...]
        last-expr# (table.remove body#)]
    (table.insert body#
      `(let [original-return# (do ,last-expr#)]
         (or ,(sym "*module*") original-return#)))
    `(do ,(unpack body#))))