-
Notifications
You must be signed in to change notification settings - Fork 35
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
RFC: refactor breakpoints #259
Conversation
18e79b0
to
b6490ff
Compare
b6490ff
to
3be7854
Compare
Codecov Report
@@ Coverage Diff @@
## master #259 +/- ##
==========================================
+ Coverage 88.03% 88.07% +0.04%
==========================================
Files 11 11
Lines 1671 1719 +48
==========================================
+ Hits 1471 1514 +43
- Misses 200 205 +5
Continue to review full report at Codecov.
|
eeddca2
to
4ea763d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, with a couple of big-picture issues worthy of careful consideration.
struct BreakpointSignature <: AbstractBreakpoint | ||
f # Method or function | ||
sig::Union{Nothing, Type} | ||
line::Int # 0 is a sentinel for first statement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about making this a lineoffset
from the first statement? That may facilitate more robust identity after revision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you give an example? If Revise has revised it, we create a new FrameCode
which will have updated line numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is conditioned on what I thought was your goal to try to preserve breakpoints through a revision. (You may be abandoning that goal, IIUC.) But suppose you wanted to preserve "break at the second line of the body" when you revise (1) a single +
to a -
somewhere in the body of your method, and (2) also make other changes elsewhere in the same source file that alter the absolute line numbers. Then if BreakpointSignature
stored a line offset rather than an absolute line number, it would be unaffected by the fact that unrelated changes occurred elsewhere in the same file. Note that the user could specify the line numbers in absolute terms, I'm just proposing that when one creates the BreakpointSignature
one should convert it to an offset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. Currently I feel like just keeping the line number is the most straight forward. FWIW, I don't see breakpoints on signatures at a given line to be used that much. Just adding file:line is likely more ergonomic.
state = bp[] | ||
bp.framecode.breakpoints[bp.stmtidx] = BreakpointState(!state.isactive, state.condition) | ||
|
||
function add_breakpoint_if_match!(framecode::FrameCode, bp::AbstractBreakpoint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do this only for BreakpointSignature
s? My biggest concern regards my last comment in #256 (comment): what to do about cases where users specified a breakpoint by file/line and then revised the file, thus changing the line numbers. Perhaps a more robust approach would be to convert BreakpointFileLocations to BreakpointSignatures as soon as they resolve?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, perhaps a BreakpointFileLocation
should only be able to bind once to a method and if the method is reevaluated you need to re-add the breakpoint. It would be similar to the current situation except that you can now do "late binding", i.e. add a breakpoint to a file:line before it is included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the same thing sort of applies to a breakpoint to a method or signature with a line number. Should those also only trigger once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm fine with triggering only once. In Juno one might imagine seeing the breakpoint vanish when you save the file, so it should be very easy for users to compensate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, how would Juno know the breakpoint is "invalid"/"stale"? The breakpoint instance will still be there in _breakpoints
, it is just that when we interpret the method, another FrameCode
will be executed.
FWIW, I think Juno should implement location based breakpoints on a level higher than here. In IDEs the breakpoint typically to follow along with edits, so if you insert a line above a breakpoint, the breakpoint will move down a line (together with the rest of the text). Clearly, we cannot breakpoint(file, line)
and remove
every time a user enter a line, so we need to "apply" the current set of line breakpoints when the debugger is started, at the locations they currently are.
cc @pfitzseb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revise could fire a callback notifying Juno of deleted methods. Though I guess revise()
will run when the user tries the next command, not when the user saves the file. So the appropriate spot may no longer be visible...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also not only Revise that can redefine methods. Ctrl + enter in Juno or just re-including a file for example. Losing breakpoints because you reinclude a file would perhaps be annoying.
971f1e2
to
4fc42cd
Compare
Happy for more comments here. @pfitzseb would be good to know how you feel about this with regards to Juno integration. |
Ok, so greedy users (e.g. me) want at least the following:
AFAICT, 1) works well with the current implementation of breakpoints, 2) kinda works on the Julia side but not the GUI side right now (because the breakpoints line number isn't updated, but maybe Revise had a hiccup). 3) fails. The new implementation makes 1) basically impossible because we can only know if a breakpoint works once we hit it, right? 2) and 3) would work with this PR as long as a prospective breakpoint can bind to any number of methods. I'll think more about this tomorrow, but for now I don't see a good way to have all three of the options above at the same time. |
What kind of feedback? Breakpoints are set through the Juno interface right (I think it almost has to be) so you would have all information there.
Yes, you would have to move the breakpoint with editing of the code which is a front end problem. Moving around the code like this should not call into JuliaInterpreter. Until debugging has started, it is just a visual thing. When the debugger starts, you need to set the breakpoints at the line they currently are.
Yes, that should work with this PR since we reinsert the breakpoints for new FrameCode's. |
Hm. Can we guarantee that we can add a valid breakpoint wherever a user might click? What happens on empty lines/comments/outside of functions?
Sure, the GUI side of this is (almost) trivial.
Right, makes sense. |
Lines outside of functions will never be reached since they will never be "activated". For comments and empty lines, I have to look exactly into the current implementation but my guess is that it takes the next statement after the line. Looking at PyCharm, it doesn't let you set breakpoints on empty lines or lines with comments. But they do full parsing of the code etc, |
cfc8d0f
to
d7f8afb
Compare
Pushed some fixes and changes to path handling (and corresponding tests). So you can set a breakpoint by just giving e.g. the file name. For absolute paths (e.g. from an IDE) this shouldn't change anything. |
Also pushed a commit to clear caches, showing that the breakpoint system still works and fixes #262 |
64e58e4
to
1fd7dea
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple more minor comments.
To me it seems the main outstanding issues are (1) implementing pieces that need to go in Juno and (2) deciding exactly how we want these to work when methods or files are revised. For (2), I'd propose we add a new test file, |
I've been thinking about this during the last couple of days and all I feel is: There is no good choice when all you get is the snapshot of a new method. From an IDE it is easy since you just let the breakpoints flow with the editing of the text. I don't feel strongly about what should happen after Revise as long as I can set a breakpoint in a arbitrary file without having to track it first. |
PR updated based on last comments. |
@pfitzseb, It would be interesting to see how Juno works on top of this PR. So, whenever you have the time, it would be valuable to try it out and see how things work. |
I'll try this as soon as I have some time (hopefully tomorrow). Last days have been very busy for me, sorry. |
So, anything left to do here for me? I kind of want to get this in because I think it provides a better breakpoint experience than the current. and I want to add a UI for breakpoints in Debugger.jl but I don't want to put in the time to do that unless this is likely to be mergeable in a reasonable timeframe. |
Uh, sorry -- I was sure I left a comment here... So yeah, I have a Atom.jl branch that uses this PR and it feels pretty good to use. Imho this is good to go as is. |
138317d
to
6b7a76d
Compare
@timholy How fine grained breakpoints do you want to use for Rebugger? Is it ok to have breakpoint in Rebugger set a breakpoint at file:line or do you need more fine grained control (e.g. statement index level control on specific methods). |
File/line is fine. Thanks! |
Added docs for the new types. |
Co-Authored-By: Tim Holy <tim.holy@gmail.com>
@pfitzseb, do you have a branch with the Atom update to use this PR? If so, I could try it out (would probably need installation instructions though). |
I have something that's compatible with these changes, but it's not quite where I want it to be. Will ping you once I have PRs up though. |
The current breakpoint system immediately "lowers" the high level breakpoints given by a user (a high level breakpoint is for example a breakpoint for a method, or for a file:line) into a set of "lower level breakpoints" (given by a statement index into a FrameCode). This has a major drawback:
convert
you don't want to show 60 breakpoints. From a users p.o.v, they added one breakpoint. That breakpoint can apply to multiple places, but it is still one. A user likely want to toggle / remove all the places where this breakpoint applies collectively.FrameCode
s that are being created. That means we lose breakpoints if Revise decide to reevaluate a method because e.g. you fixed your bug and changed a+
to a-
. It means you can't set a breakpoint onfoo
and define a newfoo
and have the breakpoint apply.This PR introduces two "high level breakpoint" structs called
BreakpointSignature
(for breakpoints on methods, signatures etc) andBreakpointFileLocation
for breakpoints on file locations. Eachbreakpoint
invocation will add one of these to a list of breakpoints_breakpoints
. Upon adding a breakpoint, we look through allFrameCode
we have created so far and insert the breakpoint if it matches. Upon creation ofFrameCode
's we insert the breakpoint if it matches. Each breakpoint struct has a fieldapplications::Vector{BreakpointRef}
which is added to as soon as we add a breakpoint to aFrameCode
. This allows toggling, removal etc by just looping through this list.This PR should be fairly non-breaking (doesn't break Debugger, but likely breaks Juno).
This PR enables the file:line breakpoints unconditionally (aka even if Revise is not loaded).