This PR adds a user-extension mechanism for the `try?` tactic. You can either use the `@[try_suggestion]` attribute on a declaration with signature ``MVarId -> Try.Info -> MetaM (Array (TSyntax `tactic))`` to produce suggestions, or the `register_try?_tactic <stx>` command with a fixed piece of syntax. User-extensions are only tried *after* the built-in try strategies have been tried and failed. I wanted to ensure that if the user provides a tactic that produces a "Try this:" suggestion, we both emit the original tactic and the suggested replacement (this is what we already do with `grind` and `simp`). I have this working, but it is quite hacky: we grab the message log and parse it. I fear this will break when the "Try this:" format is inevitably changed in the future. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds user-defined suggestion generators for `try?` via `@[try_suggestion]` and `register_try?_tactic`, executed after built-ins with priority and double-suggestion handling. > > - **Parser/Command**: > - Add command syntax `register_try?_tactic (priority := n)? <tacticSeq>` in `Lean.Parser.Command`. > - **Suggestion registry**: > - Introduce `@[try_suggestion (prio)]` attribute with a scoped env extension to register generators (`MVarId → Try.Info → MetaM (Array (TSyntax `tactic))`). > - Priority ordering (higher first); supports local/global scope. > - **Tactic engine (`try?`)**: > - New unsafe pipeline to collect and run user generators after built-in tactics; expands nested "Try this" outputs from user tactics. > - `mkTryEvalSuggestStx` now takes `(goal, info)`; integrates user tactics as fallback via `attempt_all`. > - Suppress intermediate "Try this" messages during `evalAndSuggest` by restoring the message log. > - **Imports**: > - Add `meta import Lean.Elab.Command` for command elaboration. > - **Tests**: > - `try_register_builtin.lean`: command availability and warning without import. > - `try_user_suggestions.lean`: basic, priority, built-in fallback, double-suggestion, and command registration cases. > - Update `versoDocMissing.lean.expected.out` to include `register_try?_tactic` in expected commands. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 302dc9454450eb29ad4ea9b01d87ac60365299ad. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
125 lines
3.5 KiB
Text
125 lines
3.5 KiB
Text
/-
|
|
Test that try? supports user-defined suggestion generators via @[try_suggestion]
|
|
-/
|
|
module
|
|
public import Lean
|
|
public meta import Lean.Elab.Tactic.Try
|
|
|
|
open Lean Meta Elab Tactic Try
|
|
|
|
-- Define a custom inductive predicate that built-in tactics won't solve automatically
|
|
inductive CustomProp : Prop where
|
|
| mk : CustomProp
|
|
|
|
-- Lemma about CustomProp
|
|
theorem customPropHolds : CustomProp := CustomProp.mk
|
|
|
|
section BasicTest
|
|
-- Define a generator that suggests the custom lemma
|
|
@[local try_suggestion]
|
|
meta def customPropSolver (_goal : MVarId) (_info : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| exact CustomProp.mk)]
|
|
|
|
-- Test that try? picks up the user-defined suggestion
|
|
-- Built-in tactics won't solve this, so only user suggestion appears
|
|
/--
|
|
info: Try this:
|
|
[apply] exact CustomProp.mk✝
|
|
-/
|
|
#guard_msgs in
|
|
example : CustomProp := by
|
|
try?
|
|
end BasicTest
|
|
|
|
section PriorityTest
|
|
-- Test priority ordering with multiple generators
|
|
@[local try_suggestion 2000]
|
|
meta def highPrioritySolver (_goal : MVarId) (_info : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| apply CustomProp.mk)]
|
|
|
|
@[local try_suggestion 1000]
|
|
meta def midPrioritySolver (_goal : MVarId) (_info : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| exact CustomProp.mk)]
|
|
|
|
@[local try_suggestion 500]
|
|
meta def lowPrioritySolver (_goal : MVarId) (_info : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| constructor)]
|
|
|
|
-- All generators return successful tactics, ordered by priority (highest first)
|
|
/--
|
|
info: Try these:
|
|
[apply] apply CustomProp.mk✝
|
|
[apply] exact CustomProp.mk✝
|
|
[apply] constructor
|
|
-/
|
|
#guard_msgs in
|
|
example : CustomProp := by
|
|
try?
|
|
end PriorityTest
|
|
|
|
section BuiltInFallback
|
|
-- Test that user generators only run if built-ins fail
|
|
-- For True, built-ins succeed so user generators shouldn't run
|
|
@[local try_suggestion]
|
|
meta def shouldNotRunForTrue (_goal : MVarId) (_info : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| exact True.intro)]
|
|
|
|
-- Built-ins succeed, so user suggestion doesn't appear
|
|
/--
|
|
info: Try these:
|
|
[apply] simp
|
|
[apply] simp only
|
|
[apply] grind
|
|
[apply] grind only
|
|
[apply] simp_all
|
|
-/
|
|
#guard_msgs in
|
|
example : True := by
|
|
try?
|
|
end BuiltInFallback
|
|
|
|
section DoubleSuggestion
|
|
-- Test double-suggestion: when a user tactic produces "Try this" messages,
|
|
-- both the original tactic and the suggestions should appear
|
|
-- Use CustomProp which built-ins can't solve
|
|
@[local try_suggestion]
|
|
meta def doubleSuggestionSolver (_goal : MVarId) (_info : Try.Info) : MetaM (Array (TSyntax `tactic)) := do
|
|
return #[← `(tactic| show_term apply CustomProp.mk)]
|
|
|
|
-- Double-suggestion: when show_term produces a "Try this" message,
|
|
-- both the original tactic and the extracted suggestion should appear
|
|
-- The message from show_term during extraction is suppressed
|
|
/--
|
|
info: Try these:
|
|
[apply] show_term apply CustomProp.mk✝
|
|
[apply] exact CustomProp.mk
|
|
-/
|
|
#guard_msgs in
|
|
example : CustomProp := by
|
|
try?
|
|
end DoubleSuggestion
|
|
|
|
section RegisterCommand
|
|
-- Test the register_try?_tactic convenience command
|
|
register_try?_tactic (priority := 500) constructor
|
|
|
|
/--
|
|
info: Try this:
|
|
[apply] constructor
|
|
-/
|
|
#guard_msgs in
|
|
example : CustomProp := by
|
|
try?
|
|
|
|
-- Test without explicit priority (should default to 1000, so appear before constructor at 500)
|
|
register_try?_tactic apply CustomProp.mk
|
|
|
|
/--
|
|
info: Try these:
|
|
[apply] apply CustomProp.mk
|
|
[apply] constructor
|
|
-/
|
|
#guard_msgs in
|
|
example : CustomProp := by
|
|
try?
|
|
end RegisterCommand
|