Matching goals with variables#

Our first version works great when the conclusion matches exactly the shape of a lemma, but it doesn’t generalize well in the presence of variables. For example, if we introduced variables before running ApplySearch.run (), we would not find anything, despite the lemma still applying:

Use of “Require” inside a module is fragile. It is not recommended to use this functionality in finished proof scripts. [require-in-module,fragile,default]
Open Scope list_scope.

forall (A : Type) (x y z : list A), x ++ y = x ++ z -> y = z

forall (A : Type) (x y z : list A), x ++ y = x ++ z -> y = z
A: Type
x, y, z: list A

x ++ y = x ++ z -> y = z
(* Now our tactic fails: *)
No theorem could be applied.
- : unit = ()
A: Type
x, y, z: list A

x ++ y = x ++ z -> y = z
(* Stuck. :( *) Abort.

To fix this issue, we’ll replace our variables with existential variables (evars in short), which are converted to pattern variables by Patternops.pattern_of_constr.

Turning variables into evars#

We introduce a new function, create_pattern, that creates the pattern by replacing every variable with a fresh evar:

Tutorial.v#
19(* … *)
20let run () =
21  match%rocq goal with
22  | _, {| ?conclusion |} ->
23    let* env = Tactic.env in
24    let* sigma = Tactic.sigma in
25    let pattern = create_pattern env sigma conclusion in
26    print_results (search env sigma pattern);
27    return ()

The create_pattern method looks as follows:

Tutorial.v#
 4let create_pattern env sigma t =
 5  let new_evar_for var (sigma, mappings) =
 6    let typ = EConstr.of_constr (Environ.named_type var env) in
 7    let sigma, evar = Evarutil.new_evar env sigma typ
 8      ~src:(None, Evar_kinds.MatchingVar (Evar_kinds.FirstOrderPatVar var))
 9      ~naming:(IntroIdentifier var)
10    in (sigma, (var, evar) :: mappings)
11  in
12  let vars = Termops.collect_vars sigma t in
13  let sigma, mappings = Names.Id.Set.fold new_evar_for vars (sigma, []) in
14  Patternops.pattern_of_constr env sigma
15    (EConstr.Vars.replace_vars sigma mappings t)
16
17(* … *)

We use several low-level functions from Rocq APIs:

Our file now looks as follows:

Tutorial.v#
 1Require Import Camltac.Camltac.
 2
 3Camltac Module ApplySearch := ocaml:{{
 4  let create_pattern env sigma t =
 5    let new_evar_for var (sigma, mappings) =
 6      let typ = EConstr.of_constr (Environ.named_type var env) in
 7      let sigma, evar = Evarutil.new_evar env sigma typ
 8        ~src:(None, Evar_kinds.MatchingVar (Evar_kinds.FirstOrderPatVar var))
 9        ~naming:(IntroIdentifier var)
10      in (sigma, (var, evar) :: mappings)
11    in
12    let vars = Termops.collect_vars sigma t in
13    let sigma, mappings = Names.Id.Set.fold new_evar_for vars (sigma, []) in
14    Patternops.pattern_of_constr env sigma
15      (EConstr.Vars.replace_vars sigma mappings t)
16
17  let search env sigma pattern =
18    let results = ref [] in
19    let collect_result res _ _ _ _ = results := res :: !results in
20    Search.search_pattern env sigma pattern (SearchOutside []) collect_result;
21    !results
22
23  let print_results = function
24    | [] -> Feedback.msg_info (Pp.str "No theorem could be applied.")
25    | results ->
26       let open Pp in
27       let pr_ref ref = Libnames.pr_path (Ltac2.Env.path ref) in
28       let pp = List.fold_left
29         (fun acc res -> acc ++ fnl () ++ pr_ref res)
30         (str "Try this: ") results in
31       Feedback.msg_info pp
32
33  let run () =
34    match%rocq goal with
35    | _, {| ?conclusion |} ->
36      let* env = Tactic.env in
37      let* sigma = Tactic.sigma in
38      let pattern = create_pattern env sigma conclusion in
39      print_results (search env sigma pattern);
40      return ()
41}}.

If we rerun the tactic, we now obtain the correct results:

From Stdlib Require List.
Open Scope list_scope.


forall (A : Type) (x y z : list A), x ++ y = x ++ z -> y = z

forall (A : Type) (x y z : list A), x ++ y = x ++ z -> y = z
A: Type
x, y, z: list A

x ++ y = x ++ z -> y = z
Try this: Stdlib.Lists.List.app_inv_head
- : unit = ()
A: Type
x, y, z: list A

x ++ y = x ++ z -> y = z