Skip to content

Commit 01ddf80

Browse files
authored
lowering: preserve line numbers over julia-expand-macroscope pass (#44995)
This is to preserve the line number of the macro caller in the output, in case we don't have context from eval on where it occured. But we make slightly more changes than strictly necessary to prepare for future improvements in this area.
1 parent 94bc2f3 commit 01ddf80

File tree

17 files changed

+195
-85
lines changed

17 files changed

+195
-85
lines changed

base/boot.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,10 @@ import Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, Return
533533
end # module IR
534534

535535
# docsystem basics
536-
const unescape = Symbol("hygienic-scope")
537536
macro doc(x...)
538537
docex = atdoc(__source__, __module__, x...)
539538
isa(docex, Expr) && docex.head === :escape && return docex
540-
return Expr(:escape, Expr(unescape, docex, typeof(atdoc).name.module))
539+
return Expr(:escape, Expr(:var"hygienic-scope", docex, typeof(atdoc).name.module, __source__))
541540
end
542541
macro __doc__(x)
543542
return Expr(:escape, Expr(:block, Expr(:meta, :doc), x))

base/docs/Docs.jl

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -286,12 +286,26 @@ catdoc(xs...) = vcat(xs...)
286286
const keywords = Dict{Symbol, DocStr}()
287287

288288
function unblock(@nospecialize ex)
289+
while isexpr(ex, :var"hygienic-scope")
290+
isexpr(ex.args[1], :escape) || break
291+
ex = ex.args[1].args[1]
292+
end
289293
isexpr(ex, :block) || return ex
290294
exs = filter(ex -> !(isa(ex, LineNumberNode) || isexpr(ex, :line)), ex.args)
291295
length(exs) == 1 || return ex
292296
return unblock(exs[1])
293297
end
294298

299+
# peek through ex to figure out what kind of expression it may eventually act like
300+
# but ignoring scopes and line numbers
301+
function unescape(@nospecialize ex)
302+
ex = unblock(ex)
303+
while isexpr(ex, :escape) || isexpr(ex, :var"hygienic-scope")
304+
ex = unblock(ex.args[1])
305+
end
306+
return ex
307+
end
308+
295309
uncurly(@nospecialize ex) = isexpr(ex, :curly) ? ex.args[1] : ex
296310

297311
namify(@nospecialize x) = astname(x, isexpr(x, :macro))::Union{Symbol,Expr,GlobalRef}
@@ -351,18 +365,19 @@ function metadata(__source__, __module__, expr, ismodule)
351365
fields = P[]
352366
last_docstr = nothing
353367
for each in (expr.args[3]::Expr).args
354-
if isa(each, Symbol) || isexpr(each, :(::))
368+
eachex = unescape(each)
369+
if isa(eachex, Symbol) || isexpr(eachex, :(::))
355370
# a field declaration
356371
if last_docstr !== nothing
357-
push!(fields, P(namify(each::Union{Symbol,Expr}), last_docstr))
372+
push!(fields, P(namify(eachex::Union{Symbol,Expr}), last_docstr))
358373
last_docstr = nothing
359374
end
360-
elseif isexpr(each, :function) || isexpr(each, :(=))
375+
elseif isexpr(eachex, :function) || isexpr(eachex, :(=))
361376
break
362-
elseif isa(each, String) || isexpr(each, :string) || isexpr(each, :call) ||
363-
(isexpr(each, :macrocall) && each.args[1] === Symbol("@doc_str"))
377+
elseif isa(eachex, String) || isexpr(eachex, :string) || isexpr(eachex, :call) ||
378+
(isexpr(eachex, :macrocall) && eachex.args[1] === Symbol("@doc_str"))
364379
# forms that might be doc strings
365-
last_docstr = each::Union{String,Expr}
380+
last_docstr = each
366381
end
367382
end
368383
dict = :($(Dict{Symbol,Any})($([(:($(P)($(quot(f)), $d)))::Expr for (f, d) in fields]...)))
@@ -627,8 +642,9 @@ function loaddocs(docs::Vector{Core.SimpleVector})
627642
for (mod, ex, str, file, line) in docs
628643
data = Dict{Symbol,Any}(:path => string(file), :linenumber => line)
629644
doc = docstr(str, data)
630-
docstring = docm(LineNumberNode(line, file), mod, doc, ex, false) # expand the real @doc macro now
631-
Core.eval(mod, Expr(Core.unescape, docstring, Docs))
645+
lno = LineNumberNode(line, file)
646+
docstring = docm(lno, mod, doc, ex, false) # expand the real @doc macro now
647+
Core.eval(mod, Expr(:var"hygienic-scope", docstring, Docs, lno))
632648
end
633649
empty!(docs)
634650
nothing

base/osutils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ macro static(ex)
1616
@label loop
1717
hd = ex.head
1818
if hd (:if, :elseif, :&&, :||)
19-
cond = Core.eval(__module__, ex.args[1])
19+
cond = Core.eval(__module__, ex.args[1])::Bool
2020
if xor(cond, hd === :||)
2121
return esc(ex.args[2])
2222
elseif length(ex.args) == 3

base/threadcall.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ macro threadcall(f, rettype, argtypes, argvals...)
4747
push!(body, :(return Int(Core.sizeof($rettype))))
4848

4949
# return code to generate wrapper function and send work request thread queue
50-
wrapper = Expr(Symbol("hygienic-scope"), wrapper, @__MODULE__)
50+
wrapper = Expr(:var"hygienic-scope", wrapper, @__MODULE__, __source__)
5151
return :(let fun_ptr = @cfunction($wrapper, Int, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}))
5252
# use cglobal to look up the function on the calling thread
5353
do_threadcall(fun_ptr, cglobal($f), $rettype, Any[$(argtypes...)], Any[$(argvals...)])

base/util.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ macro kwdef(expr)
604604
kwdefs = nothing
605605
end
606606
return quote
607-
Base.@__doc__ $(esc(expr))
607+
$(esc(:($Base.@__doc__ $expr)))
608608
$kwdefs
609609
end
610610
end

src/ast.c

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,8 @@ static jl_value_t *scm_to_julia(fl_context_t *fl_ctx, value_t e, jl_module_t *mo
436436
}
437437
JL_CATCH {
438438
// if expression cannot be converted, replace with error expr
439+
//jl_(jl_current_exception());
440+
//jlbacktrace();
439441
jl_expr_t *ex = jl_exprn(jl_error_sym, 1);
440442
v = (jl_value_t*)ex;
441443
jl_array_ptr_set(ex->args, 0, jl_cstr_to_string("invalid AST"));
@@ -1000,7 +1002,59 @@ int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT
10001002
return 0;
10011003
}
10021004

1003-
static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule, jl_module_t **ctx, size_t world, int throw_load_error)
1005+
// Utility function to return whether `e` is any of the special AST types or
1006+
// will always evaluate to itself exactly unchanged. This corresponds to
1007+
// `is_self_quoting` in Core.Compiler utilities.
1008+
int jl_is_ast_node(jl_value_t *e) JL_NOTSAFEPOINT
1009+
{
1010+
return jl_is_newvarnode(e)
1011+
|| jl_is_code_info(e)
1012+
|| jl_is_linenode(e)
1013+
|| jl_is_gotonode(e)
1014+
|| jl_is_gotoifnot(e)
1015+
|| jl_is_returnnode(e)
1016+
|| jl_is_ssavalue(e)
1017+
|| jl_is_slotnumber(e)
1018+
|| jl_is_argument(e)
1019+
|| jl_is_quotenode(e)
1020+
|| jl_is_globalref(e)
1021+
|| jl_is_symbol(e)
1022+
|| jl_is_pinode(e)
1023+
|| jl_is_phinode(e)
1024+
|| jl_is_phicnode(e)
1025+
|| jl_is_upsilonnode(e)
1026+
|| jl_is_expr(e);
1027+
}
1028+
1029+
static int is_self_quoting_expr(jl_expr_t *e) JL_NOTSAFEPOINT
1030+
{
1031+
return (e->head == jl_inert_sym ||
1032+
e->head == jl_core_sym ||
1033+
e->head == jl_line_sym ||
1034+
e->head == jl_lineinfo_sym ||
1035+
e->head == jl_meta_sym ||
1036+
e->head == jl_boundscheck_sym ||
1037+
e->head == jl_inline_sym ||
1038+
e->head == jl_noinline_sym);
1039+
}
1040+
1041+
// any AST, except those that cannot contain symbols
1042+
// and have no side effects
1043+
int need_esc_node(jl_value_t *e) JL_NOTSAFEPOINT
1044+
{
1045+
if (jl_is_linenode(e)
1046+
|| jl_is_ssavalue(e)
1047+
|| jl_is_slotnumber(e)
1048+
|| jl_is_argument(e)
1049+
|| jl_is_quotenode(e))
1050+
return 0;
1051+
if (jl_is_expr(e))
1052+
return !is_self_quoting_expr((jl_expr_t*)e);
1053+
// note: jl_is_globalref(e) is not included here, since we care a little about about having a line number for it
1054+
return jl_is_ast_node(e);
1055+
}
1056+
1057+
static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule, jl_module_t **ctx, jl_value_t **lineinfo, size_t world, int throw_load_error)
10041058
{
10051059
jl_task_t *ct = jl_current_task;
10061060
JL_TIMING(MACRO_INVOCATION, MACRO_INVOCATION);
@@ -1012,10 +1066,9 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule
10121066
margs[0] = jl_array_ptr_ref(args, 0);
10131067
// __source__ argument
10141068
jl_value_t *lno = jl_array_ptr_ref(args, 1);
1069+
if (!jl_is_linenode(lno))
1070+
lno = jl_new_struct(jl_linenumbernode_type, jl_box_long(0), jl_nothing);
10151071
margs[1] = lno;
1016-
if (!jl_is_linenode(lno)) {
1017-
margs[1] = jl_new_struct(jl_linenumbernode_type, jl_box_long(0), jl_nothing);
1018-
}
10191072
margs[2] = (jl_value_t*)inmodule;
10201073
for (i = 3; i < nargs; i++)
10211074
margs[i] = jl_array_ptr_ref(args, i - 1);
@@ -1054,6 +1107,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule
10541107
}
10551108
}
10561109
ct->world_age = last_age;
1110+
*lineinfo = margs[1];
10571111
JL_GC_POP();
10581112
return result;
10591113
}
@@ -1076,36 +1130,47 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str
10761130
JL_GC_POP();
10771131
return expr;
10781132
}
1079-
if (e->head == jl_hygienicscope_sym && jl_expr_nargs(e) == 2) {
1133+
if (e->head == jl_hygienicscope_sym && jl_expr_nargs(e) >= 2) {
10801134
struct macroctx_stack newctx;
10811135
newctx.m = (jl_module_t*)jl_exprarg(e, 1);
10821136
JL_TYPECHK(hygienic-scope, module, (jl_value_t*)newctx.m);
10831137
newctx.parent = macroctx;
10841138
jl_value_t *a = jl_exprarg(e, 0);
10851139
jl_value_t *a2 = jl_expand_macros(a, inmodule, &newctx, onelevel, world, throw_load_error);
1086-
if (a != a2)
1140+
if (jl_is_expr(a2) && ((jl_expr_t*)a2)->head == jl_escape_sym && !need_esc_node(jl_exprarg(a2, 0)))
1141+
expr = jl_exprarg(a2, 0);
1142+
else if (!need_esc_node(a2))
1143+
expr = a2;
1144+
else if (a != a2)
10871145
jl_array_ptr_set(e->args, 0, a2);
10881146
return expr;
10891147
}
10901148
if (e->head == jl_macrocall_sym) {
10911149
struct macroctx_stack newctx;
10921150
newctx.m = macroctx ? macroctx->m : inmodule;
10931151
newctx.parent = macroctx;
1094-
jl_value_t *result = jl_invoke_julia_macro(e->args, inmodule, &newctx.m, world, throw_load_error);
1152+
jl_value_t *lineinfo = NULL;
1153+
jl_value_t *result = jl_invoke_julia_macro(e->args, inmodule, &newctx.m, &lineinfo, world, throw_load_error);
1154+
if (!need_esc_node(result))
1155+
return result;
10951156
jl_value_t *wrap = NULL;
1096-
JL_GC_PUSH3(&result, &wrap, &newctx.m);
1157+
JL_GC_PUSH4(&result, &wrap, &newctx.m, &lineinfo);
10971158
// copy and wrap the result in `(hygienic-scope ,result ,newctx)
10981159
if (jl_is_expr(result) && ((jl_expr_t*)result)->head == jl_escape_sym)
10991160
result = jl_exprarg(result, 0);
11001161
else
1101-
wrap = (jl_value_t*)jl_exprn(jl_hygienicscope_sym, 2);
1162+
wrap = (jl_value_t*)jl_exprn(jl_hygienicscope_sym, 3);
11021163
result = jl_copy_ast(result);
11031164
if (!onelevel)
11041165
result = jl_expand_macros(result, inmodule, wrap ? &newctx : macroctx, onelevel, world, throw_load_error);
1105-
if (wrap) {
1166+
if (wrap && need_esc_node(result)) {
11061167
jl_exprargset(wrap, 0, result);
11071168
jl_exprargset(wrap, 1, newctx.m);
1108-
result = wrap;
1169+
jl_exprargset(wrap, 2, lineinfo);
1170+
if (jl_is_expr(result) && ((jl_expr_t*)result)->head == jl_escape_sym)
1171+
result = jl_exprarg(result, 0);
1172+
else
1173+
result = wrap;
11091174
}
11101175
JL_GC_POP();
11111176
return result;

src/ast.scm

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -479,12 +479,13 @@
479479
(define (eq-sym? a b)
480480
(or (eq? a b) (and (ssavalue? a) (ssavalue? b) (eqv? (cdr a) (cdr b)))))
481481

482-
(define (blockify e)
482+
(define (blockify e (lno #f))
483+
(set! lno (if lno (list lno) '()))
483484
(if (and (pair? e) (eq? (car e) 'block))
484485
(if (null? (cdr e))
485-
`(block (null))
486-
e)
487-
`(block ,e)))
486+
`(block ,@lno (null))
487+
(if (null? lno) e `(block ,@lno ,@(cdr e))))
488+
`(block ,@lno ,e)))
488489

489490
(define (make-var-info name) (list name '(core Any) 0))
490491
(define vinfo:name car)

src/jlfrontend.scm

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,38 @@
9393

9494
;; lowering entry points
9595

96+
; find the first line number in this expression, before we might eliminate them
97+
(define (first-lineno blk)
98+
(cond ((not (pair? blk)) #f)
99+
((eq? (car blk) 'line) blk)
100+
((and (eq? (car blk) 'hygienic-scope) (pair? (cdddr blk)) (pair? (cadddr blk)) (eq? (car (cadddr blk)) 'line))
101+
(cadddr blk))
102+
((memq (car blk) '(escape hygienic-scope))
103+
(first-lineno (cadr blk)))
104+
((memq (car blk) '(toplevel block))
105+
(let loop ((xs (cdr blk)))
106+
(and (pair? xs)
107+
(let ((elt (first-lineno (car xs))))
108+
(or elt (loop (cdr xs)))))))
109+
(else #f)))
110+
96111
;; return a lambda expression representing a thunk for a top-level expression
97112
;; note: expansion of stuff inside module is delayed, so the contents obey
98113
;; toplevel expansion order (don't expand until stuff before is evaluated).
99114
(define (expand-toplevel-expr-- e file line)
100-
(let ((ex0 (julia-expand-macroscope e)))
115+
(let ((lno (first-lineno e))
116+
(ex0 (julia-expand-macroscope e)))
117+
(if (and lno (or (not (length= lno 3)) (not (atom? (caddr lno))))) (set! lno #f))
101118
(if (toplevel-only-expr? ex0)
102-
ex0
103-
(let* ((ex (julia-expand0 ex0 file line))
119+
(if (and (pair? e) (memq (car ex0) '(error incomplete)))
120+
ex0
121+
(if lno `(toplevel ,lno ,ex0) ex0))
122+
(let* ((linenode (if (and lno (or (= line 0) (eq? file 'none))) lno `(line ,line ,file)))
123+
(ex (julia-expand0 ex0 linenode))
104124
(th (julia-expand1
105125
`(lambda () ()
106126
(scope-block
107-
,(blockify ex)))
127+
,(blockify ex lno)))
108128
file line)))
109129
(if (and (null? (cdadr (caddr th)))
110130
(and (length= (lam:body th) 2)

src/julia-syntax.scm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5090,8 +5090,8 @@ f(x) = yt(x)
50905090

50915091
(define *current-desugar-loc* #f)
50925092

5093-
(define (julia-expand0 ex file line)
5094-
(with-bindings ((*current-desugar-loc* `(line ,line ,file)))
5093+
(define (julia-expand0 ex lno)
5094+
(with-bindings ((*current-desugar-loc* lno))
50955095
(trycatch (expand-forms ex)
50965096
(lambda (e)
50975097
(if (and (pair? e) (eq? (car e) 'error))
@@ -5106,4 +5106,4 @@ f(x) = yt(x)
51065106
(define (julia-expand ex (file 'none) (line 0))
51075107
(julia-expand1
51085108
(julia-expand0
5109-
(julia-expand-macroscope ex) file line) file line))
5109+
(julia-expand-macroscope ex) `(line ,line ,file)) file line))

src/macroexpand.scm

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,8 @@
448448
((hygienic-scope) ; TODO: move this lowering to resolve-scopes, instead of reimplementing it here badly
449449
(let ((parent-scope (cons (list env m) parent-scope))
450450
(body (cadr e))
451-
(m (caddr e)))
451+
(m (caddr e))
452+
(lno (cdddr e)))
452453
(resolve-expansion-vars-with-new-env body env m parent-scope inarg #t)))
453454
((tuple)
454455
(cons (car e)
@@ -574,7 +575,8 @@
574575
((eq? (car e) 'module) e)
575576
((eq? (car e) 'hygienic-scope)
576577
(let ((form (cadr e)) ;; form is the expression returned from expand-macros
577-
(modu (caddr e))) ;; m is the macro's def module
578+
(modu (caddr e)) ;; m is the macro's def module
579+
(lno (cdddr e))) ;; lno is (optionally) the line number node
578580
(resolve-expansion-vars form modu)))
579581
(else
580582
(map julia-expand-macroscopes- e))))
@@ -585,8 +587,9 @@
585587
((eq? (car e) 'hygienic-scope)
586588
(let ((parent-scope (list relabels parent-scope))
587589
(body (cadr e))
588-
(m (caddr e)))
589-
`(hygienic-scope ,(rename-symbolic-labels- (cadr e) (table) parent-scope) ,m)))
590+
(m (caddr e))
591+
(lno (cdddr e)))
592+
`(hygienic-scope ,(rename-symbolic-labels- (cadr e) (table) parent-scope) ,m ,@lno)))
590593
((and (eq? (car e) 'escape) (not (null? parent-scope)))
591594
`(escape ,(apply rename-symbolic-labels- (cadr e) parent-scope)))
592595
((or (eq? (car e) 'symbolicgoto) (eq? (car e) 'symboliclabel))

0 commit comments

Comments
 (0)