Skip to content

Commit f47e092

Browse files
authored
Fix functionloc by adding line nodes to method signatures (#35138)
This comes in three pieces: * In the parser, put an additional LineNumberNode at the top of each function body to track the line of the function definition * In lowering, remove this LineNumberNode from the body and add it as the last parameter to method signature in `Expr(:method, name, sig, body)` * In the runtime, extract the line number node from sig and add it to the jl_method_t. Since the first line is now correctly tracked separately from the body, a special case in coverage codegen was removed. This was required when line numbers were tracked in a different way - see origin of this in the use of `toplineno` in bug #17442 and fix #17470.
1 parent 446870d commit f47e092

13 files changed

+72
-44
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ Language changes
6363
Now the result is "a\n b", since the space before `b` is no longer considered to occur
6464
at the start of a line. The old behavior is considered a bug ([#35001]).
6565

66+
* The line number of function definitions is now added by the parser as an
67+
additional `LineNumberNode` at the start of each function body ([#35138]).
6668

6769
Multi-threading changes
6870
-----------------------

doc/src/manual/constructors.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ julia> struct SummedArray{T<:Number,S<:Number}
550550
julia> SummedArray(Int32[1; 2; 3], Int32(6))
551551
ERROR: MethodError: no method matching SummedArray(::Array{Int32,1}, ::Int32)
552552
Closest candidates are:
553-
SummedArray(::Array{T,1}) where T at none:5
553+
SummedArray(::Array{T,1}) where T at none:4
554554
```
555555

556556
This constructor will be invoked by the syntax `SummedArray(a)`. The syntax `new{T,S}` allows

doc/src/manual/types.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1207,8 +1207,7 @@ is raised:
12071207
julia> supertype(Union{Float64,Int64})
12081208
ERROR: MethodError: no method matching supertype(::Type{Union{Float64, Int64}})
12091209
Closest candidates are:
1210-
supertype(!Matched::DataType) at operators.jl:42
1211-
supertype(!Matched::UnionAll) at operators.jl:47
1210+
[...]
12121211
```
12131212

12141213
## [Custom pretty-printing](@id man-custom-pretty-printing)

src/codegen.cpp

+3-9
Original file line numberDiff line numberDiff line change
@@ -5820,10 +5820,13 @@ static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
58205820
auto coverageVisitStmt = [&] (size_t dbg) {
58215821
if (dbg == 0)
58225822
return;
5823+
// Compute inlining stack for current line, inner frame first
58235824
while (dbg) {
58245825
new_lineinfo.push_back(dbg);
58255826
dbg = linetable.at(dbg).inlined_at;
58265827
}
5828+
// Visit frames which differ from previous statement as tracked in
5829+
// current_lineinfo (tracked outer frame first).
58275830
current_lineinfo.resize(new_lineinfo.size(), 0);
58285831
for (dbg = 0; dbg < new_lineinfo.size(); dbg++) {
58295832
unsigned newdbg = new_lineinfo[new_lineinfo.size() - dbg - 1];
@@ -5906,15 +5909,6 @@ static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
59065909
BB[label] = bb;
59075910
}
59085911

5909-
if (do_coverage(mod_is_user_mod)) {
5910-
coverageVisitLine(ctx, ctx.file, toplineno);
5911-
if (linetable.size() >= 2) {
5912-
// avoid double-counting the entry line
5913-
const auto &info = linetable.at(1);
5914-
if (info.file == ctx.file && info.line == toplineno && info.is_user_code == mod_is_user_mod)
5915-
current_lineinfo.push_back(1);
5916-
}
5917-
}
59185912
Value *sync_bytes = nullptr;
59195913
if (do_malloc_log(true))
59205914
sync_bytes = ctx.builder.CreateCall(prepare_call(diff_gc_total_bytes_func), {});

src/julia-parser.scm

+3-2
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,8 @@
14191419
`(const ,assgn))))
14201420

14211421
((function macro)
1422-
(let* ((paren (eqv? (require-token s) #\())
1422+
(let* ((loc (line-number-node s))
1423+
(paren (eqv? (require-token s) #\())
14231424
(sig (parse-def s (eq? word 'function) paren)))
14241425
(if (and (not paren) (symbol-or-interpolate? sig))
14251426
(begin (if (not (eq? (require-token s) 'end))
@@ -1440,7 +1441,7 @@
14401441
sig)))
14411442
(body (parse-block s)))
14421443
(expect-end s word)
1443-
(list word def body)))))
1444+
(list word def (add-line-number body loc))))))
14441445

14451446
((abstract)
14461447
(if (not (eq? (peek-token s) 'type))

src/julia-syntax.scm

+37-15
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@
231231
(let ((type-ex (caddr m)))
232232
(if (eq? (car type-ex) 'block)
233233
;; extract ssavalue labels of sparams from the svec-of-sparams argument to `method`
234-
(let ((sp-ssavals (cddr (last (last type-ex)))))
234+
(let ((sp-ssavals (cddr (cadddr (last type-ex)))))
235235
(map (lambda (a) ;; extract T from (= v (call (core TypeVar) (quote T) ...))
236236
(cadr (caddr (caddr a))))
237237
(filter (lambda (e)
@@ -293,6 +293,24 @@
293293
(define (non-generated-version body)
294294
(generated-part- body #f))
295295

296+
;; Remove and return the line number for the start of the function definition
297+
(define (maybe-remove-functionloc! body)
298+
(let* ((prologue (extract-method-prologue body))
299+
(prologue-lnos (filter linenum? prologue))
300+
(functionloc (if (pair? prologue-lnos)
301+
(car prologue-lnos)
302+
; Fallback - take first line anywhere in body
303+
(let ((lnos (filter linenum? body)))
304+
(if (null? lnos) '(line 0 none) (car lnos))))))
305+
(if (length> prologue-lnos 1)
306+
; First of two line numbers in prologue is function definition location
307+
; which should be removed from the body.
308+
(let loop ((stmts body))
309+
(if (eq? functionloc (cadr stmts))
310+
(set-cdr! stmts (cddr stmts))
311+
(loop (cdr body)))))
312+
functionloc))
313+
296314
;; construct the (method ...) expression for one primitive method definition,
297315
;; assuming optional and keyword args are already handled
298316
(define (method-def-expr- name sparams argl body (rett '(core Any)))
@@ -328,12 +346,12 @@
328346
(error "function argument and static parameter names must be distinct"))
329347
(if (or (and name (not (sym-ref? name))) (not (valid-name? name)))
330348
(error (string "invalid function name \"" (deparse name) "\"")))
331-
(let* ((generator (if (expr-contains-p if-generated? body (lambda (x) (not (function-def? x))))
349+
(let* ((loc (maybe-remove-functionloc! body))
350+
(generator (if (expr-contains-p if-generated? body (lambda (x) (not (function-def? x))))
332351
(let* ((gen (generated-version body))
333352
(nongen (non-generated-version body))
334353
(gname (symbol (string (gensy) "#" (current-julia-module-counter))))
335-
(gf (make-generator-function gname names anames gen))
336-
(loc (function-body-lineno body)))
354+
(gf (make-generator-function gname names anames gen)))
337355
(set! body (insert-after-meta
338356
nongen
339357
`((meta generated
@@ -343,8 +361,8 @@
343361
,(if (null? sparams)
344362
'nothing
345363
(cons 'list (map car sparams)))
346-
,(if (null? loc) 0 (cadr loc))
347-
(inert ,(if (null? loc) 'none (caddr loc)))
364+
,(cadr loc)
365+
(inert ,(caddr loc))
348366
(false))))))
349367
(list gf))
350368
'()))
@@ -356,7 +374,11 @@
356374
(renames (map cons names temps))
357375
(mdef
358376
(if (null? sparams)
359-
`(method ,name (call (core svec) (call (core svec) ,@(dots->vararg types)) (call (core svec)))
377+
`(method ,name
378+
(call (core svec)
379+
(call (core svec) ,@(dots->vararg types))
380+
(call (core svec))
381+
(inert ,loc))
360382
,body)
361383
`(method ,name
362384
(block
@@ -377,7 +399,8 @@
377399
(map (lambda (ty)
378400
(replace-vars ty renames))
379401
types)))
380-
(call (core svec) ,@temps)))
402+
(call (core svec) ,@temps)
403+
(inert ,loc)))
381404
,body))))
382405
(if (or (symbol? name) (globalref? name))
383406
`(block ,@generator (method ,name) ,mdef (unnecessary ,name)) ;; return the function
@@ -793,10 +816,6 @@
793816
wheres)
794817
,(ctor-body body curlyargs sparams))))))
795818

796-
(define (function-body-lineno body)
797-
(let ((lnos (filter linenum? body)))
798-
(if (null? lnos) '() (car lnos))))
799-
800819
;; rewrite calls to `new( ... )` to `new` expressions on the appropriate
801820
;; type, determined by the containing constructor definition.
802821
(define (rewrite-ctor ctor Tname params field-names field-types)
@@ -3021,9 +3040,12 @@ f(x) = yt(x)
30213040
(newtypes
30223041
(if iskw
30233042
`(,(car types) ,(cadr types) ,closure-type ,@(cdddr types))
3024-
`(,closure-type ,@(cdr types)))))
3025-
`(call (core svec) (call (core svec) ,@newtypes)
3026-
(call (core svec) ,@(append (cddr (cadddr te)) type-sp)))))
3043+
`(,closure-type ,@(cdr types))))
3044+
(loc (caddddr te)))
3045+
`(call (core svec)
3046+
(call (core svec) ,@newtypes)
3047+
(call (core svec) ,@(append (cddr (cadddr te)) type-sp))
3048+
,loc)))
30273049

30283050
;; collect all toplevel-butfirst expressions inside `e`, and return
30293051
;; (ex . stmts), where `ex` is the expression to evaluated and

src/method.c

+6-7
Original file line numberDiff line numberDiff line change
@@ -504,12 +504,6 @@ static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src)
504504
jl_array_t *stmts = (jl_array_t*)src->code;
505505
size_t i, n = jl_array_len(stmts);
506506
copy = jl_alloc_vec_any(n);
507-
// set location from first LineInfoNode
508-
if (jl_array_len(src->linetable) > 0) {
509-
jl_value_t *ln = jl_array_ptr_ref(src->linetable, 0);
510-
m->file = (jl_sym_t*)jl_fieldref(ln, 1);
511-
m->line = jl_unbox_long(jl_fieldref(ln, 2));
512-
}
513507
for (i = 0; i < n; i++) {
514508
jl_value_t *st = jl_array_ptr_ref(stmts, i);
515509
if (jl_is_expr(st) && ((jl_expr_t*)st)->head == meta_sym) {
@@ -698,9 +692,10 @@ JL_DLLEXPORT void jl_method_def(jl_svec_t *argdata,
698692
jl_code_info_t *f,
699693
jl_module_t *module)
700694
{
701-
// argdata is svec(svec(types...), svec(typevars...))
695+
// argdata is svec(svec(types...), svec(typevars...), functionloc)
702696
jl_svec_t *atypes = (jl_svec_t*)jl_svecref(argdata, 0);
703697
jl_svec_t *tvars = (jl_svec_t*)jl_svecref(argdata, 1);
698+
jl_value_t *functionloc = jl_svecref(argdata, 2);
704699
size_t nargs = jl_svec_len(atypes);
705700
int isva = jl_is_vararg_type(jl_svecref(atypes, nargs - 1));
706701
assert(jl_is_svec(atypes));
@@ -755,6 +750,10 @@ JL_DLLEXPORT void jl_method_def(jl_svec_t *argdata,
755750
m->name = name;
756751
m->isva = isva;
757752
m->nargs = nargs;
753+
assert(jl_is_linenode(functionloc));
754+
jl_value_t *file = jl_linenode_file(functionloc);
755+
m->file = jl_is_symbol(file) ? (jl_sym_t*)file : empty_sym;
756+
m->line = jl_linenode_line(functionloc);
758757
jl_method_set_source(m, f);
759758

760759
if (jl_has_free_typevars(argtype)) {

src/toplevel.c

+2
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,8 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex)
830830
jl_value_t *v = NULL;
831831
int last_lineno = jl_lineno;
832832
const char *last_filename = jl_filename;
833+
jl_lineno = 1;
834+
jl_filename = "none";
833835
if (jl_options.incremental && jl_generating_output()) {
834836
if (!ptrhash_has(&jl_current_modules, (void*)m)) {
835837
if (m != jl_main_module) { // TODO: this was grand-fathered in

test/reflection.jl

+3-2
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ let rts = return_types(TLayout)
288288
end
289289

290290
# issue #15447
291+
f15447_line = @__LINE__() + 1
291292
@noinline function f15447(s, a)
292293
if s
293294
return a
@@ -296,15 +297,15 @@ end
296297
return nb
297298
end
298299
end
299-
@test functionloc(f15447)[2] > 0
300+
@test functionloc(f15447)[2] == f15447_line
300301

301302
# issue #14346
302303
@noinline function f14346(id, mask, limit)
303304
if id <= limit && mask[id]
304305
return true
305306
end
306307
end
307-
@test functionloc(f14346)[2] == @__LINE__() - 4
308+
@test functionloc(f14346)[2] == @__LINE__() - 5
308309

309310
# issue #15714
310311
# show variable names for slots and suppress spurious type warnings

test/show.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,7 @@ end"""
922922
end""")) ==
923923
"""
924924
:(macro m(a, b)
925+
#= none:1 =#
925926
#= none:2 =#
926927
quote
927928
#= none:3 =#
@@ -944,7 +945,7 @@ end"""))) ==
944945
"""macro m(a, b)
945946
:(\$a + \$b)
946947
end""")) ==
947-
":(macro m(a, b)\n #= none:2 =#\n :(\$a + \$b)\n end)"
948+
":(macro m(a, b)\n #= none:1 =#\n #= none:2 =#\n :(\$a + \$b)\n end)"
948949
@test repr(Expr(:macro, Expr(:call, :m, :x), Expr(:quote, Expr(:call, :+, Expr(:($), :x), 1)))) ==
949950
":(macro m(x)\n :(\$x + 1)\n end)"
950951

test/syntax.jl

+4-2
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,12 @@ macro test999_str(args...); args; end
162162
@test parseall(":(a = &\nb)") == Expr(:quote, Expr(:(=), :a, Expr(:&, :b)))
163163
@test parseall(":(a = \$\nb)") == Expr(:quote, Expr(:(=), :a, Expr(:$, :b)))
164164

165-
# issue 11970
165+
# issue 12027 - short macro name parsing vs _str suffix
166166
@test parseall("""
167167
macro f(args...) end; @f "macro argument"
168168
""") == Expr(:toplevel,
169-
Expr(:macro, Expr(:call, :f, Expr(:..., :args)), Expr(:block, LineNumberNode(1, :none))),
169+
Expr(:macro, Expr(:call, :f, Expr(:..., :args)),
170+
Expr(:block, LineNumberNode(1, :none), LineNumberNode(1, :none))),
170171
Expr(:macrocall, Symbol("@f"), LineNumberNode(1, :none), "macro argument"))
171172

172173
# blocks vs. tuples
@@ -1423,6 +1424,7 @@ let ex = Meta.lower(@__MODULE__, Meta.parse("
14231424
@test isa(ex, Expr) && ex.head === :error
14241425
@test ex.args[1] == """
14251426
invalid assignment location "function (s, o...)
1427+
# none, line 2
14261428
# none, line 3
14271429
f(a, b) do
14281430
# none, line 4

test/testhelpers/coverage_file.info

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ DA:9,5
77
DA:11,1
88
DA:12,1
99
DA:14,0
10-
LH:6
11-
LF:8
10+
DA:17,1
11+
LH:7
12+
LF:9
1213
end_of_record

test/testhelpers/coverage_file.jl

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ function code_coverage_test()
1414
not_reached
1515
end
1616

17-
exit(code_coverage_test() == [1, 2, 3] ? 0 : 1)
17+
short_form_func_coverage_test(x) = x*x
18+
19+
success = code_coverage_test() == [1, 2, 3] &&
20+
short_form_func_coverage_test(2) == 4
21+
exit(success ? 0 : 1)
1822

1923
# end of file

0 commit comments

Comments
 (0)