-
Notifications
You must be signed in to change notification settings - Fork 8
/
check.ml
1776 lines (1730 loc) · 97.5 KB
/
check.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
open Bwd
open Util
open Perhaps
open Tbwd
open Reporter
open Syntax
open Term
open Value
open Domvars
open Raw
open Dim
open Act
open Norm
open Equal
open Readback
open Printable
open Asai.Range
include Status
let discard : type a. a -> unit = fun _ -> ()
(* Check that a given value is a zero-dimensional type family (something where an indexed datatype could live) and return the length of its domain telescope (the number of indices). Unfortunately I don't see an easy way to do this without essentially going through all the same steps of extending the context that we would do to check something at that type family. Also check whether all of its domain types are either discrete or belong to the given set of constants. *)
let rec typefam :
type a b. ?discrete:unit Constant.Map.t -> (a, b) Ctx.t -> kinetic value -> int * bool =
fun ?discrete ctx ty ->
match view_type ~severity:Asai.Diagnostic.Error ty "typefam" with
| UU tyargs -> (
match D.compare (TubeOf.inst tyargs) D.zero with
| Eq -> (0, true)
| Neq -> fatal (Unimplemented "higher-dimensional datatypes"))
| Pi (x, doms, cods, tyargs) -> (
(* In practice, these dimensions will always be zero also if the function succeeds, otherwise the eventual output would have to be higher-dimensional too. But it doesn't hurt to be more general, and will require less change if we eventually implement higher-dimensional datatypes. *)
match D.compare (TubeOf.inst tyargs) (CubeOf.dim doms) with
| Eq ->
let newargs, newnfs = dom_vars (Ctx.length ctx) doms in
let output = tyof_app cods tyargs newargs in
let n, d = typefam ?discrete (Ctx.cube_vis ctx x newnfs) output in
let disc =
(* For indices of discrete datatypes, we only allow zero-dimensional pi-types. *)
match D.compare (CubeOf.dim doms) D.zero with
| Eq -> is_discrete ?discrete (CubeOf.find_top doms)
| Neq -> false in
(n + 1, d && disc)
| Neq -> fatal (Dimension_mismatch ("typefam", TubeOf.inst tyargs, CubeOf.dim doms)))
| _ -> fatal (Checking_canonical_at_nonuniverse ("datatype", PVal (ctx, ty)))
let rec motive_of_family :
type a b. (a, b) Ctx.t -> kinetic value -> kinetic value -> (b, kinetic) term =
fun ctx tm ty ->
let module F = struct
type (_, _, _, _) t =
| Rbtm : ('left, kinetic) term -> ('left, 'c, 'any, ('left, D.zero) snoc) t
end in
let module FCube = Icube (F) in
let module C = struct
type _ t = Any_ctx : ('a, 'c) Ctx.t -> 'c t
end in
let module T = struct
type 'c t = ('c, kinetic) term
end in
let module MC = FCube.Traverse (C) in
let module MT = FCube.Traverse (T) in
let folder :
type left m any right.
(left, m, any, right) F.t -> right T.t -> left T.t * (left, m, any, right) F.t =
fun (Rbtm dom) cod -> (Pi (None, CubeOf.singleton dom, CodCube.singleton cod), Rbtm dom) in
let builder :
type left n m.
string option ->
(n, Binding.t) CubeOf.t ->
(m, n) sface ->
left C.t ->
(left, m, b) MC.fwrap_left =
fun x newnfs fa (Any_ctx ctx) ->
let v = CubeOf.find newnfs fa in
let cv = readback_val ctx (Binding.value v).ty in
Fwrap (Rbtm cv, Any_ctx (Ctx.cube_vis ctx x (CubeOf.singleton v))) in
match view_type ty "motive_of_family" with
| Pi (x, doms, cods, tyargs) ->
let newvars, newnfs = dom_vars (Ctx.length ctx) doms in
let newtm = apply_term tm newvars in
(* We extend the context, not by the cube of types of newnfs, but by its elements one at a time as singletons. This is because we want eventually to construct a 0-dimensional pi-type. As we go, we also read back thesetypes and store them to later take the pi-type over. Since they are all in different contexts, and we need to keep track of the type-indexed checked length of those contexts to ensure the later pis are well-typed, we use an indexed cube indexed over Tbwds. *)
let (Wrap (newdoms, Any_ctx newctx)) =
MC.build_left (CubeOf.dim newnfs)
{ build = (fun fa ctx -> builder x newnfs fa ctx) }
(Any_ctx ctx) in
let motive = motive_of_family newctx newtm (tyof_app cods tyargs newvars) in
let motive, _ = MT.fold_map_right { foldmap = (fun _ x y -> folder x y) } newdoms motive in
motive
| UU tyargs ->
(* This is similar, except that we add the datatype itself to the instantiation argument to get the cube of domains, and take a pi over the 0-dimensional universe rather than a recursive call. *)
let doms = TubeOf.plus_cube (val_of_norm_tube tyargs) (CubeOf.singleton tm) in
let _, newnfs = dom_vars (Ctx.length ctx) doms in
let (Wrap (newdoms, _)) =
MC.build_left (CubeOf.dim newnfs)
{ build = (fun fa ctx -> builder None newnfs fa ctx) }
(Any_ctx ctx) in
let motive, _ =
MT.fold_map_right { foldmap = (fun _ x y -> folder x y) } newdoms (UU D.zero) in
motive
| _ -> fatal (Anomaly "non-family in motive_of_family")
type (_, _, _) vars_of_vec =
| Vars :
('a, 'b, 'abc) N.plus * (N.zero, 'n, string option, 'b) NICubeOf.t
-> ('a, 'abc, 'n) vars_of_vec
let vars_of_vec :
type a c abc n.
Asai.Range.t option ->
n D.t ->
(a, c, abc) Fwn.bplus ->
(string option, c) Vec.t ->
(a, abc, n) vars_of_vec =
fun loc dim abc xs ->
let module S = struct
type 'b t =
| Ok : (a, 'b, 'ab) N.plus * ('ab, 'c, abc) Fwn.bplus * (string option, 'c) Vec.t -> 'b t
| Missing of int
end in
let module Build = NICubeOf.Traverse (S) in
match
Build.build_left dim
{
build =
(fun _ -> function
| Ok (ab, Suc abc, x :: xs) -> Fwrap (NFamOf x, Ok (Suc ab, abc, xs))
| Ok _ -> Fwrap (NFamOf None, Missing (-1))
| Missing j -> Fwrap (NFamOf None, Missing (j - 1)));
}
(Ok (Zero, abc, xs))
with
| Wrap (names, Ok (ab, Zero, [])) -> Vars (ab, names)
| Wrap (_, Ok (_, abc, _)) ->
fatal ?loc (Wrong_boundary_of_record (Fwn.to_int (Fwn.bplus_right abc)))
| Wrap (_, Missing j) -> fatal ?loc (Wrong_boundary_of_record j)
(* Slurp up an entire application spine. Returns the function, the locations of all the applications (e.g. in "f x y" returns the locations of "f x" and "f x y") and all the arguments. *)
let spine :
type a. a synth located -> a synth located * Asai.Range.t option list * a check located list =
fun tm ->
let rec spine tm locs args =
match tm.value with
| Raw.App (fn, arg) -> spine fn (tm.loc :: locs) (arg :: args)
| _ -> (tm, locs, args) in
spine tm [] []
let run_with_definition : type a s c. a potential_head -> (a, potential) term -> (unit -> c) -> c =
fun head tm f ->
match head with
| Constant c -> Global.with_definition c (Global.Defined tm) f
| Meta (m, _) -> Global.with_meta_definition m tm f
(* A "checkable branch" stores all the information about a branch in a match, both that coming from what the user wrote in the match and what is stored as properties of the datatype. *)
type (_, _, _) checkable_branch =
| Checkable_branch : {
xs : (string option, 'c) Vec.t;
plus_args : ('a, 'c, 'ac) Fwn.bplus;
(* If the body is None, that means the user omitted this branch. (That might be ok, if it can be refuted by a pattern variable belonging to an empty type.) *)
body : 'ac check located option;
env : ('m, 'b) env;
argtys : ('b, 'c, 'bc) Telescope.t;
index_terms : (('bc, kinetic) term, 'ij) Vec.t;
}
-> ('a, 'm, 'ij) checkable_branch
(* This preprocesssing step pairs each user-provided branch with the corresponding constructor information from the datatype. *)
let merge_branches head user_branches data_constrs =
let user_branches, leftovers =
Bwd.fold_left
(fun (userbrs, databrs) (constr, Branch (xs, { value = plus_args; loc }, body)) ->
(* We check at the preprocessing stage that there are no duplicate constructors in the match. *)
if Abwd.mem constr userbrs then fatal ?loc (Duplicate_constructor_in_match constr);
let databrs, databr = Abwd.extract constr databrs in
let (Value.Dataconstr { env; args = argtys; indices = index_terms }) =
Reporter.with_loc loc @@ fun () ->
databr <|> No_such_constructor_in_match (phead head, constr) in
(* We also check during preprocessing that the user has supplied the right number of pattern variable arguments to the constructor. The positive result of this check is then recorded in the common existential types bound by Checkable_branch. *)
match Fwn.compare (Fwn.bplus_right plus_args) (Telescope.length argtys) with
| Neq ->
fatal ?loc
(Wrong_number_of_arguments_to_pattern
( constr,
Fwn.to_int (Fwn.bplus_right plus_args) - Fwn.to_int (Telescope.length argtys) ))
| Eq ->
let br =
Checkable_branch { xs; plus_args; body = Some body; env; argtys; index_terms } in
(Snoc (userbrs, (constr, br)), databrs))
(Bwd.Emp, data_constrs) user_branches in
(* If there are any constructors in the datatype left over that the user didn't supply branches for, we add them to the list at the end. They will be tested for refutability. *)
Bwd_extra.append user_branches
(Bwd.map
(fun (c, Value.Dataconstr { env; args = argtys; indices = index_terms }) ->
let b = Telescope.length argtys in
let (Bplus plus_args) = Fwn.bplus b in
let xs = Vec.init (fun () -> (None, ())) b () in
(c, Checkable_branch { xs; body = None; plus_args; env; argtys; index_terms }))
leftovers)
exception Case_tree_construct_in_let
(* The output of checking a telescope includes an extended context. *)
type (_, _, _, _) checked_tel =
| Checked_tel : ('b, 'c, 'bc) Telescope.t * ('ac, 'bc) Ctx.t -> ('a, 'b, 'c, 'ac) checked_tel
(* A telescope of metavariables instead of types. Created from a telescope of types by make_letrec_metas. *)
type (_, _, _) meta_tel =
| Nil : ('b, Fwn.zero, 'b) meta_tel
| Ext :
string option * ('a, 'b, potential) Meta.t * (('b, D.zero) snoc, 'c, 'bc) meta_tel
-> ('b, 'c Fwn.suc, 'bc) meta_tel
(* Check a term or case tree (depending on the energy: terms are kinetic, case trees are potential). The ?discrete parameter is supplied if the term we are currently checking might be a discrete datatype, in which case it is a set of all the currently-being-defined mutual constants. Most term-formers are nondiscrete, so they can just ignore this argument and make their recursive calls without it. *)
let rec check :
type a b s.
?discrete:unit Constant.Map.t ->
(b, s) status ->
(a, b) Ctx.t ->
a check located ->
kinetic value ->
(b, s) term =
fun ?discrete status ctx tm ty ->
with_loc tm.loc @@ fun () : (b, s) term ->
(* If the "type" is not a type here, or not fully instantiated, that's a user error, not a bug. *)
let severity = Asai.Diagnostic.Error in
match (tm.value, status) with
(* A Let is a "synthesizing" term so that it *can* synthesize, but in checking position it checks instead. *)
| Synth (Let (x, v, body)), _ ->
let clet, Not_some = synth_or_check_let status ctx x v body (Some ty) in
clet
| Synth (Letrec (vtys, vs, body)), _ ->
let clet, Not_some = synth_or_check_letrec status ctx vtys vs body (Some ty) in
clet
(* An action can always synthesize, but can also check if its degeneracy is a pure permutation, since then the type of the argument can be inferred by applying the inverse permutation to the ambient type. *)
| Synth (Act (str, fa, x) as stm), _ -> (
match perm_of_deg fa with
| None -> check_of_synth status ctx stm tm.loc ty
| Some fa ->
let fa, fainv = (deg_of_perm fa, deg_of_perm (perm_inv fa)) in
let ty_fainv =
gact_ty None ty fainv ~err:(Low_dimensional_argument_of_degeneracy (str, cod_deg fa))
in
(* A pure permutation shouldn't ever be locking, but we may as well keep this here for consistency. *)
let ctx = if locking fa then Ctx.lock ctx else ctx in
let cx = check (Kinetic `Nolet) ctx x ty_fainv in
realize status (Term.Act (cx, fa)))
| Lam ({ value = x; _ }, cube, body), _ -> (
match view_type ~severity ty "typechecking lambda" with
| Pi (_, doms, cods, tyargs) -> (
(* TODO: Move this into a helper function, it's too long to go in here. *)
let m = CubeOf.dim doms in
(* A zero-dimensional parameter that is a discrete type doesn't block discreteness, but others do. *)
let discrete =
match D.compare m D.zero with
| Eq -> if is_discrete ?discrete (CubeOf.find_top doms) then discrete else None
| Neq -> None in
let Eq = D.plus_uniq (TubeOf.plus tyargs) (D.zero_plus m) in
(* Extend the context by one variable for each type in doms, instantiated at the appropriate previous ones. *)
let newargs, newnfs = dom_vars (Ctx.length ctx) doms in
(* A helper function to update the status *)
let mkstatus (type n) (xs : n variables) : (b, s) status -> ((b, n) snoc, s) status =
function
| Kinetic l -> Kinetic l
| Potential (c, args, hyp) ->
let arg =
Arg (CubeOf.mmap { map = (fun _ [ x ] -> Ctx.Binding.value x) } [ newnfs ]) in
Potential (c, Snoc (args, App (arg, ins_zero m)), fun tm -> hyp (Lam (xs, tm)))
in
(* Apply and instantiate the codomain to those arguments to get a type to check the body at. *)
let output = tyof_app cods tyargs newargs in
match cube with
(* If the abstraction is a cube, we slurp up the right number of lambdas for the dimension of the pi-type, and pick up the body inside them. We do this by building a cube of variables of the right dimension while maintaining the current term as an indexed state. We also build a sum of raw lengths, since we need that to extend the context. Note that we never need to manually "count" how many faces there are in a cube of any dimension, or discuss how to put them in order: the counting and ordering is handled automatically by iterating through a cube. *)
| `Normal -> (
let module S = struct
type 'b t =
| Ok : Asai.Range.t option * (a, 'b, 'ab) N.plus * 'ab check located -> 'b t
| Missing of Asai.Range.t option * int
end in
let module Build = NICubeOf.Traverse (S) in
match
Build.build_left m
{
build =
(fun _ -> function
| Ok (_, ab, { value = Lam ({ value = x; loc }, `Normal, body); _ }) ->
Fwrap (NFamOf x, Ok (loc, Suc ab, body))
| Ok (loc, _, _) -> Fwrap (NFamOf None, Missing (loc, 1))
| Missing (loc, j) -> Fwrap (NFamOf None, Missing (loc, j + 1)));
}
(Ok (None, Zero, tm))
with
| Wrap (names, Ok (_, af, body)) ->
let xs = Variables (D.zero, D.zero_plus m, names) in
let ctx = Ctx.vis ctx D.zero (D.zero_plus m) names newnfs af in
Lam (xs, check ?discrete (mkstatus xs status) ctx body output)
| Wrap (_, Missing (loc, j)) -> fatal ?loc (Not_enough_lambdas j))
| `Cube ->
(* Here we don't need to slurp up lots of lambdas, but can make do with one. *)
let xs = singleton_variables m x in
let ctx = Ctx.cube_vis ctx x newnfs in
Lam (xs, check ?discrete (mkstatus xs status) ctx body output))
| _ -> fatal (Checking_lambda_at_nonfunction (PVal (ctx, ty))))
| Struct (Noeta, tms), Potential _ -> (
match view_type ~severity ty "typechecking comatch" with
(* We don't need to name the arguments here because tyof_field, called below from check_field, uses them. *)
| Canonical (name, Codata { eta = Noeta; ins; fields; _ }, _) ->
let _ = is_id_ins ins <|> Comatching_at_degenerated_codata (phead name) in
check_struct status Noeta ctx tms ty (cod_left_ins ins) fields
| _ -> fatal (Comatching_at_noncodata (PVal (ctx, ty))))
| Struct (Eta, tms), _ -> (
match view_type ~severity ty "typechecking tuple" with
| Canonical (name, Codata { eta = Eta; ins; fields; _ }, _) ->
let _ = is_id_ins ins <|> Checking_tuple_at_degenerated_record (phead name) in
check_struct status Eta ctx tms ty (cod_left_ins ins) fields
| _ -> fatal (Checking_tuple_at_nonrecord (PVal (ctx, ty))))
| Constr ({ value = constr; loc = constr_loc }, args), _ -> (
(* TODO: Move this into a helper function, it's too long to go in here. *)
match view_type ~severity ty "typechecking constr" with
| Canonical
(name, Data { dim; indices = Filled ty_indices; constrs; discrete = _; tyfam = _ }, tyargs)
-> (
(* We don't need the *types* of the parameters or indices, which are stored in the type of the constant name. The variable ty_indices (defined above) contains the *values* of the indices of this instance of the datatype, while tyargs (defined by view_type, way above) contains the instantiation arguments of this instance of the datatype. We check that the dimensions agree, and find our current constructor in the datatype definition. *)
match Abwd.find_opt constr constrs with
| None -> fatal ?loc:constr_loc (No_such_constructor (`Data (phead name), constr))
| Some (Dataconstr { env; args = constr_arg_tys; indices = constr_indices }) ->
(* To typecheck a higher-dimensional instance of our constructor constr at the datatype, all the instantiation arguments must also be applications of lower-dimensional versions of that same constructor. We check this, and extract the arguments of those lower-dimensional constructors as a tube of lists in the variable "tyarg_args". *)
let tyarg_args =
TubeOf.mmap
{
map =
(fun fa [ tm ] ->
match view_term tm.tm with
| Constr (tmname, n, tmargs) ->
if tmname <> constr then
fatal (Missing_instantiation_constructor (constr, `Constr tmname))
else
(* Assuming the instantiation is well-typed, we must have n = dom_tface fa. I'd like to check that, but for some reason, matching this compare against Eq claims that the type variable n would escape its scope. *)
let _ = D.compare n (dom_tface fa) in
List.fold_right (fun a args -> CubeOf.find_top a :: args) tmargs []
| _ ->
fatal
(Missing_instantiation_constructor
(constr, `Nonconstr (PNormal (ctx, tm)))));
}
[ tyargs ] in
(* Now, for each argument of the constructor, we:
1. Evaluate the argument *type* of the constructor (which are assembled in the telescope constr_arg_tys) at the parameters (which are in the environment already) and the previous evaluated argument *values* (which get added to the environment as we go throurgh check_at_tel);
2. Instantiate the result at the corresponding arguments of the lower-dimensional versions of the constructor, from tyarg_args;
3. Check the coressponding argument *value*, supplied by the user, against this type;
4. Evaluate this argument value and add it to the environment, to substitute into the subsequent types, and also later to the indices. *)
let env, newargs = check_at_tel constr ctx env args constr_arg_tys tyarg_args in
(* Now we substitute all those evaluated arguments into the indices, to get the actual (higher-dimensional) indices of our constructor application. *)
let constr_indices =
Vec.mmap
(fun [ ix ] ->
CubeOf.build dim
{ build = (fun fa -> eval_term (act_env env (op_of_sface fa)) ix) })
[ constr_indices ] in
(* The last thing to do is check that these indices are equal to those of the type we are checking against. (So a constructor application "checks against the parameters but synthesizes the indices" in some sense.) I *think* it should suffice to check the top-dimensional ones, the lower-dimensional ones being automatic. For now, we check all of them, raising an anomaly in case I was wrong about that. *)
Vec.miter
(fun [ t1s; t2s ] ->
CubeOf.miter
{
it =
(fun fa [ t1; t2 ] ->
match equal_at (Ctx.length ctx) t1 t2.tm t2.ty with
| Some () -> ()
| None -> (
match is_id_sface fa with
| Some _ ->
fatal
(Unequal_indices
(PNormal (ctx, { tm = t1; ty = t2.ty }), PNormal (ctx, t2)))
| None -> fatal (Anomaly "mismatching lower-dimensional constructors")
));
}
[ t1s; t2s ])
[ constr_indices; ty_indices ];
realize status (Term.Constr (constr, dim, newargs)))
| _ -> fatal (No_such_constructor (`Other (PVal (ctx, ty)), constr)))
| Synth (Match { tm; sort = `Implicit; branches; refutables }), Potential _ ->
check_implicit_match status ctx tm branches refutables ty
| Synth (Match { tm; sort = `Nondep i; branches; refutables = _ }), Potential _ ->
let stm, sty = synth (Kinetic `Nolet) ctx tm in
check_nondep_match status ctx stm sty branches (Some i) ty
(* We don't need to deal with `Explicit matches here, since they can always synthesize a type and hence be caught by the catch-all for checking synthesizing terms, below. *)
(* Checking [] at a pi-type interprets it as a pattern-matching lambda over some empty datatype. *)
| Empty_co_match, _ -> (
match (view_type ~severity ty "checking empty (co)match", status) with
| Pi _, Potential _ -> check_empty_match_lam ctx ty `First
| Pi _, Kinetic l -> kinetic_of_potential l ctx tm ty "matching lambda"
| _, _ -> check status ctx { value = Struct (Noeta, Abwd.empty); loc = tm.loc } ty)
| Refute (tms, i), Potential _ -> check_refute status ctx tms ty i None
(* Now we go through the canonical types. *)
| Codata fields, Potential _ -> (
match view_type ~severity ty "typechecking codata" with
| UU tyargs -> check_codata status ctx tyargs Emp (Bwd.to_list fields)
| _ -> fatal (Checking_canonical_at_nonuniverse ("codatatype", PVal (ctx, ty))))
| Record (abc, xs, fields, opacity), Potential _ -> (
match view_type ~severity ty "typechecking record" with
| UU tyargs ->
let dim = TubeOf.inst tyargs in
let (Vars (af, vars)) = vars_of_vec abc.loc dim abc.value xs in
check_record status dim ctx opacity tyargs vars Emp Zero af Emp fields
| _ -> fatal (Checking_canonical_at_nonuniverse ("record type", PVal (ctx, ty))))
| Data constrs, Potential _ ->
(* For a datatype, the type to check against might not be a universe, it could include indices. We also check whether all the types of all the indices are discrete or a type being defined, to decide whether to keep evaluating the type for discreteness. *)
let n, disc = typefam ?discrete ctx ty in
let (Wrap num_indices) = Fwn.of_int n in
check_data
~discrete:(if disc then discrete else None)
status ctx ty num_indices Abwd.empty (Bwd.to_list constrs)
(* If we have a term that's not valid outside a case tree, we bind it to a global metavariable. *)
| Struct (Noeta, _), Kinetic l -> kinetic_of_potential l ctx tm ty "comatch"
| Synth (Match _), Kinetic l -> kinetic_of_potential l ctx tm ty "match"
| Refute _, Kinetic l -> kinetic_of_potential l ctx tm ty "match"
| Codata _, Kinetic l -> kinetic_of_potential l ctx tm ty "codata"
| Record _, Kinetic l -> kinetic_of_potential l ctx tm ty "sig"
| Data _, Kinetic l -> kinetic_of_potential l ctx tm ty "data"
(* If the user left a hole, we create an eternal metavariable. *)
| Hole (vars, pos), _ ->
(* Holes aren't numbered by the file they appear in. *)
let meta = Meta.make_hole (Ctx.raw_length ctx) (Ctx.dbwd ctx) (energy status) in
let ty, termctx =
Readback.Display.run ~env:true @@ fun () -> (readback_val ctx ty, readback_ctx ctx) in
Global.add_hole meta pos ~vars ~termctx ~ty ~status;
Meta (meta, energy status)
(* And lastly, if we have a synthesizing term, we synthesize it. *)
| Synth stm, _ -> check_of_synth status ctx stm tm.loc ty
(* Deal with a synthesizing term in checking position. *)
and check_of_synth :
type a b s.
(b, s) status -> (a, b) Ctx.t -> a synth -> Asai.Range.t option -> kinetic value -> (b, s) term
=
fun status ctx stm loc ty ->
let sval, sty = synth status ctx { value = stm; loc } in
equal_val (Ctx.length ctx) sty ty <|> Unequal_synthesized_type (PVal (ctx, sty), PVal (ctx, ty));
sval
(* Deal with checking a potential term in kinetic position *)
and kinetic_of_potential :
type a b.
[ `Let | `Nolet ] ->
(a, b) Ctx.t ->
a check located ->
kinetic value ->
string ->
(b, kinetic) term =
fun l ctx tm ty sort ->
match l with
| `Let -> raise Case_tree_construct_in_let
| `Nolet ->
emit (Bare_case_tree_construct sort);
(* We create a metavariable to store the potential value. *)
let meta = Meta.make_def sort None (Ctx.raw_length ctx) (Ctx.dbwd ctx) Potential in
(* We first define the metavariable without a value, as an "axiom", just as we do for global constants. This isn't necessary for recursion, since this metavariable can't refer to itself, but so that with_meta_definition will be able to find it for consistency. *)
let termctx = readback_ctx ctx in
let vty = readback_val ctx ty in
Global.add_meta meta ~termctx ~tm:`Axiom ~ty:vty ~energy:Potential;
(* Then we check the value and redefine the metavariable to be that value. *)
let tmstatus = Potential (Meta (meta, Ctx.env ctx), Emp, fun x -> x) in
let cv = check tmstatus ctx tm ty in
Global.set_meta meta ~tm:cv;
(* Finally, we return the metavariable. *)
Term.Meta (meta, Kinetic)
and synth_or_check_let :
type a b s p.
(b, s) status ->
(a, b) Ctx.t ->
string option ->
a synth located ->
a N.suc check located ->
(kinetic value, p) Perhaps.t ->
(b, s) term * (kinetic value, p) Perhaps.not =
fun status ctx name v body ty ->
let v, nf =
try
(* We first try checking the bound term first as an ordinary kinetic term. *)
let sv, svty = synth (Kinetic `Let) ctx v in
let ev = eval_term (Ctx.env ctx) sv in
(sv, { tm = ev; ty = svty })
with
(* If that encounters case-tree constructs, then we can allow the bound term to be a case tree, i.e. a potential term. But in a checked "let" expression, the term being bound is a kinetic one, and must be so that its value can be put into the environment when the term is evaluated. We deal with this by binding a *metavariable* to the bound term and then taking the value of that metavariable as the kinetic term to actually be bound. *)
| Case_tree_construct_in_let ->
(* First we make the metavariable. *)
let meta = Meta.make_def "let" name (Ctx.raw_length ctx) (Ctx.dbwd ctx) Potential in
let termctx = readback_ctx ctx in
(* A new status in which to check the value of that metavariable; now it is the "current constant" being defined. *)
let tmstatus = Potential (Meta (meta, Ctx.env ctx), Emp, fun x -> x) in
let sv, svty =
match v.value with
| Asc (vtm, rvty) ->
(* If the bound term is explicitly ascribed, then we can give the metavariable a type while checking its body. This is probably irrelevant until we have "let rec", but we do it anyway. *)
let vty = check (Kinetic `Nolet) ctx rvty (universe D.zero) in
Global.add_meta meta ~termctx ~tm:`Axiom ~ty:vty ~energy:Potential;
let evty = eval_term (Ctx.env ctx) vty in
let cv = check tmstatus ctx vtm evty in
Global.set_meta meta ~tm:cv;
(cv, evty)
| _ ->
(* Otherwise, we just synthesize the term. *)
let sv, svty = synth tmstatus ctx v in
let vty = readback_val ctx svty in
Global.add_meta meta ~termctx ~tm:(`Defined sv) ~ty:vty ~energy:Potential;
(sv, svty) in
(* We turn that metavariable into a value. *)
let head = Value.Meta { meta; env = Ctx.env ctx; ins = zero_ins D.zero } in
let tm =
if GluedEval.read () then
(* Glued evaluation: we delay evaluating the term until it's needed. *)
Uninst (Neu { head; args = Emp; value = lazy_eval (Ctx.env ctx) sv }, Lazy.from_val svty)
else
match eval (Ctx.env ctx) sv with
| Realize x -> x
| value -> Uninst (Neu { head; args = Emp; value = ready value }, Lazy.from_val svty)
in
(Term.Meta (meta, Kinetic), { tm; ty = svty }) in
(* Either way, we end up with a checked term 'v' and a normal form 'nf'. We use the latter to extend the context. *)
let newctx = Ctx.ext_let ctx name nf in
(* Now we update the status of the original constant being checked *)
let status : ((b, D.zero) snoc, s) status =
match status with
| Potential (c, args, hyp) -> Potential (c, args, fun body -> hyp (Let (name, v, body)))
| Kinetic l -> Kinetic l in
(* And synthesize or check the body in the extended context. *)
match (ty, body) with
| Some ty, _ ->
let sbody = check status newctx body ty in
(Term.Let (name, v, sbody), Not_some)
| None, { value = Synth body; loc } ->
let sbody, sbodyty = synth status newctx { value = body; loc } in
(Term.Let (name, v, sbody), Not_none sbodyty)
| None, _ -> fatal (Nonsynthesizing "let-expression without synthesizing body")
and synth_or_check_letrec :
type a b c ac s p.
(b, s) status ->
(a, b) Ctx.t ->
(a, c, ac) Raw.tel ->
(ac check located, c) Vec.t ->
ac check located ->
(kinetic value, p) Perhaps.t ->
(b, s) term * (kinetic value, p) Perhaps.not =
fun status ctx rvtys vtms body ty ->
(* First we check the types of all the bound variables, which are a telescope since each can depend on the previous ones. *)
let Checked_tel (type bc) ((vtys, _) : (_, _, bc) Telescope.t * (_, bc) Ctx.t), _ =
check_tel ctx rvtys in
(* Then we create the metavariables. *)
let metas = make_letrec_metas ctx vtys in
(* Now we check the bound terms. *)
let ac = Raw.bplus_of_tel rvtys in
let () = check_letrec_bindings ctx ac metas vtys vtms in
(* Now we update the status of the original constant being checked *)
let status : (bc, s) status =
match status with
| Potential (c, args, hyp) -> Potential (c, args, fun x -> hyp (let_metas metas x))
| Kinetic l -> Kinetic l in
(* Make a context for it *)
let _, newctx = ext_metas ctx ac metas vtys Zero Zero Zero in
(* And synthesize or check the body in the extended context. *)
match (ty, body) with
| Some ty, _ ->
let sbody = check status newctx body ty in
(let_metas metas sbody, Not_some)
| None, { value = Synth body; loc } ->
let sbody, sbodyty = synth status newctx { value = body; loc } in
(let_metas metas sbody, Not_none sbodyty)
| None, _ -> fatal (Nonsynthesizing "let-expression without synthesizing body")
and check_letrec_bindings :
type a xc b ac bc.
(a, b) Ctx.t ->
(a, xc, ac) Fwn.bplus ->
(b, xc, bc) meta_tel ->
(b, xc, bc) Telescope.t ->
(ac check located, xc) Vec.t ->
unit =
fun octx oac ometas ovtys vs ->
let rec go :
type x ax bx c d.
(a, x, ax) Fwn.bplus ->
(x, c, xc) Fwn.plus ->
(b, x, D.zero, bx) Tbwd.snocs ->
(* *)
(ax, c, ac) Fwn.bplus ->
(bx, c, bc) meta_tel ->
(bx, c, bc) Telescope.t ->
(ac check located, c) Vec.t ->
unit =
fun ax xc bx ac metas vtys vs ->
match (ac, metas, vtys, vs) with
| Zero, Nil, Emp, [] -> ()
| Suc ac, Ext (_, meta, metas), Ext (name, vty, vtys), v :: vs ->
let ctx, tmctx = ext_metas octx oac ometas ovtys ax xc bx in
let evty = eval_term (Ctx.env ctx) vty in
let hyp b = Term.Let (name, Meta (meta, Kinetic), let_metas metas b) in
let tmstatus = Potential (Meta (meta, Ctx.env ctx), Emp, hyp) in
let cv = check tmstatus tmctx v evty in
Global.set_meta meta ~tm:(hyp cv);
(* And recurse. *)
go (Fwn.bplus_suc_eq_suc ax) (Fwn.suc_plus xc) (Tbwd.snocs_suc_eq_snoc bx) ac metas vtys vs
in
go Zero Zero Zero oac ometas ovtys vs
(* Given a telescope of types for let-bound variables, create a global metavariable for each of them, and give it the correct type in Global. *)
and make_letrec_metas : type x a b ab. (x, a) Ctx.t -> (a, b, ab) Telescope.t -> (a, b, ab) meta_tel
=
fun ctx tel ->
match tel with
| Emp -> Nil
| Ext (x, vty, tel) ->
(* Create the metavariable. *)
let meta = Meta.make_def "letrec" x (Ctx.raw_length ctx) (Ctx.dbwd ctx) Potential in
(* Assign it the correct type. *)
let termctx = readback_ctx ctx in
Global.add_meta meta ~termctx ~tm:`Axiom ~ty:vty ~energy:Potential;
(* Extend the context by it, as an unrealized neutral. TODO: It's annoying that we have to evaluate the types here to extend the value-context, when the only use we're making of it is to readback that extended value-context into a termctx at each step to save with the global metavariable. It would make more sense, and be more efficient, to just carry along the termctx and extend it directly at each step with "Term.Meta (meta, Kinetic)" at the term-type "vty". Unfortunately, termctxs store terms and types in a one-longer context, so that would require directly weakening vty, or perhaps parsing and checking it in a one-longer context originally. *)
let evty = eval_term (Ctx.env ctx) vty in
let head = Value.Meta { meta; env = Ctx.env ctx; ins = zero_ins D.zero } in
let neutm = Uninst (Neu { head; args = Emp; value = ready Unrealized }, Lazy.from_val evty) in
let ctx = Ctx.ext_let ctx x { tm = neutm; ty = evty } in
(* And recurse. *)
Ext (x, meta, make_letrec_metas ctx tel)
and let_metas : type b c bc s. (b, c, bc) meta_tel -> (bc, s) term -> (b, s) term =
fun metas tm ->
match metas with
| Nil -> tm
| Ext (x, m, metas) -> Let (x, Meta (m, Kinetic), let_metas metas tm)
(* Extend a context by evaluated metavariables. We return both the fully extended context and a partially extended one. *)
and ext_metas :
type a b c ac bc d cd acd bcd.
(a, b) Ctx.t ->
(a, cd, acd) Fwn.bplus ->
(b, cd, bcd) meta_tel ->
(b, cd, bcd) Telescope.t ->
(a, c, ac) Fwn.bplus ->
(c, d, cd) Fwn.plus ->
(b, c, D.zero, bc) Tbwd.snocs ->
(ac, bc) Ctx.t * (acd, bcd) Ctx.t =
fun ctx acd metas vtys ac cd bc ->
(* First we define a helper function that returns only the fully extended context. *)
let rec ext_metas' :
type a b cd acd bcd.
(a, b) Ctx.t ->
(a, cd, acd) Fwn.bplus ->
(b, cd, bcd) meta_tel ->
(b, cd, bcd) Telescope.t ->
(acd, bcd) Ctx.t =
fun ctx acd metas vtys ->
match (acd, metas, vtys) with
| Zero, Nil, Emp -> ctx
| Suc acd, Ext (_, meta, metas), Ext (x, vty, vtys) ->
let tm = eval_term (Ctx.env ctx) (Meta (meta, Kinetic)) in
let ty = eval_term (Ctx.env ctx) vty in
ext_metas' (Ctx.ext_let ctx x { tm; ty }) acd metas vtys in
match (ac, cd, bc, acd, metas, vtys) with
| Zero, Zero, Zero, _, _, _ -> (ctx, ext_metas' ctx acd metas vtys)
| Suc ac, Suc cd, Suc bc, Suc acd, Ext (_, meta, metas), Ext (x, vty, vtys) ->
let tm = eval_term (Ctx.env ctx) (Meta (meta, Kinetic)) in
let ty = eval_term (Ctx.env ctx) vty in
ext_metas (Ctx.ext_let ctx x { tm; ty }) acd metas vtys ac cd bc
(* Check a match statement without an explicit motive supplied by the user. This means if the discriminee is a well-behaved variable, it can be a variable match; otherwise it reverts back to a non-dependent match. *)
and check_implicit_match :
type a b t.
(b, potential) status ->
(a, b) Ctx.t ->
a synth located ->
(Constr.t, a branch) Abwd.t ->
a refutables ->
kinetic value ->
(b, potential) term =
fun status ctx tm brs refutables motive ->
match tm with
(* For a variable match, the variable must not be let-bound to a value or be a field access variable. Checking that it isn't also gives us its De Bruijn level, its type, and its checked-index. If it's not a free variable, or if we're not in a case tree or if the motive was supplied explicitly, we obtain its value and type; then we pass on to the appropriate checking function. *)
| { value = Var ix; loc } -> (
match Ctx.lookup ctx ix with
| `Field (_, _, fld) ->
emit ?loc (Matching_wont_refine ("discriminee is record field", PField fld));
let tm, varty = synth (Kinetic `Nolet) ctx tm in
check_nondep_match status ctx tm varty brs None motive
| `Var (None, _, ix) ->
emit ?loc (Matching_wont_refine ("discriminee is let-bound", PTerm (ctx, Var ix)));
let tm, varty = synth (Kinetic `Nolet) ctx tm in
check_nondep_match status ctx tm varty brs None motive
| `Var (Some level, { tm = _; ty = varty }, index) ->
check_var_match status ctx level index varty brs refutables motive)
| _ ->
let tm, varty = synth (Kinetic `Nolet) ctx tm in
check_nondep_match status ctx tm varty brs None motive
(* Either check a non-dependent match against a specified type, or synthesize a type for it from the first branch and then check the others against that type. Which to do is determined by whether the motive passed is Some or None, and the return includes a type under the opposite conditions. *)
and synth_or_check_nondep_match :
type a b p.
(b, potential) status ->
(a, b) Ctx.t ->
(b, kinetic) term ->
kinetic value ->
(Constr.t, a branch) Abwd.t ->
int located option ->
(kinetic value, p) Perhaps.t ->
(b, potential) term * (kinetic value, p) Perhaps.not =
fun status ctx tm varty brs i motive ->
(* We look up the type of the discriminee, which must be a datatype, without any degeneracy applied outside, and at the same dimension as its instantiation. *)
match view_type varty "synth_or_check_nondep_match" with
| Canonical (type m)
(( name,
Data (type j ij)
({ dim; indices = Filled indices; constrs = data_constrs; discrete = _; tyfam = _ } :
(_, j, ij) data_args),
_ ) :
_ * m canonical * _) -> (
(* Yes, we really don't care what the instantiation arguments are in this case, and we really don't care what the indices are either except to check there are the right number of them. This is because in the non-dependent case, we are just applying a recursor to a value, so we don't need to know that the indices and instantiation arguments are variables; in the branches they will be whatever they will be, but we don't even need to *know* what they will be because the output type isn't getting refined either. *)
(match i with
| Some { value; loc } ->
let needed = Fwn.to_int (Vec.length indices) + 1 in
if value <> needed then fatal ?loc (Wrong_number_of_arguments_to_motive needed)
| None -> ());
(* We save the supplied type, if any. *)
let ty : kinetic value option ref =
ref
(match motive with
| Some ty -> Some ty
| None -> None) in
(* We start with a preprocesssing step that pairs each user-provided branch with the corresponding constructor information from the datatype. *)
let user_branches = merge_branches name brs data_constrs in
(* We now iterate through the constructors, typechecking the corresponding branches and inserting them in the match tree. *)
let branches =
Bwd.fold_left
(fun branches
( constr,
(Checkable_branch { xs; body; plus_args; env; argtys; index_terms = _ } :
(a, m, ij) checkable_branch) ) ->
let (Snocs efc) = Tbwd.snocs (Telescope.length argtys) in
(* Create new level variables for the pattern variables to which the constructor is applied, and add corresponding index variables to the context. The types of those variables are specified in the telescope argtys, and have to be evaluated at the closure environment 'env' and the previous new variables (this is what ext_tel does). For a higher-dimensional match, the new variables come with their boundaries in n-dimensional cubes. *)
let newctx, _, _, newnfs = ext_tel ctx env xs argtys plus_args efc in
let perm = Tbwd.id_perm in
let status = make_match_status status tm dim branches efc None perm constr in
(* Finally, we recurse into the "body" of the branch. *)
match !ty with
| Some motive -> (
(* If we have a type, check against it. *)
match body with
| Some body ->
branches
|> Constr.Map.add constr
(Term.Branch (efc, perm, check status newctx body motive))
| None ->
if any_empty newnfs then branches |> Constr.Map.add constr Term.Refute
else fatal (Missing_constructor_in_match constr))
| None -> (
(* If we don't have a type yet, try to synthesize a type from this branch. *)
match body with
| Some { value = Synth value; loc } ->
let sbr, sty = synth status newctx { value; loc } in
(* The type synthesized is only valid for the whole match if it doesn't depend on the pattern variables. We check that by reading it back into the original context. *)
Reporter.try_with ~fatal:(fun d ->
match d.message with
| No_such_level _ ->
fatal
(Invalid_synthesized_type ("first branch of match", PVal (newctx, sty)))
| _ -> fatal_diagnostic d)
@@ fun () ->
discard (readback_val ctx sty);
ty := Some sty;
branches |> Constr.Map.add constr (Term.Branch (efc, perm, sbr))
| None -> fatal (Nonsynthesizing "match with zero branches")
| _ ->
fatal
(Nonsynthesizing
"first branch in synthesizing match without return annotation")))
Constr.Map.empty user_branches in
match (motive, !ty) with
| Some _, _ -> (Match { tm; dim; branches }, Not_some)
| None, Some ty -> (Match { tm; dim; branches }, Not_none ty)
| None, None -> fatal (Nonsynthesizing "empty match"))
| _ -> fatal (Matching_on_nondatatype (PVal (ctx, varty)))
and check_nondep_match :
type a b.
(b, potential) status ->
(a, b) Ctx.t ->
(b, kinetic) term ->
kinetic value ->
(Constr.t, a branch) Abwd.t ->
int located option ->
kinetic value ->
(b, potential) term =
fun status ctx tm varty brs i motive ->
let ctm, Not_some = synth_or_check_nondep_match status ctx tm varty brs i (Some motive) in
ctm
and synth_nondep_match :
type a b.
(b, potential) status ->
(a, b) Ctx.t ->
a synth located ->
(Constr.t, a branch) Abwd.t ->
int located option ->
(b, potential) term * kinetic value =
fun status ctx tm brs i ->
let sv, varty = synth (Kinetic `Nolet) ctx tm in
let stm, Not_none sty = synth_or_check_nondep_match status ctx sv varty brs i None in
(stm, sty)
(* Check a dependently typed match, with motive supplied by the user. (Thus we have to typecheck the motive as well.) *)
and synth_dep_match :
type a b.
(b, potential) status ->
(a, b) Ctx.t ->
a synth located ->
(Constr.t, a branch) Abwd.t ->
a check located ->
(b, potential) term * kinetic value =
fun status ctx tm brs motive ->
let module S = Monad.State (struct
type t = kinetic value
end) in
let module MC = CubeOf.Monadic (S) in
let module MT = TubeOf.Monadic (S) in
(* We look up the type of the discriminee, which must be a datatype, without any degeneracy applied outside, and at the same dimension as its instantiation. *)
let ctm, varty = synth (Kinetic `Nolet) ctx tm in
match view_type varty "synth_dep_match" with
| Canonical (type m)
(( name,
Data (type j ij)
({ dim; indices = Filled var_indices; constrs = data_constrs; discrete = _; tyfam } :
(_, j, ij) data_args),
inst_args ) :
_ * m canonical * _) ->
let tyfam =
match !tyfam with
| Some tyfam -> Lazy.force tyfam
| None -> fatal (Anomaly "tyfam unset") in
let emotivety = eval_term (Ctx.env ctx) (motive_of_family ctx tyfam.tm tyfam.ty) in
let cmotive = check (Kinetic `Nolet) ctx motive emotivety in
let emotive = eval_term (Ctx.env ctx) cmotive in
(* We start with a preprocesssing step that pairs each user-provided branch with the corresponding constructor information from the datatype. *)
let user_branches = merge_branches name brs data_constrs in
(* We now iterate through the constructors, typechecking the corresponding branches and inserting them in the match tree. *)
let branches =
Bwd.fold_left
(fun branches
( constr,
(Checkable_branch { xs; body; plus_args; env; argtys; index_terms } :
(a, m, ij) checkable_branch) ) ->
let (Snocs efc) = Tbwd.snocs (Telescope.length argtys) in
(* Create new level variables for the pattern variables to which the constructor is applied, and add corresponding index variables to the context. The types of those variables are specified in the telescope argtys, and have to be evaluated at the closure environment 'env' and the previous new variables (this is what ext_tel does). For a higher-dimensional match, the new variables come with their boundaries in n-dimensional cubes. *)
let newctx, newenv, newvars, newnfs = ext_tel ctx env xs argtys plus_args efc in
let perm = Tbwd.id_perm in
let status = make_match_status status ctm dim branches efc None perm constr in
(* To get the type at which to typecheck the body of the branch, we have to evaluate the general dependent motive at the indices of this constructor, its boundaries, and itself. First we compute the indices. *)
let index_vals =
Vec.mmap (fun [ ixtm ] -> eval_with_boundary newenv ixtm) [ index_terms ] in
let bmotive = Vec.fold_left apply_singletons emotive index_vals in
(* Now we compute the constructor and its boundaries. *)
let constr_vals =
CubeOf.build dim
{
build =
(fun fa ->
Value.Constr (constr, dom_sface fa, List.map (CubeOf.subcube fa) newvars));
} in
let bmotive = apply_singletons bmotive constr_vals in
(* Finally, we recurse into the "body" of the branch. *)
match body with
| Some body ->
branches
|> Constr.Map.add constr (Term.Branch (efc, perm, check status newctx body bmotive))
| None ->
if any_empty newnfs then branches |> Constr.Map.add constr Term.Refute
else fatal (Missing_constructor_in_match constr))
Constr.Map.empty user_branches in
(* Now we compute the output type by evaluating the dependent motive at the match term's indices, boundary, and itself. *)
let result =
Vec.fold_left
(fun fn xs ->
snd
(MC.miterM
{ it = (fun _ [ x ] fn -> ((), apply_term fn (CubeOf.singleton x.tm))) }
[ xs ] fn))
emotive var_indices in
let result =
snd
(MT.miterM
{ it = (fun _ [ x ] fn -> ((), apply_term fn (CubeOf.singleton x.tm))) }
[ inst_args ] result) in
let result = apply_term result (CubeOf.singleton (eval_term (Ctx.env ctx) ctm)) in
(* We readback the result so we can store it in the term, so that when evaluating it we know what its type must be without having to do all the work again. *)
(Match { tm = ctm; dim; branches }, result)
| _ -> fatal (Matching_on_nondatatype (PVal (ctx, varty)))
(* Check a match against a well-behaved variable, which can only appear in a case tree and refines not only the goal but the context (possibly with permutation). *)
and check_var_match :
type a b.
(b, potential) status ->
(a, b) Ctx.t ->
level ->
b Term.index ->
kinetic value ->
(Constr.t, a branch) Abwd.t ->
a refutables ->
kinetic value ->
(b, potential) term =
fun status ctx level index varty brs refutables motive ->
(* We look up the type of the discriminee, which must be a datatype, without any degeneracy applied outside, and at the same dimension as its instantiation. *)
match view_type varty "check_var_match" with
| Canonical (type m)
(( name,
Data (type j ij)
({ dim; indices = Filled var_indices; constrs = data_constrs; discrete = _; tyfam } :
(_, j, ij) data_args),
inst_args ) :
_ * m canonical * _) ->
let tyfam =
match !tyfam with
| Some tyfam -> Lazy.force tyfam
| None -> fatal (Anomaly "tyfam unset") in
let tyfam_args : (D.zero, m, m, normal) TubeOf.t =
match view_type tyfam.ty "check_var_match tyfam" with
| Pi (_, _, _, tyfam_args) -> (
match D.compare dim (TubeOf.inst tyfam_args) with
| Neq -> fatal (Dimension_mismatch ("check_var_match", dim, TubeOf.inst tyfam_args))
| Eq -> tyfam_args)
| UU tyfam_args -> (
match D.compare dim (TubeOf.inst tyfam_args) with
| Neq -> fatal (Dimension_mismatch ("check_var_match", dim, TubeOf.inst tyfam_args))
| Eq -> tyfam_args)
| _ -> fatal (Show ("tyfam is not a type family", PVal (ctx, tyfam.ty))) in
(* In our simple version of pattern-matching against a variable, the "indices" and all their boundaries must be distinct free variables with no degeneracies, so that in the branch for each constructor they can be set equal to the computed value of that index for that constructor (and in which they cannot occur). This is a special case of the unification algorithm described in CDP "Pattern-matching without K" where the only allowed rule is "Solution". Later we can try to enhance it with their full unification algorithm, at least for non-higher datatypes. In addition, for a higher-dimensional match, the instantiation arguments must also all be distinct variables, distinct from the indices. If any of these conditions fail, we raise an exception, catch it, emit a hint, and revert to doing a non-dependent match. *)
let seen = Hashtbl.create 10 in
let is_fresh x =
match x.tm with
| Uninst (Neu { head = Var { level; deg }; args = Emp; value }, _) -> (
match force_eval value with
| Unrealized ->
if Option.is_none (is_id_deg deg) then
fatal (Matching_wont_refine ("index variable has degeneracy", PNormal (ctx, x)));
if Hashtbl.mem seen level then
fatal (Matching_wont_refine ("duplicate variable in indices", PNormal (ctx, x)));
Hashtbl.add seen level ();
level
| _ -> fatal (Anomaly "local variable bound to a potential term"))
| _ -> fatal (Matching_wont_refine ("index is not a free variable", PNormal (ctx, x))) in
Reporter.try_with ~fatal:(fun d ->
match d.message with
| Matching_wont_refine (str, x) ->
emit (Matching_wont_refine (str, x));
check_nondep_match status ctx (Term.Var index) varty brs None motive
| No_such_level x ->
emit (Matching_wont_refine ("index variable occurs in parameter", x));
check_nondep_match status ctx (Term.Var index) varty brs None motive
| _ -> fatal_diagnostic d)
@@ fun () ->
let index_vars =
Vec.mmap
(fun [ tm ] -> CubeOf.mmap { map = (fun _ [ x ] -> is_fresh x) } [ tm ])
[ var_indices ] in
let inst_vars = TubeOf.mmap { map = (fun _ [ x ] -> is_fresh x) } [ inst_args ] in
let constr_vars = TubeOf.plus_cube inst_vars (CubeOf.singleton level) in
(* Now we also check that none of these free variables occur in the parameters. We do this by altering the context to replace all these level variables with unknowns and doing a readback of the pre-indices type family into that context. If the readback encounters one of the missing level variables, it fails with No_such_level; above we catch that, emit a hint, and fall back to matching against a term. *)
(* TODO: This doesn't seem to be catching things it should, like attempted proofs of Axiom K; they go on and get caught by No_permutation instead. *)
let ctx_noindices = Ctx.forget_levels ctx (Hashtbl.mem seen) in
discard (readback_nf ctx_noindices tyfam);
(* If all of those checks succeed, we continue on the path of a variable match. But note that this call is still inside the try_with, so it can still fail and revert back to a non-dependent term match. *)
(* We start with a preprocesssing step that pairs each user-provided branch with the corresponding constructor information from the datatype. *)
let user_branches = merge_branches name brs data_constrs in
(* We now iterate through the constructors, typechecking the corresponding branches and inserting them in the match tree. *)
let branches =
Bwd.fold_left
(fun branches
( constr,
(Checkable_branch { xs; body; plus_args; env; argtys; index_terms } :
(a, m, ij) checkable_branch) ) ->
let (Snocs efc) = Tbwd.snocs (Telescope.length argtys) in
(* Create new level variables for the pattern variables to which the constructor is applied, and add corresponding index variables to the context. The types of those variables are specified in the telescope argtys, and have to be evaluated at the closure environment 'env' and the previous new variables (this is what ext_tel does). For a higher-dimensional match, the new variables come with their boundaries in n-dimensional cubes. *)
let newctx, newenv, newvars, newnfs = ext_tel ctx env xs argtys plus_args efc in
(* Evaluate the "index_terms" at the new pattern variables, obtaining what the indices should be for the new term that replaces the match variable in the match body. *)
let index_vals =
Vec.mmap
(fun [ ixtm ] ->
CubeOf.build dim
{ build = (fun fa -> eval_term (act_env newenv (op_of_sface fa)) ixtm) })
[ index_terms ] in
(* Assemble a term consisting of the constructor applied to the new variables, along with its boundary, and their types. To compute their types, we have to extract the datatype applied to its parameters only, pass to boundaries if necessary, and then re-apply it to the new indices. *)
let constr_tys = TubeOf.plus_cube tyfam_args (CubeOf.singleton tyfam) in
let argtbl = Hashtbl.create 10 in
let constr_nfs =
CubeOf.mmap
{
map =
(fun fa [ constrty ] ->
let k = dom_sface fa in
let tm = Value.Constr (constr, k, List.map (CubeOf.subcube fa) newvars) in
let ty =
inst