-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypecheck.ml
More file actions
1639 lines (1546 loc) · 59.3 KB
/
Copy pathtypecheck.ml
File metadata and controls
1639 lines (1546 loc) · 59.3 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
(* SPDX-License-Identifier: PMPL-1.0-or-later *)
(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *)
(** Bidirectional type checker for AffineScript.
Implements a Hindley-Milner-style type checker with bidirectional
mode switching (synth/check), let-polymorphism via levels, and
integration with the unification engine ({!Unify}).
Design:
- [synth]: infer a type for an expression (mode ⇒)
- [check]: verify an expression against an expected type (mode ⇐)
- At application sites we synth the function and check the argument
- At let-bindings we generalize to produce polymorphic schemes
- Effect annotations are unified alongside types
Limitations (Phase 1):
- Quantity checking is a separate pass (see {!Quantity})
- Refinement types are checked structurally, not via SMT
- Trait resolution uses name matching (see {!Trait})
- Effect inference is basic: we unify declared effects
*)
open Types
open Ast
let string_of_binary_op = function
| OpAdd -> "+"
| OpSub -> "-"
| OpMul -> "*"
| OpDiv -> "/"
| OpMod -> "%"
| OpEq -> "=="
| OpNe -> "!="
| OpLt -> "<"
| OpLe -> "<="
| OpGt -> ">"
| OpGe -> ">="
| OpAnd -> "&&"
| OpOr -> "||"
| OpBitAnd -> "&"
| OpBitOr -> "|"
| OpBitXor -> "^"
| OpShl -> "<<"
| OpShr -> ">>"
| OpConcat -> "++"
let rec expr_summary (expr : expr) : string =
match expr with
| ExprVar id -> id.name
| ExprLit (LitBool (b, _)) -> string_of_bool b
| ExprLit (LitInt (i, _)) -> string_of_int i
| ExprLit (LitFloat (f, _)) -> string_of_float f
| ExprLit (LitChar (c, _)) -> Printf.sprintf "%c" c
| ExprLit (LitString (s, _)) -> Printf.sprintf "\"%s\"" s
| ExprLit (LitUnit _) -> "()"
| ExprBinary (l, op, r) ->
Printf.sprintf "(%s %s %s)" (expr_summary l) (string_of_binary_op op) (expr_summary r)
| ExprUnary (_, inner) -> expr_summary inner
| ExprApp (f, args) ->
let fdesc = expr_summary f in
let argsdesc = match args with
| [] -> ""
| hd :: _ -> expr_summary hd
in
Printf.sprintf "%s(%s...)" fdesc argsdesc
| ExprField (e, id) ->
Printf.sprintf "%s.%s" (expr_summary e) id.name
| ExprTuple _ -> "<tuple>"
| ExprRecord _ -> "<record>"
| ExprBlock _ -> "{...}"
| ExprIf _ -> "if(...)"
| ExprLet _ -> "let(...)"
| ExprMatch _ -> "match(...)"
| ExprRowRestrict _ -> "row_restrict"
| ExprTupleIndex _ -> "tuple_index"
| ExprIndex _ -> "index"
| ExprArray _ -> "array"
| ExprReturn _ -> "return"
| ExprTry _ -> "try"
| ExprHandle _ -> "handle"
| ExprResume _ -> "resume"
| ExprUnsafe _ -> "unsafe"
| ExprVariant (id, _) -> id.name
| ExprSpan (inner, _) -> expr_summary inner
| _ -> "<expr>"
(** {1 Errors} *)
(** Type checking error *)
type type_error =
| UnboundVariable of string
| TypeMismatch of { expected : ty; got : ty }
| OccursCheck of string * ty
| NotImplemented of string
| ArityMismatch of { name : string; expected : int; got : int }
| NotAFunction of ty
| FieldNotFound of { field : string; record_ty : ty }
| TupleIndexOutOfBounds of { index : int; length : int }
| DuplicateField of string
| UnificationError of Unify.unify_error
| PatternTypeMismatch of string
| BranchTypeMismatch of { then_ty : ty; else_ty : ty }
| QuantityError of Quantity.quantity_error * Span.t
(** QTT quantity violation detected after type checking. *)
(** Format a type error for human consumption. *)
let show_type_error = function
| UnboundVariable v -> "Unbound variable: " ^ v
| TypeMismatch { expected; got } ->
Printf.sprintf "Type mismatch: expected %s, got %s"
(ty_to_string expected) (ty_to_string got)
| OccursCheck (v, ty) ->
Printf.sprintf "Occurs check: %s in %s" v (ty_to_string ty)
| NotImplemented msg -> "Not implemented: " ^ msg
| ArityMismatch { name; expected; got } ->
Printf.sprintf "Function %s expects %d arguments, got %d" name expected got
| NotAFunction ty ->
Printf.sprintf "Expected a function type, got %s" (ty_to_string ty)
| FieldNotFound { field; record_ty } ->
Printf.sprintf "Field '%s' not found in type %s" field (ty_to_string record_ty)
| TupleIndexOutOfBounds { index; length } ->
Printf.sprintf "Tuple index %d out of bounds (tuple has %d elements)" index length
| DuplicateField f ->
Printf.sprintf "Duplicate field: %s" f
| UnificationError ue ->
Printf.sprintf "Unification error: %s" (Unify.show_unify_error ue)
| PatternTypeMismatch msg ->
Printf.sprintf "Pattern type mismatch: %s" msg
| BranchTypeMismatch { then_ty; else_ty } ->
Printf.sprintf "Branch type mismatch: then-branch has type %s, else-branch has type %s"
(ty_to_string then_ty) (ty_to_string else_ty)
| QuantityError (qerr, _span) ->
Printf.sprintf "Quantity error: %s" (Quantity.format_quantity_error qerr)
let format_type_error = show_type_error
(** {1 Context} *)
(** Type checking context.
[level] tracks the current let-nesting depth for generalization.
Variables bound at a deeper level than the current one can be
generalized when we exit back to a shallower level. *)
type context = {
var_types : (Symbol.symbol_id, scheme) Hashtbl.t;
(** Symbol-ID-keyed type map — used by resolve.ml for imports *)
name_types : (string, scheme) Hashtbl.t;
(** Name-keyed type map — used by the type checker for lookups *)
type_env : (string, ty) Hashtbl.t;
(** Named type constructors → their definition types *)
constructor_env : (string, ty) Hashtbl.t;
(** Data constructors (enum variants) → their function types *)
symbols : Symbol.t;
mutable level : int;
mutable current_eff : eff;
(** The current effect context — unified with declared effects *)
trait_registry : Trait.trait_registry;
(** Trait registry — stores trait definitions and impls for dispatch *)
}
type 'a result = ('a, type_error) Result.t
let ( let* ) = Result.bind
(** Lift a unification result into a type-checking result. *)
let unify_or_err (t1 : ty) (t2 : ty) : unit result =
match Unify.unify t1 t2 with
| Ok () -> Ok ()
| Error ue -> Error (UnificationError ue)
let unify_eff_or_err (e1 : eff) (e2 : eff) : unit result =
match Unify.unify_eff e1 e2 with
| Ok () -> Ok ()
| Error ue -> Error (UnificationError ue)
(** {1 Context management} *)
let create_context (symbols : Symbol.t) : context =
{
var_types = Hashtbl.create 128;
name_types = Hashtbl.create 128;
type_env = Hashtbl.create 64;
constructor_env = Hashtbl.create 64;
symbols;
level = 0;
current_eff = fresh_effvar 0;
trait_registry = Trait.create_registry ();
}
(** Enter a deeper let-level. *)
let enter_level (ctx : context) : unit =
ctx.level <- ctx.level + 1
(** Exit a let-level. *)
let exit_level (ctx : context) : unit =
ctx.level <- ctx.level - 1
(** Bind a variable to a monomorphic type (no generalization). *)
let bind_var (ctx : context) (name : string) (ty : ty) : unit =
let sc = { sc_tyvars = []; sc_effvars = []; sc_rowvars = []; sc_body = ty } in
Hashtbl.replace ctx.name_types name sc
(** Bind a variable to a polymorphic scheme. *)
let bind_scheme (ctx : context) (name : string) (sc : scheme) : unit =
Hashtbl.replace ctx.name_types name sc
(** {1 Instantiation and generalization} *)
(** Instantiate a polymorphic scheme by replacing bound variables
with fresh unification variables at the current level. *)
let instantiate (level : int) (sc : scheme) : ty =
let subst = Hashtbl.create 8 in
(* Create fresh type variables for each quantified tyvar *)
List.iter (fun (tv, _kind) ->
let fresh = fresh_tyvar level in
Hashtbl.replace subst tv fresh
) sc.sc_tyvars;
(* Apply substitution to the body *)
let rec apply_subst ty =
match repr ty with
| TVar r ->
begin match !r with
| Unbound (v, _) ->
begin match Hashtbl.find_opt subst v with
| Some fresh -> fresh
| None -> ty
end
| Link t -> apply_subst t
end
| TCon _ -> ty
| TApp (t, args) ->
TApp (apply_subst t, List.map apply_subst args)
| TArrow (a, q, b, e) ->
TArrow (apply_subst a, q, apply_subst b, e)
| TTuple tys ->
TTuple (List.map apply_subst tys)
| TRecord row ->
TRecord (apply_subst_row row)
| TVariant row ->
TVariant (apply_subst_row row)
| TForall (v, k, body) ->
TForall (v, k, apply_subst body)
| TExists (v, k, body) ->
TExists (v, k, apply_subst body)
| TRef t -> TRef (apply_subst t)
| TMut t -> TMut (apply_subst t)
| TOwn t -> TOwn (apply_subst t)
and apply_subst_row row =
match repr_row row with
| REmpty -> REmpty
| RExtend (l, ty, rest) ->
RExtend (l, apply_subst ty, apply_subst_row rest)
| RVar _ -> row
in
apply_subst sc.sc_body
(** Generalize a type by quantifying over all type variables
that were introduced at a level deeper than the context's
current level. *)
let generalize (ctx : context) (ty : ty) : scheme =
let tyvars = ref [] in
let rec collect ty =
match repr ty with
| TVar r ->
begin match !r with
| Unbound (v, lev) when lev > ctx.level ->
if not (List.exists (fun (v', _) -> v' = v) !tyvars) then
tyvars := (v, Types.KType) :: !tyvars
| Unbound _ -> ()
| Link t -> collect t
end
| TCon _ -> ()
| TApp (t, args) ->
collect t; List.iter collect args
| TArrow (a, _, b, _) ->
collect a; collect b
| TTuple tys ->
List.iter collect tys
| TRecord row | TVariant row ->
collect_row row
| TForall (_, _, body) | TExists (_, _, body) ->
collect body
| TRef t | TMut t | TOwn t ->
collect t
and collect_row row =
match repr_row row with
| REmpty -> ()
| RExtend (_, ty, rest) ->
collect ty; collect_row rest
| RVar _ -> ()
in
collect ty;
{ sc_tyvars = !tyvars; sc_effvars = []; sc_rowvars = [];
sc_body = ty }
(** Look up a variable, returning a fresh instantiation of its scheme. *)
let lookup_var (ctx : context) (name : string) : ty result =
match Hashtbl.find_opt ctx.name_types name with
| Some sc -> Ok (instantiate ctx.level sc)
| None -> Error (UnboundVariable name)
(** {1 Kind checking} *)
let rec infer_kind (ctx : context) (ty : ty) : kind result =
match repr ty with
| TVar r ->
begin match !r with
| Unbound (_, _) -> Ok KType
| Link t -> infer_kind ctx t
end
| TCon name ->
begin match name with
| "Array" | "Option" | "List" | "Vec" | "Cmd" -> Ok (KArrow (KType, KType))
| "Result" -> Ok (KArrow (KType, KArrow (KType, KType)))
| _ -> Ok KType
end
| TApp (head, args) ->
let* k = infer_kind ctx head in
check_kind_app ctx k args
| TArrow (_, _, _, _) | TTuple _ | TRecord _ | TVariant _ ->
Ok KType
| TForall (_, _, body) | TExists (_, _, body) ->
let* _ = infer_kind ctx body in
Ok KType
| TRef t | TMut t | TOwn t ->
infer_kind ctx t
and check_kind (ctx : context) (ty : ty) (expected : kind) : unit result =
let* got = infer_kind ctx ty in
if got = expected then Ok ()
else Error (NotImplemented (Printf.sprintf "Kind mismatch: expected %s, got %s" (show_kind expected) (show_kind got)))
and check_kind_app (ctx : context) (k : kind) (args : ty list) : kind result =
match args with
| [] -> Ok k
| arg :: rest ->
begin match k with
| KArrow (param_k, ret_k) ->
let* () = check_kind ctx arg param_k in
check_kind_app ctx ret_k rest
| _ -> Error (NotImplemented "Too many arguments for kind")
end
(** {1 AST type_expr → internal ty conversion} *)
let lower_quantity (q : Ast.quantity) : Types.quantity =
match q with
| QZero -> QZero
| QOne -> QOne
| QOmega -> QOmega
(** Convert an AST [type_expr] to an internal [ty].
Type variables are looked up in [type_env]; unknown names become
fresh unification variables so that inference can resolve them. *)
let rec lower_type_expr (ctx : context) (te : type_expr) : ty =
match te with
| TyVar { name; _ } ->
begin match Hashtbl.find_opt ctx.type_env name with
| Some ty -> ty
| None ->
(* Unresolved type variable: create a fresh unification var *)
let tv = fresh_tyvar ctx.level in
Hashtbl.replace ctx.type_env name tv;
tv
end
| TyCon { name; _ } ->
begin match name with
| "Int" -> ty_int
| "Float" -> ty_float
| "Bool" -> ty_bool
| "String" -> ty_string
| "Char" -> ty_char
| "Unit" | "()" -> ty_unit
| "Never" -> ty_never
| _ ->
begin match Hashtbl.find_opt ctx.type_env name with
| Some ty -> ty
| None -> TCon name
end
end
| TyApp ({ name; _ }, args) ->
let head = match Hashtbl.find_opt ctx.type_env name with
| Some ty -> ty
| None -> TCon name
in
let arg_tys = List.map (fun arg ->
match arg with
| TyArg te -> lower_type_expr ctx te
) args in
TApp (head, arg_tys)
| TyArrow (a, q_opt, b, eff_opt) ->
let a' = lower_type_expr ctx a in
let b' = lower_type_expr ctx b in
let q = match q_opt with
| Some q -> lower_quantity q
| None -> QOmega (* Default to unrestricted *)
in
let eff = match eff_opt with
| Some e -> lower_effect_expr ctx e
| None -> EPure
in
TArrow (a', q, b', eff)
| TyTuple [] ->
ty_unit
| TyTuple tes ->
TTuple (List.map (lower_type_expr ctx) tes)
| TyRecord (fields, _row_var) ->
let row = List.fold_right (fun (rf : row_field) acc ->
RExtend (rf.rf_name.name, lower_type_expr ctx rf.rf_ty, acc)
) fields REmpty in
TRecord row
| TyOwn te -> TOwn (lower_type_expr ctx te)
| TyRef te -> TRef (lower_type_expr ctx te)
| TyMut te -> TMut (lower_type_expr ctx te)
| TyHole ->
fresh_tyvar ctx.level
and lower_effect_expr (ctx : context) (ee : effect_expr) : eff =
match ee with
| EffVar { name; _ } ->
begin match name with
| "Pure" -> EPure
| _ -> ESingleton name
end
| EffCon ({ name; _ }, _args) ->
ESingleton name
| EffUnion (e1, e2) ->
EUnion [lower_effect_expr ctx e1; lower_effect_expr ctx e2]
(** {1 Binary/unary operator typing} *)
(** Return the type of a binary operator given its operand types. *)
let type_of_binop (op : binary_op) : ty * ty * ty =
match op with
(* Arithmetic: Int -> Int -> Int *)
| OpAdd | OpSub | OpMul | OpDiv | OpMod ->
(ty_int, ty_int, ty_int)
(* Comparison: Int -> Int -> Bool *)
| OpLt | OpLe | OpGt | OpGe ->
(ty_int, ty_int, ty_bool)
(* Equality: polymorphic, but we approximate as 'a -> 'a -> Bool *)
| OpEq | OpNe ->
let tv = fresh_tyvar 0 in
(tv, tv, ty_bool)
(* Logical: Bool -> Bool -> Bool *)
| OpAnd | OpOr ->
(ty_bool, ty_bool, ty_bool)
(* Bitwise: Int -> Int -> Int *)
| OpBitAnd | OpBitOr | OpBitXor | OpShl | OpShr ->
(ty_int, ty_int, ty_int)
| OpConcat ->
(ty_string, ty_string, ty_string)
let type_of_unop (op : unary_op) : ty * ty =
match op with
| OpNeg -> (ty_int, ty_int)
| OpNot -> (ty_bool, ty_bool)
| OpBitNot -> (ty_int, ty_int)
| OpRef ->
let tv = fresh_tyvar 0 in
(tv, TRef tv)
| OpDeref ->
let tv = fresh_tyvar 0 in
(TRef tv, tv)
(** {1 Pattern typing} *)
(** Type-check a pattern and return the list of bindings it introduces.
Each binding is [(name, ty)]. The pattern is checked against [expected_ty]
when in checking mode. *)
let rec check_pattern (ctx : context) (pat : pattern) (expected : ty)
: ((string * ty) list) result =
match pat with
| PatWildcard _ ->
Ok []
| PatVar { name; _ } ->
Ok [(name, expected)]
| PatLit lit ->
let lit_ty = type_of_literal lit in
let* () = unify_or_err expected lit_ty in
Ok []
| PatTuple pats ->
let n = List.length pats in
let elem_tys = List.init n (fun _ -> fresh_tyvar ctx.level) in
let* () = unify_or_err expected (TTuple elem_tys) in
let* bindings_list = check_patterns ctx pats elem_tys in
Ok (List.concat bindings_list)
| PatCon ({ name; _ }, sub_pats) ->
(* Look up the constructor type *)
begin match Hashtbl.find_opt ctx.constructor_env name with
| Some ctor_ty ->
let ctor_ty' = instantiate ctx.level
{ sc_tyvars = []; sc_effvars = []; sc_rowvars = []; sc_body = ctor_ty } in
(* Constructor type should be T1 -> T2 -> ... -> ResultType *)
let rec peel_arrows ty pats acc =
match pats with
| [] ->
let* () = unify_or_err expected ty in
Ok (List.rev acc)
| p :: rest ->
begin match repr ty with
| TArrow (param_ty, _, ret_ty, _) ->
let* binds = check_pattern ctx p param_ty in
peel_arrows ret_ty rest (binds :: acc)
| _ ->
Error (ArityMismatch { name; expected = List.length sub_pats;
got = List.length pats - List.length rest })
end
in
let* bindings_list = peel_arrows ctor_ty' sub_pats [] in
Ok (List.concat bindings_list)
| None ->
(* Unknown constructor — if it has no arguments, treat as a variant tag *)
if sub_pats = [] then begin
(* Unify expected with a variant containing this tag *)
Ok []
end else
Error (UnboundVariable name)
end
| PatRecord (fields, _has_rest) ->
let bindings = ref [] in
List.iter (fun (({ name; _ } : ident), pat_opt) ->
let field_ty = fresh_tyvar ctx.level in
begin match pat_opt with
| Some sub_pat ->
begin match check_pattern ctx sub_pat field_ty with
| Ok bs -> bindings := bs @ !bindings
| Error _ -> ()
end
| None ->
bindings := (name, field_ty) :: !bindings
end
) fields;
Ok !bindings
| PatOr (p1, _p2) ->
(* Both branches must produce the same bindings; we check the first *)
check_pattern ctx p1 expected
| PatAs ({ name; _ }, sub_pat) ->
let* binds = check_pattern ctx sub_pat expected in
Ok ((name, expected) :: binds)
and check_patterns (ctx : context) (pats : pattern list) (tys : ty list)
: ((string * ty) list list) result =
match pats, tys with
| [], [] -> Ok []
| p :: ps, t :: ts ->
let* binds = check_pattern ctx p t in
let* rest = check_patterns ctx ps ts in
Ok (binds :: rest)
| _ -> Error (PatternTypeMismatch "pattern/type list length mismatch")
(** {1 Literal typing} *)
and type_of_literal (lit : literal) : ty =
match lit with
| LitInt _ -> ty_int
| LitFloat _ -> ty_float
| LitBool _ -> ty_bool
| LitChar _ -> ty_char
| LitString _ -> ty_string
| LitUnit _ -> ty_unit
(** {1 Expression synthesis (mode ⇒)} *)
(** Synthesize a type for an expression. *)
let rec synth (ctx : context) (expr : expr) : ty result =
match expr with
(* Literals *)
| ExprLit lit ->
Ok (type_of_literal lit)
(* Variables — instantiate their scheme *)
| ExprVar { name; _ } ->
lookup_var ctx name
(* Let bindings: let pat = e1 in e2 *)
| ExprLet { el_pat; el_ty; el_value; el_body; el_mut = _; el_quantity = _ } ->
(* Synthesize or check the value *)
enter_level ctx;
let* val_ty = begin match el_ty with
| Some te ->
let ann_ty = lower_type_expr ctx te in
let* () = check ctx el_value ann_ty in
Ok ann_ty
| None ->
synth ctx el_value
end in
exit_level ctx;
(* Generalize the value type *)
let sc = generalize ctx val_ty in
(* Bind pattern variables *)
let* bindings = check_pattern ctx el_pat val_ty in
let old_bindings = List.map (fun (name, _) ->
(name, Hashtbl.find_opt ctx.name_types name)
) bindings in
List.iter (fun (name, _ty) ->
bind_scheme ctx name sc
) bindings;
(* Type-check the body if present, else return Unit *)
let result = begin match el_body with
| Some body -> synth ctx body
| None -> Ok ty_unit
end in
(* Restore old bindings *)
List.iter (fun (name, old) ->
match old with
| Some sc' -> Hashtbl.replace ctx.name_types name sc'
| None -> Hashtbl.remove ctx.name_types name
) old_bindings;
result
(* If-then-else *)
| ExprIf { ei_cond; ei_then; ei_else } ->
let* () = check ctx ei_cond ty_bool in
let* then_ty = synth ctx ei_then in
begin match ei_else with
| Some else_expr ->
let* else_ty = synth ctx else_expr in
let* () = unify_or_err then_ty else_ty in
Ok then_ty
| None ->
let () =
if ty_to_string then_ty <> ty_to_string ty_unit then
Format.eprintf "If without else returns %s; then=%s cond=%s\n%!"
(ty_to_string then_ty) (expr_summary ei_then) (expr_summary ei_cond)
else
()
in
(* No else branch: result is Unit *)
let* () = unify_or_err then_ty ty_unit in
Ok ty_unit
end
(* Match expressions *)
| ExprMatch { em_scrutinee; em_arms } ->
let* scrut_ty = synth ctx em_scrutinee in
let result_ty = fresh_tyvar ctx.level in
let* () = List.fold_left (fun acc (arm : match_arm) ->
let* () = acc in
let* bindings = check_pattern ctx arm.ma_pat scrut_ty in
(* Save and bind pattern variables *)
let old = List.map (fun (n, _) ->
(n, Hashtbl.find_opt ctx.name_types n)
) bindings in
List.iter (fun (n, t) -> bind_var ctx n t) bindings;
(* Check guard if present *)
let* () = match arm.ma_guard with
| Some guard -> check ctx guard ty_bool
| None -> Ok ()
in
(* Body must unify with result type *)
let* arm_ty = synth ctx arm.ma_body in
let* () = unify_or_err result_ty arm_ty in
(* Restore *)
List.iter (fun (n, old_sc) ->
match old_sc with
| Some sc -> Hashtbl.replace ctx.name_types n sc
| None -> Hashtbl.remove ctx.name_types n
) old;
Ok ()
) (Ok ()) em_arms in
Ok result_ty
(* Lambda *)
| ExprLambda { elam_params; elam_ret_ty; elam_body } ->
let param_tys = List.map (fun (p : param) ->
lower_type_expr ctx p.p_ty
) elam_params in
(* Save old bindings, bind params *)
let old = List.map2 (fun (p : param) ty ->
let old = Hashtbl.find_opt ctx.name_types p.p_name.name in
bind_var ctx p.p_name.name ty;
(p.p_name.name, old)
) elam_params param_tys in
(* Synthesize or check body *)
let* body_ty = begin match elam_ret_ty with
| Some te ->
let ret_ty = lower_type_expr ctx te in
let* () = check ctx elam_body ret_ty in
Ok ret_ty
| None ->
synth ctx elam_body
end in
(* Restore *)
List.iter (fun (n, old_sc) ->
match old_sc with
| Some sc -> Hashtbl.replace ctx.name_types n sc
| None -> Hashtbl.remove ctx.name_types n
) old;
(* Build the arrow type: curried for multi-param.
Each arrow carries the declared quantity of the corresponding parameter.
If the parameter has no explicit annotation, we default to QOmega
(unrestricted), matching the convention used by check_fn_decl. *)
let eff = fresh_effvar ctx.level in
let param_qty_pairs = List.map2 (fun (p : param) param_ty ->
let q = match p.p_quantity with
| Some q -> lower_quantity q
| None -> QOmega
in
(q, param_ty)
) elam_params param_tys in
let ty = List.fold_right (fun (q, param_ty) acc ->
TArrow (param_ty, q, acc, eff)
) param_qty_pairs body_ty in
Ok ty
(* Function application *)
| ExprApp (fn_expr, args) ->
let* fn_ty = synth ctx fn_expr in
apply_args ctx fn_ty args
(* Tuple *)
| ExprTuple exprs ->
let* tys = synth_list ctx exprs in
Ok (TTuple tys)
(* Array *)
| ExprArray exprs ->
begin match exprs with
| [] ->
let elem_ty = fresh_tyvar ctx.level in
Ok (TApp (TCon "Array", [elem_ty]))
| first :: rest ->
let* first_ty = synth ctx first in
let* () = List.fold_left (fun acc e ->
let* () = acc in
check ctx e first_ty
) (Ok ()) rest in
Ok (TApp (TCon "Array", [first_ty]))
end
(* Record literal *)
| ExprRecord { er_fields; er_spread = _ } ->
let* field_tys = List.fold_left (fun acc (({ name; _ } : ident), expr_opt) ->
let* fields = acc in
let* ty = begin match expr_opt with
| Some e -> synth ctx e
| None -> lookup_var ctx name
end in
Ok ((name, ty) :: fields)
) (Ok []) er_fields in
let row = List.fold_right (fun (name, ty) acc ->
RExtend (name, ty, acc)
) field_tys REmpty in
Ok (TRecord row)
(* Field access — first try record-field projection, then trait method lookup *)
| ExprField (obj, { name = field; _ }) ->
let* obj_ty = synth ctx obj in
(* Auto-deref reference/owned wrappers before record projection
(issue #122 v2): `c.field` where `c : ref/own/mut S` projects the
field of S — the borrow checker still governs aliasing separately.
Without this, `fn(c: ref S) = c.field` failed with
"Field 'field' not found in type ref {...}". *)
let rec strip_ref t =
match repr t with
| TRef u | TMut u | TOwn u -> strip_ref u
| u -> u
in
let obj_ty_deref = strip_ref obj_ty in
let field_ty = fresh_tyvar ctx.level in
let rest_row = fresh_rowvar ctx.level in
let expected_record = TRecord (RExtend (field, field_ty, rest_row)) in
begin match Unify.unify (repr obj_ty_deref) expected_record with
| Ok () -> Ok field_ty
| Error _ ->
(* Record projection failed — try trait method dispatch.
We search all registered impls for a method named [field]
whose self type unifies with [obj_ty]. *)
begin match Trait.find_method_for_type ctx.trait_registry obj_ty field with
| Some (_impl, method_decl) ->
(* Build the method's monomorphic type from the fn_decl.
Parameters: each p_ty is lowered to an internal ty.
Return type defaults to a fresh type variable when omitted.
Effects are left as a fresh effect variable (unannotated). *)
let param_tys = List.map (fun (p : Ast.param) ->
lower_type_expr ctx p.p_ty
) method_decl.Ast.fd_params in
let ret_ty = match method_decl.Ast.fd_ret_ty with
| Some te -> lower_type_expr ctx te
| None -> fresh_tyvar ctx.level
in
let eff = fresh_effvar ctx.level in
(* Fold params into a curried arrow, right-to-left *)
let method_ty = List.fold_right (fun (param_and_ty) acc ->
let (p, pt) = (param_and_ty : Ast.param * ty) in
let q = match p.Ast.p_quantity with
| Some q -> lower_quantity q
| None -> QOmega
in
TArrow (pt, q, acc, eff)
) (List.combine method_decl.Ast.fd_params param_tys) ret_ty in
Ok method_ty
| None ->
(* Neither record field nor trait method — report a field-not-found error *)
Error (FieldNotFound { field; record_ty = obj_ty })
end
end
(* Tuple indexing *)
| ExprTupleIndex (tup, idx) ->
let* tup_ty = synth ctx tup in
begin match repr tup_ty with
| TTuple tys ->
if idx >= 0 && idx < List.length tys then
Ok (List.nth tys idx)
else
Error (TupleIndexOutOfBounds { index = idx; length = List.length tys })
| _ ->
(* Create a tuple type with enough slots *)
let n = idx + 1 in
let elem_tys = List.init n (fun _ -> fresh_tyvar ctx.level) in
let* () = unify_or_err tup_ty (TTuple elem_tys) in
Ok (List.nth elem_tys idx)
end
(* Array indexing *)
| ExprIndex (arr, idx_expr) ->
let* arr_ty = synth ctx arr in
let* () = check ctx idx_expr ty_int in
let elem_ty = fresh_tyvar ctx.level in
let* () = unify_or_err arr_ty (TApp (TCon "Array", [elem_ty])) in
Ok elem_ty
(* Binary operators
Arithmetic and comparison ops are dispatched on the lhs type so that
[Float] kernels typecheck without requiring a typeclass system. Order:
synthesise lhs, walk through repr to pierce variables; if it's a Float,
pin rhs to Float and return the matching result type; otherwise fall
through to the legacy [type_of_binop] path which is Int-monomorphic. *)
| ExprBinary (lhs, op, rhs) ->
let arith_or_bitwise = match op with
| OpAdd | OpSub | OpMul | OpDiv | OpMod
| OpBitAnd | OpBitOr | OpBitXor | OpShl | OpShr -> true
| _ -> false
in
let comparison = match op with
| OpLt | OpLe | OpGt | OpGe -> true
| _ -> false
in
if arith_or_bitwise then begin
let* lhs_ty = synth ctx lhs in
match repr lhs_ty with
| TCon "Float" ->
let* () = check ctx rhs ty_float in
Ok ty_float
| _ ->
let (lhs_ty', rhs_ty, result_ty) = type_of_binop op in
let* () = unify_or_err lhs_ty lhs_ty' in
let* () = check ctx rhs rhs_ty in
Ok result_ty
end else if comparison then begin
let* lhs_ty = synth ctx lhs in
match repr lhs_ty with
| TCon "Float" ->
let* () = check ctx rhs ty_float in
Ok ty_bool
| _ ->
let (lhs_ty', rhs_ty, result_ty) = type_of_binop op in
let* () = unify_or_err lhs_ty lhs_ty' in
let* () = check ctx rhs rhs_ty in
Ok result_ty
end else if (match op with OpConcat -> true | _ -> false) then begin
(* `++` is polymorphic over String and [T] (issue #122 v2.5):
`a ++ b` (string concat) and `acc ++ [x]` (array concat) — the
latter is how stdlib/string.affine's split/join/etc. accumulate.
Dispatch on the synthesised lhs type. *)
let* lhs_ty = synth ctx lhs in
(match repr lhs_ty with
| TApp (TCon "Array", [elem]) ->
let* () = check ctx rhs (TApp (TCon "Array", [elem])) in
Ok (TApp (TCon "Array", [elem]))
| TCon "String" ->
let* () = check ctx rhs ty_string in
Ok ty_string
| _ ->
(* lhs type not yet determined (e.g. `let mut acc = []`):
disambiguate on the rhs rather than defaulting to String. *)
let* rhs_ty = synth ctx rhs in
(match repr rhs_ty with
| TApp (TCon "Array", [elem]) ->
let* () = unify_or_err lhs_ty (TApp (TCon "Array", [elem])) in
Ok (TApp (TCon "Array", [elem]))
| _ ->
let* () = unify_or_err lhs_ty ty_string in
let* () = unify_or_err rhs_ty ty_string in
Ok ty_string))
end else begin
let (lhs_ty, rhs_ty, result_ty) = type_of_binop op in
let* () = check ctx lhs lhs_ty in
let* () = check ctx rhs rhs_ty in
Ok result_ty
end
(* Unary operators — OpNeg dispatches Int/Float on operand, like binops. *)
| ExprUnary (op, operand) ->
(match op with
| OpNeg ->
let* operand_ty = synth ctx operand in
(match repr operand_ty with
| TCon "Float" -> Ok ty_float
| _ ->
let* () = unify_or_err operand_ty ty_int in
Ok ty_int)
| _ ->
let (operand_ty, result_ty) = type_of_unop op in
let* () = check ctx operand operand_ty in
Ok result_ty)
(* Block *)
| ExprBlock blk ->
synth_block ctx blk
(* Return — return type is Never (it doesn't produce a value locally) *)
| ExprReturn expr_opt ->
begin match expr_opt with
| Some e ->
let* _ty = synth ctx e in
Ok ty_never
| None ->
Ok ty_never
end
(* Variant constructor: Type::Variant *)
| ExprVariant ({ name = _type_name; _ }, { name = variant_name; _ }) ->
begin match Hashtbl.find_opt ctx.constructor_env variant_name with
| Some ctor_ty -> Ok ctor_ty
| None ->
(* Unknown variant — return a fresh variable *)
Ok (fresh_tyvar ctx.level)
end
(* Row restriction *)
| ExprRowRestrict (obj, { name = field; _ }) ->
let* obj_ty = synth ctx obj in
let field_ty = fresh_tyvar ctx.level in
let rest_row = fresh_rowvar ctx.level in
let* () = unify_or_err obj_ty (TRecord (RExtend (field, field_ty, rest_row))) in
Ok (TRecord rest_row)
(* Span wrapper — unwrap and recurse *)
| ExprSpan (inner, _span) ->
synth ctx inner
(* Effect handling *)
| ExprHandle { eh_body; _ } ->
synth ctx eh_body
(* Resume *)
| ExprResume expr_opt ->
begin match expr_opt with
| Some e -> synth ctx e
| None -> Ok ty_unit
end
(* Try-catch-finally.
Body type is synthesised first and becomes the overall result type.
Each catch arm is type-checked against a fresh error-type variable
(the effect system will constrain this once effect inference is
complete) and its body type must unify with the result type.
The finally block (if present) is checked for unit — its value is
discarded at runtime; a non-unit finally type is a type error. *)
| ExprTry { et_body; et_catch; et_finally } ->
let* body_ty = synth_block ctx et_body in
let result_ty = fresh_tyvar ctx.level in
let* () = unify_or_err result_ty body_ty in
let* () = match et_catch with
| None -> Ok ()
| Some arms ->
(* All catch arms match against a single opaque error type. *)
let err_ty = fresh_tyvar ctx.level in
List.fold_left (fun acc (arm : match_arm) ->
let* () = acc in
let* bindings = check_pattern ctx arm.ma_pat err_ty in
(* Save bindings that will be shadowed, then install new ones. *)
let old = List.map (fun (n, _) ->
(n, Hashtbl.find_opt ctx.name_types n)
) bindings in
List.iter (fun (n, t) -> bind_var ctx n t) bindings;
let* arm_ty = synth ctx arm.ma_body in
let* () = unify_or_err result_ty arm_ty in
(* Restore previous bindings. *)
List.iter (fun (n, old_sc) ->
match old_sc with
| Some sc -> Hashtbl.replace ctx.name_types n sc
| None -> Hashtbl.remove ctx.name_types n
) old;
Ok ()
) (Ok ()) arms
in
let* () = match et_finally with
| None -> Ok ()
| Some blk ->
(* Finally must be unit — its value is always discarded. *)
let* fin_ty = synth_block ctx blk in
unify_or_err fin_ty ty_unit
in