~technomancy/fennel#214: 
macrodebug should expand macros w/ scope at invocation point

Macrodebug is currently implemented as

(fn macrodebug* [form return?]
  "Print the resulting form after performing macroexpansion.
With a second argument, returns expanded form as a string instead of printing."
  (let [handle (if return? `do `print)]
    `(,handle ,(view (macroexpand form _SCOPE)))))

The _SCOPE variable it's passing to macroexpand is the scope in which the macro itself was defined, but it should be (get-scope), the scope at the point the macro is invoked.

This likely hasn't come up because most of what depends on correct scope in a macro relates to when it comes time to compile to lua - gensym for hygiene and the like. Still, it should be fixed.

Status
REPORTED
Submitter
~jaawerth
Assigned to
No-one
Submitted
11 months ago
Updated
11 months ago
Labels
No labels applied.

~jaawerth 11 months ago

Tests on a fix suggest I may be misremembering the difference between _SCOPE and (get-scope).. I remember (get-scope) being created distinctly for a reason, though. Will look into this further before I close it.

~jaawerth 11 months ago

For clarity, it's this change to the tests that seems to still pass without changing macrodebug to use get-scope:

diff --git a/test/macro.fnl b/test/macro.fnl
index 983c3d5..514d9f3 100644
--- a/test/macro.fnl
+++ b/test/macro.fnl
@@ -127,9 +127,13 @@
   (let [eval-normalize #(-> (pick-values 1 (fennel.eval $1 $2))
                             (: :gsub "table: 0x[0-9a-f]+" "#<TABLE>")
                             (: :gsub "\n%s*" " "))
-        code "(macrodebug (when (= 1 1) (let [x :X] {: x})) true)"
-        expected "(if (= 1 1) (do (let [x \"X\"] {:x x})))"]
-    (t.= (eval-normalize code) expected)))
+        cases [["(macrodebug (when (= 1 1) (let [x :X] {: x})) true)"
+                "(if (= 1 1) (do (let [x \"X\"] {:x x})))"]
+               ["(macro reflect-parent [] (next (. (get-scope) :parent :parent :macros)))
+                 (do (do (macrodebug (reflect-parent) true)))"
+                "\"reflect-parent\""]]]
+    (each [_ [code expected] (ipairs cases)]
+      (t.= expected (eval-normalize code)))))
 
 ;; many of these are copied wholesale from test-match, pending implementation,
 ;; if match is implemented via case then it should be reasonable to remove much

~technomancy 11 months ago

Yeah... this looks wrong, but as far as I can tell ... it works right?

(let [x 9]
  (macrodebug (match [9] [x] :hi))
  (macrodebug (match [9] [y] :hi)))

; ->

(let [(_1_) [9]] (if (and (= (_G.type _1_) "table") (= (. _1_ 1) x)) (let {} "hi")))
(let [(_2_) [9]] (if (and (= (_G.type _2_) "table") (not= nil (. _2_ 1))) (let [y (. _2_ 1)] "hi")))

It can tell that x is in scope, but y isn't.

Register here or Log in to comment, or comment via email.