Repository navigation
Expand file tree
/
Copy pathpipeline.ml
More file actions
2879 lines (2721 loc) · 141 KB
/
Copy pathpipeline.ml
File metadata and controls
2879 lines (2721 loc) · 141 KB
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
(* Source string -> ... convenience functions.
Handles top-level decls (let, let rec, type) in order. *)
(* Names promised by `let fn <name>: <ty>;`. The interpreter binds each to a
placeholder ref when the promise is made, so a caller written before the
definition captures the ref the definition will fill in -- the same
back-patching a `let rec ... and ...` group already does for its members.
Prepending a NEW binding at the definition would shadow instead, and every
caller above it would go on calling the placeholder. *)
let forward_promised : (string, Ast.ty * Loc.t) Hashtbl.t = Hashtbl.create 16
(* the promises this program has kept, so the end-of-program check can name
the ones it did not *)
let forward_kept : (string, unit) Hashtbl.t = Hashtbl.create 16
let forward_reset () = Hashtbl.reset forward_promised; Hashtbl.reset forward_kept
(* ⚠ THE DEFINITION HAS TO MEAN WHAT THE DECLARATION PROMISED. Without this the
declared type is what callers ABOVE the definition see and the inferred one
is what callers below see, and the two can disagree -- `let fn f : int -> int;`
followed by `let f = fn (s: str) -> s;` type-checked, and the program had two
incompatible ideas of f. *)
let forward_check_def name loc (t : Ast.ty) =
match Hashtbl.find_opt forward_promised name with
| None -> ()
| Some (declared, dloc) ->
Hashtbl.replace forward_kept name ();
(* SUBSUMPTION, NOT INSTANTIATION. The first version instantiated the
declaration and unified the definition against the instance -- the wrong
direction, and it cost two things at once:
- a definition could be MORE SPECIFIC than its declaration. `let fn idl:
'a list -> 'a list;` with `let idl = fn (xs: int list) -> xs;` was
accepted, and a caller written ABOVE the definition -- the only reason
to declare a name -- could then call it on a `str list`. It type-checked,
and the C backend failed with a codegen error on a program the typer
had passed.
- the fresh variables the instantiation made were created at the OUTER
level, so unifying them with the definition's own variables pulled those
down and `generalize` could no longer quantify them. A declared
`'a -> 'a` was monomorphic from its first call site -- `ident 7` then
`ident "s"` was a type error -- while the same definition WITHOUT a
declaration was polymorphic. Declaring a type made a name less general
than not declaring it.
Unifying against the declaration AS WRITTEN fixes both: the parser makes
`'a` a `TyParam`, and a TyParam unifies only with itself or an unbound
variable, which is exactly a skolem. `fn x -> x` (`?1 -> ?1`) unifies; `fn
(xs: int list) -> xs` does not, and is refused here rather than in a
backend. *)
(try Typer.unify loc declared t
with _ ->
raise (Typer.Type_error (loc,
Printf.sprintf
"`%s` was declared `%s` at line %d, and this definition is not that general"
name (Ast.pp_ty declared) dloc.Loc.line)))
(* THE DECLARED TYPE IS WHAT THE NAME MEANS, above the definition and below it.
Publishing the definition's INFERRED scheme instead gave the name two meanings:
callers above got the declaration's, callers below the definition's, and a
declaration exists precisely so that those are one thing. `forward_check_def`
has already refused any definition less general than its promise, so the
declared scheme is the sound one to publish. *)
let forward_scheme name (inferred : Typer.scheme) : Typer.scheme =
match Hashtbl.find_opt forward_promised name with
| Some (declared, _) -> Typer.scheme_of_written declared
| None -> inferred
(* every promise must be kept: a name declared and never defined is a name
nothing can call. *)
let forward_check_all_kept () =
Hashtbl.iter (fun name (ty, loc) ->
if not (Hashtbl.mem forward_kept name) then
raise (Typer.Type_error (loc,
Printf.sprintf
"`let fn %s: %s;` promises a definition that this program never gives"
name (Ast.pp_ty ty)))) forward_promised
(* Phase 19.4: parses the auto-imported prelude and returns its decls.
When the user's parse starts, these decls are inserted at the front of
the user decls. Disabled by `?prelude:false` (for tests / debug). *)
(* How many of a parsed program's declarations came from the auto-imported
prelude, which is prepended to every user program. It matters to anything that
turns a name into a *place*: a prelude binding's position is a line in the
prelude's own text, and reporting it against the user's file would send an
editor somewhere arbitrary. The number is a constant of the build (the prelude
does not change), so recording it as parse_program runs is exact. *)
let prelude_count = ref 0
let prelude_decl_count () = !prelude_count
(* How many entries of Parser.declared_types belong to the prelude rather than to the
program being checked. *)
let prelude_type_count = ref 0
(* The name prelude positions carry. It is not a path and no file exists at it:
that is the point — a position from the prelude is Mere code, but not *this*
program's, and anything that turns a position into a place has to be able to
tell. *)
let prelude_file = Prelude_stdlib.file_name
(* v0.1.665: Mere source a backend needs only for some programs (LLVM's JSON
parser, for one that calls of_json), added after the stdlib prelude for the
next parse and positioned as prelude, so nothing treats it as the user's. *)
let extra_prelude : string list ref = ref []
let parse_prelude () : Ast.top_decl list =
let tokens = Lexer.tokenize ~file:prelude_file
(String.concat "\n" (Prelude_stdlib.contents :: !extra_prelude)) in
let prog = Parser.parse_program tokens in
prog.Ast.decls
(* The declarations that say what types exist. Kept next to `parse_program` because
that is where they are now applied; `process_decls` and `Trait_elab` still make the
same calls on their own walks, which is harmless (each registrar replaces). *)
let register_declared_types (decls : Ast.top_decl list) : unit =
List.iter (fun decl ->
match decl with
| Ast.Top_type (name, params, variants) -> Typer.register_type name params variants
| Ast.Top_extern_type name -> Typer.register_type name [] []
| Ast.Top_record (name, params, fields) -> Typer.register_record name params fields
| Ast.Top_view (name, region, fields) -> Typer.register_view name region fields
| Ast.Top_drop name -> Typer.register_drop_type name
| Ast.Top_sync name -> Typer.register_sync_type name
| Ast.Top_local name -> Typer.register_local_type name
| Ast.Top_ctor_alias (alias, target) -> Typer.alias_ctor alias target
| Ast.Top_record_alias (alias, target) -> Typer.alias_record alias target
| _ -> ()) decls
(* `pub` inside a module: what the module did not mark, nobody outside it calls.
The parser records the private QUALIFIED names while it prefixes a module's
declarations (`Parser.private_module_names`); this is the pass that enforces
them. Declarations are flat by the time anyone sees them — `module M { let
f }` is a top-level `M.f` — so "inside the module" is a question about the
name of the declaration a reference sits in, and nothing deeper is needed.
Reported as a type error because that is what it is: a name that does not
resolve from where it was written. The message says which module, because
"unbound variable: Store.secret" would send the reader looking for a typo. *)
(* Q-166: the file-level half of the same rule. A file that marked anything with
`pub` keeps its unmarked top-level bindings to itself; a reference from
another file is refused by name and location.
TWO THINGS MAKE THIS HARDER THAN THE MODULE VERSION, and the first draft of
it had both wrong. A module keys on a QUALIFIED name (`M.foo`), which nothing
else in a program can spell. A file keys on the bare one.
1. A LOCAL CAN SPELL IT. `fn (helper: int) -> helper + 1` in another file
was refused, and so was `let helper = n + 1 in ...` -- the walk looked at
every `Var` node and knew nothing about what bound it. The walk carries
the names in scope now: parameters, `let` and `match` binders, `with`,
and the loop forms.
2. SO CAN ANOTHER FILE. Two files may bind the same top-level name -- the
splice has always allowed it -- and one of them marking `pub` must not
reach into the other's. A name bound at top level by more than one file
is therefore NOT checked: which binding a reference means is a question
this pass cannot answer, and refusing on a guess would refuse correct
programs. Erring toward accepting is the right direction for a rule
whose whole job is to say "you did not mean to reach in here".
Both were found by asking the design rather than the tests -- the gate's four
directions did not include a local that happens to share a name. *)
let check_file_privacy (prog : Ast.program) =
if Hashtbl.length Parser.pub_files > 0 then begin
let names_of (d : Ast.top_decl) : string list * string =
match d with
| Ast.Top_let ({ Ast.pnode = Ast.P_var n; Ast.ploc = l; _ }, _) ->
([n], Parser.file_key l.Loc.file)
| Ast.Top_let_rec ((_, _, (v : Ast.expr)) :: _ as bs) ->
(List.map Ast.rb_name bs, Parser.file_key v.Ast.loc.Loc.file)
| _ -> ([], "")
in
(* name -> the file that keeps it to itself; and name -> how many files bind it *)
let private_here : (string, string) Hashtbl.t = Hashtbl.create 32 in
let bound_by : (string, string list) Hashtbl.t = Hashtbl.create 64 in
List.iter (fun d ->
let (ns, key) = names_of d in
List.iter (fun n ->
let prev = match Hashtbl.find_opt bound_by n with Some l -> l | None -> [] in
if not (List.mem key prev) then Hashtbl.replace bound_by n (key :: prev);
if Hashtbl.mem Parser.pub_files key
&& not (List.mem (key, n) !Parser.file_pub_names)
then Hashtbl.replace private_here n key) ns)
prog.Ast.decls;
(* Drop the ambiguous ones: more than one file binds that name. *)
Hashtbl.iter (fun n _ ->
match Hashtbl.find_opt bound_by n with
| Some (_ :: _ :: _) -> Hashtbl.remove private_here n
| _ -> ()) (Hashtbl.copy private_here);
if Hashtbl.length private_here > 0 then begin
let rec pat_names (p : Ast.pattern) : string list =
match p.Ast.pnode with
| Ast.P_var n -> [n]
| Ast.P_str_prefix (_, n) -> [n]
| Ast.P_as (inner, n) -> n :: pat_names inner
| Ast.P_constr (_, Some sub) -> pat_names sub
| Ast.P_tuple ps -> List.concat_map pat_names ps
| Ast.P_record (_, fs) -> List.concat_map (fun (_, q) -> pat_names q) fs
| Ast.P_or (a, b) -> pat_names a @ pat_names b
| _ -> []
in
let check (e : Ast.expr) =
let rec go bound (x : Ast.expr) =
match x.Ast.node with
| Ast.Var n when not (List.mem n bound) ->
(match Hashtbl.find_opt private_here n with
| Some owner ->
let here = Parser.file_key x.Ast.loc.Loc.file in
if here <> owner then
raise (Typer.Type_error (x.Ast.loc,
Printf.sprintf
"`%s` is internal to %s (that file marks its exports with `pub`)"
n (if owner = "" then "the file it is written in" else owner)))
| None -> ())
| Ast.Var _ -> ()
| Ast.Fun (param, _, body) -> go (param :: bound) body
| Ast.Let (pat, value, body) ->
go bound value; go (pat_names pat @ bound) body
| Ast.Let_rec (bs, body) ->
let bound' = List.map Ast.rb_name bs @ bound in
List.iter (fun (_, _, v) -> go bound' v) bs;
go bound' body
| Ast.With (name, value, body) ->
go bound value; go (name :: bound) body
| Ast.Match (scrut, arms) ->
go bound scrut;
List.iter (fun (pat, guard, body) ->
let bound' = pat_names pat @ bound in
(match guard with Some g -> go bound' g | None -> ());
go bound' body) arms
| Ast.Region_block (name, body) -> go (name :: bound) body
| Ast.Region_loop (a, b, body) -> go (a :: b :: bound) body
| _ -> List.iter (go bound) (Ast.children x)
in
go [] e
in
List.iter (fun d ->
match d with
| Ast.Top_let (_, v) -> check v
| Ast.Top_let_rec bs -> List.iter (fun (_, _, v) -> check v) bs
| _ -> ()) prog.Ast.decls;
check prog.Ast.main
end
end
let check_module_privacy (prog : Ast.program) =
if Hashtbl.length Parser.private_module_names > 0 then begin
let module_of (n : string) =
match String.rindex_opt n '.' with
| Some i -> String.sub n 0 i
| None -> ""
in
let check_in (owner : string) (e : Ast.expr) =
let rec go (x : Ast.expr) =
(match x.Ast.node with
| Ast.Var n when Hashtbl.mem Parser.private_module_names n ->
let home = module_of n in
(* Inside its own module, or inside one nested within it. *)
let inside =
owner = home
|| (String.length owner > String.length home
&& String.sub owner 0 (String.length home) = home
&& owner.[String.length home] = '.')
in
if not inside then
raise (Typer.Type_error (x.Ast.loc,
Printf.sprintf
"`%s` is internal to module `%s` (the module marks its exports with `pub`)"
n home))
| _ -> ());
List.iter go (Ast.children x)
in
go e
in
List.iter (fun d ->
let owner =
match d with
| Ast.Top_let ({ Ast.pnode = Ast.P_var n; _ }, _) -> module_of n
| Ast.Top_let_rec ((n, _, _) :: _) -> module_of n
| _ -> ""
in
List.iter (check_in owner) (Ast.decl_exprs d)) prog.Ast.decls;
check_in "" prog.Ast.main
end
(* `| "lit" <> rest ->` : the arm becomes a guard and a binding.
The rewrite, once, so that no backend has to learn a pattern:
match s with | "http://" <> rest -> body
=>
match s with | __pfxN when str_starts_with __pfxN "http://" ->
let rest = utf8_sub __pfxN 7 (utf8_len __pfxN - 7) in body
Every piece of that is a function the programs doing this by hand already
call (`str_starts_with` and a slice, 13 times in `contrib/` alone). The arm
binds the scrutinee rather than naming it, so the subject is evaluated once
by `match` and the guard and the binding both read the same value.
A GUARDED ARM CLOSES NOTHING, which is what exhaustiveness should already
say about a prefix test: `| "a" <> r -> ...` covers some strings and the
checker has no way to know which, so a `_` arm is still required. That falls
out of the existing rule rather than being added to it.
The literal's length is counted in CODE POINTS, because `utf8_sub` is
codepoint-indexed. "http://" is 7 either way; "日本" is 2 here and 6 bytes.
⚠ `utf8_sub` walks the string. That is the right first implementation and the
wrong one for a hot loop; a byte-indexed slice would be a builtin, which is
five backends, and is worth adding when something measured asks for it. *)
let prefix_desugar (prog : Ast.program) : Ast.program =
let counter = ref 0 in
let codepoints (s : string) =
let n = ref 0 in
String.iter (fun c -> if Char.code c land 0xC0 <> 0x80 then incr n) s;
!n
in
let rec go (e : Ast.expr) : Ast.expr =
Ast.rv_map_scoped ~shadow:[] (fun _ (x : Ast.expr) ->
match x.Ast.node with
| Ast.Match (scrut, arms)
when List.exists (fun ((p : Ast.pattern), _, _) ->
match p.Ast.pnode with Ast.P_str_prefix _ -> true | _ -> false) arms ->
let arms' =
List.map (fun ((p : Ast.pattern), guard, body) ->
let guard = Option.map go guard and body = go body in
match p.Ast.pnode with
| Ast.P_str_prefix (lit, name) ->
incr counter;
let fresh = Printf.sprintf "__pfx%d" !counter in
let loc = p.Ast.ploc in
let mk node = { Ast.loc; ty = None; node } in
let var n = mk (Ast.Var n) in
let app f a = mk (Ast.App (f, a)) in
let k = codepoints lit in
let test =
app (app (var "str_starts_with") (var fresh)) (mk (Ast.Str_lit lit))
in
let slice () =
app (app (app (var "utf8_sub") (var fresh)) (mk (Ast.Int_lit k)))
(mk (Ast.Bin (Ast.Sub,
app (var "utf8_len") (var fresh),
mk (Ast.Int_lit k))))
in
let bind_rest e =
if name = "_" then e
else
{ e with Ast.node =
Ast.Let ({ Ast.ploc = loc; pnode = Ast.P_var name }, slice (), e) }
in
let guard' =
match guard with
| None -> Some test
(* THE USER'S GUARD CAN NAME THE BINDER — `| "ab" <> r when
str_len r > 1` — so `r` has to be bound for the guard too,
not only for the body. `&&` short-circuits, so the slice is
only computed once the prefix has matched. *)
| Some g -> Some (mk (Ast.Logic (Ast.And, test, bind_rest g)))
in
let body' = bind_rest body in
({ Ast.ploc = loc; pnode = Ast.P_var fresh }, guard', body')
| _ -> (p, guard, body)) arms
in
Some { x with Ast.node = Ast.Match (go scrut, arms') }
| _ -> None) e
in
let decl d =
match d with
| Ast.Top_let (p, e) -> Ast.Top_let (p, go e)
| Ast.Top_let_rec bs -> Ast.Top_let_rec (List.map (fun (n, l, e) -> (n, l, go e)) bs)
| other -> other
in
{ Ast.decls = List.map decl prog.Ast.decls; Ast.main = go prog.Ast.main }
(* v0.1.605: `"a" ++ "b"` is one literal. A text written as a chain of
literals, one line each -- mere-ruby's Ruby prelude, 6703 lines -- was
concatenated at run time, every link copying everything before it: the
copies summed to the square of the length (192 MB of garbage at mere-ruby's
startup), and on RISC-V, where a region gives nothing back, that was past
the 256 MB a program has. Folded here, after parsing and before typing, so
`mere fmt` (which keeps the sugar) still prints the chain as written.
Literals only: `x ++ "a" ++ "b"` folds its tail ("ab"), `"a" ++ x` is left
alone. ++ is associative, so regrouping a literal tail changes nothing. *)
let fold_str_concat (prog : Ast.program) : Ast.program =
let rec go (e : Ast.expr) : Ast.expr =
Ast.rv_map_scoped ~shadow:[] (fun _ (x : Ast.expr) ->
match x.Ast.node with
| Ast.Bin (Ast.Concat, a, b) ->
let a = go a and b = go b in
(match a.Ast.node, b.Ast.node with
| Ast.Str_lit s, Ast.Str_lit t -> Some { x with Ast.node = Ast.Str_lit (s ^ t) }
| Ast.Bin (Ast.Concat, a1, ({ Ast.node = Ast.Str_lit s; _ } as l)), Ast.Str_lit t ->
Some { x with Ast.node = Ast.Bin (Ast.Concat, a1, { l with Ast.node = Ast.Str_lit (s ^ t) }) }
| Ast.Str_lit s, Ast.Bin (Ast.Concat, ({ Ast.node = Ast.Str_lit t; _ } as l), b1) ->
Some { x with Ast.node = Ast.Bin (Ast.Concat, { l with Ast.node = Ast.Str_lit (s ^ t) }, b1) }
| _ -> Some { x with Ast.node = Ast.Bin (Ast.Concat, a, b) })
| _ -> None) e
in
let decl d =
match d with
| Ast.Top_let (p, e) -> Ast.Top_let (p, go e)
| Ast.Top_let_rec bs -> Ast.Top_let_rec (List.map (fun (n, l, e) -> (n, l, go e)) bs)
| other -> other
in
{ Ast.decls = List.map decl prog.Ast.decls; Ast.main = go prog.Ast.main }
(* `echo` -> `echo_at "<where>"`: the debug print gets its position.
WHY A PASS AND NOT A PARSER CASE. The first version of this rewrote the
token, which is simpler and wrong: `test/parity/graphql_stack_portable.mere`
binds `echo` as a name of its own, and rewriting every occurrence turned its
value into a partially applied function and the program into a type error.
A debug helper that breaks a program which never asked for it is not worth
having, so the rewrite runs over the parsed tree with scope tracked
(`Ast.rv_map_scoped`), and a `echo` the user bound -- at top level or in any
scope around the use -- is left alone and means what they said.
The prelude's own `echo` does NOT shadow it: the prelude is prepended to
every program, so counting its top-level names as user bindings would
disable the rewrite everywhere. Top-level names are collected from
declarations the user wrote, which are the ones whose positions carry no
`<prelude>` file. *)
let echo_rewrite (prog : Ast.program) : Ast.program =
let user_top =
List.concat_map (fun d ->
match d with
| Ast.Top_let (p, _) ->
List.filter_map (fun (n, (l : Loc.t)) ->
if l.Loc.file = Some prelude_file then None else Some n)
(Query.pattern_bindings p)
| Ast.Top_let_rec bs ->
List.filter_map (fun (n, _, (v : Ast.expr)) ->
if v.Ast.loc.Loc.file = Some prelude_file then None else Some n) bs
| Ast.Top_forward (n, _, (l : Loc.t)) ->
if l.Loc.file = Some prelude_file then [] else [ n ]
| _ -> []) prog.Ast.decls
in
if List.mem "echo" user_top then prog
else
let go (e : Ast.expr) =
Ast.rv_map_scoped ~shadow:[] (fun sh (x : Ast.expr) ->
match x.Ast.node with
| Ast.Var "echo" when not (List.mem "echo" sh) ->
let where =
match x.Ast.loc.Loc.file with
| Some f when f <> prelude_file ->
Printf.sprintf "%s:%d" f x.Ast.loc.Loc.line
| _ ->
(* Minus whatever a driver glued in front of the file: on `-rv`
the prelude is prepended as TEXT, and an `echo` on line 2 was
reporting line 1925. A position inside the glue would go
non-positive, so it is clamped. *)
Printf.sprintf "line %d"
(max 1 (x.Ast.loc.Loc.line - !Loc.glued_lines))
in
Some { x with Ast.node =
Ast.App ({ x with Ast.node = Ast.Var "echo_at"; ty = None },
{ x with Ast.node = Ast.Str_lit where; ty = None }) }
| _ -> None) e
in
let decl d =
match d with
| Ast.Top_let (p, e) -> Ast.Top_let (p, go e)
| Ast.Top_let_rec bs -> Ast.Top_let_rec (List.map (fun (n, l, e) -> (n, l, go e)) bs)
| other -> other
in
{ Ast.decls = List.map decl prog.Ast.decls; Ast.main = go prog.Ast.main }
(* v0.1.561 (Q-184): `coro_new` is lowered here, before type-checking, into the
two forms every backend runs:
__coro_new_raw : (Coro['m] -> CoroExit) -> Coro['m] the body is handed its
own handle
__coro_msg : Coro['m] -> 'm what was sent to it
coro_new (fn me -> fn m -> b) => __coro_new_raw (fn me -> let m = __coro_msg me in b)
coro_new f => let k = f in __coro_new_raw (fn s -> k s (__coro_msg s))
WHY BEFORE THE TYPER, AND WHY THE FIRST FORM. A runtime that calls a curried
typed body would need one trampoline per message type on every compiled
backend; this way the runtime stays untyped and the message is read at its
type by ordinary code. The literal form makes no closure beyond the one the
user wrote -- a wrapper around their closure would copy its env on every
`coro_new`, which on C is the default-region growth v0.1.558 removed.
The old shape, a body of `fn () -> ..` that ended by returning the next
coroutine, is named as such: it would otherwise be a unification error about
an arrow nobody wrote. Scope is tracked as in `echo_rewrite`: a `coro_new` the
program binds itself is its own. *)
let coro_rewrite (prog : Ast.program) : Ast.program =
let user_top =
List.concat_map (fun d ->
match d with
| Ast.Top_let (p, _) -> List.map fst (Query.pattern_bindings p)
| Ast.Top_let_rec bs -> List.map (fun (n, _, _) -> n) bs
| Ast.Top_forward (n, _, _) -> [ n ]
| _ -> []) (List.filter (fun d ->
match d with
| Ast.Top_let (_, (v : Ast.expr)) | Ast.Top_let_rec ((_, _, v) :: _) ->
v.Ast.loc.Loc.file <> Some prelude_file
| _ -> true) prog.Ast.decls)
in
(* v0.1.566: and `coro_new_sized n f` the same way, into `__coro_new_sized_raw n`. A
program that binds one name of its own keeps both, so the two never mix. *)
if List.mem "coro_new" user_top || List.mem "coro_new_sized" user_top then prog
else
let counter = ref 0 in
let fresh base = incr counter; Printf.sprintf "__%s%d" base !counter in
let rec go (e : Ast.expr) : Ast.expr =
Ast.rv_map_scoped ~shadow:[] (fun sh (x : Ast.expr) ->
let mk node = { Ast.loc = x.Ast.loc; ty = None; node } in
let var n = mk (Ast.Var n) in
let app f a = mk (Ast.App (f, a)) in
let raw_for size body =
match size with
| None -> app (var "__coro_new_raw") body
| Some n -> app (app (var "__coro_new_sized_raw") n) body in
let msg_of s = app (var "__coro_msg") (var s) in
let general_for size (f : Ast.expr) =
let k = fresh "coro_body" and s = fresh "coro_self" in
mk (Ast.Let ({ Ast.ploc = x.Ast.loc; pnode = Ast.P_var k }, f,
raw_for size (mk (Ast.Fun (s, None, app (app (var k) (var s)) (msg_of s))))))
in
let lower size (arg : Ast.expr) =
let raw = raw_for size and general = general_for size in
(match arg.Ast.node with
| Ast.Fun (_, Some t, body)
when (match Ast.walk t with Ast.TyUnit -> true | _ -> false)
&& (match body.Ast.node with Ast.Fun _ -> false | _ -> true) ->
raise (Typer.Type_error (arg.Ast.loc,
"coro_new: a body is `fn me -> fn msg -> ..` since v0.1.561 -- it is handed \
its own handle and its first message, and it ends with `coro_exit next v` \
instead of returning the coroutine to run next"))
| Ast.Fun (me, me_ty, ({ Ast.node = Ast.Fun (m, m_ty, b); _ } as inner)) ->
let me' = if me = "_" then fresh "coro_self" else me in
let read = msg_of me' in
let read = match m_ty with
| Some t -> { read with Ast.node = Ast.Annot (read, t) }
| None -> read in
let pat = { Ast.ploc = inner.Ast.loc; pnode = Ast.P_var m } in
Some (raw { arg with Ast.node =
Ast.Fun (me', me_ty, { inner with Ast.node = Ast.Let (pat, read, go b) }) })
| _ -> Some (general (go arg)))
in
match x.Ast.node with
| Ast.App ({ Ast.node = Ast.Var "coro_new"; _ }, arg) when not (List.mem "coro_new" sh) ->
lower None arg
| Ast.App ({ Ast.node = Ast.App ({ Ast.node = Ast.Var "coro_new_sized"; _ }, n); _ }, arg)
when not (List.mem "coro_new_sized" sh) ->
(* the size is evaluated first, as an application's arguments are *)
let nn = fresh "coro_size" in
(match lower (Some (var nn)) arg with
| Some body -> Some (mk (Ast.Let ({ Ast.ploc = x.Ast.loc; pnode = Ast.P_var nn }, go n, body)))
| None -> None)
| Ast.App ({ Ast.node = Ast.Var "coro_new_sized"; _ }, n) when not (List.mem "coro_new_sized" sh) ->
(* partly applied: the size now, the body later *)
let nn = fresh "coro_size" and f = fresh "coro_fn" in
Some (mk (Ast.Let ({ Ast.ploc = x.Ast.loc; pnode = Ast.P_var nn }, go n,
mk (Ast.Fun (f, None, general_for (Some (var nn)) (var f))))))
| Ast.Var "coro_new" when not (List.mem "coro_new" sh) ->
(* as a value: the general form, under a lambda *)
let f = fresh "coro_fn" in
Some (mk (Ast.Fun (f, None, general_for None (var f))))
| Ast.Var "coro_new_sized" when not (List.mem "coro_new_sized" sh) ->
let nn = fresh "coro_size" and f = fresh "coro_fn" in
Some (mk (Ast.Fun (nn, None, mk (Ast.Fun (f, None, general_for (Some (var nn)) (var f))))))
| _ -> None) e
in
let decl d =
match d with
| Ast.Top_let (p, e) -> Ast.Top_let (p, go e)
| Ast.Top_let_rec bs -> Ast.Top_let_rec (List.map (fun (n, l, e) -> (n, l, go e)) bs)
| other -> other
in
{ Ast.decls = List.map decl prog.Ast.decls; Ast.main = go prog.Ast.main }
(* A syntax error, with what was actually there added to it.
The hint is a `help:` line on the message, which is the same shape every
semantic error already uses — so nothing renders differently, and the 149
places in the parser that raise stay untouched. What they could not know is
what token the reader wrote; that is here, where the token list is.
The failure position identifies the token by line and column. The one before
it is what makes a compound operator legible (`x += 1` fails at `=` with `+`
behind it), so both are looked up. *)
(* The names this file BINDS are identifiers, not attempts at another
language's keyword. `&mut R v`, `let var = ...`, `fn val ->` and
`fn (case: int) -> case * 2` are all real Mere in the repositories, and all
four words are also in the hint table — so the table is told which names are
spoken for here, and says nothing about those.
A binding is recognised by SHAPE, not by scope: `let`/`and`/`rec` then a
name then `=` or `:`, a parameter with an annotation, `fn name ->`, or a
name after `&` (Mere's own borrow modes). `let mut x = 1;` does NOT match —
`mut` is followed by a name, not by `=` — which is the case that has to keep
its hint. *)
let bound_names (tokens : (Loc.t * Lexer.token) list) : string list =
let rec go acc = function
| a :: (Lexer.T_ident n as b) :: (c :: _ as rest) ->
let is_binding =
match a, c with
| (Lexer.T_let | Lexer.T_and | Lexer.T_rec), (Lexer.T_eq | Lexer.T_colon) -> true
| Lexer.T_fn, Lexer.T_arrow -> true
(* An element of a pattern or a parameter list: `let (val, j) = ...`
and `let (classes, var, prefix, r2) = ...` are how mere-ruby binds
two of these words, and the `let NAME =` shape above cannot see a
name inside the parentheses. *)
| (Lexer.T_lparen | Lexer.T_comma),
(Lexer.T_comma | Lexer.T_rparen | Lexer.T_colon) -> true
| Lexer.T_fn, Lexer.T_colon -> true
| Lexer.T_amp, _ -> true
| _ -> false
in
go (if is_binding then n :: acc else acc) (b :: rest)
| _ :: rest -> go acc rest
| [] -> acc
in
go [] (List.map snd tokens)
(* Where the hint is attached. The parser raises in 149 places and all 149
places in the parser stay untouched. What they could not know is what the
reader wrote; that is here, where the token list is.
The failure position identifies the token by line and column. The REST OF
THAT LINE, nearest first, is what makes the spelling legible: `x += 1` fails
at `=` with `+` behind it, and `if c then 1 elif x < 0 then 2` fails at the
second `then`, four tokens past the `elif` that explains it. The line is the
unit because a syntax error is explained by the line it is on. *)
let with_syntax_hint (tokens : (Loc.t * Lexer.token) list) (loc : Loc.t) (msg : string) : string =
let rec find before = function
| [] -> (before, None, [])
| ((l : Loc.t), t) :: rest ->
if l.Loc.line = loc.Loc.line && l.Loc.col = loc.Loc.col then
(before, Some t, List.filteri (fun i _ -> i < 2) (List.map snd rest))
else find ((l, t) :: before) rest
in
let (before, cur, after) = find [] tokens in
(* Q-161. THE WINDOW IS THE STATEMENT, NOT THE LINE. It was the line, and that
is right when the evidence is where the parser stopped:
def f(n):
return n <- fails here, `def` is on this window
but a broken line often makes the parser fail on the NEXT one, and there
the evidence is a line back:
def f(n):
0 <- fails here, `def` is not on this window
Mere separates top-level declarations with `;`, and a line the reader wrote
in another language does not have one -- so walking back to the nearest `;`
reaches the evidence and stops at the previous statement.
⚠ CAPPED IN LINES AS WELL. Without a cap, a file with no `;` for fifty
lines would let a spelling from the top of the file explain a failure at
the bottom. TWO is measured, not chosen: at 1 the window is the old line
and the case above is silent; at 2 it answers, the 18 catalogue rows all
still answer, and the three poisons all still go red. Going wider buys
nothing that was asked for.
⚠ THE FEAR THIS DESIGN RECORDED DID NOT MATERIALISE. Q-161 said widening
the window widens the false-positive window for `var` / `case` / `val` /
`mut`. It does not: `bound_names` walks the WHOLE token list, so "does this
file bind that name?" never depended on the window. Measured rather than
assumed -- the poisons were run at each width. *)
let max_lines =
match Sys.getenv_opt "MERE_HINT_WINDOW_LINES" with
| Some s -> (try int_of_string s with _ -> 2)
| None -> 2
in
let before =
let rec take acc = function
| [] -> List.rev acc
| ((l : Loc.t), t) :: rest ->
if loc.Loc.line - l.Loc.line >= max_lines then List.rev acc
else if t = Lexer.T_semi then List.rev acc
else take (t :: acc) rest
in
(* `before` is nearest-first already (the walk conses as it goes). *)
take [] before
in
let bound = bound_names tokens in
let bound n = List.mem n bound in
match Syntax_hint.for_window ~before ~cur ~after ~bound with
| Some hint -> msg ^ "\n" ^ hint
| None -> msg
(* The same for the lexer, where the offending thing is a character rather than
a token: the position names it in the source. *)
let with_lex_hint (source : string) (loc : Loc.t) (msg : string) : string =
let lines = String.split_on_char '\n' source in
let line =
match List.nth_opt lines (loc.Loc.line - 1) with Some l -> l | None -> ""
in
if loc.Loc.col < 1 || loc.Loc.col > String.length line then msg
else
match Syntax_hint.for_char line.[loc.Loc.col - 1] with
| Some hint -> msg ^ "\n" ^ hint
| None -> msg
(* Tokenize, and say what the character was when it refuses. *)
let tokenize_hinted ?file (source : string) =
try Lexer.tokenize ?file source with
| Lexer.Lex_error (loc, msg) when loc.Loc.file = None ->
raise (Lexer.Lex_error (loc, with_lex_hint source loc msg))
(* `keep_sugar` is for the FORMATTER, and only for it.
Sugar that is lowered while parsing (`echo` -> `echo_at "<where>"`) is
invisible to everything downstream, which is the point — and the formatter
is downstream. `mere fmt` on a file containing `echo x` printed
`echo_at "line 2" x`: correct, equivalent, and not what the person wrote.
A formatter that rewrites the source it is formatting is a tool people stop
running.
So the lowering is skipped on that one path. It is a parameter rather than a
global because the formatter and the compiler run in the same process (the
language server does both on every keystroke). *)
let parse_program ?(prelude = true) ?(keep_sugar = false) ?base_dir ?(search_paths = []) s =
(* Clear the parser's per-program declaration tables so a `type` / `module`
from a previously-parsed program in this process cannot leak into this one
(see Parser.reset_decl_state). Done BEFORE the prelude parse, which then
re-registers the prelude's own types / constructors. *)
Parser.reset_decl_state ();
(* And the typer's registries, which are the same kind of per-program state and
were never cleared: one process checking two programs had the first one's
constructors, records and views still registered for the second. A compiler
never noticed; the language server checks one document per keystroke. *)
Typer.reset_type_registries ();
(* Phase 19.4: parse the prelude FIRST so parser.constructors etc.
have the prelude's types/ctors registered before the user's source
is tokenized + parsed. Otherwise `Cons` in user code lookups arity
0 and produces a payload-less ctor. *)
let prelude_decls =
if prelude then parse_prelude () else []
in
prelude_count := List.length prelude_decls;
(* The parser's type-name table now holds the prelude's; anything the user declares
lands in front of it (the list is consed). See warn_declared_types. *)
prelude_type_count := List.length !Parser.declared_types;
let tokens = tokenize_hinted s in
let user_prog =
try
match base_dir with
| Some d -> Parser.parse_program ~base_dir:d ~search_paths tokens
| None -> Parser.parse_program ~search_paths tokens
with
(* Only for THIS file: an error from inside an `import` carries that
file's position, and its tokens are not the ones in hand here. *)
| Parser.Parse_error (loc, msg) when loc.Loc.file = None ->
raise (Parser.Parse_error (loc, with_syntax_hint tokens loc msg))
in
(* Q-012 Phase 32: lower saturated `par_map f xs` to spawn + channel +
list_map so it works on every backend, not just the interpreter.
2048-dogfood P3: then α-rename nested fn bindings to unique names so
same-named inner fns (e.g. a `let rec go` per if-branch) don't collide
in the backends' name-keyed inner-fn lift resolution. *)
(* The builtin names a USER binding may shadow (Q-045). The prelude deliberately
shadows ten builtins of its own (`pow`, `divmod`, …) and those must keep the
names they have always had, so the prelude's own top-level names are subtracted:
what is left is the set where a user binding is the FIRST shadow and has to be
renamed. *)
let prelude_bound =
List.concat_map (fun d ->
match d with
| Ast.Top_let ({ Ast.pnode = Ast.P_var n; _ }, _) -> [n]
| Ast.Top_let_rec bs -> List.map (fun (n, _, _) -> n) bs
| _ -> []) prelude_decls
in
(* v0.1.638: and the same for a prelude a driver GLUED onto the front of the
file (-rv's: `abs`, `utf8_chars`, `vec_sort`, ... are Mere functions there).
Counted as a first shadow, its `abs` became `abs__v2`, and the stdlib
prelude's `lcm` -- which sits in front of it and calls `abs` -- still meant
the builtin, which that target does not have: the program was refused for a
function the backend had. Its names keep themselves, as the stdlib
prelude's do. *)
let glued_bound =
if !Loc.glued_lines <= 0 then [] else begin
let loc_of d = match d with
| Ast.Top_let (p, _) -> Some p.Ast.ploc
| Ast.Top_let_rec ((_, l, _) :: _) -> Some l
| _ -> None in
let file0 = List.find_map (fun d -> Option.map (fun l -> l.Loc.file) (loc_of d)) user_prog.Ast.decls in
let rec take acc = function
| [] -> acc
| d :: rest ->
(match loc_of d with
| Some l when l.Loc.line <= !Loc.glued_lines && Some l.Loc.file = file0 ->
let ns = match d with
| Ast.Top_let ({ Ast.pnode = Ast.P_var n; _ }, _) -> [n]
| Ast.Top_let_rec bs -> List.map (fun (n, _, _) -> n) bs
| _ -> [] in
take (ns @ acc) rest
| Some _ -> acc
| None -> take acc rest) in
take [] user_prog.Ast.decls
end
in
let shadowable =
List.filter (fun n -> not (List.mem n prelude_bound) && not (List.mem n glued_bound))
(List.map fst Typer.initial_env)
in
(* Q-108: the builtins whose arguments include a function are the ones that can
run code the range-check versioning pass cannot see (a closure that pushes to
the very Vec the loop reads). Derived from the typer's environment, not
listed by hand: a table of names goes stale, the environment does not. *)
let higher_order_builtins =
let rec has_fn_param (t : Ast.ty) =
match Ast.walk t with
| Ast.TyArrow (p, r) ->
(match Ast.walk p with Ast.TyArrow _ -> true | _ -> false) || has_fn_param r
| _ -> false
in
List.filter_map (fun (n, (sch : Typer.scheme)) -> if has_fn_param sch.Typer.body then Some n else None)
Typer.initial_env
in
(* Q-173: everything from here down is preparation for CODE GENERATION --
range-check versioning splits a loop into `f__rvfast` / `f__rvslow`,
uniquifying renames inner functions to `go_uq3`, `par_map` lowers to its
runtime shape. `keep_sugar` used to guard only the desugaring, so `mere
fmt` printed all of it back: 19 example files came out with names no one
wrote, and formatting them AGAIN split the already-split loops. A formatter
that edits the source it formats is a tool people stop running -- the same
sentence v0.1.504 wrote about `echo`, one layer down. *)
let for_codegen p =
(* v0.1.588 (Q-156): after the names are one declaration each *)
Ast.order_toplevel ~skip:(List.length prelude_decls) @@
Ast.uniquify_toplevel_shadows ~shadowable
(Ast.reserve_toplevel_main
(Ast.uniquify_inner_fns_program ~skip:(List.length prelude_decls)
~builtins:(List.map fst Typer.initial_env)
(Ast.range_version_program ~unsafe_builtins:higher_order_builtins
(Ast.lower_par_map_program p))))
in
let prog =
(if keep_sugar then (fun p -> p) else for_codegen)
((if keep_sugar then fun p -> p
else (fun p ->
(* The privacy check rides with the sugar lowering for one
reason: both are skipped when formatting. A formatter that
refuses to format a program because of a name it cannot
call is a formatter people stop running on broken files,
which is when they need it. *)
check_module_privacy p; check_file_privacy p;
fold_str_concat (coro_rewrite (prefix_desugar (echo_rewrite p)))))
{ user_prog with Ast.decls = prelude_decls @ user_prog.Ast.decls })
in
(* Tell the typer what this program declares, here rather than only when the
declarations are later walked. What types exist is a fact about the program, and
making it one keeps every entry point consistent: `Typer.infer` on a desugared
program never registered anything, so a caller that skipped `process_decls`
type-checked against whatever the *previous* program in this process had
declared. That worked only because nothing was ever cleared. Registering is
idempotent, so the later walk is unaffected. *)
register_declared_types prog.Ast.decls;
prog
(* Every syntax error in a source string, not just the first: lex, then parse
with declaration-level recovery. Used by the CLI to report a whole file's
worth at once, and the shape an editor wants.
The prelude is parsed first for the same reason `parse_program` does it —
constructors have to be registered before the user's source is parsed, or
`Cons` in user code looks up arity 0. *)
let syntax_errors ?base_dir ?(search_paths = []) s
: (string option * Loc.t * string) list =
Parser.reset_decl_state ();
ignore (parse_prelude ());
let tokens = tokenize_hinted s in
let (_, errors) =
Parser.parse_program_recover ?base_dir ~search_paths tokens
in
(* The recovering path returns its errors rather than raising them, so the
hint is added to each. A position from an imported file is left alone for
the same reason as above. *)
List.map (fun (file, (loc : Loc.t), msg) ->
if file = None && loc.Loc.file = None then (file, loc, with_syntax_hint tokens loc msg)
else (file, loc, msg))
errors
let parse_only s =
(* Phase 21.2: parse_only is used by pretty-print / AST-shape tests
where the prelude noise (let-rec helpers wrapping the user's expr)
would obscure the AST under test. Disable prelude here. Type decls
(list / option / result) still aren't needed for these shape
tests since the input rarely uses them. *)
let prog = parse_program ~prelude:false s in
Ast.desugar_program prog
(* Process top-decls in order, updating envs and the typer's constructor table. *)
(* Phase 38.A3: a top-level fn name that collides with libc / libm / C
keywords causes a C codegen compile error. typer / eval are unaffected,
but we emit a warning at the parser stage to help the user. See
docs/patterns.md §5 for details. *)
let reserved_c_names =
[
(* C keywords *)
"short"; "long"; "int"; "char"; "float"; "double";
"signed"; "unsigned"; "register"; "static"; "auto"; "extern";
"const"; "volatile"; "restrict"; "inline";
"goto"; "return"; "break"; "continue"; "switch"; "case"; "default";
"do"; "while"; "for"; "if"; "else";
"sizeof"; "typedef"; "struct"; "union"; "enum"; "void";
(* libc stdlib.h *)
"div"; "ldiv"; "exit"; "abort"; "atexit"; "atof"; "atoi"; "atol";
"free"; "malloc"; "calloc"; "realloc"; "system";
"getenv"; "setenv"; "putenv"; "unsetenv";
"rand"; "srand"; "abs"; "labs";
"qsort"; "bsearch"; "mergesort";
(* libm math.h *)
"pow"; "sqrt"; "sin"; "cos"; "tan"; "asin"; "acos"; "atan"; "atan2";
"exp"; "log"; "log10"; "log2"; "ceil"; "floor"; "round"; "trunc";
"fabs"; "fmod"; "hypot"; "sinh"; "cosh"; "tanh";
(* libc time.h *)
"time"; "clock"; "ctime"; "asctime"; "gmtime"; "localtime"; "mktime";
"difftime"; "strftime";
(* POSIX I/O *)
"read"; "write"; "open"; "close"; "lseek"; "stat"; "fstat";
"fopen"; "fclose"; "fread"; "fwrite"; "fseek"; "ftell"; "rewind";
"printf"; "scanf"; "fprintf"; "fscanf"; "sprintf"; "sscanf";
"puts"; "gets"; "fputs"; "fgets"; "putchar"; "getchar";
(* misc libc *)
"strlen"; "strcpy"; "strncpy"; "strcat"; "strncat"; "strcmp"; "strncmp";
"strchr"; "strrchr"; "strstr"; "strdup"; "strerror";
"memcpy"; "memmove"; "memset"; "memcmp"; "memchr";
"main";
]
(* Warnings the compiler produces while checking, collected rather than printed.
They used to go straight to stderr, which is fine for a terminal and useless to
anything else: an editor cannot underline a line that was written to a stream it
is not reading. The CLI prints them now, which is the only place that should
decide how a warning looks.
A warning may carry a fix: the edit that answers it, with the position it
goes at (`Exhaustive.fix`). Nothing on the terminal path reads it — the CLI
renders the message and the `help:` lines exactly as before — but a warning
that knows how to be answered should not have to be re-derived by whoever
wants to answer it. *)
let warnings : (Loc.t * string * Exhaustive.fix option) list ref = ref []
let reset_warnings () = warnings := []
let take_warnings () = let ws = List.rev !warnings in warnings := []; ws
let warn loc msg = warnings := (loc, msg, None) :: !warnings
let warn_fix loc msg fix = warnings := (loc, msg, fix) :: !warnings
(* Drain the exhaustiveness findings on a path that is about to run or emit the
program, and stop it if any of them names a case.
This is called from `process` and from `infer_program_inner`'s `post`, which
is to say from both sides of the same fork the compile path already had to be
taught about once (see the note on `post`). Before this, a non-exhaustive
match was a line on the interpreter's stderr and *nothing at all* under
`-c` / `-ll` / `-w` / `-rv`: the four backends that produce the artifact were
the four that did not mention it.
v0.1.470 corrects what this note said about the consequence. It said each
backend "invented" a value and the answer came back wrong. MEASURED, on a
program that actually reaches the missing arm: the interpreter raises
`no matching arm in match` and points at the line, C and LLVM ran a bare
`abort()` (exit 134, no output), Wasm executed `unreachable` (exit 1, no
output), and only RV32IM really did carry on with a value — the saved stack
pointer. So the failure was LATE AND MUTE on three of the four rather than
wrong, which is a smaller claim than the one written here and still reason
enough to move it to compile time. The runtime half is now one answer on all
five paths; see the note in `Codegen_c`'s fallthrough. *)
(* Raised on the paths that would otherwise run or emit the program: a type
declared twice with different constructors leaves the compiler holding one
model for two types, and everything downstream -- exhaustiveness, `==`,
codegen's variant tags -- answers from whichever declaration came last. See
the note on `Typer.type_redecls` for what was measured before making this an
error rather than a warning (zero sites in 945 files).
No position: `Ast.Top_type` carries none, so there is no line to point at.
The message names the type and both constructor sets instead, which is what
a reader needs to find the two declarations. *)
exception Type_redeclared of ([ `Variant | `Record | `Both ] * string * string * string) list
(* Where a type name was declared, newest first. The parser conses an entry per
declaration, so a name declared twice has two entries -- which is exactly the
pair this error is about and could not point at until v0.1.506. Prelude
positions are dropped: they are in a text nobody can open. *)
let declaration_sites (name : string) : Loc.t list =
List.filter_map (fun (n, (l : Loc.t)) ->
if n = name && l.Loc.file = None && l.Loc.line > 0 then Some l else None)
!Parser.declared_types
(* ⚠ THE VARIANT WORDING IS NOT TOUCHED. Three unit tests and
`scripts/doc_claims_check.sh`'s catalogue pin "declared twice with different
constructors" verbatim; a record says "fields" because that is what it
clashes on, and the two branches exist so that neither has to be softened
into wording that fits both. *)
let redecl_message (kind, name, a, b) =
let first_line =
match List.rev (declaration_sites name) with
| (first : Loc.t) :: _ :: _ -> Printf.sprintf " (first declared on line %d)" first.Loc.line
| _ -> ""
in
match kind with
| `Variant ->
String.concat "\n"
[ Printf.sprintf
"type `%s` is declared twice with different constructors (`%s` and `%s`)%s"