Skip to content

Commit fe0da2c

Browse files
Report the proof state when an error occurs during a proof
When a command fails while a proof is in progress, the error now carries the proof state, printed after the error message (as the err_desc of Fatal): - a tactic failure reports the state the tactic was applied to; - a subproof-count mismatch reports the state before and after the tactic (previously the goals were embedded in the message itself, and one of the four cases did not show them at all); - `end` on an unfinished proof reports the remaining goals (previously embedded in the message). Error messages themselves are unchanged and stay on the first line, printed in red; the proof state follows uncolored. The LSP server does not attach the state to tactic failures since editors display the proof state themselves.
1 parent ec24f61 commit fe0da2c

11 files changed

Lines changed: 178 additions & 15 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
3333
- LSP server: position of the error is removed from diagnostics when the error occurs in the file currently open in the editor.
3434
- Syntax of search query is modified as follows : `in` is used instead of `|` (filtering). `with` is used instead of `,` (conjunction). `|` is used instead of `;` (disjunction).
3535
- Type of `#assume` in order to generate a new symbol and use it inside a tactic term.
36+
- Errors occurring while a proof is in progress now report the proof state: the goals a failing tactic was applied to, the goals before and after the tactic for a subproof-count mismatch, and the remaining goals when a proof is unfinished at `end`. The state is printed after the error message, which stays unchanged. The LSP server does not attach the proof state to tactic failures since editors display it themselves.
3637

3738
### Fixed
3839

src/handle/command.ml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,9 @@ let get_proof_data : compiler -> sig_state -> p_command -> cmd_output =
569569
| P_proof_end ->
570570
(* Check that the proof is indeed finished. *)
571571
if not (finished ps) then
572-
fatal pe.pos "The proof is not finished:@.%a" goals ps;
572+
fatal pe.pos
573+
~err_desc:(Format.asprintf "Proof state:@.%a@." goals ps)
574+
"The proof is not finished.";
573575
(* Keep the definition only if the symbol is not opaque. *)
574576
let d =
575577
if opaq then None else

src/handle/proof.ml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,31 @@ let goals : proof_state pp = fun ppf ps ->
2727
let goal ppf i g = out ppf "\n%d. %a" (i+1) Goal.pp_no_hyp g in
2828
List.iteri (goal ppf) gs
2929

30+
(** [state_on_error] controls whether the failure of a tactic attaches the
31+
current proof state to the error, as the [err_desc] argument of the
32+
[Fatal] exception, printed after the error message. It is enabled by
33+
default, and disabled by the LSP server, which has its own means of
34+
displaying the proof state. Errors that always reported the proof state
35+
(subproof-count mismatches, unfinished proofs) keep doing so regardless
36+
of this setting. *)
37+
let state_on_error : bool Stdlib.ref = Stdlib.ref true
38+
39+
(** [error_state ?after ps] renders, for attachment to an error report, the
40+
proof state [ps] a failing tactic was applied to, followed, when the
41+
failure was only detected after the tactic was applied (a subproof-count
42+
mismatch), by the proof state [after] its application. *)
43+
let error_state : ?after:proof_state -> proof_state -> string =
44+
fun ?after before ->
45+
match after with
46+
| None ->
47+
Format.asprintf "Proof state before tactic application:@.%a@."
48+
goals before
49+
| Some after ->
50+
Format.asprintf
51+
"Proof state before tactic application:@.%a@.\
52+
Proof state after tactic application:@.%a@."
53+
goals before goals after
54+
3055
(** [remove_solved_goals ps] removes from the proof state [ps] the typing
3156
goals that are solved. *)
3257
let remove_solved_goals : proof_state -> proof_state = fun ps ->

src/handle/tactic.ml

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -811,30 +811,53 @@ let handle :
811811

812812
(** [handle sym_pos priv r tac n] applies the tactic [tac] from the previous
813813
tactic output [r] and checks that the number of goals of the new proof
814-
state is compatible with the number [n] of subproofs. *)
814+
state is compatible with the number [n] of subproofs. When [tac] fails,
815+
the proof state it was applied to is attached to the error (see
816+
{!val:Proof.state_on_error}). *)
815817
let handle :
816818
Sig_state.t -> popt -> bool -> tac_output -> p_tactic -> int -> tac_output =
817819
fun ss sym_pos priv (ps, _) t nb_subproofs ->
818-
let (ps', _) as a = handle ss sym_pos priv ps t in
820+
(* Attach the proof state [t] was applied to, to any error escaping its
821+
application. Errors raised inside tacticals like [try] or [orelse] are
822+
caught below this point, so only failures actually reported to the user
823+
are concerned. *)
824+
let (ps', _) as a =
825+
try handle ss sym_pos priv ps t
826+
with Fatal(p, msg, desc)
827+
when Stdlib.(!state_on_error) && ps.proof_goals <> [] ->
828+
let state = error_state ps in
829+
let desc = if desc = "" then state else desc ^ "\n" ^ state in
830+
raise (Fatal(p, msg, desc))
831+
in
819832
let nb_goals_before = List.length ps.proof_goals in
820833
let nb_goals_after = List.length ps'.proof_goals in
821834
let nb_newgoals = nb_goals_after - nb_goals_before in
835+
(* [t] ran, but the number of subproofs given does not match the number of
836+
subgoals it produced: report the proof state before and after its
837+
application. *)
838+
let mismatch : string -> 'a = fun reason ->
839+
fatal t.pos ~err_desc:(error_state ~after:ps' ps) "%s" reason in
822840
if nb_newgoals <= 0 then
823841
if nb_subproofs = 0 then a
824-
else fatal t.pos "A subproof is given but there is no subgoal."
842+
else mismatch "A subproof is given but there is no subgoal."
825843
else if is_destructive t then
826-
match nb_newgoals + 1 - nb_subproofs with
844+
(match nb_newgoals + 1 - nb_subproofs with
827845
| 0 -> a
828846
| n when n > 0 ->
829-
fatal t.pos "Missing subproofs (%d subproofs for %d subgoals):@.%a"
830-
nb_subproofs (nb_newgoals + 1) goals ps'
847+
mismatch (Printf.sprintf
848+
"Missing subproofs (%d subproofs for %d subgoals)."
849+
nb_subproofs (nb_newgoals + 1))
831850
| _ ->
832-
fatal t.pos "Too many subproofs (%d subproofs for %d subgoals):@.%a"
833-
nb_subproofs (nb_newgoals + 1) goals ps'
851+
mismatch (Printf.sprintf
852+
"Too many subproofs (%d subproofs for %d subgoals)."
853+
nb_subproofs (nb_newgoals + 1)))
834854
else match nb_newgoals - nb_subproofs with
835855
| 0 -> a
836856
| n when n > 0 ->
837-
fatal t.pos "Missing subproofs (%d subproofs for %d subgoals):@.%a"
838-
nb_subproofs nb_newgoals goals ps'
839-
| _ -> fatal t.pos "Too many subproofs (%d subproofs for %d subgoals)."
840-
nb_subproofs nb_newgoals
857+
mismatch (Printf.sprintf
858+
"Missing subproofs (%d subproofs for %d subgoals)."
859+
nb_subproofs nb_newgoals)
860+
| _ ->
861+
mismatch (Printf.sprintf
862+
"Too many subproofs (%d subproofs for %d subgoals)."
863+
nb_subproofs nb_newgoals)

src/lsp/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
(name lsp)
33
(public_name lambdapi.lsp)
44
(modules lsp_base lsp_io lp_doc lp_lsp)
5-
(libraries yojson lambdapi.pure))
5+
(libraries yojson lambdapi.handle lambdapi.pure))

src/lsp/lp_lsp.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,9 @@ let main std log_file =
460460
let lp_fmt = F.formatter_of_buffer Lp_doc.lp_logger in
461461
Console.out_fmt := lp_fmt;
462462
Error.err_fmt := lp_fmt;
463+
(* Editors display the proof state themselves, so do not attach it to
464+
tactic failures. *)
465+
Handle.Proof.state_on_error := false;
463466
(* Console.verbose := 4; *)
464467

465468
let rec loop () =

tests/KO/proof_state_fail.lp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// A tactic failing mid-proof: the error must carry the proof state
2+
// (hypotheses, separator, goals) as its description (see proof_state.ml).
3+
constant symbol Prop : TYPE;
4+
injective symbol π : Prop → TYPE;
5+
constant symbol A : Prop;
6+
constant symbol B : Prop;
7+
symbol imp : Prop → Prop → Prop;
8+
rule π (imp $a $b) ↪ π $a → π $b;
9+
opaque symbol demo : π (imp A (imp B A)) ≔
10+
begin
11+
assume a b;
12+
fail
13+
end;

tests/KO/proof_state_mismatch.lp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// A subproof-count mismatch is only detected after the tactic ran: the error
2+
// must carry the proof state before AND after it (see proof_state.ml).
3+
constant symbol Prop : TYPE;
4+
builtin "Prop" ≔ Prop;
5+
injective symbol π : Prop → TYPE;
6+
builtin "P" ≔ π;
7+
inductive N : TYPE ≔ | z : N | succ : N → N;
8+
symbol Q : N → Prop;
9+
opaque symbol demo : Π n, π (Q n) ≔
10+
begin
11+
induction
12+
end;

tests/KO/proof_state_unfinished.lp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Reaching `end` with goals remaining: the error must carry the remaining
2+
// proof state as its description (see proof_state.ml).
3+
constant symbol Prop : TYPE;
4+
injective symbol π : Prop → TYPE;
5+
constant symbol A : Prop;
6+
symbol imp : Prop → Prop → Prop;
7+
rule π (imp $a $b) ↪ π $a → π $b;
8+
opaque symbol demo : π (imp A A) ≔
9+
begin
10+
assume a
11+
end;

tests/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(tests
2-
(names ok_ko rewriting purity kernel)
2+
(names ok_ko rewriting purity kernel proof_state)
33
(deps
44
(glob_files OK/*.lp)
55
(glob_files OK/*.dk)

0 commit comments

Comments
 (0)