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

Interactive goals & diagnostics #596

Merged
merged 51 commits into from
Aug 24, 2021
Merged

Conversation

Vtec234
Copy link
Member

@Vtec234 Vtec234 commented Aug 3, 2021

Implements server-side support for interactive goals and diagnostics. Currently the only supported mode of interaction is exploring Exprs (we can check the types of subexpressions and explicit arguments). We add a new taggedMsg field to the Diagnostic type which clients can ignore, while the infoview uses it to visualize things.

We support the following interactive objects:

  • "code"; currently this is only ever a pretty-printed Expr but other objects such as general Syntax will also work, as long as they have associated Elab.Info
  • goals; we can interact with goals and the "code" embedded in the hypothesis/goal types as well as let-bound values
  • traces; messages with trace annotations can be explored interactively; they are pretty-printed lazily to make massive traces reasonably performant
  • general messages; can contain code, goals, and traces

TODOs:

Copy link
Member

@gebner gebner left a comment

Choose a reason for hiding this comment

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

We add a new taggedMsg field to the Diagnostic type which clients can ignore [...]

Is this true? Don't you get a memory leak if you don't release the RPC references? (Also, diagnostics are updated really often. This will cause a lot of release requests.)

(There's also the question of who owns these references in the vscode extension, i.e., who is responsible for releasing them. If it is the infoview, what happens if it is closed?)

src/Lean/Widget/ExprWithCtx.lean Outdated Show resolved Hide resolved
src/Lean/Widget/InteractiveGoals.lean Outdated Show resolved Hide resolved
src/Lean/Widget/TaggedText.lean Outdated Show resolved Hide resolved
src/Lean/Widget/InteractiveDiagnostics.lean Outdated Show resolved Hide resolved
src/Lean/Widget/InteractiveGoals.lean Outdated Show resolved Hide resolved
src/Lean/Widget/InteractiveDiagnostics.lean Outdated Show resolved Hide resolved
@Vtec234
Copy link
Member Author

Vtec234 commented Aug 3, 2021

Don't you get a memory leak if you don't release the RPC references?

This is correct. We can have a lean.hasInfoview client capability s.t. refs are only sent if the capability is set.

@gebner
Copy link
Member

gebner commented Aug 4, 2021

This is correct. We can have a lean.hasInfoview client capability s.t. refs are only sent if the capability is set.

What happens if the infoview is closed?

@gebner
Copy link
Member

gebner commented Aug 12, 2021

There's also quite a bit of discussion on the corresponding vscode-lean4 PR: leanprover/vscode-lean4#32

@Vtec234
Copy link
Member Author

Vtec234 commented Aug 13, 2021

This should now be in a reviewable state. There are some TODOs for which I will make issues, but the backend seems to be working.

src/Lean/Data/Json/Basic.lean Outdated Show resolved Hide resolved
let ckvs := kvs.fold (fun acc k j => s!"{renderString k}:{compress j}" :: acc) []
let ckvs := ",".intercalate ckvs
s!"\{{ckvs}}"
protected inductive CompressWorkItem
Copy link
Member

Choose a reason for hiding this comment

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

Could be private?

Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't seem possible to open a private inductive to get its constructors (unknown namespace 'CompressWorkItem').

Copy link
Member

Choose a reason for hiding this comment

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

Interesting, I think that could be considered a bug

src/Lean/PrettyPrinter/Delaborator/Basic.lean Outdated Show resolved Hide resolved
src/Lean/Server/FileWorker/RequestHandling.lean Outdated Show resolved Hide resolved
src/Lean/Server/Snapshots.lean Outdated Show resolved Hide resolved
def compileNextCmd (contents : String) (snap : Snapshot) : IO (Sum Snapshot MessageLog) := do
let inputCtx := Parser.mkInputContext contents "<input>"
def compileNextCmd (text : FileMap) (snap : Snapshot) : IO Snapshot := do
let inputCtx := Parser.mkInputContext text.source "<input>"
let cmdState := snap.cmdState
let scope := cmdState.scopes.head!
let pmctx := { env := cmdState.env, options := scope.opts, currNamespace := scope.currNamespace, openDecls := scope.openDecls }
let (cmdStx, cmdParserState, msgLog) :=
Parser.parseCommand inputCtx pmctx snap.mpState snap.msgLog
Copy link
Member

Choose a reason for hiding this comment

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

I think we can also just pass a fresh message log here so that the result contains the new messages only. A command shouldn't care about messages before it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that would get rid of computing the MessageLog diff. But we do want Snapshot.interactiveDiags to contain every diagnostic up to a point so that we can send them easily without folding over all snapshots. So we'd have a MessageLog with messages just for this snapshot but an interactive diagnostics log for everything, which is perhaps a bit weird. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

I think that's fine, we can call it allInteractiveDiags or something similar. Not sure folding would even be a performance issue, concatenating mostly-empty arrays should be quite fast even with hundreds of snapshots.

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 tried this out now, but it is still clunky. The reason is that the MessageLog is part of Command.State which is designed for a "rolling" usage. So we'd have to leave most of the command state as-is but clear out just the message log. I would leave it as is, but added a comment clarifying it.

src/Lean/Widget/InteractiveDiagnostic.lean Outdated Show resolved Hide resolved
Comment on lines 41 to 43
partial def map (f : α → β) : TaggedText α → TaggedText β :=
go
where go : TaggedText α → TaggedText β
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
partial def map (f : α → β) : TaggedText α → TaggedText β :=
go
where go : TaggedText α → TaggedText β
variable (f : α → β) in
partial def map : TaggedText α → TaggedText β

etc

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed, but I must note that this is one of few things where the Lean 3 syntax actually seems more natural/sensible to me. The old fixed-before-colon/recursive-after-colon makes sense once one gets used to it. It's kind of weird that variable (a : T) in def foo has a different behaviour than directly writing def foo (a : T) and indeed I forgot about it here.

@gebner
Copy link
Member

gebner commented Aug 16, 2021

I've tried implementing the RPC protocol in the neovim plugin (Julian/lean.nvim#118). Some thoughts:

There is another problem with $/lean/rpc/connect being a notification instead of a request: you don't know if the request failed. In particular, there is no way to tell if you are connected to an old lean version which does not support the request or if you just need to wait a little longer. This should really be a request and not a notification, synchronous communication is what requests are made for.

The keep-alive mechanism is not particularly helpful for the neovim plugin. It doesn't do anything that can't be done by sending a $/lean/rpc/connect request to close the current session. (Sending a $/lean/rpc/connect notification might also be an option for the vscode implementation.)

I'd like to reiterate at this point that I think it is worth to get the basic design right from the beginning. It is trivial to add new functions and fields to existing structures; and this is unlikely to break anything. However the current approach really hardcodes the assumption that every editor has at most one infoview which uses at most one session, and doesn't use the RPC for anything else. I would at least like to see a plan on how to support two infoviews. There are all kinds of cool things on the horizon (e.g. there could be a vscode API to embed a webview in the editor), and I would hate it if we couldn't make use of them due to basic design constraints.

The ToJson Name and FromJson Name instances surprisingly require a backtick. This has the effect that you need to write "`Lean.Widget.getInteractiveGoals" as a method name. I think the backtick should be removed from the instances.

The names field in InteractiveHypothesis should probably be an array.

The getInteractiveTermGoal function doesn't return the range (which the plainTermGoal request does).

I don't completely understand the ExprWithCtx API. Is there even a way to get an ExprWithCtx reference? From what I can tell, this seems to be supplanted by the new InfoWithCtx API, which seems to be less general (e.g. it doesn't have inferType). Either way, if one of them is already obsolete, then this should be cleaned up before merging.

@gebner
Copy link
Member

gebner commented Aug 16, 2021

Something else that would be super cool and might be hard to retrofit (because the InfoWithCtx is a reference, and we can't just add a field there) is semantic highlighting. That is, information about which parts of a CodeWithInfos are keywords, literals, operators, etc.

@Vtec234
Copy link
Member Author

Vtec234 commented Aug 20, 2021

@gebner I am thinking more about backwards compatibility. We should preserve the interface when possible, but ultimately I do want to at least be able to make breaking changes. I am assuming that we do not have to worry about a new server<->old client situation because at least VSCode will auto-update extensions, and in Vim or Emacs we can expect users to update as well. This leaves new client<->old server in cases when one is working on a project that e.g. uses older mathlib. For this, I think we should start properly versioning the Lean server (currently on 0.0.1) and detect this in editor extensions when necessary, then use the API for that server version.

@Vtec234
Copy link
Member Author

Vtec234 commented Aug 23, 2021

All outstanding comments should be addressed now.

@leodemoura
Copy link
Member

@Vtec234 Is it ready to be merged?

@leodemoura leodemoura requested a review from gebner August 24, 2021 03:50
| k =>
(do let stx ← (delabAttribute.getValues (← getEnv) k).firstM id
let stx ← annotateCurPos stx
addTermInfo (← getPos) stx (← getExpr)
Copy link
Member

Choose a reason for hiding this comment

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

What is the overhead of using addTermInfo here? Is the slowdown significant? Just curious.

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 didn't test in a profiler, but in terms of wall time on both a long trace (~1s to print) and a long expression (~1.3s), I observed no difference with and without the addTermInfo/addFieldInfo calls.

@Vtec234
Copy link
Member Author

Vtec234 commented Aug 24, 2021

@Vtec234 Is it ready to be merged?

I think so. There are some smaller discussions left which can be addressed later.

@leodemoura leodemoura merged commit 81eff79 into leanprover:master Aug 24, 2021
ChrisHughes24 pushed a commit to ChrisHughes24/lean4 that referenced this pull request Dec 2, 2022
* `false` -> `False` (we're talking about the Prop, not the Bool).
* `begin .. end` -> `by ...`
* Remove obsolete comment about `push_neg` not being implemented. (It landed in leanprover#344).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants