This PR provides a basic API for a premise selection tool, which can be provided in downstream libraries. It does not implement premise selection itself!
135 lines
5.1 KiB
Text
135 lines
5.1 KiB
Text
/-
|
|
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
Authors: Kim Morrison
|
|
-/
|
|
prelude
|
|
import Lean.Elab.Command
|
|
import Lean.Meta.Eval
|
|
import Lean.Meta.CompletionName
|
|
import Init.Data.Random
|
|
|
|
/-!
|
|
# An API for premise selection algorithms.
|
|
|
|
This module provides a basic API for premise selection algorithms,
|
|
which are used to suggest identifiers that should be introduced in a proof.
|
|
|
|
The core interface is the `Selector` type, which is a function from a metavariable
|
|
and a configuration to a list of suggestions.
|
|
|
|
The `Selector` is registered as an environment extension, and the trivial (no suggestions) implementation
|
|
is `Lean.PremiseSelection.empty`.
|
|
|
|
Lean does not provide a default premise selector, so this module is intended to be used in conjunction
|
|
with a downstream package which registers a premise selector.
|
|
-/
|
|
|
|
namespace Lean.PremiseSelection
|
|
|
|
/--
|
|
A `Suggestion` is essentially just an identifier and a confidence score that the identifier is relevant.
|
|
If the premise selection request included information about the intended use (e.g. in the simplifier, in `grind`, etc.)
|
|
the score may be adjusted for that application.
|
|
|
|
-/
|
|
structure Suggestion where
|
|
name : Name
|
|
/--
|
|
The score of the suggestion, as a probability that this suggestion should be used.
|
|
-/
|
|
score : Float
|
|
|
|
structure Config where
|
|
/--
|
|
The maximum number of suggestions to return.
|
|
-/
|
|
maxSuggestions : Option Nat := none
|
|
/--
|
|
The tactic that is calling the premise selection, e.g. `simp`, `grind`, or `aesop`.
|
|
This may be used to adjust the score of the suggestions
|
|
-/
|
|
caller : Option Name := none
|
|
/--
|
|
A filter on suggestions; only suggestions returning `true` should be returned.
|
|
(It can be better to filter on the premise selection side, to ensure that enough suggestions are returned.)
|
|
-/
|
|
filter : Name → MetaM Bool := fun _ => pure true
|
|
/--
|
|
An optional arbitrary "hint" to the premise selection algorithm.
|
|
There is no guarantee that the algorithm will make any use of the hint.
|
|
|
|
Potential use cases include a natural language comment provided by the user
|
|
(e.g. allowing use of the premise selector as a search engine)
|
|
or including context from the current proof and/or file.
|
|
|
|
We may later split these use cases into separate fields if necessary.
|
|
-/
|
|
hint : Option String := none
|
|
|
|
abbrev Selector : Type := MVarId → Config → MetaM (Array Suggestion)
|
|
|
|
/--
|
|
The trivial premise selector, which returns no suggestions.
|
|
-/
|
|
def empty : Selector := fun _ _ => pure #[]
|
|
|
|
/-- A random premise selection algorithm, provided solely for testing purposes. -/
|
|
def random (gen : StdGen := ⟨37, 59⟩) : Selector := fun _ cfg => do
|
|
IO.stdGenRef.set gen
|
|
let env ← getEnv
|
|
let max := cfg.maxSuggestions.getD 10
|
|
let consts := env.const2ModIdx.keysArray
|
|
let mut suggestions := #[]
|
|
while suggestions.size < max do
|
|
let i ← IO.rand 0 consts.size
|
|
let name := consts[i]!
|
|
if ! (`Lean).isPrefixOf name && Lean.Meta.allowCompletion env name then
|
|
suggestions := suggestions.push { name := name, score := 1.0 / consts.size.toFloat }
|
|
return suggestions
|
|
|
|
initialize premiseSelectorExt : EnvExtension (Option Selector) ←
|
|
registerEnvExtension (pure none)
|
|
|
|
/-- Generate premise suggestions for the given metavariable, using the currently registered premise selector. -/
|
|
def select (m : MVarId) (c : Config := {}) : MetaM (Array Suggestion) := do
|
|
let some selector := premiseSelectorExt.getState (← getEnv) |
|
|
throwError "No premise selector registered. \
|
|
(Note the Lean does not provide a default premise selector, these must be installed by a downstream library.)"
|
|
selector m c
|
|
|
|
/-!
|
|
Currently the registration mechanism is just global state.
|
|
This means that if multiple modules register premise selectors,
|
|
the behaviour will be dependent on the order of loading modules.
|
|
|
|
We should replace this with a mechanism so that
|
|
premise selectors are configured via options in the `lakefile`, and
|
|
commands are only used to override in a single declaration or file.
|
|
-/
|
|
|
|
/-- Set the current premise selector.-/
|
|
def registerPremiseSelector (selector : Selector) : CoreM Unit := do
|
|
modifyEnv fun env => premiseSelectorExt.setState env (some selector)
|
|
|
|
open Lean Elab Command in
|
|
@[builtin_command_elab setPremiseSelectorCmd, inherit_doc setPremiseSelectorCmd]
|
|
def elabSetPremiseSelector : CommandElab
|
|
| `(command| set_premise_selector $selector) => do
|
|
let selector ← liftTermElabM do
|
|
try
|
|
let selectorTerm ← Term.elabTermEnsuringType selector (some (Expr.const ``Selector []))
|
|
unsafe Meta.evalExpr Selector (Expr.const ``Selector []) selectorTerm
|
|
catch _ =>
|
|
throwError "Failed to elaborate {selector} as a `MVarId → Config → MetaM (Array Suggestion)`."
|
|
liftCoreM (registerPremiseSelector selector)
|
|
| _ => throwUnsupportedSyntax
|
|
|
|
open Lean.Elab.Tactic in
|
|
@[builtin_tactic Lean.Parser.Tactic.suggestPremises] def evalSuggestPremises : Tactic := fun _ =>
|
|
liftMetaTactic1 fun mvarId => do
|
|
let suggestions ← select mvarId
|
|
logInfo m!"Premise suggestions: {suggestions.map (·.name)}"
|
|
return mvarId
|
|
|
|
end Lean.PremiseSelection
|