Repository: hylang/hy

Issue:
Macros can't use previous definitions from the same module
The following code works when pasted into a REPL, but not in a module:

```
(defn foo [x y]
  (quasiquote (+ (unquote x) (unquote y))))

(defmacro bar [x y]
  (foo x y))

(bar 1 2)
```

The cause is that the definition of `foo` hasn't been compiled yet when `bar` is executed. There is a special hack in `compile_macro` that compiles and executes macro definitions immediately to make them available, but it doesn't handle the code that a macro definition could depend on.

The only solution I see is to process Hy modules as a sequence of top-level forms, each of which is compiled and evaluated before the next one is looked at. Does anyone see a problem with that, other than that it makes things a bit more complicated?



Discussion:
This one is going to be a pain in the ass

After much thought, I figured that we should consider Hy a Lisp compiler and thus distinguish between compile time (Hy to Python-AST) and load/eval time (import of the Python module), just like Scheme does. And then the solution is obvious: introduce `eval-when-compile` for definitions that are accessible from macros. It's perhaps less elegant than the solutions I had envisaged earlier, but also clearer (to those who understand the problem) and a lot simpler to implement. Plus there are respected precedences in the Lisp universe.

That sounds perfectly reasonable to me. @jd / @olasd ? I'll get on reviewing this

I'm +1 on keeping the dichotomy between compile and eval. Having compile-time evaluation by default could be very confusing, and `eval-when-compile` looks quite straightforward.


Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -50,6 +50,16 @@
 from collections import defaultdict
 
 
+_compile_time_ns = {}
+
+def compile_time_ns(module_name):
+    ns = _compile_time_ns.get(module_name)
+    if ns is None:
+        ns = {'hy': hy, '__name__': module_name}
+        _compile_time_ns[module_name] = ns
+    return ns
+
+
 class HyCompileError(HyError):
     def __init__(self, exception, traceback=None):
         self.exception = exception
@@ -1645,7 +1655,7 @@ def compile_macro(self, expression):
         # We must provide __name__ in the namespace to make the Python
         # compiler set the __module__ attribute of the macro function.
         hy.importer.hy_eval(new_expression,
-                            {'hy': hy, '__name__': self.module_name},
+                            compile_time_ns(self.module_name),
                             self.module_name)
 
         # We really want to have a `hy` import to get hy.macro in
@@ -1654,6 +1664,23 @@ def compile_macro(self, expression):
 
         return ret
 
+    @builds("eval_and_compile")
+    def compile_eval_and_compile(self, expression):
+        expression[0] = HySymbol("progn")
+        hy.importer.hy_eval(expression,
+                            compile_time_ns(self.module_name),
+                            self.module_name)
+        expression.pop(0)
+        return self._compile_branch(expression)
+
+    @builds("eval_when_compile")
+    def compile_eval_when_compile(self, expression):
+        expression[0] = HySymbol("progn")
+        hy.importer.hy_eval(expression,
+                            compile_time_ns(self.module_name),
+                            self.module_name)
+        return Result()
+
     @builds(HyInteger)
     def compile_integer(self, number):
         return ast.Num(n=int(number),
diff --git a/hy/macros.py b/hy/macros.py
--- a/hy/macros.py
+++ b/hy/macros.py
@@ -20,7 +20,13 @@
 
 from hy.models.expression import HyExpression
 from hy.models.string import HyString
+from hy.models.symbol import HySymbol
 from hy.models.list import HyList
+from hy.models.integer import HyInteger
+from hy.models.float import HyFloat
+from hy.models.complex import HyComplex
+from hy.models.dict import HyDict
+from hy.util import str_type
 
 from collections import defaultdict
 
@@ -44,6 +50,21 @@ def require(source_module_name, target_module_name):
         refs[name] = macro
 
 
+def _wrap_value(x):
+    wrapper = _wrappers.get(type(x))
+    if wrapper is None:
+        return x
+    else:
+        return wrapper(x)
+
+_wrappers = {int: HyInteger,
+             bool: lambda x: HySymbol("True") if x else HySymbol("False"),
+             float: HyFloat,
+             complex: HyComplex,
+             str_type: HyString,
+             dict: lambda d: HyDict(_wrap_value(x) for x in sum(d.items(), ())),
+             list: lambda l: HyList(_wrap_value(x) for x in l)}
+
 def process(tree, module_name):
     if isinstance(tree, HyExpression):
         fn = tree[0]
@@ -57,7 +78,7 @@ def process(tree, module_name):
             if m is None:
                 m = _hy_macros[None].get(fn)
             if m is not None:
-                obj = m(*ntree[1:])
+                obj = _wrap_value(m(*ntree[1:]))
                 obj.replace(tree)
                 return obj
 


Test patch:
diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy
--- a/tests/native_tests/native_macros.hy
+++ b/tests/native_tests/native_macros.hy
@@ -8,3 +8,56 @@
   (setv x [])
   (rev (.append x 1) (.append x 2) (.append x 3))
   (assert (= x [3 2 1])))
+
+; Macros returning constants
+
+(defmacro an-int [] 42)
+(assert (= (an-int) 42))
+
+(defmacro a-true [] True)
+(assert (= (a-true) True))
+(defmacro a-false [] False)
+(assert (= (a-false) False))
+
+(defmacro a-float [] 42.)
+(assert (= (a-float) 42.))
+
+(defmacro a-complex [] 42j)
+(assert (= (a-complex) 42j))
+
+(defmacro a-string [] "foo")
+(assert (= (a-string) "foo"))
+
+(defmacro a-list [] [1 2])
+(assert (= (a-list) [1 2]))
+
+(defmacro a-dict [] {1 2})
+(assert (= (a-dict) {1 2}))
+
+; A macro calling a previously defined function
+(eval-when-compile
+ (defn foo [x y]
+   (quasiquote (+ (unquote x) (unquote y)))))
+
+(defmacro bar [x y]
+  (foo x y))
+
+(defn test-fn-calling-macro []
+  "NATIVE: test macro calling a plain function"
+  (assert (= 3 (bar 1 2))))
+
+; Macro that checks a variable defined at compile or load time
+(setv phase "load")
+(eval-when-compile
+ (setv phase "compile"))
+(defmacro phase-when-compiling [] phase)
+(assert (= phase "load"))
+(assert (= (phase-when-compiling) "compile"))
+
+(setv initialized False)
+(eval-and-compile
+ (setv initialized True))
+(defmacro test-initialized [] initialized)
+(assert initialized)
+(assert (test-initialized))
+