lean4-htt/tests/lean/run/try_user_suggestions.lean
Kim Morrison eb675f708b
feat: user extensibility in try? (#11149)
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 -->
2025-11-12 23:49:54 +00:00

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