Repository: hylang/hy

Issue:
Hy on Python3.4: setting None to values is possible
The following code seems to work in Hy with Python 3.4

``` clj
=> (nil? None)
True
=> (setv None 1)
=> (nil? None)
False
```

BTW `(setv None 1)` seems to work on other pythons as well though the last statement ie, (nil? None) still evaluates to true as expected.

Problem seems to arise because Python 3.4 compiles None (and True, False) as an `ast.NameConstant` instead of an `ast.Name`  

EDIT: re-worded the title of issue



Discussion:

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -47,6 +47,7 @@
 import codecs
 import ast
 import sys
+import keyword
 
 from collections import defaultdict
 
@@ -73,6 +74,21 @@ def load_stdlib():
             _stdlib[e] = module
 
 
+# True, False and None included here since they
+# are assignable in Python 2.* but become
+# keywords in Python 3.*
+def _is_hy_builtin(name, module_name):
+    extras = ['True', 'False', 'None',
+              'true', 'false', 'nil', 'null']
+    if name in extras or keyword.iskeyword(name):
+        return True
+    # for non-Hy modules, check for pre-existing name in
+    # _compile_table
+    if not module_name.startswith("hy."):
+        return name in _compile_table
+    return False
+
+
 _compile_table = {}
 
 
@@ -1751,6 +1767,12 @@ def compile_def_expression(self, expression):
 
     def _compile_assign(self, name, result,
                         start_line, start_column):
+
+        str_name = "%s" % name
+        if _is_hy_builtin(str_name, self.module_name):
+            raise HyTypeError(name,
+                              "Can't assign to a builtin: `%s'" % str_name)
+
         result = self.compile(result)
         ld_name = self.compile(name)
 


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
@@ -28,6 +28,28 @@
   (setv (get foo 0) 12)
   (assert (= (get foo 0) 12)))
 
+(defn test-setv-builtin []
+  "NATIVE: test that setv doesn't work on builtins"
+  (try (eval '(setv False 1))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(setv True 0))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(setv None 1))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(setv false 1))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(setv true 0))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(setv nil 1))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(setv null 1))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(defn defclass [] (print "hello")))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(defn get [] (print "hello")))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
+  (try (eval '(defn lambda [] (print "hello")))
+       (catch [e [TypeError]] (assert (in "Can't assign to a builtin" (str e))))))
 
 (defn test-for-loop []
   "NATIVE: test for loops"