Skip to content

Commit 1a6e34b

Browse files
authored
preserve structure of using and import statements in parser (#25256)
This allows round-trip printing and more flexibility in how the statements are interpreted.
1 parent 5f59ef5 commit 1a6e34b

File tree

12 files changed

+208
-99
lines changed

12 files changed

+208
-99
lines changed

base/show.jl

+31-9
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,31 @@ function show_generator(io, ex, indent)
903903
end
904904
end
905905

906+
function show_import_path(io::IO, ex)
907+
if !isa(ex, Expr)
908+
print(io, ex)
909+
elseif ex.head === :(:)
910+
show_import_path(io, ex.args[1])
911+
print(io, ": ")
912+
for i = 2:length(ex.args)
913+
if i > 2
914+
print(io, ", ")
915+
end
916+
show_import_path(io, ex.args[i])
917+
end
918+
elseif ex.head === :(.)
919+
print(io, ex.args[1])
920+
for i = 2:length(ex.args)
921+
if ex.args[i-1] != :(.)
922+
print(io, '.')
923+
end
924+
print(io, ex.args[i])
925+
end
926+
else
927+
show_unquoted(io, ex)
928+
end
929+
end
930+
906931
# TODO: implement interpolated strings
907932
function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int)
908933
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)
12511276

12521277
elseif head === :import || head === :using
12531278
print(io, head)
1279+
print(io, ' ')
12541280
first = true
1255-
for a = args
1256-
if first
1257-
print(io, ' ')
1258-
first = false
1259-
else
1260-
print(io, '.')
1261-
end
1262-
if a !== :.
1263-
print(io, a)
1281+
for a in args
1282+
if !first
1283+
print(io, ", ")
12641284
end
1285+
first = false
1286+
show_import_path(io, a)
12651287
end
12661288
elseif head === :meta && length(args) >= 2 && args[1] === :push_loc
12671289
print(io, "# meta: location ", join(args[2:end], " "))

doc/src/devdocs/ast.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -435,12 +435,12 @@ parses as `(macrocall (|.| Core '@doc) (line) "some docs" (= (call f x) (block x
435435

436436
| Input | AST |
437437
|:------------------- |:-------------------------------------------- |
438-
| `import a` | `(import a)` |
439-
| `import a.b.c` | `(import a b c)` |
440-
| `import ...a` | `(import . . . a)` |
441-
| `import a.b, c.d` | `(toplevel (import a b) (import c d))` |
442-
| `import Base: x` | `(import Base x)` |
443-
| `import Base: x, y` | `(toplevel (import Base x) (import Base y))` |
438+
| `import a` | `(import (. a))` |
439+
| `import a.b.c` | `(import (. a b c))` |
440+
| `import ...a` | `(import (. . . . a))` |
441+
| `import a.b, c.d` | `(import (. a b) (. c d))` |
442+
| `import Base: x` | `(import (: (. Base) (. x)))` |
443+
| `import Base: x, y` | `(import (: (. Base) (. x) (. y)))` |
444444
| `export a, b` | `(export a b)` |
445445

446446
### Numbers

src/ast.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jl_sym_t *polly_sym; jl_sym_t *inline_sym;
5858
jl_sym_t *propagate_inbounds_sym; jl_sym_t *generated_sym;
5959
jl_sym_t *generated_only_sym;
6060
jl_sym_t *isdefined_sym; jl_sym_t *nospecialize_sym;
61-
jl_sym_t *macrocall_sym;
61+
jl_sym_t *macrocall_sym; jl_sym_t *colon_sym;
6262
jl_sym_t *hygienicscope_sym;
6363
jl_sym_t *escape_sym;
6464
jl_sym_t *gc_preserve_begin_sym; jl_sym_t *gc_preserve_end_sym;
@@ -319,6 +319,7 @@ void jl_init_frontend(void)
319319
structtype_sym = jl_symbol("struct_type");
320320
toplevel_sym = jl_symbol("toplevel");
321321
dot_sym = jl_symbol(".");
322+
colon_sym = jl_symbol(":");
322323
boundscheck_sym = jl_symbol("boundscheck");
323324
inbounds_sym = jl_symbol("inbounds");
324325
fastmath_sym = jl_symbol("fastmath");

src/julia-parser.scm

+5-10
Original file line numberDiff line numberDiff line change
@@ -1574,10 +1574,7 @@
15741574
(error "invalid \"export\" statement"))
15751575
`(export ,@es)))
15761576
((import using importall)
1577-
(let ((imports (parse-imports s word)))
1578-
(if (length= imports 1)
1579-
(car imports)
1580-
(cons 'toplevel imports))))
1577+
(parse-imports s word))
15811578
((do)
15821579
(error "invalid \"do\" syntax"))
15831580
(else (error "unhandled reserved word")))))))
@@ -1612,10 +1609,8 @@
16121609
(parse-comma-separated s (lambda (s)
16131610
(parse-import s word))))))
16141611
(if from
1615-
(map (lambda (x)
1616-
(cons (car x) (append (cdr first) (cdr x))))
1617-
rest)
1618-
(cons first rest))))
1612+
`(,word (|:| ,first ,@rest))
1613+
(list* word first rest))))
16191614

16201615
(define (parse-import-dots s)
16211616
(let loop ((l '())
@@ -1647,13 +1642,13 @@
16471642
(loop (cons (macrocall-to-atsym (parse-unary-prefix s)) path)))
16481643
((or (memv nxt '(#\newline #\; #\, :))
16491644
(eof-object? nxt))
1650-
`(,word ,@(reverse path)))
1645+
(cons '|.| (reverse path)))
16511646
((eqv? (string.sub (string nxt) 0 1) ".")
16521647
(take-token s)
16531648
(loop (cons (symbol (string.sub (string nxt) 1))
16541649
path)))
16551650
(else
1656-
`(,word ,@(reverse path)))))))
1651+
(cons '|.| (reverse path)))))))
16571652

16581653
;; parse comma-separated assignments, like "i=1:n,j=1:m,..."
16591654
(define (parse-comma-separated s what)

src/julia-syntax.scm

+1-1
Original file line numberDiff line numberDiff line change
@@ -1888,7 +1888,7 @@
18881888
(error (string "invalid " syntax-str " \"" (deparse el) "\""))))))))
18891889

18901890
(define (expand-forms e)
1891-
(if (or (atom? e) (memq (car e) '(quote inert top core globalref outerref line module toplevel ssavalue null meta)))
1891+
(if (or (atom? e) (memq (car e) '(quote inert top core globalref outerref line module toplevel ssavalue null meta using import importall export)))
18921892
e
18931893
(let ((ex (get expand-table (car e) #f)))
18941894
(if ex

src/julia_internal.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,7 @@ extern jl_sym_t *enter_sym; extern jl_sym_t *leave_sym;
10011001
extern jl_sym_t *exc_sym; extern jl_sym_t *new_sym;
10021002
extern jl_sym_t *compiler_temp_sym; extern jl_sym_t *foreigncall_sym;
10031003
extern jl_sym_t *const_sym; extern jl_sym_t *thunk_sym;
1004-
extern jl_sym_t *underscore_sym;
1004+
extern jl_sym_t *underscore_sym; extern jl_sym_t *colon_sym;
10051005
extern jl_sym_t *abstracttype_sym; extern jl_sym_t *primtype_sym;
10061006
extern jl_sym_t *structtype_sym;
10071007
extern jl_sym_t *global_sym; extern jl_sym_t *unused_sym;

src/toplevel.c

+102-35
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ static jl_module_t *call_require(jl_sym_t *var)
420420
// either:
421421
// - sets *name and returns the module to import *name from
422422
// - sets *name to NULL and returns a module to import
423-
static jl_module_t *eval_import_path(jl_module_t *from, jl_array_t *args, jl_sym_t **name, const char *keyword)
423+
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)
424424
{
425425
jl_sym_t *var = (jl_sym_t*)jl_array_ptr_ref(args, 0);
426426
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
429429
if (!jl_is_symbol(var))
430430
jl_type_error(keyword, (jl_value_t*)jl_sym_type, (jl_value_t*)var);
431431

432-
if (var != dot_sym) {
432+
if (from != NULL) {
433+
m = from;
434+
i = 0;
435+
}
436+
else if (var != dot_sym) {
433437
// `A.B`: call the loader to obtain the root A in the current environment.
434438
if (jl_core_module && var == jl_core_module->name) {
435439
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
445449
}
446450
else {
447451
// `.A.B.C`: strip off leading dots by following parent links
448-
m = from;
452+
m = where;
449453
while (1) {
450454
if (i >= jl_array_len(args))
451455
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
461465
var = (jl_sym_t*)jl_array_ptr_ref(args, i);
462466
if (!jl_is_symbol(var))
463467
jl_type_error(keyword, (jl_value_t*)jl_sym_type, (jl_value_t*)var);
468+
if (var == dot_sym)
469+
jl_errorf("invalid %s path: \".\" in identifier path", keyword);
464470
if (i == jl_array_len(args)-1)
465471
break;
466472
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
522528
return NULL;
523529
}
524530

531+
// in `import A.B: x, y, ...`, evaluate the `A.B` part if it exists
532+
static jl_module_t *eval_import_from(jl_module_t *m, jl_expr_t *ex, const char *keyword)
533+
{
534+
if (jl_expr_nargs(ex) == 1 && jl_is_expr(jl_exprarg(ex, 0))) {
535+
jl_expr_t *fr = (jl_expr_t*)jl_exprarg(ex, 0);
536+
if (fr->head == colon_sym) {
537+
if (jl_expr_nargs(fr) > 0 && jl_is_expr(jl_exprarg(fr, 0))) {
538+
jl_expr_t *path = (jl_expr_t*)jl_exprarg(fr, 0);
539+
if (((jl_expr_t*)path)->head == dot_sym) {
540+
jl_sym_t *name = NULL;
541+
jl_module_t *from = eval_import_path(m, NULL, path->args, &name, "import");
542+
if (name != NULL) {
543+
from = (jl_module_t*)jl_eval_global_var(from, name);
544+
if (!jl_is_module(from))
545+
jl_errorf("invalid %s path: \"%s\" does not name a module", keyword, jl_symbol_name(name));
546+
}
547+
return from;
548+
}
549+
}
550+
jl_errorf("malformed \"%s:\" expression", keyword);
551+
}
552+
}
553+
return NULL;
554+
}
555+
525556
static jl_code_info_t *expr_to_code_info(jl_value_t *expr)
526557
{
527558
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
569600
jl_sym_t *name = NULL;
570601
jl_depwarn("`importall` is deprecated, use `using` or individual `import` statements instead",
571602
(jl_value_t*)jl_symbol("importall"));
572-
jl_module_t *import = eval_import_path(m, ex->args, &name, "importall");
573-
if (name != NULL) {
574-
import = (jl_module_t*)jl_eval_global_var(import, name);
575-
if (!jl_is_module(import))
576-
jl_errorf("invalid %s statement: name exists but does not refer to a module", jl_symbol_name(ex->head));
603+
jl_module_t *from = eval_import_from(m, ex, "importall");
604+
size_t i = 0;
605+
if (from) {
606+
i = 1;
607+
ex = (jl_expr_t*)jl_exprarg(ex, 0);
608+
}
609+
for (; i < jl_expr_nargs(ex); i++) {
610+
jl_value_t *a = jl_exprarg(ex, i);
611+
if (jl_is_expr(a) && ((jl_expr_t*)a)->head == dot_sym) {
612+
name = NULL;
613+
jl_module_t *import = eval_import_path(m, from, ((jl_expr_t*)a)->args, &name, "importall");
614+
if (name != NULL) {
615+
import = (jl_module_t*)jl_eval_global_var(import, name);
616+
if (!jl_is_module(import))
617+
jl_errorf("invalid %s statement: name exists but does not refer to a module", jl_symbol_name(ex->head));
618+
}
619+
jl_module_importall(m, import);
620+
}
577621
}
578-
jl_module_importall(m, import);
579622
return jl_nothing;
580623
}
581624
else if (ex->head == using_sym) {
582625
jl_sym_t *name = NULL;
583-
jl_module_t *import = eval_import_path(m, ex->args, &name, "using");
584-
jl_module_t *u = import;
585-
if (name != NULL)
586-
u = (jl_module_t*)jl_eval_global_var(import, name);
587-
if (jl_is_module(u)) {
588-
jl_module_using(m, u);
589-
if (m == jl_main_module && name == NULL) {
590-
// TODO: for now, `using A` in Main also creates an explicit binding for `A`
591-
// This will possibly be extended to all modules.
592-
import_module(m, u);
593-
}
626+
jl_module_t *from = eval_import_from(m, ex, "using");
627+
size_t i = 0;
628+
if (from) {
629+
i = 1;
630+
ex = (jl_expr_t*)jl_exprarg(ex, 0);
594631
}
595-
else {
596-
jl_module_t *replacement = deprecation_replacement_module(import, name);
597-
if (replacement)
598-
jl_module_using(m, replacement);
599-
else
600-
jl_module_use(m, import, name);
632+
for (; i < jl_expr_nargs(ex); i++) {
633+
jl_value_t *a = jl_exprarg(ex, i);
634+
if (jl_is_expr(a) && ((jl_expr_t*)a)->head == dot_sym) {
635+
name = NULL;
636+
jl_module_t *import = eval_import_path(m, from, ((jl_expr_t*)a)->args, &name, "using");
637+
jl_module_t *u = import;
638+
if (name != NULL)
639+
u = (jl_module_t*)jl_eval_global_var(import, name);
640+
if (jl_is_module(u)) {
641+
jl_module_using(m, u);
642+
if (m == jl_main_module && name == NULL) {
643+
// TODO: for now, `using A` in Main also creates an explicit binding for `A`
644+
// This will possibly be extended to all modules.
645+
import_module(m, u);
646+
}
647+
}
648+
else {
649+
jl_module_t *replacement = deprecation_replacement_module(import, name);
650+
if (replacement)
651+
jl_module_using(m, replacement);
652+
else
653+
jl_module_use(m, import, name);
654+
}
655+
}
601656
}
602657
return jl_nothing;
603658
}
604659
else if (ex->head == import_sym) {
605660
jl_sym_t *name = NULL;
606-
jl_module_t *import = eval_import_path(m, ex->args, &name, "import");
607-
if (name == NULL) {
608-
import_module(m, import);
661+
jl_module_t *from = eval_import_from(m, ex, "import");
662+
size_t i = 0;
663+
if (from) {
664+
i = 1;
665+
ex = (jl_expr_t*)jl_exprarg(ex, 0);
609666
}
610-
else {
611-
jl_module_t *replacement = deprecation_replacement_module(import, name);
612-
if (replacement)
613-
import_module(m, replacement);
614-
else
615-
jl_module_import(m, import, name);
667+
for (; i < jl_expr_nargs(ex); i++) {
668+
jl_value_t *a = jl_exprarg(ex, i);
669+
if (jl_is_expr(a) && ((jl_expr_t*)a)->head == dot_sym) {
670+
name = NULL;
671+
jl_module_t *import = eval_import_path(m, from, ((jl_expr_t*)a)->args, &name, "import");
672+
if (name == NULL) {
673+
import_module(m, import);
674+
}
675+
else {
676+
jl_module_t *replacement = deprecation_replacement_module(import, name);
677+
if (replacement)
678+
import_module(m, replacement);
679+
else
680+
jl_module_import(m, import, name);
681+
}
682+
}
616683
}
617684
return jl_nothing;
618685
}

stdlib/Distributed/src/macros.jl

+9-2
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,16 @@ end
121121
extract_imports!(imports, x) = imports
122122
function extract_imports!(imports, ex::Expr)
123123
if Meta.isexpr(ex, (:import, :using))
124-
return push!(imports, ex.args[1])
124+
m = ex.args[1]
125+
if isa(m, Expr) && m.head === :(:)
126+
push!(imports, m.args[1].args[1])
127+
else
128+
for a in ex.args
129+
push!(imports, a.args[1])
130+
end
131+
end
125132
elseif Meta.isexpr(ex, :let)
126-
return extract_imports!(imports, ex.args[2])
133+
extract_imports!(imports, ex.args[2])
127134
elseif Meta.isexpr(ex, (:toplevel, :block))
128135
for i in eachindex(ex.args)
129136
extract_imports!(imports, ex.args[i])

stdlib/Distributed/test/distributed_exec.jl

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import Distributed: launch, manage
55

66
include(joinpath(Sys.BINDIR, "..", "share", "julia", "test", "testenv.jl"))
77

8+
@test Distributed.extract_imports(:(begin; import Foo, Bar; let; using Baz; end; end)) ==
9+
[:Foo, :Bar, :Baz]
10+
811
# Test a few "remote" invocations when no workers are present
912
@test remote(myid)() == 1
1013
@test pmap(identity, 1:100) == [1:100...]

test/meta.jl

-4
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,6 @@ show_sexpr(ioB,:(1+1))
128128

129129
show_sexpr(ioB,QuoteNode(1),1)
130130

131-
using Distributed
132-
@test Distributed.extract_imports(:(begin; import Foo, Bar; let; using Baz; end; end)) ==
133-
[:Foo, :Bar, :Baz]
134-
135131
# test base/expr.jl
136132
baremodule B
137133
eval = 0

0 commit comments

Comments
 (0)