Repository: hylang/hy

Issue:
import syntax improvements
We've used to have three forms for importing:

```clj
(import foo)          ; import foo
(import [foo])        ; import foo
(import [foo [bar]])  ; from foo import bar
```

We've currently dropped the second form, but that leaves a weird inconsistency:

```clj
(import foo)          ; import foo
(import [foo [bar]])  ; from foo import bar
```

The inner list is no longer necessary. We're now free to change the syntax to be less nested:

```clj
(import foo)          ; import foo
(import [foo bar])    ; from foo import bar
```

And this is close to how Clojure does it (though with expressions instead of lists):

```clj
(import (java.util Date Calendar)
        (java.net URI ServerSocket)
        java.sql.DriverManager)
```



Discussion:
If you drop the inner list like that, I think we should switch to `()` like Clojure. Semantically, `()` means the head is special, and it's highlighted as such, while the tail has the arguments, while `[]` means either the elements are equal status or it contains assignment pairs. I think we used nested `[]` before because this assignment hint holds for many of Hy's forms, with `setv` as a notable exception. But what's weird about `import` is the assignment targets are on the right, which breaks the `[]` pattern anyway.
We should also make `require` consistent with `import` if we change it.
Fortunately, that's easy now that `import` and `require` are parsed with the same code.
Clojure's approach could perhaps be simplified further, like
```
(import
  bacon
  (foo.bar
    (baz quux norlf)
    spam
    eggs))
```
Which would be like
```
import bacon
from foo.bar.baz import quux, norlf
from foo.bar import spam, eggs
```
So an expression sets the prefix for all of its children.

But this potentially breaks the "preferably only one obvious way to do it" rule, since you can now use either a `.` or a new expression.
```
(import
  bacon
  (foo
    (bar
      (baz quux norlf)
      spam
      eggs)))
```
Removing the `.` separator is an option. We could even nest in more `()` to replace the `.` prefix for relative imports. But for common, simple cases, it gets ugly.
```
;; from .foo.bar.baz import quux
(import [.foo.bar.baz [quux]])  ; old way
(import (.foo.bar.baz quux))  ; proposed
(import ((foo (bar (baz quux)))))  ; no dots. How bad?
```
Maybe it's not worth the trouble. Clojure's way is closer to the Python compilation and it's not too hard to build macros on top of simpler special forms.
Related #851.
I just did the basic implementation for now.

> But this potentially breaks the "preferably only one obvious way to do it" rule, since you can now use either a `.` or a new expression

It might not be worth it as Python doesn't typically tend to have deeply nested namespaces - its instead tends to being broad.

> Removing the `.` separator is an option. We could even nest in more `()` to replace the `.` prefix for relative imports. But for common, simple cases, it gets ugly.

Not a bad idea, but I think I see two problems:

Multiple dots are valid as a indicator to go up. Now I guess you can just toss in more parenthesis, but the question is how clearly indent level is that conveyed:

```
(import [...foo.bar [baz]])
(import (...foo.bar baz))
(import ((((foo (bar (baz)))))))  ; Much harder to understand at a glance
```

The other problem is I have no idea how you'd express this:

```
(import [... [foo]])  ; translates to `from ... import foo`
(import (... foo))
```

> Python doesn't typically tend to have deeply nested namespaces

A good point. Even though I wished Clojure had something like this, it's probably not worth the trouble in Hy.
`org.python.lang.python.util.time.dates.datetime.dateime.now.AbstractHandlerFactoryManagerFactoryDecoratorProxy`
```clj
;; Clojure-like
(import (os.path exists
                 isdir :as dir?
                 isfile :as file?)
        sys :as systest
        re)
```
```Python
from os.path import exists, isdir as is_dir, isfile :as is_file
import sys as systest
import re
```
The closest we could get to Python would be with two special forms, `import` and `from`.
```clj
;; Python-like
(from os.path :import exists isdir :as dir? isfile :as file?)
(import sys :as systest)
(import re)
```
If we reverse the order, we don't need the `from` form. A keyword will do.
```clj
(import exists isdir :as dir? isfile :as file? :from os.path)
```
These have no internal brackets, but like Python, you'll need to repeat `import`.

Resolving patch:
diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py
--- a/hy/core/result_macros.py
+++ b/hy/core/result_macros.py
@@ -1379,10 +1379,10 @@ def compile_class_expression(compiler, expr, root, name, rest):
 def importlike(*name_types):
     name = some(lambda x: isinstance(x, name_types) and "." not in x)
     return [many(
-        SYM |
-        brackets(SYM, sym(":as"), name) |
-        brackets(SYM, brackets(many(
-            name + maybe(sym(":as") + name)))))]
+        SYM + maybe(sym("*")
+                    | (sym(":as") + name)
+                    | brackets(many(
+                        name + maybe(sym(":as") + name)))))]
 
 @pattern_macro("import", importlike(Symbol))
 @pattern_macro("require", importlike(Symbol, String))
@@ -1394,49 +1394,44 @@ def compile_import_or_require(compiler, expr, root, entries):
         prefix = ""
 
         if isinstance(entry, Symbol):
-            # e.g., (import foo)
-            module, prefix = entry, entry
-        elif isinstance(entry, List) and isinstance(entry[1], Symbol):
-            # e.g., (import [foo :as bar])
-            module, prefix = entry
+            # e.g., (import foo *)
+            module = entry
         else:
-            # e.g., (import [foo [bar baz :as MyBaz bing]])
-            # or (import [foo [*]])
-            module, kids = entry
-            kids = kids[0]
-            if (Symbol('*'), None) in kids:
-                if len(kids) != 1:
-                    star = kids[kids.index((Symbol('*'), None))][0]
-                    raise compiler._syntax_error(star,
-                        "* in an import name list must be on its own")
+            module, rest = entry
+            if rest is None:
+                # e.g., (import foo)
+                prefix = module
+            elif isinstance(rest, Symbol):
+                # e.g., (import foo :as bar)
+                prefix = rest
             else:
-                assignments = [(k, v or k) for k, v in kids]
+                # e.g., (import foo [bar baz :as MyBaz bing])
+                assignments = [(k, v or k) for k, v in rest[0]]
 
         ast_module = mangle(module)
 
         if root == "import":
-            module = ast_module.lstrip(".")
-            level = len(ast_module) - len(module)
+            module_name = ast_module.lstrip(".")
+            level = len(ast_module) - len(module_name)
             if assignments == "ALL" and prefix == "":
                 node = asty.ImportFrom
-                names = [asty.alias(entry, name="*", asname=None)]
+                names = [asty.alias(module, name="*", asname=None)]
             elif assignments == "ALL":
                 node = asty.Import
-                prefix = mangle(prefix)
                 names = [asty.alias(
-                    entry,
+                    module,
                     name=ast_module,
-                    asname=prefix if prefix != module else None)]
+                    asname=mangle(prefix) if prefix != module else None)]
             else:
                 node = asty.ImportFrom
                 names = [
                     asty.alias(
-                        entry,
+                        module,
                         name=mangle(k),
                         asname=None if v == k else mangle(v))
                     for k, v in assignments]
             ret += node(
-                expr, module=module or None, names=names, level=level)
+                expr, module=module_name or None, names=names, level=level)
 
         elif require(ast_module, compiler.module, assignments=assignments,
                      prefix=prefix):


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
@@ -272,36 +272,33 @@ def test_ast_import_mangle_dotted():
 
 def test_ast_good_import_from():
     "Make sure AST can compile valid selective import"
-    can_compile("(import [x [y]])")
+    can_compile("(import x [y])")
 
 
 def test_ast_require():
     "Make sure AST respects (require) syntax"
     can_compile("(require tests.resources.tlib)")
-    can_compile('(require [tests.resources.tlib [qplah parald "#taggart"]])')
-    can_compile("(require [tests.resources.tlib [*]])")
-    can_compile("(require [tests.resources.tlib :as foobar])")
-    can_compile("(require [tests.resources.tlib [qplah :as quiz]])")
-    can_compile("(require [tests.resources.tlib [qplah :as quiz parald]])")
+    can_compile('(require tests.resources.tlib [qplah parald "#taggart"])')
+    can_compile("(require tests.resources.tlib *)")
+    can_compile("(require tests.resources.tlib :as foobar)")
+    can_compile("(require tests.resources.tlib [qplah :as quiz])")
+    can_compile("(require tests.resources.tlib [qplah :as quiz parald])")
     cant_compile("(require [tests.resources.tlib])")
-    cant_compile("(require [tests.resources.tlib [* qplah]])")
-    cant_compile("(require [tests.resources.tlib [qplah *]])")
-    cant_compile("(require [tests.resources.tlib [* *]])")
-    cant_compile("(require [tests.resources.tlib [#taggart]]")
+    cant_compile("(require tests.resources.tlib [#taggart]")
 
 
 def test_ast_import_require_dotted():
     """As in Python, it should be a compile-time error to attempt to
 import a dotted name."""
-    cant_compile("(import [spam [foo.bar]])")
-    cant_compile("(require [spam [foo.bar]])")
+    cant_compile("(import spam [foo.bar])")
+    cant_compile("(require spam [foo.bar])")
 
 
 def test_ast_multi_require():
     # https://github.com/hylang/hy/issues/1903
     x = can_compile("""(require
-      [tests.resources.tlib [qplah]]
-      [tests.resources.macros [threadtail-set-cd]])""")
+      tests.resources.tlib [qplah]
+      tests.resources.macros [threadtail-set-cd])""")
     assert sum(1 for stmt in x.body if isinstance(stmt, ast.Expr)) == 2
     dump = ast.dump(x)
     assert "qplah" in dump
@@ -623,7 +620,7 @@ def test_futures_imports():
     """Make sure __future__ imports go first, especially when builtins are
     automatically added (e.g. via use of a builtin name like `rest`)."""
     hy_ast = can_compile((
-        '(import [__future__ [print_function]])\n'
+        '(import __future__ [print_function])\n'
         '(import sys)\n'
         '(setv some [1 2])'
         '(print (list (rest some)))'))
diff --git a/tests/importer/test_importer.py b/tests/importer/test_importer.py
--- a/tests/importer/test_importer.py
+++ b/tests/importer/test_importer.py
@@ -293,5 +293,5 @@ def test_docstring():
 
 def test_hy_python_require():
     # https://github.com/hylang/hy/issues/1911
-    test = "(do (require [tests.resources.macros [test-macro]]) (test-macro) blah)"
+    test = "(do (require tests.resources.macros [test-macro]) (test-macro) blah)"
     assert hy.eval(hy.read_str(test)) == 1
diff --git a/tests/native_tests/contrib/destructure.hy b/tests/native_tests/contrib/destructure.hy
--- a/tests/native_tests/contrib/destructure.hy
+++ b/tests/native_tests/contrib/destructure.hy
@@ -4,9 +4,9 @@
 
 (import pytest)
 (import collections.abc)
-(import [itertools [cycle count islice]])
-(import [hy.contrib.destructure [destructure]])
-(require [hy.contrib.destructure [setv+ dict=: defn+ fn+ let+]])
+(import itertools [cycle count islice])
+(import hy.contrib.destructure [destructure])
+(require hy.contrib.destructure [setv+ dict=: defn+ fn+ let+])
 
 (defn iterator? [x]
   (isinstance x collections.abc.Iterator))
diff --git a/tests/native_tests/contrib/loop.hy b/tests/native_tests/contrib/loop.hy
--- a/tests/native_tests/contrib/loop.hy
+++ b/tests/native_tests/contrib/loop.hy
@@ -2,7 +2,7 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(require [hy.contrib.loop [loop]])
+(require hy.contrib.loop [loop])
 (import sys)
 
 (defn tco-sum [x y]
diff --git a/tests/native_tests/contrib/sequences.hy b/tests/native_tests/contrib/sequences.hy
--- a/tests/native_tests/contrib/sequences.hy
+++ b/tests/native_tests/contrib/sequences.hy
@@ -2,10 +2,10 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(require [hy.contrib.sequences [seq defseq]])
+(require hy.contrib.sequences [seq defseq])
 
 (import
-  [hy.contrib.sequences [Sequence end-sequence]])
+  hy.contrib.sequences [Sequence end-sequence])
 
 (defn test-infinite-sequence []
   "NATIVE: test creating infinite sequence"
diff --git a/tests/native_tests/contrib/slicing.hy b/tests/native_tests/contrib/slicing.hy
--- a/tests/native_tests/contrib/slicing.hy
+++ b/tests/native_tests/contrib/slicing.hy
@@ -1,7 +1,7 @@
 (import pytest
-        [hy.errors [HyMacroExpansionError HySyntaxError]]
-        [hy.lex [hy-parse exceptions]])
-(require [hy.contrib.slicing [*]])
+        hy.errors [HyMacroExpansionError HySyntaxError]
+        hy.lex [hy-parse exceptions])
+(require hy.contrib.slicing *)
 
 (defn test-ncuts-slicing []
   (assert (= (hy.macroexpand-1 '(ncut df 1:5:-1))           '(get df (slice 1 5 -1))))
diff --git a/tests/native_tests/contrib/test_pprint.hy b/tests/native_tests/contrib/test_pprint.hy
--- a/tests/native_tests/contrib/test_pprint.hy
+++ b/tests/native_tests/contrib/test_pprint.hy
@@ -1,9 +1,9 @@
 (import
   pytest
   io
-  [pprint :as pypprint]
-  [hy._compat [PY3_8]]
-  [hy.contrib.pprint :as pprint])
+  pprint :as pypprint
+  hy._compat [PY3_8]
+  hy.contrib.pprint :as pprint)
 
 (defn large-list-b []
   (list (range 200)))
diff --git a/tests/native_tests/contrib/walk.hy b/tests/native_tests/contrib/walk.hy
--- a/tests/native_tests/contrib/walk.hy
+++ b/tests/native_tests/contrib/walk.hy
@@ -2,8 +2,8 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(import [hy.contrib.walk [*]])
-(require [hy.contrib.walk [*]])
+(import hy.contrib.walk *)
+(require hy.contrib.walk *)
 
 (import pytest)
 
@@ -69,7 +69,7 @@
 
   (defmacro require-macro []
     `(do
-       (require [tests.resources.macros [test-macro :as my-test-macro]])
+       (require tests.resources.macros [test-macro :as my-test-macro])
        (my-test-macro)))
 
   (assert (= (get (macroexpand-all '(require-macro)) -1)
diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy
--- a/tests/native_tests/core.hy
+++ b/tests/native_tests/core.hy
@@ -3,7 +3,7 @@
 ;; license. See the LICENSE.
 
 (import
-  [itertools [repeat cycle islice]]
+  itertools [repeat cycle islice]
   pytest)
 
 ;;;; some simple helpers
diff --git a/tests/native_tests/extra/anaphoric.hy b/tests/native_tests/extra/anaphoric.hy
--- a/tests/native_tests/extra/anaphoric.hy
+++ b/tests/native_tests/extra/anaphoric.hy
@@ -2,8 +2,8 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(import [hy.errors [HyMacroExpansionError]])
-(require [hy.extra.anaphoric [*]])
+(import hy.errors [HyMacroExpansionError])
+(require hy.extra.anaphoric *)
 
 (defn even? [x] (= (% x 2) 0))
 (defn odd?  [x] (= (% x 2) 1))
diff --git a/tests/native_tests/extra/anaphoric_single.hy b/tests/native_tests/extra/anaphoric_single.hy
--- a/tests/native_tests/extra/anaphoric_single.hy
+++ b/tests/native_tests/extra/anaphoric_single.hy
@@ -2,7 +2,7 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(require [hy.extra.anaphoric [ap-last]])
+(require hy.extra.anaphoric [ap-last])
 
 (defn test-anaphoric-single-require []
   ; https://github.com/hylang/hy/issues/1853#issuecomment-568192529
diff --git a/tests/native_tests/extra/reserved.hy b/tests/native_tests/extra/reserved.hy
--- a/tests/native_tests/extra/reserved.hy
+++ b/tests/native_tests/extra/reserved.hy
@@ -2,7 +2,7 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(import [hy.extra.reserved [macros names]])
+(import hy.extra.reserved [macros names])
 
 (defn test-reserved-macros []
   (assert (is (type (macros)) frozenset))
diff --git a/tests/native_tests/hy_repr.hy b/tests/native_tests/hy_repr.hy
--- a/tests/native_tests/hy_repr.hy
+++ b/tests/native_tests/hy_repr.hy
@@ -3,8 +3,8 @@
 ;; license. See the LICENSE.
 
 (import
-  [hy._compat [PY3_7]]
-  [math [isnan]])
+  hy._compat [PY3_7]
+  math [isnan])
 
 (defn test-hy-repr-roundtrip-from-value []
   ; Test that a variety of values round-trip properly.
@@ -91,7 +91,7 @@
   (assert (= (hy.repr (.items {1 2})) "(dict-items [(, 1 2)])")))
 
 (defn test-datetime []
-  (import [datetime :as D])
+  (import datetime :as D)
 
   (assert (= (hy.repr (D.datetime 2009 1 15 15 27 5 0))
     "(datetime.datetime 2009 1 15 15 27 5)"))
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
@@ -2,21 +2,21 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(import [tests.resources [kwtest function-with-a-dash AsyncWithTest]]
-        [os.path [exists isdir isfile]]
-        [sys :as systest]
+(import tests.resources [kwtest function-with-a-dash AsyncWithTest]
+        os.path [exists isdir isfile]
+        sys :as systest
         re
-        [operator [or_]]
-        [itertools [repeat count islice]]
-        [fractions [Fraction]]
+        operator [or_]
+        itertools [repeat count islice]
+        fractions [Fraction]
         pickle
-        [typing [get-type-hints List Dict]]
+        typing [get-type-hints List Dict]
         asyncio
-        [hy.errors [HyLanguageError HySyntaxError]]
+        hy.errors [HyLanguageError HySyntaxError]
         pytest)
 (import sys)
 
-(import [hy._compat [PY3_8]])
+(import hy._compat [PY3_8])
 
 (defn test-sys-argv []
   "NATIVE: test sys.argv"
@@ -1332,21 +1332,22 @@ cee\"} dee" "ey bee\ncee dee"))
   (import sys os)
 
   ;; from os.path import basename
-  (import [os.path [basename]])
+  (import os.path [basename])
   (assert (= (basename "/some/path") "path"))
 
   ;; import os.path as p
-  (import [os.path :as p])
+  (import os.path :as p)
   (assert (= p.basename basename))
 
   ;; from os.path import basename as bn
-  (import [os.path [basename :as bn]])
+  (import os.path [basename :as bn])
   (assert (= bn basename))
 
   ;; Multiple stuff to import
-  (import sys [os.path [dirname]]
-          [os.path :as op]
-          [os.path [dirname :as dn]])
+  (import sys
+          os.path [dirname]
+          os.path :as op
+          os.path [dirname :as dn])
   (assert (= (dirname "/some/path") "/some"))
   (assert (= op.dirname dirname))
   (assert (= dn dirname)))
@@ -1432,7 +1433,7 @@ cee\"} dee" "ey bee\ncee dee"))
   (with [(pytest.raises NameError)]
     (hyx_XairplaneX 1 2 3 4))
 
-  (require [tests.resources.tlib [qplah]])
+  (require tests.resources.tlib [qplah])
   (assert (= (qplah 1 2 3) [8 1 2 3]))
   (with [(pytest.raises NameError)]
     (parald 1 2 3 4))
@@ -1445,7 +1446,7 @@ cee\"} dee" "ey bee\ncee dee"))
   (with [(pytest.raises NameError)]
     (parald 1 2 3 4))
 
-  (require [tests.resources.tlib :as T])
+  (require tests.resources.tlib :as T)
   (assert (= (T.parald 1 2 3) [9 1 2 3]))
   (assert (= (T.✈ "silly") "plane silly"))
   (assert (= (T.hyx_XairplaneX "foolish") "plane foolish"))
@@ -1453,25 +1454,25 @@ cee\"} dee" "ey bee\ncee dee"))
   (with [(pytest.raises NameError)]
     (parald 1 2 3 4))
 
-  (require [tests.resources.tlib [parald :as p]])
+  (require tests.resources.tlib [parald :as p])
   (assert (= (p 1 2 3) [9 1 2 3]))
   (with [(pytest.raises NameError)]
     (parald 1 2 3 4))
 
-  (require [tests.resources.tlib ["#taggart"]])
+  (require tests.resources.tlib ["#taggart"])
   (assert (= #taggart 15 [10 15]))
 
-  (require [tests.resources.tlib [*]])
+  (require tests.resources.tlib *)
   (assert (= (parald 1 2 3) [9 1 2 3]))
   (assert (= (✈ "silly") "plane silly"))
   (assert (= (hyx_XairplaneX "foolish") "plane foolish"))
 
-  (require [tests.resources [tlib macros :as m]])
+  (require tests.resources [tlib macros :as m])
   (assert (in "tlib.qplah" __macros__))
   (assert (in (hy.mangle "m.test-macro") __macros__))
-  (require [os [path]])
+  (require os [path])
   (with [(pytest.raises hy.errors.HyRequireError)]
-    (hy.eval '(require [tests.resources [does-not-exist]]))))
+    (hy.eval '(require tests.resources [does-not-exist]))))
 
 
 (defn test-require-native []
@@ -1481,19 +1482,19 @@ cee\"} dee" "ey bee\ncee dee"))
   (import tests.native_tests.native_macros)
   (with [(pytest.raises NameError)]
     (rev 1))
-  (require [tests.native_tests.native_macros [rev]])
+  (require tests.native_tests.native_macros [rev])
   (setv x [])
   (rev (.append x 1) (.append x 2) (.append x 3))
   (assert (= x [3 2 1])))
 
 (defn test-relative-require []
-  (require [..resources.macros [test-macro]])
+  (require ..resources.macros [test-macro])
   (assert (in "test_macro" __macros__))
 
-  (require [.native-macros [rev]])
+  (require .native-macros [rev])
   (assert (in "rev" __macros__))
 
-  (require [. [native-macros :as m]])
+  (require . [native-macros :as m])
   (assert (in "m.rev" __macros__)))
 
 
@@ -1570,7 +1571,7 @@ cee\"} dee" "ey bee\ncee dee"))
 (defn test-macroexpand-with-named-import []
   ; https://github.com/hylang/hy/issues/1207
   (defmacro m-with-named-import []
-    (import [math [pow]])
+    (import math [pow])
     (pow 2 3))
   (assert (= (hy.macroexpand '(m-with-named-import)) (hy.models.Float (** 2 3)))))
 
@@ -1621,7 +1622,7 @@ cee\"} dee" "ey bee\ncee dee"))
 
 (defn test-read []
   "NATIVE: test that read takes something for stdin and reads"
-  (import [io [StringIO]])
+  (import io [StringIO])
 
   (setv stdin-buffer (StringIO "(+ 2 2)\n(- 2 2)"))
   (assert (= (hy.eval (hy.read stdin-buffer)) 4))
@@ -1708,13 +1709,13 @@ cee\"} dee" "ey bee\ncee dee"))
   (assert (= (f3) "not a docstring")))
 
 (defn test-module-docstring []
-  (import [tests.resources.module-docstring-example :as m])
+  (import tests.resources.module-docstring-example :as m)
   (assert (= m.__doc__ "This is the module docstring."))
   (assert (= m.foo 5)))
 
 (defn test-relative-import []
   "Make sure relative imports work properly"
-  (import [..resources [tlib]])
+  (import ..resources [tlib])
   (assert (= tlib.SECRET-MESSAGE "Hello World")))
 
 
diff --git a/tests/native_tests/model_patterns.hy b/tests/native_tests/model_patterns.hy
--- a/tests/native_tests/model_patterns.hy
+++ b/tests/native_tests/model_patterns.hy
@@ -4,8 +4,8 @@
 
 (defmacro do-until [#* args]
   (import
-    [hy.model-patterns [whole FORM notpexpr dolike]]
-    [funcparserlib.parser [many]])
+    hy.model-patterns [whole FORM notpexpr dolike]
+    funcparserlib.parser [many])
   (setv [body condition] (->> args (.parse (whole
     [(many (notpexpr "until")) (dolike "until")]))))
   (setv g (hy.gensym))
@@ -28,8 +28,8 @@
 
 (defmacro loop [#* args]
   (import
-    [hy.model-patterns [whole FORM sym SYM]]
-    [funcparserlib.parser [many]])
+    hy.model-patterns [whole FORM sym SYM]
+    funcparserlib.parser [many])
   (setv [loopers body] (->> args (.parse (whole [
     (many (|
       (>> (+ (sym "while") FORM) (fn [x] [x]))
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
@@ -5,7 +5,7 @@
 (import os sys
         importlib
         pytest
-        [hy.errors [HySyntaxError HyTypeError HyMacroExpansionError]])
+        hy.errors [HySyntaxError HyTypeError HyMacroExpansionError])
 
 (defmacro rev [#* body]
   "Execute the `body` statements in reverse"
@@ -149,8 +149,8 @@
 
 (defn test-gensym-in-macros []
   (import ast)
-  (import [hy.compiler [hy-compile]])
-  (import [hy.lex [hy-parse]])
+  (import hy.compiler [hy-compile])
+  (import hy.lex [hy-parse])
   (setv macro1 "(defmacro nif [expr pos zero neg]
       (setv g (hy.gensym))
       `(do
@@ -175,8 +175,8 @@
 
 (defn test-with-gensym []
   (import ast)
-  (import [hy.compiler [hy-compile]])
-  (import [hy.lex [hy-parse]])
+  (import hy.compiler [hy-compile])
+  (import hy.lex [hy-parse])
   (setv macro1 "(defmacro nif [expr pos zero neg]
       (with-gensyms [a]
         `(do
@@ -199,8 +199,8 @@
 
 (defn test-defmacro/g! []
   (import ast)
-  (import [hy.compiler [hy-compile]])
-  (import [hy.lex [hy-parse]])
+  (import hy.compiler [hy-compile])
+  (import hy.lex [hy-parse])
   (setv macro1 "(defmacro/g! nif [expr pos zero neg]
         `(do
            (setv ~g!res ~expr)
@@ -228,8 +228,8 @@
 (defn test-defmacro! []
   ;; defmacro! must do everything defmacro/g! can
   (import ast)
-  (import [hy.compiler [hy-compile]])
-  (import [hy.lex [hy-parse]])
+  (import hy.compiler [hy-compile])
+  (import hy.lex [hy-parse])
   (setv macro1 "(defmacro! nif [expr pos zero neg]
         `(do
            (setv ~g!res ~expr)
@@ -349,7 +349,7 @@ in expansions."
 
   (setv nonlocal-test-macro (get __macros__ "nonlocal_test_macro"))
 
-  (require [tests.resources.macro-with-require [*]])
+  (require tests.resources.macro-with-require *)
 
   (setv module-name-var "tests.native_tests.native_macros.test-macro-namespace-resolution")
   (assert (= (+ "This macro was created in tests.resources.macros, "
@@ -394,7 +394,7 @@ in expansions."
   (assert (not (os.path.isfile pyc-file)))
 
   (defn require-macros []
-    (require [tests.resources.macros :as m])
+    (require tests.resources.macros :as m)
 
     (assert (in (hy.mangle "m.test-macro") __macros__))
     (for [macro-name __macros__]
@@ -443,8 +443,8 @@ in expansions."
   (assert (not (os.path.isfile pyc-file)))
 
   (defn test-requires-and-macros []
-    (require [tests.resources.macro-with-require
-              [test-module-macro]])
+    (require tests.resources.macro-with-require
+             [test-module-macro])
 
     ;; Make sure that `require` didn't add any of its `require`s
     (assert (not (in (hy.mangle "nonlocal-test-macro") __macros__)))
@@ -452,7 +452,7 @@ in expansions."
     (assert (not (in (hy.mangle "#test-module-tag") __macros__)))
 
     ;; Now, require everything.
-    (require [tests.resources.macro-with-require [*]])
+    (require tests.resources.macro-with-require *)
 
     ;; Again, make sure it didn't add its required macros and/or tags.
     (assert (not (in (hy.mangle "nonlocal-test-macro") __macros__)))
@@ -490,8 +490,8 @@ in expansions."
 
 
 (defn test-recursive-require-star []
-  "(require [foo [*]]) should pull in macros required by `foo`."
-  (require [tests.resources.macro-with-require [*]])
+  "(require foo *) should pull in macros required by `foo`."
+  (require tests.resources.macro-with-require *)
 
   (test-macro)
   (assert (= blah 1)))
@@ -499,7 +499,7 @@ in expansions."
 
 (defn test-macro-errors []
   (import traceback
-          [hy.importer [hy-parse]])
+          hy.importer [hy-parse])
 
   (setv test-expr (hy-parse "(defmacro blah [x] `(print ~@z)) (blah y)"))
 
diff --git a/tests/native_tests/operators.hy b/tests/native_tests/operators.hy
--- a/tests/native_tests/operators.hy
+++ b/tests/native_tests/operators.hy
@@ -12,7 +12,7 @@
   ;
   ; `op` can also be a list of operators, in which case two tests are
   ; created for each operator.
-  (import [collections.abc [Iterable]])
+  (import collections.abc [Iterable])
   (setv defns [])
   (for [o (if (isinstance op hy.models.Symbol) [op] op)]
     (defn replace [x]
diff --git a/tests/native_tests/py3_10_only_tests.hy b/tests/native_tests/py3_10_only_tests.hy
--- a/tests/native_tests/py3_10_only_tests.hy
+++ b/tests/native_tests/py3_10_only_tests.hy
@@ -1,8 +1,8 @@
 (import pytest
-        [dataclasses [dataclass]]
-        [hy.errors [HySyntaxError]])
+        dataclasses [dataclass]
+        hy.errors [HySyntaxError])
 
-(require [hy.contrib.walk [let]])
+(require hy.contrib.walk [let])
 
 (defn test-pattern-matching []
   (assert (is (match 0
diff --git a/tests/native_tests/tag_macros.hy b/tests/native_tests/tag_macros.hy
--- a/tests/native_tests/tag_macros.hy
+++ b/tests/native_tests/tag_macros.hy
@@ -3,7 +3,7 @@
 ;; license. See the LICENSE.
 
 (import pytest
-        [functools [wraps]])
+        functools [wraps])
 
 
 (defn test-tag-macro []
diff --git a/tests/resources/bin/circular_macro_require.hy b/tests/resources/bin/circular_macro_require.hy
--- a/tests/resources/bin/circular_macro_require.hy
+++ b/tests/resources/bin/circular_macro_require.hy
@@ -2,7 +2,7 @@
   `(print ~expr))
 
 (defmacro foo [expr]
-  `(do (require [tests.resources.bin.circular-macro-require [bar]])
+  `(do (require tests.resources.bin.circular-macro-require [bar])
        (bar ~expr)))
 
 (foo 42)
diff --git a/tests/resources/bin/require_and_eval.hy b/tests/resources/bin/require_and_eval.hy
--- a/tests/resources/bin/require_and_eval.hy
+++ b/tests/resources/bin/require_and_eval.hy
@@ -1,3 +1,3 @@
-(require [hy.extra.anaphoric [ap-if]])
+(require hy.extra.anaphoric [ap-if])
 
 (print (hy.eval '(ap-if (+ "a" "b") (+ it "c"))))
diff --git a/tests/resources/macro_with_require.hy b/tests/resources/macro_with_require.hy
--- a/tests/resources/macro_with_require.hy
+++ b/tests/resources/macro_with_require.hy
@@ -1,6 +1,6 @@
 ;; Require all the macros and make sure they don't pollute namespaces/modules
 ;; that require `*` from this.
-(require [tests.resources.macros [*]])
+(require tests.resources.macros *)
 
 (defmacro test-module-macro [a]
   "The variable `macro-level-var' here should not bind to the same-named symbol
diff --git a/tests/resources/pydemo.hy b/tests/resources/pydemo.hy
--- a/tests/resources/pydemo.hy
+++ b/tests/resources/pydemo.hy
@@ -39,7 +39,7 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
 (setv mysetcomp (sfor x (range 5) :if (not (% x 2)) x))
 (setv mydictcomp (dfor k "abcde" :if (!= k "c") [k (.upper k)]))
 
-(import [itertools [cycle]])
+(import itertools [cycle])
 (setv mygenexpr (gfor x (cycle [1 2 3]) :if (!= x 2) x))
 
 (setv attr-ref str.upper)
@@ -64,9 +64,9 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
 (del (get delstatement 1))
 
 (import math)
-(import [math [sqrt]])
-(import [math [sin :as sine]])
-(import [datetime [*]])
+(import math [sqrt])
+(import math [sin :as sine])
+(import datetime *)
 
 (setv if-block "")
 (if 0
@@ -153,7 +153,7 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
   (setv attr1 5)
   (setv attr2 6))
 
-(import [contextlib [closing]])
+(import contextlib [closing])
 (setv closed [])
 (defclass Closeable []
   (defn close [self] (.append closed self.x)))
@@ -173,7 +173,8 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
 (setv py-accum (py "''.join(map(str, pys_accum))"))
 
 (defn/a coro []
-  (import asyncio [tests.resources [AsyncWithTest async-loop]])
+  (import asyncio
+          tests.resources [AsyncWithTest async-loop])
   (await (asyncio.sleep 0))
   (setv values ["a"])
   (with/a [t (AsyncWithTest "b")]
diff --git a/tests/test_bin.py b/tests/test_bin.py
--- a/tests/test_bin.py
+++ b/tests/test_bin.py
@@ -165,7 +165,7 @@ def test_bin_hy_error_parts_length():
     expression."""
     prg_str = """
     (import hy.errors
-            [hy.importer [hy-parse]])
+            hy.importer [hy-parse])
 
     (setv test-expr (hy-parse "(+ 1\n\n'a 2 3\n\n 1)"))
     (setv test-expr.start-line {})
@@ -223,7 +223,7 @@ def test_bin_hy_syntax_errors():
     assert 'SyntaxError: duplicate argument' in err
 
     # https://github.com/hylang/hy/issues/2014
-    _, err = run_cmd("hy", "(defn foo []\n(import [re [*]]))")
+    _, err = run_cmd("hy", "(defn foo []\n(import re *))")
     assert 'SyntaxError: import * only allowed' in err
     assert 'PrematureEndOfInput' not in err