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:
You realize that `/.*` refers to the attribute `hyx_XasteriskX` of the shadow `/` operator, right? You shouldn't be setting attributes of shadow operators. And Hy doesn't implement importing dotted names. I'm not sure what exactly `(import [foo [bar.baz]])` would do if it worked.
Yes, I saw you on that topic before, this might be related to that and might be fixed with new import form syntax.

I also re-read the document recently and learned the `mangle` function got improved, I did not know this is related though.
```clojure
=> (mangle '/)
'hyx_XsolidusX'
=> (mangle '.)
'hyx_Xfull_stopX'
=> (mangle '*)
'hyx_XasteriskX'
=> (mangle '/.*)
'hyx_XsolidusXXfull_stopXXasteriskX'
``` 

FYI,
```python
$ python
Python 3.6.5 (default, Apr 26 2018, 17:48:52) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hy
>>> from test import hyx_XsolidusXXfull_stopXXasteriskX
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pyx/projects/hy/hy/importer.py", line 251, in load_module
    return import_file_to_module(fullname, self.path, self)
  File "/home/pyx/projects/hy/hy/importer.py", line 96, in import_file_to_module
    load_module(module_name))
  File "/home/pyx/projects/ena/test.hy", line 1, in <module>
    (setv /.* "Hidden Files")
ImportError: cannot import name '/'
```
The change in the syntax of `import` shouldn't affect this because all it will do is make `(import test [/.*])` work like `(import [test [/.*]])` currently does.

Do you still believe there is a bug here? If so, what exactly do you think should be different?
Thanks. I am fine with disallowing . (dot) in symbols in import form except expressing relative import path, I am just thinking, maybe better documentation and/or error message can reduce the surprise factor.
Okay, it would make sense for `import` and `require` to protest if you try to import a name with a dot in it.
Yes, that will help.

While we're at it, it might help to also address `(require [.macros [some-macro]])`, we should mention that in documentation somewhere, the current behaviour, `ModuleNotFoundError: No module named HySymbol('.macros')`, is surprising.

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -1176,10 +1176,12 @@ def compile_unary_operator(self, expr, root, arg):
 
         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
--- 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)