@@ -15,7 +15,7 @@ open Ast
1515
1616(* * A place is an l-value that can be borrowed *)
1717type place =
18- | PlaceVar of Symbol .symbol_id
18+ | PlaceVar of string * Symbol .symbol_id (* * human name, symbol id *)
1919 | PlaceField of place * string
2020 | PlaceIndex of place * int option (* * None for dynamic index *)
2121 | PlaceDeref of place
@@ -129,10 +129,10 @@ let rec is_copy_type (ty_opt : type_expr option) : bool =
129129 | None -> false (* Unknown type, assume not Copy *)
130130 | Some ty ->
131131 begin match ty with
132- | TyNamed id when id.name = " Int" || id.name = " Bool" || id.name = " Char" -> true
133- | TyNamed id when id.name = " Unit" -> true
132+ | TyCon id when id.name = " Int" || id.name = " Bool" || id.name = " Char" -> true
133+ | TyCon id when id.name = " Unit" -> true
134134 | TyTuple tys -> List. for_all (fun t -> is_copy_type (Some t)) tys
135- | TyApp (TyNamed id , _ ) when id.name = " Ref " -> true (* Shared references are Copy *)
135+ | TyRef _ -> true (* Shared references are Copy *)
136136 | _ -> false (* Records, arrays, owned types, etc. are not Copy *)
137137 end
138138
@@ -149,7 +149,7 @@ let is_copy_expr (expr : expr) : bool =
149149(* * Check if two places overlap *)
150150let rec places_overlap (p1 : place ) (p2 : place ) : bool =
151151 match (p1, p2) with
152- | (PlaceVar v1 , PlaceVar v2 ) -> v1 = v2
152+ | (PlaceVar ( _ , v1 ) , PlaceVar ( _ , v2 ) ) -> v1 = v2
153153 | (PlaceField (base1 , _ ), PlaceField (base2 , _ )) ->
154154 places_overlap base1 base2
155155 | (PlaceVar _, PlaceField (base, _))
@@ -199,14 +199,17 @@ let record_borrow (state : state) (place : place) (kind : borrow_kind)
199199 Error (UseAfterMove (place, span, move_site))
200200 | None ->
201201 (* Check if trying to mutably borrow an immutable place *)
202- begin match kind with
202+ let mut_check = match kind with
203203 | Exclusive ->
204204 if not (is_mutable state place) then
205205 Error (CannotBorrowAsMutable (place, span))
206206 else
207- ()
208- | Shared -> ()
209- end ;
207+ Ok ()
208+ | Shared -> Ok ()
209+ in
210+ match mut_check with
211+ | Error _ as err -> err
212+ | Ok () ->
210213 let new_borrow = {
211214 b_place = place;
212215 b_kind = kind;
@@ -229,6 +232,52 @@ let check_use (state : state) (place : place) (span : Span.t) : unit result =
229232 | Some move_site -> Error (UseAfterMove (place, span, move_site))
230233 | None -> Ok ()
231234
235+ (* * Format a place for display in error messages. *)
236+ let rec format_place (p : place ) : string =
237+ match p with
238+ | PlaceVar (name , _ ) -> name
239+ | PlaceField (base , f ) -> format_place base ^ " ." ^ f
240+ | PlaceIndex (base , Some i ) -> format_place base ^ " [" ^ string_of_int i ^ " ]"
241+ | PlaceIndex (base , None) -> format_place base ^ " [_]"
242+ | PlaceDeref p' -> " *" ^ format_place p'
243+
244+ (* * Format a span for display. *)
245+ let format_span (span : Span.t ) : string =
246+ Format. asprintf " %a" Span. pp_short span
247+
248+ (* * Format a borrow error as a human-readable string. *)
249+ let format_borrow_error (e : borrow_error ) : string =
250+ match e with
251+ | UseAfterMove (place , use_span , move_span ) ->
252+ Printf. sprintf
253+ " use of moved value: `%s`\n \
254+ value used at %s\n \
255+ value moved at %s"
256+ (format_place place) (format_span use_span) (format_span move_span)
257+ | ConflictingBorrow (b1 , b2 ) ->
258+ Printf. sprintf
259+ " conflicting borrows on `%s`:\n \
260+ %s borrow (id %d) at %s conflicts with earlier %s borrow (id %d) at %s"
261+ (format_place b1.b_place)
262+ (show_borrow_kind b1.b_kind) b1.b_id (format_span b1.b_span)
263+ (show_borrow_kind b2.b_kind) b2.b_id (format_span b2.b_span)
264+ | BorrowOutlivesOwner (b , sym_id ) ->
265+ Printf. sprintf
266+ " borrow of `%s` (id %d) outlives its owner (symbol %d)"
267+ (format_place b.b_place) b.b_id sym_id
268+ | MoveWhileBorrowed (place , b ) ->
269+ Printf. sprintf
270+ " cannot move `%s` while it is %s-borrowed at %s"
271+ (format_place place) (show_borrow_kind b.b_kind) (format_span b.b_span)
272+ | CannotMoveOutOfBorrow (place , b ) ->
273+ Printf. sprintf
274+ " cannot move out of `%s`, which is behind a %s borrow at %s"
275+ (format_place place) (show_borrow_kind b.b_kind) (format_span b.b_span)
276+ | CannotBorrowAsMutable (place , span ) ->
277+ Printf. sprintf
278+ " cannot borrow `%s` as mutable — it is not declared with `let mut` (at %s)"
279+ (format_place place) (format_span span)
280+
232281(* * Get span from an expression *)
233282let rec expr_span (expr : expr ) : Span.t =
234283 match expr with
@@ -327,7 +376,7 @@ let rec expr_to_place (symbols : Symbol.t) (expr : expr) : place option =
327376 match expr with
328377 | ExprVar id ->
329378 begin match lookup_symbol_by_name symbols id.name with
330- | Some sym -> Some (PlaceVar sym.sym_id)
379+ | Some sym -> Some (PlaceVar (id.name, sym.sym_id) )
331380 | None -> None
332381 end
333382 | ExprField (base , field ) ->
@@ -447,14 +496,61 @@ let rec check_expr (ctx : context) (state : state) (symbols : Symbol.t) (expr :
447496
448497 | ExprMatch em ->
449498 let * () = check_expr ctx state symbols em.em_scrutinee in
450- List. fold_left (fun acc arm ->
451- let * () = acc in
452- let * () = match arm.ma_guard with
453- | Some g -> check_expr ctx state symbols g
454- | None -> Ok ()
499+ (* Each arm is independent: run each against the post-scrutinee state,
500+ then merge. A place is moved after the match if it is moved in any
501+ arm (conservative: we require moves to happen in all arms before
502+ assuming the value is gone from the outer scope). Borrows from
503+ individual arms expire at arm exit. *)
504+ let base_borrows = state.borrows in
505+ let base_moved = state.moved in
506+ let arm_results = List. map (fun arm ->
507+ (* Reset to post-scrutinee state for each arm *)
508+ state.borrows < - base_borrows;
509+ state.moved < - base_moved;
510+ let r =
511+ let open Result in
512+ bind (match arm.ma_guard with
513+ | Some g -> check_expr ctx state symbols g
514+ | None -> Ok () )
515+ (fun () -> check_expr ctx state symbols arm.ma_body)
516+ in
517+ (r, state.borrows, state.moved)
518+ ) em.em_arms in
519+ (* Propagate the first error, or merge successful states *)
520+ let errors = List. filter_map (fun (r , _ , _ ) ->
521+ match r with Error e -> Some e | Ok () -> None ) arm_results in
522+ begin match errors with
523+ | e :: _ -> Error e
524+ | [] ->
525+ (* Borrows: active after match only if active in ALL arms *)
526+ let all_borrows = List. map (fun (_ , bs , _ ) -> bs) arm_results in
527+ let merged_borrows = match all_borrows with
528+ | [] -> base_borrows
529+ | first :: rest ->
530+ List. fold_left (fun acc arm_borrows ->
531+ List. filter (fun b ->
532+ List. exists (fun b' -> b.b_id = b'.b_id) arm_borrows
533+ ) acc
534+ ) first rest
455535 in
456- check_expr ctx state symbols arm.ma_body
457- ) (Ok () ) em.em_arms
536+ (* Moves: conservative union — moved in any arm *)
537+ let all_moves = List. concat_map (fun (_ , _ , ms ) ->
538+ List. filter (fun mr ->
539+ not (List. exists (fun base_mr ->
540+ places_overlap mr.m_place base_mr.m_place
541+ ) base_moved)
542+ ) ms
543+ ) arm_results in
544+ (* Deduplicate moves by place *)
545+ let unique_moves = List. fold_left (fun acc mr ->
546+ if List. exists (fun mr' -> places_overlap mr.m_place mr'.m_place) acc
547+ then acc
548+ else mr :: acc
549+ ) [] all_moves in
550+ state.borrows < - merged_borrows;
551+ state.moved < - base_moved @ unique_moves;
552+ Ok ()
553+ end
458554
459555 | ExprTuple exprs ->
460556 List. fold_left (fun acc e ->
@@ -581,13 +677,24 @@ let rec check_expr (ctx : context) (state : state) (symbols : Symbol.t) (expr :
581677 check_expr ctx state symbols e
582678
583679and check_block (ctx : context ) (state : state ) (symbols : Symbol.t ) (blk : block ) : unit result =
680+ (* Snapshot borrows at block entry. Borrows created inside this block for
681+ block-local variables expire at block exit (lexical lifetimes).
682+ Moves persist — a moved value stays moved after the block. *)
683+ let borrows_at_entry = state.borrows in
584684 let * () = List. fold_left (fun acc stmt ->
585685 let * () = acc in
586686 check_stmt ctx state symbols stmt
587687 ) (Ok () ) blk.blk_stmts in
588- match blk.blk_expr with
589- | Some e -> check_expr ctx state symbols e
590- | None -> Ok ()
688+ let * () = match blk.blk_expr with
689+ | Some e -> check_expr ctx state symbols e
690+ | None -> Ok ()
691+ in
692+ (* End borrows for places bound in this block: restore to pre-block borrows,
693+ keeping only those that existed before the block (i.e., borrow outer-scope
694+ variables that the block merely uses — these are unaffected).
695+ This is a conservative lexical-lifetime approximation. *)
696+ state.borrows < - borrows_at_entry;
697+ Ok ()
591698
592699and check_stmt (ctx : context ) (state : state ) (symbols : Symbol.t ) (stmt : stmt ) : unit result =
593700 match stmt with
0 commit comments