Skip to content

Commit c65e627

Browse files
committed
congr: add congr pat and congr * variants
`congr pat` requires the pattern to match both sides of the goal equality (or iff); each pattern variable yields one subgoal equating its bindings on the two sides. `congr *` walks both sides in lock-step without any reduction, emitting one subgoal per pair of differing positions. Both variants are first-order (binders are opaque, matching the existing `congr`'s behavior).
1 parent 171c464 commit c65e627

9 files changed

Lines changed: 322 additions & 8 deletions

File tree

src/ecHiGoal.ml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2262,7 +2262,8 @@ let process_exists args (tc : tcenv1) =
22622262
EcLowGoal.t_exists_intro_s args tc
22632263

22642264
(* -------------------------------------------------------------------- *)
2265-
let process_congr tc =
2265+
(* Default [congr]: one structural step on the head of the equality. *)
2266+
let process_congr_default tc =
22662267
let (env, hyps, concl) = FApi.tc1_eflat tc in
22672268

22682269
if not (EcFol.is_eq_or_iff concl) then
@@ -2310,6 +2311,31 @@ let process_congr tc =
23102311

23112312
| _, _ -> tacuerror "not a congruence"
23122313

2314+
(* -------------------------------------------------------------------- *)
2315+
(* [congr pat]: thin wrapper around [EcLowGoal.t_congr_pattern]. *)
2316+
let process_congr_pattern p tc =
2317+
let concl = FApi.tc1_goal tc in
2318+
if not (EcFol.is_eq_or_iff concl) then
2319+
tc_error !!tc "goal must be an equality or an equivalence";
2320+
let (ps, ue), pat = TTC.tc1_process_pattern tc p in
2321+
let pvars = Mid.keys ps in
2322+
EcLowGoal.t_congr_pattern ~pat ~pvars ~ue tc
2323+
2324+
(* -------------------------------------------------------------------- *)
2325+
(* [congr *]: thin wrapper around [EcLowGoal.t_congr_star]. *)
2326+
let process_congr_star tc =
2327+
let concl = FApi.tc1_goal tc in
2328+
if not (EcFol.is_eq_or_iff concl) then
2329+
tc_error !!tc "goal must be an equality or an equivalence";
2330+
EcLowGoal.t_congr_star tc
2331+
2332+
(* -------------------------------------------------------------------- *)
2333+
let process_congr (mode : pcongr_mode) tc =
2334+
match mode with
2335+
| PCongrDefault -> process_congr_default tc
2336+
| PCongrStar -> process_congr_star tc
2337+
| PCongrPattern p -> process_congr_pattern p tc
2338+
23132339
(* -------------------------------------------------------------------- *)
23142340
let process_wlog ids wlog tc =
23152341
let hyps, _ = FApi.tc1_flat tc in

src/ecHiGoal.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ val process_split_all : must:bool -> backward
9292
val process_elim : prevert * pqsymbol option -> backward
9393
val process_case : ?doeq:bool -> prevertv -> backward
9494
val process_exists : ppt_arg located list -> backward
95-
val process_congr : backward
95+
val process_congr : pcongr_mode -> backward
9696
val process_solve : ?bases:symbol list -> ?depth:int -> backward
9797
val process_trivial : backward
9898
val process_change : pformula -> backward

src/ecHiTacticals.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ and process1_logic (ttenv : ttenv) (t : logtactic located) (tc : tcenv1) =
139139
| Pexists fs -> process_exists fs
140140
| Pleft -> process_left
141141
| Pright -> process_right
142-
| Pcongr -> process_congr
142+
| Pcongr mode -> process_congr mode
143143
| Ptrivial -> process_trivial
144144
| Pelim pe -> process_elim pe
145145
| Papply pe -> process_apply ~implicits:ttenv.tt_implicits pe

src/ecLowGoal.ml

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2568,6 +2568,181 @@ let t_congr (f1, f2) (args, ty) tc =
25682568
in
25692569
doit (List.rev args) ty tc
25702570
2571+
(* -------------------------------------------------------------------- *)
2572+
(* Read the equality (or iff) at the head of the goal and return:
2573+
- the two sides [f1, f2];
2574+
- [t_ensure_eq], a tactic that coerces an iff goal to an eq goal;
2575+
- [t_subgoal], the auto-discharge tactic for trivial side goals
2576+
(alpha-reflexivity and alpha-assumption).
2577+
It is the caller's responsibility to have checked that the goal is an
2578+
equality or an equivalence (e.g. via [EcFol.is_eq_or_iff]). *)
2579+
let congr_split_eq_or_iff tc =
2580+
let concl = FApi.tc1_goal tc in
2581+
assert (EcFol.is_eq_or_iff concl);
2582+
2583+
let ((f1, f2), iseq) =
2584+
if EcFol.is_eq concl
2585+
then (EcFol.destr_eq concl, true )
2586+
else (EcFol.destr_iff concl, false) in
2587+
2588+
let t_ensure_eq =
2589+
if iseq then t_id
2590+
else
2591+
(fun tc ->
2592+
let hyps = FApi.tc1_hyps tc in
2593+
Apply.t_apply_bwd_r
2594+
(PT.pt_of_uglobal !!tc hyps LG.p_eq_iff) tc) in
2595+
2596+
let t_subgoal =
2597+
FApi.t_ors [t_reflex ~mode:`Alpha; t_assumption `Alpha; t_id] in
2598+
2599+
(f1, f2, t_ensure_eq, t_subgoal)
2600+
2601+
(* -------------------------------------------------------------------- *)
2602+
(* Discharge an equality goal whose two sides are [skel[xi := Li]] and
2603+
[skel[xi := Ri]] (up to beta), by:
2604+
1. coercing iff to eq if needed;
2605+
2. changing the goal to [(\xi. skel) Li... = (\xi. skel) Ri...];
2606+
3. unfolding the congruence one step per hole with [t_congr];
2607+
4. auto-closing trivial side goals.
2608+
The goal type is read from [f1.f_ty]; [holes], [lvec] and [rvec] must
2609+
have the same length, and each [Li/Ri] must have the type of [xi]. *)
2610+
let t_congr_from_skeleton ~holes ~skel ~lvec ~rvec tc =
2611+
let (f1, _f2, t_ensure_eq, t_subgoal) = congr_split_eq_or_iff tc in
2612+
2613+
if List.is_empty holes then
2614+
FApi.t_seq t_ensure_eq t_reflex tc
2615+
else
2616+
let ty = f1.f_ty in
2617+
let bds = List.map (fun (x, t) -> (x, GTty t)) holes in
2618+
let lam = f_lambda bds skel in
2619+
let app_lhs = f_app lam lvec ty in
2620+
let app_rhs = f_app lam rvec ty in
2621+
let newgoal = f_eq app_lhs app_rhs in
2622+
let pairs = List.combine lvec rvec in
2623+
let tcgr = t_congr (lam, lam) (pairs, ty) in
2624+
FApi.t_seqs
2625+
[t_ensure_eq;
2626+
(fun tc -> FApi.tcenv_of_tcenv1 (t_change1 newgoal tc));
2627+
tcgr;
2628+
t_subgoal]
2629+
tc
2630+
2631+
(* -------------------------------------------------------------------- *)
2632+
(* [congr pat]: pattern matches both sides of an equality/iff goal; one
2633+
subgoal per pattern variable (skipping syntactically-equal sides).
2634+
- [pat] is the elaborated pattern formula (containing free [f_local]s
2635+
for each pattern variable);
2636+
- [pvars] is the list of pattern-variable identifiers;
2637+
- [ue] is the unification environment from pattern elaboration. *)
2638+
let t_congr_pattern ~pat ~pvars ~ue tc =
2639+
let (env, hyps, _concl) = FApi.tc1_eflat tc in
2640+
let (f1, f2, _, _) = congr_split_eq_or_iff tc in
2641+
2642+
let ev0 = MEV.of_idents pvars `Form in
2643+
2644+
let match_side label side =
2645+
let ue' = EcUnify.UniEnv.copy ue in
2646+
try
2647+
let (ue'', _, ev'') =
2648+
f_match fmsearch hyps (ue', ev0) pat side
2649+
in (ue'', ev'')
2650+
with MatchFailure ->
2651+
tc_error !!tc "pattern does not match %s of the goal" label
2652+
in
2653+
2654+
let (ueL, evL) = match_side "left-hand side" f1 in
2655+
let (ueR, evR) = match_side "right-hand side" f2 in
2656+
2657+
let substL = MEV.assubst ueL evL env in
2658+
let substR = MEV.assubst ueR evR env in
2659+
2660+
let holes_full =
2661+
List.map (fun x ->
2662+
let probe_ty = EcUnify.UniEnv.fresh ueL in
2663+
let probe = f_local x probe_ty in
2664+
let li = Fsubst.f_subst substL probe in
2665+
let ri = Fsubst.f_subst substR probe in
2666+
(x, li.f_ty, li, ri)
2667+
) pvars
2668+
in
2669+
2670+
let kept, dropped =
2671+
List.partition
2672+
(fun (_, _, li, ri) -> not (is_alpha_eq hyps li ri))
2673+
holes_full
2674+
in
2675+
2676+
let holes = List.map (fun (x, t, _, _) -> (x, t)) kept in
2677+
let lvec = List.map (fun (_, _, l, _) -> l) kept in
2678+
let rvec = List.map (fun (_, _, _, r) -> r) kept in
2679+
2680+
let skel =
2681+
List.fold_left
2682+
(fun acc (x, _, li, _) -> Fsubst.f_subst_local x li acc)
2683+
pat dropped
2684+
in
2685+
2686+
t_congr_from_skeleton ~holes ~skel ~lvec ~rvec tc
2687+
2688+
(* -------------------------------------------------------------------- *)
2689+
(* [congr *]: walk LHS/RHS in lock-step without reduction; emit one
2690+
subgoal per pair of differing positions. Binders are opaque. *)
2691+
let t_congr_star tc =
2692+
let (env, hyps, _concl) = FApi.tc1_eflat tc in
2693+
let (f1, f2, _, _) = congr_split_eq_or_iff tc in
2694+
2695+
let holes = ref [] in
2696+
let lvec = ref [] in
2697+
let rvec = ref [] in
2698+
2699+
let mk_hole (l : form) (r : form) : form =
2700+
let x = EcIdent.create "_x" in
2701+
holes := (x, l.f_ty) :: !holes;
2702+
lvec := l :: !lvec;
2703+
rvec := r :: !rvec;
2704+
f_local x l.f_ty
2705+
in
2706+
2707+
let rec walk (l : form) (r : form) : form =
2708+
if is_alpha_eq hyps l r then
2709+
l
2710+
else
2711+
match l.f_node, r.f_node with
2712+
| Fapp (hL, aL), Fapp (hR, aR)
2713+
when is_alpha_eq hyps hL hR
2714+
&& List.length aL = List.length aR
2715+
&& EqTest.for_type env l.f_ty r.f_ty ->
2716+
let args' = List.map2 walk aL aR in
2717+
f_app hL args' l.f_ty
2718+
2719+
| Fif (cL, tL, eL), Fif (cR, tR, eR) ->
2720+
let c' = walk cL cR in
2721+
let t' = walk tL tR in
2722+
let e' = walk eL eR in
2723+
f_if c' t' e'
2724+
2725+
| Ftuple xs, Ftuple ys when List.length xs = List.length ys ->
2726+
let zs = List.map2 walk xs ys in
2727+
f_tuple zs
2728+
2729+
| Fproj (xL, iL), Fproj (xR, iR)
2730+
when iL = iR && EqTest.for_type env l.f_ty r.f_ty ->
2731+
f_proj (walk xL xR) iL l.f_ty
2732+
2733+
| _, _ ->
2734+
if not (EqTest.for_type env l.f_ty r.f_ty) then
2735+
tc_error !!tc "congr*: cannot equate subterms of different types";
2736+
mk_hole l r
2737+
in
2738+
2739+
let skel = walk f1 f2 in
2740+
let holes = List.rev !holes in
2741+
let lvec = List.rev !lvec in
2742+
let rvec = List.rev !rvec in
2743+
2744+
t_congr_from_skeleton ~holes ~skel ~lvec ~rvec tc
2745+
25712746
(* -------------------------------------------------------------------- *)
25722747
type smtmode = [`Sloppy | `Strict | `Report of EcLocation.t option]
25732748

src/ecLowGoal.mli

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,21 @@ val t_crush_fwd : ?delta:bool -> int -> FApi.backward
335335
(* -------------------------------------------------------------------- *)
336336
val t_congr : form pair -> form pair list * ty -> FApi.backward
337337

338+
val t_congr_from_skeleton :
339+
holes:(EcIdent.t * ty) list
340+
-> skel:form
341+
-> lvec:form list
342+
-> rvec:form list
343+
-> FApi.backward
344+
345+
val t_congr_pattern :
346+
pat:form
347+
-> pvars:EcIdent.t list
348+
-> ue:EcUnify.unienv
349+
-> FApi.backward
350+
351+
val t_congr_star : FApi.backward
352+
338353
(* -------------------------------------------------------------------- *)
339354
type smtmode = [`Sloppy | `Strict | `Report of EcLocation.t option]
340355

src/ecParser.mly

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2802,7 +2802,13 @@ logtactic:
28022802
{ Pclear (`Include l) }
28032803

28042804
| CONGR
2805-
{ Pcongr }
2805+
{ Pcongr PCongrDefault }
2806+
2807+
| CONGR STAR
2808+
{ Pcongr PCongrStar }
2809+
2810+
| CONGR p=sform_h
2811+
{ Pcongr (PCongrPattern p) }
28062812

28072813
| TRIVIAL
28082814
{ Ptrivial }

src/ecParsetree.ml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,12 @@ type clear_info = [
10391039
(* -------------------------------------------------------------------- *)
10401040
type pgenhave = psymbol * intropattern option * psymbol list * pformula
10411041

1042+
(* -------------------------------------------------------------------- *)
1043+
type pcongr_mode =
1044+
| PCongrDefault
1045+
| PCongrStar
1046+
| PCongrPattern of pformula
1047+
10421048
(* -------------------------------------------------------------------- *)
10431049
type logtactic =
10441050
| Preflexivity
@@ -1052,7 +1058,7 @@ type logtactic =
10521058
| Pleft
10531059
| Pright
10541060
| Ptrivial
1055-
| Pcongr
1061+
| Pcongr of pcongr_mode
10561062
| Pelim of (prevert * pqsymbol option)
10571063
| Papply of (apply_info * prevert option)
10581064
| Pcut of pcut

src/phl/ecPhlDeno.ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,9 @@ let t_equiv_deno_bad2 pre bad1 tc =
383383
t_intros_i [hcpre; hequiv] @!
384384
t_real_le_trans fabs' @+
385385
[ t_apply_prept (`UG real_eq_le) @!
386-
process_congr @! (* abs *)
387-
process_congr @~ (* add *)
388-
(t_last process_congr) @~+ (* opp *)
386+
(process_congr PCongrDefault) @! (* abs *)
387+
(process_congr PCongrDefault) @~ (* add *)
388+
(t_last (process_congr PCongrDefault)) @~+ (* opp *)
389389
[ t_pr_rewrite_i ("mu_split", Some bad1) @! t_reflex;
390390
t_pr_rewrite_i ("mu_split", Some bad2) @! t_reflex ] ;
391391
t_apply_prept (`UG real_upto) @+

0 commit comments

Comments
 (0)