Skip to content

Commit

Permalink
add import ... as ... syntax. closes #1255
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson committed Sep 4, 2020
1 parent 315ad46 commit f3e8d03
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 39 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ New language features
* The library name passed to `ccall` or `@ccall` can now be an expression involving
global variables and function calls. The expression will be evaluated the first
time the `ccall` executes ([#36458]).
* The syntax `import A as B` (plus `import A: x as y`, etc.) can now be used to
rename imported modules and identifiers ([#1255]).

Language changes
----------------
Expand Down
9 changes: 8 additions & 1 deletion base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1444,7 +1444,10 @@ function show_generator(io, ex::Expr, indent, quote_level)
end
end

function valid_import_path(@nospecialize ex)
function valid_import_path(@nospecialize(ex), allow_as = true)
if allow_as && is_expr(ex, :as) && length((ex::Expr).args) == 2
ex = (ex::Expr).args[1]
end
return is_expr(ex, :(.)) && length((ex::Expr).args) > 0 && all(a->isa(a,Symbol), (ex::Expr).args)
end

Expand Down Expand Up @@ -1992,6 +1995,10 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
first = false
show_import_path(io, a, quote_level)
end
elseif head === :as && nargs == 2 && valid_import_path(args[1], false)
show_import_path(io, args[1], quote_level)
print(io, " as ")
show_unquoted(io, args[2], indent, 0, quote_level)
elseif head === :meta && nargs >= 2 && args[1] === :push_loc
print(io, "# meta: location ", join(args[2:end], " "))
elseif head === :meta && nargs == 1 && args[1] === :pop_loc
Expand Down
31 changes: 29 additions & 2 deletions doc/src/manual/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ using Lib

using BigLib: thing1, thing2

import Base.show
import Base.show, Base.print as pr

export MyType, foo

Expand All @@ -26,7 +26,7 @@ end
bar(x) = 2x
foo(a::MyType) = bar(a.x) + 1

show(io::IO, a::MyType) = print(io, "MyType $(a.x)")
show(io::IO, a::MyType) = pr(io, "MyType $(a.x)")
end
```

Expand Down Expand Up @@ -86,6 +86,33 @@ functions into the current workspace:
| `import MyModule.x, MyModule.p` | `x` and `p` | `x` and `p` |
| `import MyModule: x, p` | `x` and `p` | `x` and `p` |

### Import renaming

An identifier brought into scope by `import` can be renamed using the keyword `as`. This is useful for
working around name conflicts as well as for shortening names.
For example, `Base` exports the function name `read`, but the CSV.jl package also provides `CSV.read`.
If we are going to invoke CSV reading many times, it would be convenient to drop the `CSV.` qualifier.
But then it is ambiguous whether we are referring to `Base.read` or `CSV.read`:

```julia
julia> read;

julia> import CSV.read
WARNING: ignoring conflicting import of CSV.read into Main
```

Renaming provides a solution:

```julia
julia> import CSV.read as rd
```

Imported packages themselves can also be renamed:

```julia
import BenchmarkTools as bt
```

### Modules and files

Files and file names are mostly unrelated to modules; modules are associated only with module
Expand Down
3 changes: 2 additions & 1 deletion src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jl_sym_t *exc_sym; jl_sym_t *error_sym;
jl_sym_t *new_sym; jl_sym_t *using_sym;
jl_sym_t *splatnew_sym;
jl_sym_t *const_sym; jl_sym_t *thunk_sym;
jl_sym_t *foreigncall_sym;
jl_sym_t *foreigncall_sym; jl_sym_t *as_sym;
jl_sym_t *global_sym; jl_sym_t *list_sym;
jl_sym_t *dot_sym; jl_sym_t *newvar_sym;
jl_sym_t *boundscheck_sym; jl_sym_t *inbounds_sym;
Expand Down Expand Up @@ -363,6 +363,7 @@ void jl_init_common_symbols(void)
thunk_sym = jl_symbol("thunk");
toplevel_sym = jl_symbol("toplevel");
dot_sym = jl_symbol(".");
as_sym = jl_symbol("as");
colon_sym = jl_symbol(":");
boundscheck_sym = jl_symbol("boundscheck");
inbounds_sym = jl_symbol("inbounds");
Expand Down
33 changes: 18 additions & 15 deletions src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@
(string ":" (deparse e))
(deparse e)))

(define (deparse-import-path e)
(cond ((and (pair? e) (eq? (car e) '|.|))
(let loop ((lst (cdr e))
(ndots 0))
(if (or (null? lst)
(not (eq? (car lst) '|.|)))
(string (string.rep "." ndots)
(string.join (map deparse lst) "."))
(loop (cdr lst) (+ ndots 1)))))
((and (pair? e) (eq? (car e) ':))
(string (deparse-import-path (cadr e)) ": "
(string.join (map deparse-import-path (cddr e)) ", ")))
(else
(string e))))

(define (deparse e (ilvl 0))
(cond ((or (symbol? e) (number? e)) (string e))
((string? e) (print-to-string e))
Expand All @@ -71,6 +86,8 @@
(if (length= e 2)
(string (car e) (deparse (cadr e)))
(string (deparse (cadr e)) " " (car e) " " (deparse (caddr e)))))
((eq? (car e) 'as)
(string (deparse-import-path (cadr e)) " as " (deparse (caddr e))))
(else
(case (car e)
((null) "nothing")
Expand Down Expand Up @@ -209,21 +226,7 @@
"end"))
;; misc syntax forms
((import using)
(define (deparse-path e)
(cond ((and (pair? e) (eq? (car e) '|.|))
(let loop ((lst (cdr e))
(ndots 0))
(if (or (null? lst)
(not (eq? (car lst) '|.|)))
(string (string.rep "." ndots)
(string.join (map deparse lst) "."))
(loop (cdr lst) (+ ndots 1)))))
((and (pair? e) (eq? (car e) ':))
(string (deparse-path (cadr e)) ": "
(string.join (map deparse-path (cddr e)) ", ")))
(else
(string e))))
(string (car e) " " (string.join (map deparse-path (cdr e)) ", ")))
(string (car e) " " (string.join (map deparse-import-path (cdr e)) ", ")))
((global local export) (string (car e) " " (string.join (map deparse (cdr e)) ", ")))
((const) (string "const " (deparse (cadr e))))
((top) (deparse (cadr e)))
Expand Down
19 changes: 16 additions & 3 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1578,10 +1578,13 @@
(define (parse-imports s word)
(let* ((first (parse-import s word))
(next (peek-token s))
(from (and (eq? next ':) (not (ts:space? s))))
(from (and (eq? next ':) (not (ts:space? s))
(or (not (and (pair? first) (eq? (car first) 'as)))
(error (string "invalid syntax \"" word " " (deparse first) ":\"")))))
(done (cond ((or from (eqv? next #\,))
(begin (take-token s) #f))
((or (eq? next '|.|)
;; TODO: this seems to be wrong; figure out if it's needed
#;((or (eq? next '|.|)
(eqv? (string.sub (string next) 0 1) ".")) #f)
(else #t)))
(rest (if done
Expand Down Expand Up @@ -1615,7 +1618,7 @@
(else
(cons (macrocall-to-atsym (parse-unary-prefix s)) l)))))

(define (parse-import s word)
(define (parse-import-path s word)
(let loop ((path (parse-import-dots s)))
(if (not (symbol-or-interpolate? (car path)))
(error (string "invalid \"" word "\" statement: expected identifier")))
Expand All @@ -1635,6 +1638,16 @@
(else
(cons '|.| (reverse path)))))))

(define (parse-import s word)
(let ((path (parse-import-path s word)))
(if (eq? (peek-token s) 'as)
(begin
(if (eq? word 'using)
(error "\"as\" can only be used with \"import\", not \"using\""))
(take-token s)
`(as ,path ,(parse-unary-prefix s)))
path)))

;; parse comma-separated assignments, like "i=1:n,j=1:m,..."
(define (parse-comma-separated s what)
(let loop ((exprs '()))
Expand Down
4 changes: 2 additions & 2 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1474,8 +1474,8 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_
JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b);
JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from);
JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s);
JL_DLLEXPORT void jl_module_import(jl_module_t *to, jl_module_t *from,
jl_sym_t *s);
JL_DLLEXPORT void jl_module_import(jl_module_t *to, jl_module_t *from, jl_sym_t *s);
JL_DLLEXPORT void jl_module_import_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname);
JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s);
JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s);
JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) JL_NOTSAFEPOINT;
Expand Down
2 changes: 1 addition & 1 deletion src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,7 @@ extern jl_sym_t *new_sym; extern jl_sym_t *using_sym;
extern jl_sym_t *splatnew_sym;
extern jl_sym_t *pop_exception_sym;
extern jl_sym_t *const_sym; extern jl_sym_t *thunk_sym;
extern jl_sym_t *foreigncall_sym;
extern jl_sym_t *foreigncall_sym; extern jl_sym_t *as_sym;
extern jl_sym_t *global_sym; extern jl_sym_t *list_sym;
extern jl_sym_t *dot_sym; extern jl_sym_t *newvar_sym;
extern jl_sym_t *boundscheck_sym; extern jl_sym_t *inbounds_sym;
Expand Down
29 changes: 21 additions & 8 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_
return b;
}

static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s,
static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname,
int explici);

typedef struct _modstack_t {
Expand Down Expand Up @@ -321,14 +321,14 @@ static jl_binding_t *jl_get_binding_(jl_module_t *m, jl_sym_t *var, modstack_t *
// do a full import to prevent the result of this lookup
// from changing, for example if this var is assigned to
// later.
module_import_(m, b->owner, var, 0);
module_import_(m, b->owner, var, var, 0);
return b;
}
return NULL;
}
JL_UNLOCK(&m->lock);
if (b->owner != m)
return jl_get_binding_(b->owner, var, &top);
return jl_get_binding_(b->owner, b->name, &top);
return b;
}

Expand Down Expand Up @@ -394,7 +394,7 @@ JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s)
}

// NOTE: we use explici since explicit is a C++ keyword
static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, int explici)
static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname, int explici)
{
jl_binding_t *b = jl_get_binding(from, s);
if (b == NULL) {
Expand All @@ -421,19 +421,27 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, int
}

JL_LOCK(&to->lock);
jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&to->bindings, s);
jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&to->bindings, asname);
jl_binding_t *bto = *bp;
if (bto != HT_NOTFOUND) {
if (bto == b) {
// importing a binding on top of itself. harmless.
}
else if (bto->name != s) {
JL_UNLOCK(&to->lock);
jl_printf(JL_STDERR,
"WARNING: ignoring conflicting import of %s.%s into %s\n",
jl_symbol_name(from->name), jl_symbol_name(s),
jl_symbol_name(to->name));
return;
}
else if (bto->owner == b->owner) {
// already imported
bto->imported = (explici!=0);
}
else if (bto->owner != to && bto->owner != NULL) {
// already imported from somewhere else
jl_binding_t *bval = jl_get_binding(to, s);
jl_binding_t *bval = jl_get_binding(to, asname);
if (bval->constp && bval->value && b->constp && b->value == bval->value) {
// equivalent binding
bto->imported = (explici!=0);
Expand Down Expand Up @@ -483,12 +491,17 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, int

JL_DLLEXPORT void jl_module_import(jl_module_t *to, jl_module_t *from, jl_sym_t *s)
{
module_import_(to, from, s, 1);
module_import_(to, from, s, s, 1);
}

JL_DLLEXPORT void jl_module_import_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname)
{
module_import_(to, from, s, asname, 1);
}

JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s)
{
module_import_(to, from, s, 0);
module_import_(to, from, s, s, 0);
}

JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from)
Expand Down
27 changes: 21 additions & 6 deletions src/toplevel.c
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,10 @@ static jl_method_instance_t *method_instance_for_thunk(jl_code_info_t *src, jl_m
return li;
}

static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import)
static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym_t *asname)
{
assert(m);
jl_sym_t *name = import->name;
jl_sym_t *name = asname ? asname : import->name;
jl_binding_t *b;
if (jl_binding_resolved_p(m, name)) {
b = jl_get_binding(m, name);
Expand Down Expand Up @@ -691,7 +691,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int
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);
import_module(m, u, NULL);
}
}
}
Expand All @@ -716,15 +716,30 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int
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);
import_module(m, import, NULL);
}
else {
jl_module_import(m, import, name);
}
continue;
}
else {
jl_eval_errorf(m, "syntax: malformed \"import\" statement");
else if (jl_is_expr(a) && ((jl_expr_t*)a)->head == as_sym && jl_expr_nargs(a) == 2 &&
jl_is_expr(jl_exprarg(a, 0)) && ((jl_expr_t*)jl_exprarg(a, 0))->head == dot_sym) {
jl_sym_t *asname = (jl_sym_t*)jl_exprarg(a, 1);
if (jl_is_symbol(asname)) {
jl_expr_t *path = (jl_expr_t*)jl_exprarg(a, 0);
name = NULL;
jl_module_t *import = eval_import_path(m, from, ((jl_expr_t*)path)->args, &name, "import");
if (name == NULL) {
import_module(m, import, asname);
}
else {
jl_module_import_as(m, import, name, asname);
}
continue;
}
}
jl_eval_errorf(m, "syntax: malformed \"import\" statement");
}
JL_GC_POP();
return jl_nothing;
Expand Down
5 changes: 5 additions & 0 deletions test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ end
@test_repr "import A.B.C: a, x, y.z"
@test_repr "import ..A: a, x, y.z"
@test_repr "import A.B, C.D"
@test_repr "import A as B"
@test_repr "import A.x as y"
@test_repr "import A: x as y"
@test_repr "import A.B: x, y as z"
@test_repr "import A.B: x, y as z, a.b as c, xx"

# keyword args (issue #34023 and #32775)
@test_repr "f(a, b=c)"
Expand Down
Loading

0 comments on commit f3e8d03

Please sign in to comment.