Skip to content

Commit c4fa328

Browse files
Merge PR #21642: Ltac2.Control.reorder_goals
Reviewed-by: ppedrot Ack-by: JasonGross Co-authored-by: ppedrot <ppedrot@users.noreply.github.com>
2 parents 51f9940 + 281b658 commit c4fa328

File tree

6 files changed

+74
-15
lines changed

6 files changed

+74
-15
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- **Added:**
2+
`Ltac2.Control.reorder_goals`
3+
(`#21642 <https://github.com/rocq-prover/rocq/pull/21642>`_,
4+
fixes `#20087 <https://github.com/rocq-prover/rocq/issues/20087>`_,
5+
by Gaëtan Gilbert).

plugins/ltac2/tac2core.ml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,30 @@ let () =
10811081
Proofview.tclUNIT ()
10821082
else throw Tac2ffi.err_notfound
10831083

1084+
let is_permutation len l =
1085+
if not (Int.equal len (Array.length l)) then false else
1086+
let items = Array.make len false in
1087+
(* returns true iff [l] (seen as a 1-indexed list) maps ints in [1; len] to [1; len] injectively.
1088+
Thanks to pigeonhole theorem this means [l] is a permutation of [1; len]. *)
1089+
Array.for_all (fun x ->
1090+
if 1 <= x && x <= len && not items.(x-1) then
1091+
let () = items.(x-1) <- true in
1092+
true
1093+
else false)
1094+
l
1095+
1096+
let () =
1097+
define "reorder_goals" (list int @-> tac unit) @@ fun l ->
1098+
Proofview.Unsafe.tclGETGOALS >>= fun gls ->
1099+
let len = List.length gls in
1100+
let l = Array.of_list l in
1101+
if not (is_permutation len l) then
1102+
throw (err_invalid_arg (Pp.str "reorder_goals"))
1103+
else
1104+
let gls = Array.of_list gls in
1105+
let gls = List.init len (fun i -> gls.(l.(i) - 1)) in
1106+
Proofview.Unsafe.tclSETGOALS gls
1107+
10841108
let () =
10851109
define "unshelve" (thunk valexpr @-> tac valexpr) @@ fun t ->
10861110
Proofview.with_shelf (thaw t) >>= fun (gls,v) ->

plugins/ltac2/tac2ffi.ml

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -277,21 +277,6 @@ let rocq_core n = Names.(KerName.make Tac2env.rocq_prefix (Id.of_string_soft n))
277277

278278
let internal_err = rocq_core "Internal"
279279

280-
let err_notfocussed =
281-
LtacError (rocq_core "Not_focussed", [||])
282-
283-
let err_outofbounds =
284-
LtacError (rocq_core "Out_of_bounds", [||])
285-
286-
let err_notfound =
287-
LtacError (rocq_core "Not_found", [||])
288-
289-
let err_matchfailure =
290-
LtacError (rocq_core "Match_failure", [||])
291-
292-
let err_division_by_zero =
293-
LtacError (rocq_core "Division_by_zero", [||])
294-
295280
let of_exninfo = of_ext val_exninfo
296281
let to_exninfo = to_ext val_exninfo
297282

@@ -514,3 +499,21 @@ let reference = {
514499
r_of = of_reference;
515500
r_to = to_reference;
516501
}
502+
503+
let err_notfocussed =
504+
LtacError (rocq_core "Not_focussed", [||])
505+
506+
let err_outofbounds =
507+
LtacError (rocq_core "Out_of_bounds", [||])
508+
509+
let err_notfound =
510+
LtacError (rocq_core "Not_found", [||])
511+
512+
let err_matchfailure =
513+
LtacError (rocq_core "Match_failure", [||])
514+
515+
let err_division_by_zero =
516+
LtacError (rocq_core "Division_by_zero", [||])
517+
518+
let err_invalid_arg msg =
519+
LtacError (rocq_core "Invalid_argument", [|of_option of_pp (Some msg)|])

plugins/ltac2/tac2ffi.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,4 @@ val err_outofbounds : exn
275275
val err_notfound : exn
276276
val err_matchfailure : exn
277277
val err_division_by_zero : exn
278+
val err_invalid_arg : Pp.t -> exn

test-suite/ltac2/reorder_goals.v

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Require Import Ltac2.Ltac2.
2+
3+
Axiom P : nat -> Prop.
4+
Axiom p : forall n, P n.
5+
6+
Goal P 1 /\ P 2 /\ P 3 /\ P 4.
7+
Proof.
8+
repeat split.
9+
Fail 1:exact (p 3). (* sanity check: "exact (p n)" assert that the goal was originally goal n *)
10+
all:Control.reorder_goals [1;3;4;2].
11+
4: exact (p 2).
12+
Fail all:Control.reorder_goals [1;2]. (* missing goal 3 *)
13+
Fail all:Control.reorder_goals [1;2;3;3]. (* duplicated goal 3 *)
14+
Fail all:Control.reorder_goals [1;4;3]. (* non existing goal 4 *)
15+
all:Control.reorder_goals [3;2;1];
16+
Control.dispatch [
17+
(fun () => exact (p 4));
18+
(fun () => exact (p 3));
19+
(fun () => exact (p 1))
20+
].
21+
Qed.

theories/Ltac2/Control.v

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ Ltac2 @ external new_goal : evar -> unit := "rocq-runtime.plugins.ltac2" "new_go
8888
already defined in the current state, don't do anything. Panics if the
8989
evar is not in the current state. *)
9090

91+
Ltac2 @external reorder_goals : int list -> unit := "rocq-runtime.plugins.ltac2" "reorder_goals".
92+
(** [reorder_goals l] reorders the goals according to (1-indexed) list [l]:
93+
goal [i] after executing the tactic was goal [nth l (i-1)] before executing the tactic.
94+
Throws if [l] is not a permutation of ints from [1] to [numgoals()]. *)
95+
9196
Ltac2 @ external unshelve : (unit -> 'a) -> 'a := "rocq-runtime.plugins.ltac2" "unshelve".
9297
(** Runs the closure, then unshelves existential variables added to the
9398
shelf by its execution, prepending them to the current goal.

0 commit comments

Comments
 (0)