Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add import ... as ... syntax #37396

Merged
merged 2 commits into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1242,7 +1242,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