Skip to content

Commit

Permalink
add import ... as ... syntax. closes JuliaLang#1255 (JuliaLang#37396)
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson committed Oct 8, 2020
1 parent b738ce8 commit 40fee86
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 48 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ New language features
`(x...) -> (op).(x...)`, which can be useful for passing the broadcasted version of an
operator to higher-order functions, like for example `map(.*, A, B)` for an elementwise
product of two arrays of arrays. ([#37583])
* The syntax `import A as B` (plus `import A: x as y`, `import A.x as y`, and `using A: x as y`)
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 @@ -1447,7 +1447,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 @@ -1998,6 +2001,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
37 changes: 34 additions & 3 deletions doc/src/manual/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ is shown for illustrative purposes:
module MyModule
using Lib

using BigLib: thing1, thing2
using BigLib: thing1, thing2, thing3 as t3

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,37 @@ 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` or `using` can be renamed with 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
```

`as` works with `using` only when a single identifier is brought into scope.
For example `using CSV: read as rd` works, but `using CSV as C` does not, since it operates
on all of the exported names in `CSV`.

### 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
27 changes: 21 additions & 6 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1580,26 +1580,31 @@
e))

(define (parse-imports s word)
(let* ((first (parse-import s word))
(let* ((first (parse-import s word #f))
(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
'()
(let ((ex (parse-comma-separated s (lambda (s)
(parse-import s word)))))
(parse-import s word from)))))
(if (eq? (peek-token s) ':)
(error (string "\":\" in \"" word "\" syntax can only be used "
"when importing a single module. "
"Split imports into multiple lines."))
ex)))))
(if from
`(,word (|:| ,first ,@rest))
(list* word first rest))))
(begin (if (and (eq? word 'using) (pair? first) (eq? (car first) 'as))
(error (string "invalid syntax \"using " (deparse-import-path (cadr first)) " as ...\"")))
(list* word first rest)))))

(define (parse-import-dots s)
(let loop ((l '())
Expand All @@ -1619,7 +1624,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 @@ -1639,6 +1644,16 @@
(else
(cons '|.| (reverse path)))))))

(define (parse-import s word from)
(let ((path (parse-import-path s word)))
(if (eq? (peek-token s) 'as)
(begin
(if (and (not from) (eq? word 'using))
(error (string "invalid syntax \"using " (deparse-import-path path) " as ...\"")))
(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
5 changes: 3 additions & 2 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1474,8 +1474,9 @@ 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_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname);
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 @@ -1244,7 +1244,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
40 changes: 29 additions & 11 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,14 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_
}
else {
JL_UNLOCK_NOGC(&m->lock);
jl_binding_t *b2 = jl_get_binding(b->owner, var);
jl_binding_t *b2 = jl_get_binding(b->owner, b->name);
if (b2 == NULL || b2->value == NULL)
jl_errorf("invalid method definition: imported function %s.%s does not exist",
jl_symbol_name(b->owner->name), jl_symbol_name(var));
jl_symbol_name(b->owner->name), jl_symbol_name(b->name));
// TODO: we might want to require explicitly importing types to add constructors
if (!b->imported && !jl_is_type(b2->value)) {
jl_errorf("error in method definition: function %s.%s must be explicitly imported to be extended",
jl_symbol_name(b->owner->name), jl_symbol_name(var));
jl_symbol_name(b->owner->name), jl_symbol_name(b->name));
}
return b2;
}
Expand All @@ -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 @@ -331,14 +331,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 @@ -404,7 +404,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 @@ -431,19 +431,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 @@ -493,12 +501,22 @@ 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_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname)
{
module_import_(to, from, s, asname, 0);
}

JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from)
Expand Down
Loading

0 comments on commit 40fee86

Please sign in to comment.