Repository: hylang/hy

Issue:
Special forms can be string literals?
I noticed a quirk in the compiler, was this intentional?
```
=> (defmacro foo [x] `(print "foo" ~x))
import hy
from hy import HyExpression, HyString, HySymbol
hy.macros.macro('foo')((lambda x: HyExpression(((([] + [HySymbol('print')]) + [HyString('foo')]) + [x]))))
<function <lambda> at 0x00000232735AA6A8>
=> ("foo" 2)
print('foo', 2)
foo 2
=> ("+" 1 1)
(1 + 1)
2
```
It seems that special forms and macros can be named by string literals instead of symbols. This doesn't seem to work for callables though.
```
=> ("print" 2)
'print'(2)
Traceback (most recent call last):
  File "c:\users\me\documents\github\hy\hy\importer.py", line 177, in hy_eval
    return eval(ast_compile(expr, "<eval>", "eval"), namespace)
  File "<eval>", line 1, in <module>
TypeError: 'str' object is not callable
```


Discussion:
Using a symbol in these positions is simply one of the syntactic conventions that the compiler doesn't enforce, like using square brackets instead of parentheses around the second argument of `for`. If you want a stricter compiler, you should probably make an umbrella issue for that. Fixing this would probably entail more declarative machinery for the compiler that would produce better error messages in situations such as #1277.

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -2006,7 +2006,7 @@ def compile_expression(self, expression):
         if isinstance(fn, HyKeyword):
             return self._compile_keyword_call(expression)
 
-        if isinstance(fn, HyString):
+        if isinstance(fn, HySymbol):
             ret = self.compile_atom(fn, expression)
             if ret:
                 return ret
@@ -2242,7 +2242,7 @@ def compile_function_def(self, expression):
                 args[i] = var = HySymbol(self.get_anon_var())
                 expression = HyExpression([
                     HyExpression([
-                        HyString("setv"), arg, var
+                        HySymbol("setv"), arg, var
                     ])]
                 ) + expression
                 expression = expression.replace(arg[0])
diff --git a/hy/macros.py b/hy/macros.py
--- a/hy/macros.py
+++ b/hy/macros.py
@@ -3,7 +3,7 @@
 # license. See the LICENSE.
 
 from inspect import getargspec, formatargspec
-from hy.models import replace_hy_obj, HyExpression, HyString
+from hy.models import replace_hy_obj, HyExpression, HySymbol
 
 from hy.errors import HyTypeError, HyMacroExpansionError
 
@@ -179,7 +179,7 @@ def macroexpand_1(tree, compiler):
 
         opts = {}
 
-        if isinstance(fn, HyString):
+        if isinstance(fn, HySymbol):
             m = _hy_macros[compiler.module_name].get(fn)
             if m is None:
                 m = _hy_macros[None].get(fn)


Test patch:
diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy
--- a/tests/native_tests/language.hy
+++ b/tests/native_tests/language.hy
@@ -169,6 +169,14 @@
       (assert (= e.message "Can't assign or delete a HyInteger")))))
 
 
+(defn test-no-str-as-sym []
+  "Don't treat strings as symbols in the calling position"
+  (with [(pytest.raises TypeError)] ("setv" True 3))  ; A special form
+  (with [(pytest.raises TypeError)] ("abs" -2))       ; A function
+  (with [(pytest.raises TypeError)] ("when" 1 2))     ; A macro
+  None)  ; Avoid https://github.com/hylang/hy/issues/1320
+
+
 (defn test-fn-corner-cases []
   "NATIVE: tests that fn/defn handles corner cases gracefully"
   (try (eval '(fn "foo"))