-
Notifications
You must be signed in to change notification settings - Fork 767
Description
RFC: contextual suggestions
People who are probably interested: @KaseQuark @Kha @javra @Vtec234 @leodemoura @gebner
Links
Goal
The purpose of contextual suggestions is to add some interactivity to the infoview's tactic state. The idea is that if you click on a particular subexpression in the target type of a goal, Lean should suggest rewrites or simplification tactics or conv tactics that could be applied at that subexpression.
Similarly if the user clicks on a hypothesis, and the hypothesis is an inductive datatype, Lean should suggest cases or induction on that type.
Another, more ambitious example is for lean to suggest lemmas that could be applyed depending on the subexpression you clicked on according to some heuristics.
A potential extension to this feature to consider in the future is where Lean will also return the interactive goal state after the suggestion has been applied. Perhaps rendered by the infoview as a diff between the current goal state and the new one.
Proposal
In order to develop this, the tactic state infoview javascript code needs a way of telling the Lean server where the user clicked in the interactive tactic state.
Workflow
- User clicks in a tactic script
- Infoview renders the tactic state as usual
- User clicks somewhere within the interactive goals, let's say at position
5on the main goal's target. - A popup appears above the subexpression, showing the type and the explicit arguments and maybe a docstring. So far this is normal operation.
- New bit: the type popup React component also calls the
Lean.Widget.queryContextualSuggestionsRPC method with the argument being[goal.mvarid, "type", 5], representing the location that the user clicked. - Now Lean takes this information and runs a set of
SuggestionProviders (registered using an attribute@[suggestion_provider]). The proposed definition ofSuggestionProvideris given below. - The Lean server runs each suggestion provider with the tactic state set to be the tactic state used to render the interactive goals.
- (optional) the Lean server then elaborates the tactics in the
Suggestionobject and then runs them on the tactic state, producing a new set ofInteractiveGoalsthat can be used to make a diff for the user. - The server returns the
Lean.Widget.queryContextualSuggestionswith the set of succeeding suggestions. - The infoview then appends these suggestions to the type popup
- (optional) hovering over a suggestion will show the diff / new goal state
- clicking a suggestion will insert the suggestion in to the document. In the prototype this works by simply inserting a new line, however it might be interesting to have a function
diff : Syntax → Syntax → TextEditthat determines a more precise edit (eg so you could add new rewrite lemmas to )
structure SuggestionQuery where
pos : Lean.Lsp.TextDocumentPositionParams
loc : Json
structure Suggestion where
/-- What the user sees -/
description : String
/-- What should be inserted -/
insert : Syntax
SuggestionProvider : SuggestionQuery → TacticM (List Suggestion)
Potential suggestion providers
Congruence tactics
If you click on a subexpression, what are the rewrite rules in mathlib that can be applied at that point? This should be fairly efficient to calculate because the rewrite lemmas can be stored in a discrimination tree, and we can restrict to rewrites that only hit the head expression.
In general any tactic that works inside conv will be available with this method. @KaseQuark is working on this.
Other, more heavy suggestions could involve simp, ring, norm_num, e-matching etc. In this case, performance would become an issue and we will need to start thinking about concurrency and having cancellable versions of these tactic.
other tactics
I think that the suggestions api has some potential in teaching new users which tactics to use. Most of these could be implemented as just a flat list of suggestions for the goal, but clicking in the target or hypothesis that is relevent for the tactic can help narrow the list of suggestions that the new user has to choose from.
For example,
-
if they click on the goal and it is a pi-binder, it can suggest
intros. The number of items that are intro'd could depend on exactly where they click in the expression. -
cases,rcasesclicking on an inductive datatype -
apply, clicking on a hypothesis that matches with the goal, clicking on a subexpression of the goal and doing a discrimination tree search of lemmas that match with the subexpression the user clicked on. (perhaps with some heuristics to keep the results list small) -
rflif the goal can be solved with rfl.
Specialised use cases
Since suggestions are added with the @[suggestion_provider] attribute. Users can add their own special suggestions based on new, future tactics. Eg perhaps one for zooming in on a subexpression in an inequality proof using monotonicity lemmas.
Detail on locations
To specify a position in the interactive goal, rather than having a special-purpose type I propse I having an ad-hoc approach, where a location is represented as an array of numbers and strings. This has the advantage of keeping the types passed through the RPC as simple as possible, with the disadvantage being that we lose some type safety.
interface ContextualSuggestionQueryRequest {
pos : TextDocumentPositionParams;
loc : (string | number)[];
}
interface Suggestion {
/** The text to show to the user in the suggestion viewer. */
display : string
/** The text that should be inserted on the above line. */
insert : string
}
interface ContextualSuggestionQueryResponse{
completions: Suggestion[]
}In the current prototype the location has the following format:
- goal type
[mvarid, "type", subexprPos] - hypothesis type
[mvarid, "hyps", fvarids[0], "type"](the hypothesis bundle may have multiple fvars in it, in which case we just assume the suggestion is for the first fvar in the bundle) - hypothesis type
[mvarid, "hyps", fvarids[0], "type", subexprPos] - hypothesis value
[mvarid, "hyps", fvarids[0], "value", subexprPos] - hypothesis name
[mvarid, "hyps", fvarid]
These are stored as a React context (eg see here and consumed here) that is passed to various parts of the interactive goal view UI. So the computed location is decided entirely by vscode. Another approach would be to have the location passed directly from Lean server as an identifier for each InteractiveGoal and InteractiveHypothesisBundle object.
Tasks
- Rather than
insertbeing a string, perhaps have it be a set of text-edit instructions? - How should long-running tactics and cancellations be managed? Suggestion providers should return fast, but the tactics they are suggesting might be long-running. You don't want these triggering and consuming a lot of compute every time the user moves their mouse around.
- Is too clunky?
