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

Fix functionloc #35138

Merged
merged 1 commit into from
Apr 3, 2020
Merged

Fix functionloc #35138

merged 1 commit into from
Apr 3, 2020

Conversation

c42f
Copy link
Member

@c42f c42f commented Mar 17, 2020

This is a WIP of one way to fix #34820 — I'm not sure it's the best approach, but throwing it up there for discussion.

The approach taken here is to add a line number node to the method signature argument in Expr(:method, name, signature, body) after the signature argument types and names. I tried several options involving lowering of Expr(:method) and it turns out that this is quite minimally invasive way to do this. I also think it makes some sense: the method declaration is more a property of the method than it is relevant to the body CodeInfo.

However, there's one downside I know about, which is that functions defined in expressions (rather than as statements) can have a line number which is incorrect. In particular, the following test breaks:

let f(x) =
      g(x) = 1
    @test_broken functionloc(f(1))[2] > functionloc(f)[2]
end

Presumably this could be fixed by changing parsing of functions which occur inside expressions to be enclosed in an Expr(:block). Perhaps that's a reasonable way to go?

TODO

@JeffBezanson
Copy link
Member

Ah, interesting. f(x) = x syntax has the opposite problem: it only records the line of the signature and not the first body expression. So indeed it seems a comprehensive solution requires modifying the parser. I think adding a block expression around every function is too disruptive; it would be easier for the parser to record two locations at the start of each function body, and have lowering move the first one into the signature field of method exprs as you have here.

@c42f
Copy link
Member Author

c42f commented Mar 18, 2020

Ok, the doctests bring up some more serious ways that this approach can lead to unfortunate results:

  • Ignoring the body leads to surprises when eval()ing ASTs containing function definitions which are not embedded in a block. This is hard to fix because in the current AST there's just nowhere natural to stash source location of a bare function definition unless we store it in the body statements. Wrapping in a block seems really disruptive.
  • It seems that source location tracking is broken for inner constructors because structs ASTs have special case traversal in expand-forms. This can be fixed, but leads me to think location tracking within lowering is too fragile for this use.

In general I like this approach on the runtime side but it seems too fragile to use with confidence without parser support.

I think adding a block expression around every function is too disruptive; it would be easier for the parser to record two locations at the start of each function body, and have lowering move the first one into the signature field of method exprs as you have here.

Absolutely agreed! Let's do it this way.

@JeffBezanson
Copy link
Member

LGTM, just has the inevitable detailed test errors.

@c42f c42f added error messages Better, more actionable error messages parser Language parsing and surface syntax labels Mar 24, 2020
@c42f
Copy link
Member Author

c42f commented Mar 24, 2020

Thanks for taking another look, I'll sort out those test issues, then merge.

I think this is likely to incidentally break the occasional package even though it's technically-not-API-breaking. Is there any way to flag this kind of thing so users can easily understand what has changed and how to fix their code? Applying the breaking label seems like an overstatement.

@c42f c42f changed the title WIP: Fix functionloc Fix functionloc Mar 24, 2020
@KristofferC KristofferC added the minor change Marginal behavior change acceptable for a minor release label Mar 24, 2020
; which should be removed from the body.
(let loop ((stmts body))
(if (eq? functionloc (cadr stmts))
(set-cdr! stmts (cddr stmts))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt like using set-cdr! here wasn't great. Is there a more natural idiomatic way to do this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Taking into account that we'll need to return both the line number and the body if this is pure.)

@c42f
Copy link
Member Author

c42f commented Mar 26, 2020

Ok, so this should be very close. There's one remaining problem: code coverage has changed because it now includes the line of the function definition. It turns out this is because the line from the jl_method_t is used in codegen for coverage:

toplineno = lam->def.method->line;

coverageVisitLine(ctx, ctx.file, toplineno);

I'm not sure what the desired outcome would be here — is it useful to consider the function definition line as code which should be included in the coverage output, or not?

There's also other uses of toplineno related to debug info. From what I can see these relate to emitting debug info for the function and function arguments so using the true function line number should be fine for these I guess.

@c42f
Copy link
Member Author

c42f commented Mar 31, 2020

To record a discussion about coverage with Jeff and Keno - the consensus was that we shouldn't visit the function definition line (unless it contains executable code), so some small adjustments to coverage codegen will be needed here.

@c42f
Copy link
Member Author

c42f commented Apr 2, 2020

Some code archeology on the special case handling of toplineno in coverage generation:

I think the code which the toplineno workaround related to has since been replace by the line table and having linearization as part of lowering, so should be possible to revert #17470 now.

This comes in three pieces:
* In the parser, put an addition 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.
if (info.file == ctx.file && info.line == toplineno && info.is_user_code == mod_is_user_mod)
current_lineinfo.push_back(1);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed this coverage stuff for the reasons explained in the commit message: It seems like a workaround for an old bug #17442, and now we have this double-counting workaround which appears to be a workaround-workaround. So I just deleted this. I also found coverageVisitStmt a bit confusing so I added some comments above, which I hope correctly state what is going on.

@c42f c42f merged commit f47e092 into master Apr 3, 2020
@c42f c42f deleted the cjf/functionloc-fix branch April 3, 2020 02:05
oxinabox pushed a commit to oxinabox/julia that referenced this pull request Apr 8, 2020
…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 JuliaLang#17442 and fix JuliaLang#17470.
ravibitsgoa pushed a commit to ravibitsgoa/julia that referenced this pull request Apr 9, 2020
…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 JuliaLang#17442 and fix JuliaLang#17470.
timholy added a commit to timholy/Revise.jl that referenced this pull request Apr 15, 2020
timholy added a commit to timholy/Revise.jl that referenced this pull request Apr 16, 2020
JuliaLang/julia#35138 changed it so that the
method's line number is the declaration, not the first line of the body.

This also reworks the line-number detection logic on older releases,
and it should be more robust.
timholy added a commit to timholy/Revise.jl that referenced this pull request Apr 16, 2020
JuliaLang/julia#35138 changed it so that the
method's line number is the declaration, not the first line of the body.

This also reworks the line-number detection logic on older releases,
and it should be more robust.
ztultrebor pushed a commit to ztultrebor/julia that referenced this pull request Apr 17, 2020
…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 JuliaLang#17442 and fix JuliaLang#17470.
staticfloat pushed a commit that referenced this pull request Apr 21, 2020
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.
@vtjnash vtjnash mentioned this pull request Jul 10, 2020
thautwarm added a commit to thautwarm/MLStyle.jl that referenced this pull request Aug 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
error messages Better, more actionable error messages minor change Marginal behavior change acceptable for a minor release parser Language parsing and surface syntax
Projects
None yet
Development

Successfully merging this pull request may close these issues.

funcionloc should return line number of method sign, not line of first statement in func
3 participants