Skip to content

Commit 8d85aa4

Browse files
authored
Merge pull request JuliaLang#22985 from JuliaLang/jn/22307
fix some macro expansion scope bugs
2 parents 952dc93 + 7ec9b18 commit 8d85aa4

File tree

10 files changed

+149
-90
lines changed

10 files changed

+149
-90
lines changed

base/deprecated.jl

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -232,25 +232,19 @@ end
232232

233233
# For deprecating vectorized functions in favor of compact broadcast syntax
234234
macro dep_vectorize_1arg(S, f)
235-
S = esc(S)
236-
f = esc(f)
237-
T = esc(:T)
238-
x = esc(:x)
239-
AbsArr = esc(:AbstractArray)
240-
:( @deprecate $f($x::$AbsArr{$T}) where {$T<:$S} $f.($x) )
235+
x = esc(:x) # work around macro hygiene bug
236+
T = esc(:T) # work around macro hygiene bug
237+
return :( @deprecate $f($x::AbstractArray{$T}) where {$T<:$S} $f.($x) )
241238
end
242239
macro dep_vectorize_2arg(S, f)
243-
S = esc(S)
244-
f = esc(f)
245-
T1 = esc(:T1)
246-
T2 = esc(:T2)
247-
x = esc(:x)
248-
y = esc(:y)
249-
AbsArr = esc(:AbstractArray)
250-
quote
251-
@deprecate $f($x::$S, $y::$AbsArr{$T1}) where {$T1<:$S} $f.($x,$y)
252-
@deprecate $f($x::$AbsArr{$T1}, $y::$S) where {$T1<:$S} $f.($x,$y)
253-
@deprecate $f($x::$AbsArr{$T1}, $y::$AbsArr{$T2}) where {$T1<:$S,$T2<:$S} $f.($x,$y)
240+
x = esc(:x) # work around macro hygiene bug
241+
y = esc(:y) # work around macro hygiene bug
242+
T1 = esc(:T1) # work around macro hygiene bug
243+
T2 = esc(:T2) # work around macro hygiene bug
244+
return quote
245+
@deprecate $f($x::$S, $y::AbstractArray{$T1}) where {$T1<:$S} $f.($x, $y)
246+
@deprecate $f($x::AbstractArray{$T1}, $y::$S) where {$T1<:$S} $f.($x, $y)
247+
@deprecate $f($x::AbstractArray{$T1}, $y::AbstractArray{$T2}) where {$T1<:$S, $T2<:$S} $f.($x, $y)
254248
end
255249
end
256250

@@ -338,20 +332,20 @@ for f in (
338332
end
339333

340334
# Deprecate @vectorize_1arg and @vectorize_2arg themselves
341-
macro vectorize_1arg(S,f)
335+
macro vectorize_1arg(S, f)
342336
depwarn(string("`@vectorize_1arg` is deprecated in favor of compact broadcast syntax. ",
343337
"Instead of `@vectorize_1arg`'ing function `f` and calling `f(arg)`, call `f.(arg)`."),
344338
:vectorize_1arg)
345339
quote
346-
@dep_vectorize_1arg($(esc(S)),$(esc(f)))
340+
@dep_vectorize_1arg($S, $f)
347341
end
348342
end
349-
macro vectorize_2arg(S,f)
343+
macro vectorize_2arg(S, f)
350344
depwarn(string("`@vectorize_2arg` is deprecated in favor of compact broadcast syntax. ",
351345
"Instead of `@vectorize_2arg`'ing function `f` and calling `f(arg1, arg2)`, call ",
352-
"`f.(arg1,arg2)`. "), :vectorize_2arg)
346+
"`f.(arg1, arg2)`. "), :vectorize_2arg)
353347
quote
354-
@dep_vectorize_2arg($(esc(S)),$(esc(f)))
348+
@dep_vectorize_2arg($S, $f)
355349
end
356350
end
357351
export @vectorize_1arg, @vectorize_2arg

base/distributed/macros.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ processes to have execute the expression.
157157
Equivalent to calling `remotecall_eval(Main, procs, expr)`.
158158
"""
159159
macro everywhere(ex)
160-
return :(@everywhere $procs() $(esc(ex))) # interpolation needs to work around hygiene bugs (#22307)
160+
return :(@everywhere procs() $ex)
161161
end
162162

163163
macro everywhere(procs, ex)

base/docs/utils.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,15 @@ end
195195
isregex(x) = isexpr(x, :macrocall, 3) && x.args[1] === Symbol("@r_str") && !isempty(x.args[3])
196196
repl(io::IO, ex::Expr) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex)
197197
repl(io::IO, str::AbstractString) = :(apropos($io, $str))
198-
repl(io::IO, other) = :(@doc $(esc(other)))
198+
repl(io::IO, other) = :(@doc $other)
199199

200200
repl(x) = repl(STDOUT, x)
201201

202202
function _repl(x)
203203
if (isexpr(x, :call) && !any(isexpr(x, :(::)) for x in x.args))
204204
x.args[2:end] = [:(::typeof($arg)) for arg in x.args[2:end]]
205205
end
206-
docs = :(@doc $(esc(x)))
206+
docs = :(@doc $x)
207207
if isfield(x)
208208
quote
209209
if isa($(esc(x.args[1])), DataType)

base/interactiveutil.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ function workspace()
670670
:(const LastMain = $last),
671671
:(include(x) = $include($m, x))))
672672
empty!(package_locks)
673-
nothing
673+
return m
674674
end
675675

676676
# testing

src/ast.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,16 @@ value_t fl_invoke_julia_macro(fl_context_t *fl_ctx, value_t *args, uint32_t narg
173173
JL_TIMING(MACRO_INVOCATION);
174174
jl_ptls_t ptls = jl_get_ptls_states();
175175
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
176-
if (nargs < 2) // macro name and location
177-
argcount(fl_ctx, "invoke-julia-macro", nargs, 2);
178-
nargs++; // add __module__ argument
176+
if (nargs < 3) // macro name and location
177+
argcount(fl_ctx, "invoke-julia-macro", nargs, 3);
179178
jl_method_instance_t *mfunc = NULL;
180179
jl_value_t **margs;
181180
// Reserve one more slot for the result
182181
JL_GC_PUSHARGS(margs, nargs + 1);
183182
int i;
184-
margs[0] = scm_to_julia(fl_ctx, args[0], 1);
183+
margs[0] = scm_to_julia(fl_ctx, args[1], 1);
185184
// __source__ argument
186-
jl_value_t *lno = scm_to_julia(fl_ctx, args[1], 1);
185+
jl_value_t *lno = scm_to_julia(fl_ctx, args[2], 1);
187186
margs[1] = lno;
188187
if (jl_is_expr(lno) && ((jl_expr_t*)lno)->head == line_sym) {
189188
jl_value_t *file = jl_nothing;
@@ -206,12 +205,16 @@ value_t fl_invoke_julia_macro(fl_context_t *fl_ctx, value_t *args, uint32_t narg
206205
}
207206
margs[2] = (jl_value_t*)ctx->module;
208207
for (i = 3; i < nargs; i++)
209-
margs[i] = scm_to_julia(fl_ctx, args[i - 1], 1);
208+
margs[i] = scm_to_julia(fl_ctx, args[i], 1);
209+
margs[nargs] = scm_to_julia(fl_ctx, args[0], 1); // module context for @macrocall argument
210210
jl_value_t *result = NULL;
211211
size_t world = jl_get_ptls_states()->world_age;
212212

213213
JL_TRY {
214-
margs[0] = jl_toplevel_eval(ctx->module, margs[0]);
214+
jl_module_t *m = (jl_module_t*)margs[nargs];
215+
if (!jl_is_module(m))
216+
m = ctx->module;
217+
margs[0] = jl_toplevel_eval(m, margs[0]);
215218
mfunc = jl_method_lookup(jl_gf_mtable(margs[0]), margs, nargs, 1, world);
216219
if (mfunc == NULL) {
217220
jl_method_error((jl_function_t*)margs[0], margs, nargs, world);

src/macroexpand.scm

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
(call (top append_any) ,@forms)))
4747
(loop (cdr p) (cons (julia-bq-bracket (car p) d) q)))))))
4848

49+
(define (julia-bq-expand-hygienic x unhygienic)
50+
(let ((expanded (julia-bq-expand x 0)))
51+
(if unhygienic expanded `(escape ,expanded))))
52+
4953
;; hygiene
5054

5155
;; return the names of vars introduced by forms, instead of their transformations.
@@ -191,6 +195,7 @@
191195
(case (car v)
192196
((... kw |::|) (try-arg-name (cadr v)))
193197
((escape) (list v))
198+
((hygienic-scope) (try-arg-name (cadr v)))
194199
((meta) ;; allow certain per-argument annotations
195200
(if (nospecialize-meta? v #t)
196201
(try-arg-name (caddr v))
@@ -230,15 +235,15 @@
230235
;; resolve-expansion-vars-with-new-env, but turn on `inarg` once we get inside
231236
;; the formal argument list. `e` in general might be e.g. `(f{T}(x)::T) where T`,
232237
;; and we want `inarg` to be true for the `(x)` part.
233-
(define (resolve-in-function-lhs e env m inarg)
234-
(define (recur x) (resolve-in-function-lhs x env m inarg))
235-
(define (other x) (resolve-expansion-vars-with-new-env x env m inarg))
238+
(define (resolve-in-function-lhs e env m parent-scope inarg)
239+
(define (recur x) (resolve-in-function-lhs x env m parent-scope inarg))
240+
(define (other x) (resolve-expansion-vars-with-new-env x env m parent-scope inarg))
236241
(case (car e)
237242
((where) `(where ,(recur (cadr e)) ,@(map other (cddr e))))
238243
((|::|) `(|::| ,(recur (cadr e)) ,(other (caddr e))))
239244
((call) `(call ,(other (cadr e))
240245
,@(map (lambda (x)
241-
(resolve-expansion-vars-with-new-env x env m #t))
246+
(resolve-expansion-vars-with-new-env x env m parent-scope #t))
242247
(cddr e))))
243248
(else (other e))))
244249

@@ -268,17 +273,17 @@
268273
(diff (keywords-introduced-by x) globals))))
269274
env)))))))
270275

271-
(define (resolve-expansion-vars-with-new-env x env m inarg (outermost #f))
276+
(define (resolve-expansion-vars-with-new-env x env m parent-scope inarg (outermost #f))
272277
(resolve-expansion-vars-
273278
x
274279
(if (and (pair? x) (eq? (car x) 'let))
275280
;; let is strange in that it needs both old and new envs within
276281
;; the same expression
277282
env
278283
(new-expansion-env-for x env outermost))
279-
m inarg))
284+
m parent-scope inarg))
280285

281-
(define (resolve-expansion-vars- e env m inarg)
286+
(define (resolve-expansion-vars- e env m parent-scope inarg)
282287
(cond ((or (eq? e 'true) (eq? e 'false) (eq? e 'end) (eq? e 'ccall))
283288
e)
284289
((symbol? e)
@@ -291,7 +296,13 @@
291296
(else
292297
(case (car e)
293298
((ssavalue) e)
294-
((escape) (cadr e))
299+
((escape) (if (null? parent-scope)
300+
(julia-expand-macroscopes (cadr e))
301+
(let* ((scope (car parent-scope))
302+
(env (car scope))
303+
(m (cadr scope))
304+
(parent-scope (cdr parent-scope)))
305+
(resolve-expansion-vars-with-new-env (cadr e) env m parent-scope inarg))))
295306
((global) (let ((arg (cadr e)))
296307
(cond ((symbol? arg) e)
297308
((assignment? arg)
@@ -301,77 +312,78 @@
301312
(else
302313
`(global ,(resolve-expansion-vars-with-new-env arg env m inarg))))))
303314
((using import importall export meta line inbounds boundscheck simdloop) (map unescape e))
304-
((macrocall)
305-
(if (or (eq? (cadr e) '@label) (eq? (cadr e) '@goto)) e
306-
`(macrocall ,.(map (lambda (x)
307-
(resolve-expansion-vars-with-new-env x env m inarg))
308-
(cdr e)))))
315+
((macrocall) e) ; invalid syntax anyways, so just act like it's quoted.
309316
((symboliclabel) e)
310317
((symbolicgoto) e)
311318
((type)
312-
`(type ,(cadr e) ,(resolve-expansion-vars- (caddr e) env m inarg)
319+
`(type ,(cadr e) ,(resolve-expansion-vars- (caddr e) env m parent-scope inarg)
313320
;; type has special behavior: identifiers inside are
314321
;; field names, not expressions.
315322
,(map (lambda (x)
316323
(cond ((atom? x) x)
317324
((and (pair? x) (eq? (car x) '|::|))
318325
`(|::| ,(cadr x)
319-
,(resolve-expansion-vars- (caddr x) env m inarg)))
326+
,(resolve-expansion-vars- (caddr x) env m parent-scope inarg)))
320327
(else
321-
(resolve-expansion-vars-with-new-env x env m inarg))))
328+
(resolve-expansion-vars-with-new-env x env m parent-scope inarg))))
322329
(cadddr e))))
323330

324331
((parameters)
325332
(cons 'parameters
326333
(map (lambda (x)
327-
(resolve-expansion-vars- x env m #f))
334+
(resolve-expansion-vars- x env m parent-scope #f))
328335
(cdr e))))
329336

330337
((= function)
331338
(if (and (pair? (cadr e)) (function-def? e))
332339
;; in (kw x 1) inside an arglist, the x isn't actually a kwarg
333-
`(,(car e) ,(resolve-in-function-lhs (cadr e) env m inarg)
334-
,(resolve-expansion-vars-with-new-env (caddr e) env m inarg))
340+
`(,(car e) ,(resolve-in-function-lhs (cadr e) env m parent-scope inarg)
341+
,(resolve-expansion-vars-with-new-env (caddr e) env m parent-scope inarg))
335342
`(,(car e) ,@(map (lambda (x)
336-
(resolve-expansion-vars-with-new-env x env m inarg))
343+
(resolve-expansion-vars-with-new-env x env m parent-scope inarg))
337344
(cdr e)))))
338345

339346
((kw)
340347
(if (and (pair? (cadr e))
341348
(eq? (caadr e) '|::|))
342349
`(kw (|::|
343350
,(if inarg
344-
(resolve-expansion-vars- (cadr (cadr e)) env m inarg)
351+
(resolve-expansion-vars- (cadr (cadr e)) env m parent-scope inarg)
345352
;; in keyword arg A=B, don't transform "A"
346353
(unescape (cadr (cadr e))))
347-
,(resolve-expansion-vars- (caddr (cadr e)) env m inarg))
348-
,(resolve-expansion-vars- (caddr e) env m inarg))
354+
,(resolve-expansion-vars- (caddr (cadr e)) env m parent-scope inarg))
355+
,(resolve-expansion-vars- (caddr e) env m parent-scope inarg))
349356
`(kw ,(if inarg
350-
(resolve-expansion-vars- (cadr e) env m inarg)
357+
(resolve-expansion-vars- (cadr e) env m parent-scope inarg)
351358
(unescape (cadr e)))
352-
,(resolve-expansion-vars- (caddr e) env m inarg))))
359+
,(resolve-expansion-vars- (caddr e) env m parent-scope inarg))))
353360

354361
((let)
355362
(let* ((newenv (new-expansion-env-for e env))
356-
(body (resolve-expansion-vars- (cadr e) newenv m inarg)))
363+
(body (resolve-expansion-vars- (cadr e) newenv m parent-scope inarg)))
357364
`(let ,body
358365
,@(map
359366
(lambda (bind)
360367
(if (assignment? bind)
361368
(make-assignment
362369
;; expand binds in old env with dummy RHS
363370
(cadr (resolve-expansion-vars- (make-assignment (cadr bind) 0)
364-
newenv m inarg))
371+
newenv m parent-scope inarg))
365372
;; expand initial values in old env
366-
(resolve-expansion-vars- (caddr bind) env m inarg))
373+
(resolve-expansion-vars- (caddr bind) env m parent-scope inarg))
367374
bind))
368375
(cddr e)))))
376+
((hygienic-scope) ; TODO: move this lowering to resolve-scopes, instead of reimplementing it here badly
377+
(let ((parent-scope (cons (list env m) parent-scope))
378+
(body (cadr e))
379+
(m (caddr e)))
380+
(resolve-expansion-vars-with-new-env body env m parent-scope inarg)))
369381

370382
;; todo: trycatch
371383
(else
372384
(cons (car e)
373385
(map (lambda (x)
374-
(resolve-expansion-vars-with-new-env x env m inarg))
386+
(resolve-expansion-vars-with-new-env x env m parent-scope inarg))
375387
(cdr e))))))))
376388

377389
;; decl-var that also identifies f in f()=...
@@ -398,6 +410,7 @@
398410
(define (find-declared-vars-in-expansion e decl (outer #t))
399411
(cond ((or (not (pair? e)) (quoted? e)) '())
400412
((eq? (car e) 'escape) '())
413+
((eq? (car e) 'hygienic-scope) '())
401414
((eq? (car e) decl) (map decl-var* (cdr e)))
402415
((and (not outer) (function-def? e)) '())
403416
(else
@@ -408,6 +421,7 @@
408421
(define (find-assigned-vars-in-expansion e (outer #t))
409422
(cond ((or (not (pair? e)) (quoted? e)) '())
410423
((eq? (car e) 'escape) '())
424+
((eq? (car e) 'hygienic-scope) '())
411425
((and (not outer) (function-def? e))
412426
;; pick up only function name
413427
(let ((fname (cond ((eq? (car e) '=) (decl-var* (cadr e)))
@@ -436,8 +450,8 @@
436450
(define (resolve-expansion-vars e m)
437451
;; expand binding form patterns
438452
;; keep track of environment, rename locals to gensyms
439-
;; and wrap globals in (getfield module var) for macro's home module
440-
(resolve-expansion-vars-with-new-env e '() m #f #t))
453+
;; and wrap globals in (globalref module var) for macro's home module
454+
(resolve-expansion-vars-with-new-env e '() m '() #f #t))
441455

442456
(define (find-symbolic-labels e)
443457
(let ((defs (table))
@@ -470,24 +484,45 @@
470484
;; macro expander entry point
471485

472486
(define (julia-expand-macros e (max-depth -1))
487+
(julia-expand-macroscopes
488+
(julia-expand-macros- '() e max-depth)))
489+
490+
(define (julia-expand-macros- m e max-depth)
473491
(cond ((= max-depth 0) e)
474-
((not (pair? e)) e)
492+
((not (pair? e)) e)
475493
((eq? (car e) 'quote)
476-
;; backquote is essentially a built-in macro at the moment
477-
(julia-expand-macros (julia-bq-expand (cadr e) 0) max-depth))
494+
;; backquote is essentially a built-in unhygienic macro at the moment
495+
(julia-expand-macros- m (julia-bq-expand-hygienic (cadr e) (null? m)) max-depth))
478496
((eq? (car e) 'inert) e)
479497
((eq? (car e) 'macrocall)
480498
;; expand macro
481-
(let ((form (apply invoke-julia-macro (cadr e) (cddr e))))
499+
(let ((form (apply invoke-julia-macro (if (null? m) 'false (car m)) (cdr e))))
482500
(if (not form)
483501
(error (string "macro \"" (cadr e) "\" not defined")))
484502
(if (and (pair? form) (eq? (car form) 'error))
485503
(error (cadr form)))
486-
(let ((form (car form))
487-
(m (cdr form)))
488-
;; m is the macro's def module
489-
(rename-symbolic-labels
490-
(julia-expand-macros (resolve-expansion-vars form m) (- max-depth 1))))))
504+
(let ((form (car form)) ;; form is the expression returned from expand-macros
505+
(modu (cdr form))) ;; modu is the macro's def module
506+
`(hygienic-scope
507+
,(julia-expand-macros- (cons modu m) (rename-symbolic-labels form) (- max-depth 1))
508+
,modu))))
509+
((eq? (car e) 'module) e)
510+
((eq? (car e) 'escape)
511+
(let ((m (if (null? m) m (cdr m))))
512+
`(escape ,(julia-expand-macros- m (cadr e) max-depth))))
513+
(else
514+
(map (lambda (ex)
515+
(julia-expand-macros- m ex max-depth))
516+
e))))
517+
518+
;; TODO: delete this file and fold this operation into resolve-scopes
519+
(define (julia-expand-macroscopes e)
520+
(cond ((not (pair? e)) e)
521+
((eq? (car e) 'inert) e)
491522
((eq? (car e) 'module) e)
523+
((eq? (car e) 'hygienic-scope)
524+
(let ((form (cadr e)) ;; form is the expression returned from expand-macros
525+
(modu (caddr e))) ;; m is the macro's def module
526+
(resolve-expansion-vars form modu)))
492527
(else
493-
(map (lambda (ex) (julia-expand-macros ex max-depth)) e))))
528+
(map julia-expand-macroscopes e))))

0 commit comments

Comments
 (0)