Repository: hylang/hy

Issue:
Edge case with symbol contains special characters (/ . *, etc.) and import
I found an edge case.
Let's say, if we have a symbol `/.*`, it worked in REPL
```clojure
=> (setv /.* 42)
=> /.*
42
```
If it is in a module, then importing will fail if we name it directly
```bash
$ cat test.hy
(setv /.* "Hidden Files")
```
```clojure
=> (import [test [/.*]])
Traceback (most recent call last):
  File "/home/pyx/.virtualenvs/hy/lib/python3.6/site-packages/hy/importer.py", line 198, in hy_eval
    eval(ast_compile(_ast, "<eval_body>", "exec"), namespace)
  File "<eval_body>", line 1, in <module>
ImportError: cannot import name '/.*'
=> (import test)
=> test./.*
'Hidden Files'
=> (import [test [*]])
=> /.*
'Hidden Files'
```

Notice that with both qualified name and `import all` worked, but explicit import failed.

> **Background, in case you are curious why I came up such a weird name, can be skipped.**
> The static site generator I wrote, implements a DSL, there is a function to test file name patterns, when I wrote a predefined function for hidden files, I thought the function name `/.*` is both _ingenious and insane_ . Something like:
> ```clojure
> (defn name= [&rest patterns]
>   "create a function that test if the name of document matches ``patterns``"
>   (fn [document]
>     (-> document get-filename (match? #* patterns))))
> (setv
>   hidden-file? (name= ".*")
>   /.* hidden-file?)
> ```
> 
> Everything went well, I could write the following and be proud of myself :rofl:  
> ```clojure
> (remove /.* files)
> ```
> ... until I tried to import it from other file, then I encountered the problem described above.

Discussion:

Patch:
diff --git a/NEWS.rst b/NEWS.rst
index 159fdfd1..bd46cb9f 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -67,6 +67,7 @@ Bug Fixes
 * `NaN` can no longer create an infinite loop during macro-expansion
 * Fixed a bug that caused `try` to drop expressions
 * Fixed a bug where the compiler didn't properly compile `unquote-splice`
+* Trying to import a dotted name is now a syntax error, as in Python
 
 Misc. Improvements
 ----------------------------
diff --git a/hy/compiler.py b/hy/compiler.py
index 2d065c2c..04d195d5 100755
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -1176,10 +1176,12 @@ class HyASTCompiler(object):
 
         return operand
 
+    _symn = some(lambda x: isinstance(x, HySymbol) and "." not in x)
+
     @special(["import", "require"], [many(
         SYM |
-        brackets(SYM, sym(":as"), SYM) |
-        brackets(SYM, brackets(many(SYM + maybe(sym(":as") + SYM)))))])
+        brackets(SYM, sym(":as"), _symn) |
+        brackets(SYM, brackets(many(_symn + maybe(sym(":as") + _symn)))))])
     def compile_import_or_require(self, expr, root, entries):
         """
         TODO for `require`: keep track of what we've imported in this run and


Test patch:
diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
index bbf964a3..5fbc7273 100644
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -283,6 +283,13 @@ def test_ast_require():
     cant_compile("(require [tests.resources.tlib [* *]])")
 
 
+def test_ast_import_require_dotted():
+    """As in Python, it should be a compile-type error to attempt to
+import a dotted name."""
+    cant_compile("(import [spam [foo.bar]])")
+    cant_compile("(require [spam [foo.bar]])")
+
+
 def test_ast_no_pointless_imports():
     def contains_import_from(code):
         return any([isinstance(node, ast.ImportFrom)