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

feat: upstream addHaveSuggestion and addRewriteSuggestion #210

Merged
merged 14 commits into from
Jan 27, 2024
1 change: 1 addition & 0 deletions Std.lean
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import Std.Lean.Parser
import Std.Lean.PersistentHashMap
import Std.Lean.PersistentHashSet
import Std.Lean.Position
import Std.Lean.Syntax
import Std.Lean.Tactic
import Std.Lean.TagAttribute
import Std.Linter
Expand Down
29 changes: 29 additions & 0 deletions Std/Lean/Syntax.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/-
Copyright (c) 2022 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner
-/
import Lean

/-!
# Helper functions for working with typed syntaxes.
-/

namespace Lean

/--
Applies the given function to every subsyntax.

Like `Syntax.replaceM` but for typed syntax.
kim-em marked this conversation as resolved.
Show resolved Hide resolved
-/
def TSyntax.replaceM [Monad M] (f : Syntax → M (Option Syntax)) (stx : TSyntax k) : M (TSyntax k) :=
.mk <$> stx.1.replaceM f

/--
Constructs a typed separated array from elements.
The given array does not include the separators.

Like `Syntax.SepArray.ofElems` but for typed syntax.
-/
def Syntax.TSepArray.ofElems {sep} (elems : Array (TSyntax k)) : TSepArray k sep :=
.mk (SepArray.ofElems (sep := sep) elems).1
57 changes: 56 additions & 1 deletion Std/Tactic/TryThis.lean
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/-
Copyright (c) 2021 Gabriel Ebner. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner, Mario Carneiro
Authors: Gabriel Ebner, Mario Carneiro, Scott Morrison
-/
import Lean.Server.CodeActions
import Lean.Widget.UserWidget
import Std.Lean.Name
import Std.Lean.Format
import Std.Lean.Position
import Std.Lean.Syntax

/-!
# "Try this" support
Expand Down Expand Up @@ -147,3 +148,57 @@ def addTermSuggestion (ref : Syntax) (e : Expr)
(origSpan? : Option Syntax := none) : TermElabM Unit := do
addSuggestion ref (← delabToRefinableSyntax e)
(suggestionForMessage? := e) (origSpan? := origSpan?)

open Lean Elab Elab.Tactic PrettyPrinter Meta Std.Tactic.TryThis

/-- Add a suggestion for `have : t := e`. -/
def addHaveSuggestion (ref : Syntax) (t? : Option Expr) (e : Expr)
(origSpan? : Option Syntax := none) : TermElabM Unit := do
let estx ← delabToRefinableSyntax e
let prop ← isProp (← inferType e)
let tac ← if let some t := t? then
let tstx ← delabToRefinableSyntax t
if prop then
`(tactic| have : $tstx := $estx)
else
`(tactic| let this : $tstx := $estx)
Copy link
Member

Choose a reason for hiding this comment

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

does this put this† in the output?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed, it does, and there's a FIXME in Mathlib's test/propose.lean about this.

Copy link
Member

Choose a reason for hiding this comment

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

it should use $(mkIdent `this) instead

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, I've fixed the behaviour of Mathlib's have? and observe? in leanprover-community/mathlib4#9454, and will modify this PR accordingly.

else
if prop then
`(tactic| have := $estx)
else
`(tactic| let this := $estx)
addSuggestion ref tac none origSpan?

open Lean.Parser.Tactic
open Lean.Syntax

/-- Add a suggestion for `rw [h₁, ← h₂] at loc`. -/
def addRewriteSuggestion (ref : Syntax) (rules : List (Expr × Bool))
(type? : Option Expr := none) (loc? : Option Expr := none)
(origSpan? : Option Syntax := none) :
TermElabM Unit := do
let rules_stx := TSepArray.ofElems <| ← rules.toArray.mapM fun ⟨e, symm⟩ => do
let t ← delabToRefinableSyntax e
if symm then `(rwRule| ← $t:term) else `(rwRule| $t:term)
let tac ← do
let loc ← loc?.mapM fun loc => do `(location| at $(← delab loc):term)
`(tactic| rw [$rules_stx,*] $(loc)?)

-- We don't simply write `let mut tacMsg := m!"{tac}"` here
-- but instead rebuild it, so that there are embedded `Expr`s in the message,
-- thus giving more information in the hovers.
-- Perhaps in future we will have a better way to attached elaboration information to
kim-em marked this conversation as resolved.
Show resolved Hide resolved
-- `Syntax` embedded in a `MessageData`.
let mut tacMsg :=
let rulesMsg := MessageData.sbracket <| MessageData.joinSep
(rules.map fun ⟨e, symm⟩ => (if symm then "← " else "") ++ m!"{e}") ", "
if let some loc := loc? then
m!"rw {rulesMsg} at {loc}"
else
m!"rw {rulesMsg}"
kim-em marked this conversation as resolved.
Show resolved Hide resolved
let mut extraMsg := ""
if let some type := type? then
tacMsg := tacMsg ++ m!"\n-- {type}"
extraMsg := extraMsg ++ s!"\n-- {← PrettyPrinter.ppExpr type}"
addSuggestion ref tac (suggestionForMessage? := tacMsg)
(extraMsg := extraMsg) (origSpan? := origSpan?)