diff --git a/base/show.jl b/base/show.jl index 98f8a897bd77d..cafce6d34364c 100644 --- a/base/show.jl +++ b/base/show.jl @@ -903,6 +903,31 @@ function show_generator(io, ex, indent) end end +function show_import_path(io::IO, ex) + if !isa(ex, Expr) + print(io, ex) + elseif ex.head === :(:) + show_import_path(io, ex.args[1]) + print(io, ": ") + for i = 2:length(ex.args) + if i > 2 + print(io, ", ") + end + show_import_path(io, ex.args[i]) + end + elseif ex.head === :(.) + print(io, ex.args[1]) + for i = 2:length(ex.args) + if ex.args[i-1] != :(.) + print(io, '.') + end + print(io, ex.args[i]) + end + else + show_unquoted(io, ex) + end +end + # TODO: implement interpolated strings function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) head, args, nargs = ex.head, ex.args, length(ex.args) @@ -1251,17 +1276,14 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) elseif head === :import || head === :using print(io, head) + print(io, ' ') first = true - for a = args - if first - print(io, ' ') - first = false - else - print(io, '.') - end - if a !== :. - print(io, a) + for a in args + if !first + print(io, ", ") end + first = false + show_import_path(io, a) end elseif head === :meta && length(args) >= 2 && args[1] === :push_loc print(io, "# meta: location ", join(args[2:end], " ")) diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index df37d47729a11..65639f51d4bcf 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -435,12 +435,12 @@ parses as `(macrocall (|.| Core '@doc) (line) "some docs" (= (call f x) (block x | Input | AST | |:------------------- |:-------------------------------------------- | -| `import a` | `(import a)` | -| `import a.b.c` | `(import a b c)` | -| `import ...a` | `(import . . . a)` | -| `import a.b, c.d` | `(toplevel (import a b) (import c d))` | -| `import Base: x` | `(import Base x)` | -| `import Base: x, y` | `(toplevel (import Base x) (import Base y))` | +| `import a` | `(import (. a))` | +| `import a.b.c` | `(import (. a b c))` | +| `import ...a` | `(import (. . . . a))` | +| `import a.b, c.d` | `(import (. a b) (. c d))` | +| `import Base: x` | `(import (: (. Base) (. x)))` | +| `import Base: x, y` | `(import (: (. Base) (. x) (. y)))` | | `export a, b` | `(export a b)` | ### Numbers diff --git a/src/ast.c b/src/ast.c index fba225b231feb..0df36cfc30f90 100644 --- a/src/ast.c +++ b/src/ast.c @@ -58,7 +58,7 @@ jl_sym_t *polly_sym; jl_sym_t *inline_sym; jl_sym_t *propagate_inbounds_sym; jl_sym_t *generated_sym; jl_sym_t *generated_only_sym; jl_sym_t *isdefined_sym; jl_sym_t *nospecialize_sym; -jl_sym_t *macrocall_sym; +jl_sym_t *macrocall_sym; jl_sym_t *colon_sym; jl_sym_t *hygienicscope_sym; jl_sym_t *escape_sym; jl_sym_t *gc_preserve_begin_sym; jl_sym_t *gc_preserve_end_sym; @@ -319,6 +319,7 @@ void jl_init_frontend(void) structtype_sym = jl_symbol("struct_type"); toplevel_sym = jl_symbol("toplevel"); dot_sym = jl_symbol("."); + colon_sym = jl_symbol(":"); boundscheck_sym = jl_symbol("boundscheck"); inbounds_sym = jl_symbol("inbounds"); fastmath_sym = jl_symbol("fastmath"); diff --git a/src/julia-parser.scm b/src/julia-parser.scm index c4fe6af43a213..296c5d912774c 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -1574,10 +1574,7 @@ (error "invalid \"export\" statement")) `(export ,@es))) ((import using importall) - (let ((imports (parse-imports s word))) - (if (length= imports 1) - (car imports) - (cons 'toplevel imports)))) + (parse-imports s word)) ((do) (error "invalid \"do\" syntax")) (else (error "unhandled reserved word"))))))) @@ -1612,10 +1609,8 @@ (parse-comma-separated s (lambda (s) (parse-import s word)))))) (if from - (map (lambda (x) - (cons (car x) (append (cdr first) (cdr x)))) - rest) - (cons first rest)))) + `(,word (|:| ,first ,@rest)) + (list* word first rest)))) (define (parse-import-dots s) (let loop ((l '()) @@ -1647,13 +1642,13 @@ (loop (cons (macrocall-to-atsym (parse-unary-prefix s)) path))) ((or (memv nxt '(#\newline #\; #\, :)) (eof-object? nxt)) - `(,word ,@(reverse path))) + (cons '|.| (reverse path))) ((eqv? (string.sub (string nxt) 0 1) ".") (take-token s) (loop (cons (symbol (string.sub (string nxt) 1)) path))) (else - `(,word ,@(reverse path))))))) + (cons '|.| (reverse path))))))) ;; parse comma-separated assignments, like "i=1:n,j=1:m,..." (define (parse-comma-separated s what) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 1dbd50c752d17..52c4c5bf31bfe 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1888,7 +1888,7 @@ (error (string "invalid " syntax-str " \"" (deparse el) "\"")))))))) (define (expand-forms e) - (if (or (atom? e) (memq (car e) '(quote inert top core globalref outerref line module toplevel ssavalue null meta))) + (if (or (atom? e) (memq (car e) '(quote inert top core globalref outerref line module toplevel ssavalue null meta using import importall export))) e (let ((ex (get expand-table (car e) #f))) (if ex diff --git a/src/julia_internal.h b/src/julia_internal.h index 4f384dca23312..61255c949bb62 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1001,7 +1001,7 @@ extern jl_sym_t *enter_sym; extern jl_sym_t *leave_sym; extern jl_sym_t *exc_sym; extern jl_sym_t *new_sym; extern jl_sym_t *compiler_temp_sym; extern jl_sym_t *foreigncall_sym; extern jl_sym_t *const_sym; extern jl_sym_t *thunk_sym; -extern jl_sym_t *underscore_sym; +extern jl_sym_t *underscore_sym; extern jl_sym_t *colon_sym; extern jl_sym_t *abstracttype_sym; extern jl_sym_t *primtype_sym; extern jl_sym_t *structtype_sym; extern jl_sym_t *global_sym; extern jl_sym_t *unused_sym; diff --git a/src/toplevel.c b/src/toplevel.c index 8f09e430a9a20..05298a7bae3a1 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -420,7 +420,7 @@ static jl_module_t *call_require(jl_sym_t *var) // either: // - sets *name and returns the module to import *name from // - sets *name to NULL and returns a module to import -static jl_module_t *eval_import_path(jl_module_t *from, jl_array_t *args, jl_sym_t **name, const char *keyword) +static jl_module_t *eval_import_path(jl_module_t *where, jl_module_t *from, jl_array_t *args, jl_sym_t **name, const char *keyword) { jl_sym_t *var = (jl_sym_t*)jl_array_ptr_ref(args, 0); size_t i = 1; @@ -429,7 +429,11 @@ static jl_module_t *eval_import_path(jl_module_t *from, jl_array_t *args, jl_sym if (!jl_is_symbol(var)) jl_type_error(keyword, (jl_value_t*)jl_sym_type, (jl_value_t*)var); - if (var != dot_sym) { + if (from != NULL) { + m = from; + i = 0; + } + else if (var != dot_sym) { // `A.B`: call the loader to obtain the root A in the current environment. if (jl_core_module && var == jl_core_module->name) { m = jl_core_module; @@ -445,7 +449,7 @@ static jl_module_t *eval_import_path(jl_module_t *from, jl_array_t *args, jl_sym } else { // `.A.B.C`: strip off leading dots by following parent links - m = from; + m = where; while (1) { if (i >= jl_array_len(args)) jl_error("invalid module path"); @@ -461,6 +465,8 @@ static jl_module_t *eval_import_path(jl_module_t *from, jl_array_t *args, jl_sym var = (jl_sym_t*)jl_array_ptr_ref(args, i); if (!jl_is_symbol(var)) jl_type_error(keyword, (jl_value_t*)jl_sym_type, (jl_value_t*)var); + if (var == dot_sym) + jl_errorf("invalid %s path: \".\" in identifier path", keyword); if (i == jl_array_len(args)-1) break; m = (jl_module_t*)jl_eval_global_var(m, var); @@ -522,6 +528,31 @@ static jl_module_t *deprecation_replacement_module(jl_module_t *parent, jl_sym_t return NULL; } +// in `import A.B: x, y, ...`, evaluate the `A.B` part if it exists +static jl_module_t *eval_import_from(jl_module_t *m, jl_expr_t *ex, const char *keyword) +{ + if (jl_expr_nargs(ex) == 1 && jl_is_expr(jl_exprarg(ex, 0))) { + jl_expr_t *fr = (jl_expr_t*)jl_exprarg(ex, 0); + if (fr->head == colon_sym) { + if (jl_expr_nargs(fr) > 0 && jl_is_expr(jl_exprarg(fr, 0))) { + jl_expr_t *path = (jl_expr_t*)jl_exprarg(fr, 0); + if (((jl_expr_t*)path)->head == dot_sym) { + jl_sym_t *name = NULL; + jl_module_t *from = eval_import_path(m, NULL, path->args, &name, "import"); + if (name != NULL) { + from = (jl_module_t*)jl_eval_global_var(from, name); + if (!jl_is_module(from)) + jl_errorf("invalid %s path: \"%s\" does not name a module", keyword, jl_symbol_name(name)); + } + return from; + } + } + jl_errorf("malformed \"%s:\" expression", keyword); + } + } + return NULL; +} + static jl_code_info_t *expr_to_code_info(jl_value_t *expr) { jl_code_info_t *src = jl_new_code_info_uninit(); @@ -569,50 +600,86 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int e jl_sym_t *name = NULL; jl_depwarn("`importall` is deprecated, use `using` or individual `import` statements instead", (jl_value_t*)jl_symbol("importall")); - jl_module_t *import = eval_import_path(m, ex->args, &name, "importall"); - if (name != NULL) { - import = (jl_module_t*)jl_eval_global_var(import, name); - if (!jl_is_module(import)) - jl_errorf("invalid %s statement: name exists but does not refer to a module", jl_symbol_name(ex->head)); + jl_module_t *from = eval_import_from(m, ex, "importall"); + size_t i = 0; + if (from) { + i = 1; + ex = (jl_expr_t*)jl_exprarg(ex, 0); + } + for (; i < jl_expr_nargs(ex); i++) { + jl_value_t *a = jl_exprarg(ex, i); + if (jl_is_expr(a) && ((jl_expr_t*)a)->head == dot_sym) { + name = NULL; + jl_module_t *import = eval_import_path(m, from, ((jl_expr_t*)a)->args, &name, "importall"); + if (name != NULL) { + import = (jl_module_t*)jl_eval_global_var(import, name); + if (!jl_is_module(import)) + jl_errorf("invalid %s statement: name exists but does not refer to a module", jl_symbol_name(ex->head)); + } + jl_module_importall(m, import); + } } - jl_module_importall(m, import); return jl_nothing; } else if (ex->head == using_sym) { jl_sym_t *name = NULL; - jl_module_t *import = eval_import_path(m, ex->args, &name, "using"); - jl_module_t *u = import; - if (name != NULL) - u = (jl_module_t*)jl_eval_global_var(import, name); - if (jl_is_module(u)) { - jl_module_using(m, u); - if (m == jl_main_module && name == NULL) { - // TODO: for now, `using A` in Main also creates an explicit binding for `A` - // This will possibly be extended to all modules. - import_module(m, u); - } + jl_module_t *from = eval_import_from(m, ex, "using"); + size_t i = 0; + if (from) { + i = 1; + ex = (jl_expr_t*)jl_exprarg(ex, 0); } - else { - jl_module_t *replacement = deprecation_replacement_module(import, name); - if (replacement) - jl_module_using(m, replacement); - else - jl_module_use(m, import, name); + for (; i < jl_expr_nargs(ex); i++) { + jl_value_t *a = jl_exprarg(ex, i); + if (jl_is_expr(a) && ((jl_expr_t*)a)->head == dot_sym) { + name = NULL; + jl_module_t *import = eval_import_path(m, from, ((jl_expr_t*)a)->args, &name, "using"); + jl_module_t *u = import; + if (name != NULL) + u = (jl_module_t*)jl_eval_global_var(import, name); + if (jl_is_module(u)) { + jl_module_using(m, u); + if (m == jl_main_module && name == NULL) { + // TODO: for now, `using A` in Main also creates an explicit binding for `A` + // This will possibly be extended to all modules. + import_module(m, u); + } + } + else { + jl_module_t *replacement = deprecation_replacement_module(import, name); + if (replacement) + jl_module_using(m, replacement); + else + jl_module_use(m, import, name); + } + } } return jl_nothing; } else if (ex->head == import_sym) { jl_sym_t *name = NULL; - jl_module_t *import = eval_import_path(m, ex->args, &name, "import"); - if (name == NULL) { - import_module(m, import); + jl_module_t *from = eval_import_from(m, ex, "import"); + size_t i = 0; + if (from) { + i = 1; + ex = (jl_expr_t*)jl_exprarg(ex, 0); } - else { - jl_module_t *replacement = deprecation_replacement_module(import, name); - if (replacement) - import_module(m, replacement); - else - jl_module_import(m, import, name); + for (; i < jl_expr_nargs(ex); i++) { + jl_value_t *a = jl_exprarg(ex, i); + if (jl_is_expr(a) && ((jl_expr_t*)a)->head == dot_sym) { + name = NULL; + jl_module_t *import = eval_import_path(m, from, ((jl_expr_t*)a)->args, &name, "import"); + if (name == NULL) { + import_module(m, import); + } + else { + jl_module_t *replacement = deprecation_replacement_module(import, name); + if (replacement) + import_module(m, replacement); + else + jl_module_import(m, import, name); + } + } } return jl_nothing; } diff --git a/stdlib/Distributed/src/macros.jl b/stdlib/Distributed/src/macros.jl index 26f51dc9991ed..6a3be1fbc0a41 100644 --- a/stdlib/Distributed/src/macros.jl +++ b/stdlib/Distributed/src/macros.jl @@ -121,9 +121,16 @@ end extract_imports!(imports, x) = imports function extract_imports!(imports, ex::Expr) if Meta.isexpr(ex, (:import, :using)) - return push!(imports, ex.args[1]) + m = ex.args[1] + if isa(m, Expr) && m.head === :(:) + push!(imports, m.args[1].args[1]) + else + for a in ex.args + push!(imports, a.args[1]) + end + end elseif Meta.isexpr(ex, :let) - return extract_imports!(imports, ex.args[2]) + extract_imports!(imports, ex.args[2]) elseif Meta.isexpr(ex, (:toplevel, :block)) for i in eachindex(ex.args) extract_imports!(imports, ex.args[i]) diff --git a/stdlib/Distributed/test/distributed_exec.jl b/stdlib/Distributed/test/distributed_exec.jl index bc90e5e33730e..9f219085b0a54 100644 --- a/stdlib/Distributed/test/distributed_exec.jl +++ b/stdlib/Distributed/test/distributed_exec.jl @@ -5,6 +5,9 @@ import Distributed: launch, manage include(joinpath(Sys.BINDIR, "..", "share", "julia", "test", "testenv.jl")) +@test Distributed.extract_imports(:(begin; import Foo, Bar; let; using Baz; end; end)) == + [:Foo, :Bar, :Baz] + # Test a few "remote" invocations when no workers are present @test remote(myid)() == 1 @test pmap(identity, 1:100) == [1:100...] diff --git a/test/meta.jl b/test/meta.jl index 85426239f5741..42377e2e632f4 100644 --- a/test/meta.jl +++ b/test/meta.jl @@ -128,10 +128,6 @@ show_sexpr(ioB,:(1+1)) show_sexpr(ioB,QuoteNode(1),1) -using Distributed -@test Distributed.extract_imports(:(begin; import Foo, Bar; let; using Baz; end; end)) == - [:Foo, :Bar, :Baz] - # test base/expr.jl baremodule B eval = 0 diff --git a/test/show.jl b/test/show.jl index c4af75dba85a7..e8682dd603975 100644 --- a/test/show.jl +++ b/test/show.jl @@ -113,6 +113,20 @@ end @test_repr "\"\\xe0\\xb0\\xb0\"" @test_repr "\"\\xf0\\xb0\\xb0\\xb0\"" +# import statements +@test_repr "using A" +@test_repr "using A, B.C, D" +@test_repr "using A: b" +@test_repr "using A: a, x, y.z" +@test_repr "using A.B.C: a, x, y.z" +@test_repr "using ..A: a, x, y.z" +@test_repr "import A" +@test_repr "import A, B.C, D" +@test_repr "import A: b" +@test_repr "import A: a, x, y.z" +@test_repr "import A.B.C: a, x, y.z" +@test_repr "import ..A: a, x, y.z" + # Complex # Meta.parse(repr(:(...))) returns a double-quoted block, so we need to eval twice to unquote it diff --git a/test/syntax.jl b/test/syntax.jl index 7ec73431b60d4..d737d098edf68 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -114,42 +114,46 @@ macro test999_str(args...); args; end @test Meta.parse("1 + #= \0 =# 2") == :(1 + 2) # issue #10910 -@test Meta.parse(":(using A)") == Expr(:quote, Expr(:using, :A)) +@test Meta.parse(":(using A)") == Expr(:quote, Expr(:using, Expr(:., :A))) @test Meta.parse(":(using A.b, B)") == Expr(:quote, - Expr(:toplevel, - Expr(:using, :A, :b), - Expr(:using, :B))) + Expr(:using, + Expr(:., :A, :b), + Expr(:., :B))) @test Meta.parse(":(using A: b, c.d)") == Expr(:quote, - Expr(:toplevel, - Expr(:using, :A, :b), - Expr(:using, :A, :c, :d))) + Expr(:using, + Expr(:(:), + Expr(:., :A), + Expr(:., :b), + Expr(:., :c, :d)))) -@test Meta.parse(":(import A)") == Expr(:quote, Expr(:import, :A)) +@test Meta.parse(":(import A)") == Expr(:quote, Expr(:import, Expr(:., :A))) @test Meta.parse(":(import A.b, B)") == Expr(:quote, - Expr(:toplevel, - Expr(:import, :A, :b), - Expr(:import, :B))) + Expr(:import, + Expr(:., :A, :b), + Expr(:., :B))) @test Meta.parse(":(import A: b, c.d)") == Expr(:quote, - Expr(:toplevel, - Expr(:import, :A, :b), - Expr(:import, :A, :c, :d))) + Expr(:import, + Expr(:(:), + Expr(:., :A), + Expr(:., :b), + Expr(:., :c, :d)))) # issue #11332 @test Meta.parse("export \$(Symbol(\"A\"))") == :(export $(Expr(:$, :(Symbol("A"))))) @test Meta.parse("export \$A") == :(export $(Expr(:$, :A))) -@test Meta.parse("using \$a.\$b") == Expr(:using, Expr(:$, :a), Expr(:$, :b)) -@test Meta.parse("using \$a.\$b, \$c") == Expr(:toplevel, Expr(:using, Expr(:$, :a), - Expr(:$, :b)), - Expr(:using, Expr(:$, :c))) +@test Meta.parse("using \$a.\$b") == Expr(:using, Expr(:., Expr(:$, :a), Expr(:$, :b))) +@test Meta.parse("using \$a.\$b, \$c") == Expr(:using, + Expr(:., Expr(:$, :a), Expr(:$, :b)), + Expr(:., Expr(:$, :c))) @test Meta.parse("using \$a: \$b, \$c.\$d") == - Expr(:toplevel, Expr(:using, Expr(:$, :a), Expr(:$, :b)), - Expr(:using, Expr(:$, :a), Expr(:$, :c), Expr(:$, :d))) + Expr(:using, + Expr(:(:), Expr(:., Expr(:$, :a)), Expr(:., Expr(:$, :b)), + Expr(:., Expr(:$, :c), Expr(:$, :d)))) # fix pr #11338 and test for #11497 -@test parseall("using \$\na") == Expr(:block, Expr(:using, :$), :a) -@test parseall("using \$,\na") == Expr(:toplevel, Expr(:using, :$), - Expr(:using, :a)) -@test parseall("using &\na") == Expr(:block, Expr(:using, :&), :a) +@test parseall("using \$\na") == Expr(:block, Expr(:using, Expr(:., :$)), :a) +@test parseall("using \$,\na") == Expr(:using, Expr(:., :$), Expr(:., :a)) +@test parseall("using &\na") == Expr(:block, Expr(:using, Expr(:., :&)), :a) @test parseall("a = &\nb") == Expr(:block, Expr(:(=), :a, :&), :b) @test parseall("a = \$\nb") == Expr(:block, Expr(:(=), :a, :$), :b) @@ -952,18 +956,18 @@ end using ..x, ..y end").args[3].args)[1] == - Expr(:toplevel, - Expr(:using, Symbol("."), Symbol("."), :x), - Expr(:using, Symbol("."), Symbol("."), :y)) + Expr(:using, + Expr(:., :., :., :x), + Expr(:., :., :., :y)) @test filter(!isline, Meta.parse("module A using .B, .C end").args[3].args)[1] == - Expr(:toplevel, - Expr(:using, Symbol("."), :B), - Expr(:using, Symbol("."), :C)) + Expr(:using, + Expr(:., :., :B), + Expr(:., :., :C)) # issue #21440 @test Meta.parse("+(x::T,y::T) where {T} = 0") == Meta.parse("(+)(x::T,y::T) where {T} = 0")