open Pure
open PureUtils
open TypesAnalysis
open SymbolicToPureCore
open SymbolicToPureTypes
open SymbolicToPureValues
module NormSymbProjMap = InterpBorrowsCore.NormSymbProjMap

(** The local logger *)
let log = Logging.symbolic_to_pure_abs_log

(** Explore an abstraction value and compute the type of the value consumed upon
    ending the loans. *)
let rec tavalue_to_consumed_ty_aux ~(filter : bool) (ctx : bs_ctx)
    (abs_regions : T.RegionId.Set.t) (abs_level : abs_level)
    (current_level : abs_level) (av : V.tavalue) : ty option =
  match av.value with
  | AAdt adt_v ->
      adt_avalue_to_consumed_ty_aux ~filter ctx abs_regions abs_level
        current_level av adt_v
  | ALoan lc ->
      aloan_content_to_consumed_ty_aux ~filter ctx abs_regions av.ty lc
  | ABorrow _ ->
      (* This value should have been generated by a loan projector: there
         can't be aborrows unless there are nested borrows, which are not
         supported yet. *)
      [%craise] ctx.span "Unreachable"
  | ASymbolic (pm, aproj) ->
      [%sanity_check] ctx.span (pm = PNone);
      aproj_to_consumed_ty_aux ctx abs_regions aproj av.ty
  | AIgnored _ ->
      if filter then None
      else
        (* If we do not filter it means we are inside an ADT. *)
        Some (ctx_translate_fwd_ty ctx av.ty)

and adt_avalue_to_consumed_ty_aux ~(filter : bool) (ctx : bs_ctx)
    (abs_regions : T.RegionId.Set.t) (abs_level : abs_level)
    (current_level : abs_level) (av : V.tavalue) (adt_v : V.adt_avalue) :
    ty option =
  let _, out =
    gtranslate_adt_fields ~project_borrows:false (tavalue_to_string ctx)
      (pure_ty_to_string ctx)
      (fun ~filter ctx v ->
        ( ctx,
          match
            tavalue_to_consumed_ty_aux ~filter ctx abs_regions abs_level
              current_level v
          with
          | None -> None
          | Some x -> Some ((), x) ))
      (compute_tavalue_proj_kind ctx.span ctx.type_ctx.type_infos abs_regions
         abs_level current_level)
      (fun _ -> translate_fwd_ty (Some ctx.span) ctx.decls_ctx av.ty)
      mk_simpl_tuple_ty ~filter ctx av av.ty adt_v.fields
  in
  Option.map snd out

and aloan_content_to_consumed_ty_aux ~(filter : bool) (ctx : bs_ctx)
    (_abs_regions : T.RegionId.Set.t) (ty : T.ty) (lc : V.aloan_content) :
    ty option =
  let _ = filter in
  match lc with
  | AMutLoan (pm, _, child) ->
      [%sanity_check] ctx.span (pm = PNone);
      [%sanity_check] ctx.span (ValuesUtils.is_aignored child.value);
      Some (ctx_translate_fwd_ty ctx ty)
  | ASharedLoan (_, _, _, _) -> [%craise] ctx.span "Unreachable"
  | AEndedMutLoan _ | AEndedSharedLoan _ ->
      (* We shouldn't compute the type of a region abstraction if it contains ended loans *)
      [%craise] ctx.span "Unreachable"
  | AIgnoredMutLoan (_, _) ->
      (* There can be *inner* not ended mutable loans, but not outer ones *)
      [%craise] ctx.span "Unreachable"
  | AEndedIgnoredMutLoan _ ->
      (* This happens with nested borrows: we need to dive in *)
      [%craise] ctx.span "Unimplemented"
  | AIgnoredSharedLoan _ ->
      (* This case only happens with nested borrows *)
      [%craise] ctx.span "Unimplemented"

and aproj_to_consumed_ty_aux (ctx : bs_ctx) (_abs_regions : T.RegionId.Set.t)
    (aproj : V.aproj) (_ty : T.ty) : ty option =
  match aproj with
  | V.AProjLoans { proj; consumed; borrows } ->
      [%sanity_check] ctx.span (consumed = []);
      [%sanity_check] ctx.span (borrows = []);
      Some (ctx_translate_fwd_ty ctx proj.proj_ty)
  | V.AEndedProjLoans _ | AEndedProjBorrows _ | AEmpty | AProjBorrows _ ->
      [%craise] ctx.span "Unreachable"

let tavalue_to_consumed_ty (ctx : bs_ctx) (abs_regions : T.RegionId.Set.t)
    (abs_level : abs_level) (av : V.tavalue) : ty option =
  [%cassert] ctx.span (abs_level = 0) "Unimplemented";
  (* Check if the value was generated from a loan projector: if yes, and if
     it contains mutable loans, then we generate a consumed value (because
     upon ending the borrow we consumed a value).
     Otherwise we ignore it. *)
  [%ltrace tavalue_to_string ~with_ended:true ctx av];
  match
    compute_tavalue_proj_kind ctx.span ctx.type_ctx.type_infos abs_regions
      abs_level 0 av
  with
  | LoanProj BMut ->
      [%ltrace "the value contains mutable loan projectors"];
      tavalue_to_consumed_ty_aux ~filter:true ctx abs_regions abs_level 0 av
  | LoanProj BShared | BorrowProj _ | UnknownProj ->
      (* If it is a borrow proj we ignore it. If it is an unknown projection,
         it means the value doesn't contain loans nor borrows, so nothing
         is consumed upon ending the abstraction: we can ignore it as well. *)
      [%ltrace
        "the value doesn't contains mutable loan projectors (ignoring it)"];
      None

let rec tavalue_to_given_back_ty_aux ~(filter : bool) (ctx : bs_ctx)
    (abs_regions : T.RegionId.Set.t) (abs_level : abs_level)
    (current_level : abs_level) (av : V.tavalue) : ty option =
  match av.value with
  | AAdt adt_v ->
      adt_avalue_to_given_back_ty_aux ~filter abs_regions abs_level
        current_level av adt_v ctx
  | ALoan _ ->
      (* The avalue should have been generated by a borrow projector: this case is unreachable *)
      [%craise] ctx.span "Unreachable"
  | ABorrow bc -> aborrow_content_to_given_back_ty_aux ~filter bc av.ty ctx
  | ASymbolic (pm, aproj) ->
      [%sanity_check] ctx.span (pm = PNone);
      aproj_to_given_back_ty_aux aproj av.ty ctx
  | AIgnored _ ->
      if filter then None
      else Some (translate_fwd_ty (Some ctx.span) ctx.decls_ctx av.ty)

and adt_avalue_to_given_back_ty_aux ~(filter : bool)
    (abs_regions : T.RegionId.Set.t) (abs_level : abs_level)
    (current_level : abs_level) (av : V.tavalue) (adt_v : V.adt_avalue)
    (ctx : bs_ctx) : ty option =
  let _, out =
    gtranslate_adt_fields ~project_borrows:true (tavalue_to_string ctx)
      (pure_ty_to_string ctx)
      (fun ~filter ctx v ->
        ( ctx,
          match
            tavalue_to_given_back_ty_aux ~filter ctx abs_regions abs_level
              current_level v
          with
          | None -> None
          | Some x -> Some ((), x) ))
      (compute_tavalue_proj_kind ctx.span ctx.type_ctx.type_infos abs_regions
         abs_level current_level)
      (fun _ -> translate_fwd_ty (Some ctx.span) ctx.decls_ctx av.ty)
      mk_simpl_tuple_ty ~filter ctx av av.ty adt_v.fields
  in
  Option.map snd out

and aborrow_content_to_given_back_ty_aux ~(filter : bool)
    (bc : V.aborrow_content) (ty : T.ty) (ctx : bs_ctx) : ty option =
  match bc with
  | V.AMutBorrow _ -> Some (translate_fwd_ty (Some ctx.span) ctx.decls_ctx ty)
  | ASharedBorrow _ ->
      if filter then None
      else Some (translate_fwd_ty (Some ctx.span) ctx.decls_ctx ty)
  | AIgnoredMutBorrow _ ->
      (* Can happen in case of nested borrows, ignoring for now *)
      [%craise] ctx.span "Unimplemented"
  | AEndedMutBorrow _
  | AEndedIgnoredMutBorrow _
  | AEndedSharedBorrow
  | AProjSharedBorrow _ -> [%craise] ctx.span "Unreachable"

and aproj_to_given_back_ty_aux (aproj : V.aproj) (ty : T.ty) (ctx : bs_ctx) :
    ty option =
  match aproj with
  | V.AProjBorrows { proj = _; loans } ->
      [%sanity_check] ctx.span (loans = []);
      Some (translate_fwd_ty (Some ctx.span) ctx.decls_ctx ty)
  | V.AEndedProjLoans _ | AEndedProjBorrows _ | AEmpty | AProjLoans _ ->
      [%craise] ctx.span "Unreachable"

let tavalue_to_given_back_ty (ctx : bs_ctx) (abs_regions : T.RegionId.Set.t)
    (abs_level : abs_level) (v : V.tavalue) : ty option =
  [%cassert] ctx.span (abs_level = 0) "Unimplemented";
  (* Check if the value was generated from a borrow projector: if yes, and if
     it contains mutable borrows we generate a given back pattern (because
     upon ending the borrow the abstraction gave back a value).
     Otherwise we ignore it. *)
  match
    compute_tavalue_proj_kind ctx.span ctx.type_ctx.type_infos abs_regions
      abs_level 0 v
  with
  | BorrowProj BMut ->
      tavalue_to_given_back_ty_aux ctx abs_regions abs_level 0 ~filter:true v
  | BorrowProj BShared | LoanProj _ | UnknownProj ->
      (* If it is a loan proj we ignore it. If it is an unknown projection,
         it means the value doesn't contain loans nor borrows, so nothing
         is given back: we can ignore it as well. *)
      None

(** Compute the type of a region abstraction, seen as a continuation.

    Remark: there shouldn't be any ended loans in this continuation. Also note
    that we flatten the values. *)
let abs_to_input_output_tys (ctx : bs_ctx) (abs : V.abs) : ty list * ty list =
  (* TODO: properly handle the sub-abstractions *)
  let abs_level = 0 in
  let inputs =
    List.filter_map
      (tavalue_to_consumed_ty ctx abs.regions.owned abs_level)
      abs.avalues
  in

  let outputs =
    List.filter_map
      (tavalue_to_given_back_ty ctx abs.regions.owned abs_level)
      abs.avalues
  in

  [%ldebug
    "- abs:\n" ^ abs_to_string ctx abs ^ "\n- inputs:\n"
    ^ String.concat "\n" (List.map (pure_ty_to_string ctx) inputs)
    ^ "\n- outputs:\n"
    ^ String.concat "\n" (List.map (pure_ty_to_string ctx) outputs)];
  (inputs, outputs)

(** Compute the type of a region abstraction, seen as a continuation.

    The result is [None] if the type is actually [unit].

    Remark: there shouldn't be any ended loans in this continuation. Also note
    that we flatten the values.

    TODO: the result is incorrect if the loans have been partially ended (in
    particular, inside ADTs). *)
let abs_to_ty (ctx : bs_ctx) (abs : V.abs) : ty option =
  let inputs, outputs = abs_to_input_output_tys ctx abs in
  if inputs = [] && outputs = [] then None
  else Some (mk_arrows inputs (mk_simpl_tuple_ty outputs))

let compute_tevalue_proj_kind (span : Meta.span) (type_infos : type_infos)
    (abs_regions : T.RegionId.Set.t) (abs_level : abs_level)
    (current_level : abs_level) (ev : V.tevalue) : proj_kind =
  let has_borrows = ref false in
  let has_mut_borrows = ref false in
  let has_loans = ref false in
  let has_mut_loans = ref false in

  let set_has_loans level = if level = abs_level then has_loans := true in
  let set_has_borrows level = if level = abs_level then has_borrows := true in
  let set_has_mut_loans level =
    if level = abs_level then (
      has_loans := true;
      has_mut_loans := true)
  in
  let set_has_mut_borrows level =
    if level = abs_level then (
      has_borrows := true;
      has_mut_borrows := true)
  in

  let keep_region (r : T.region) =
    match r with
    | T.RVar (Free rid) -> T.RegionId.Set.mem rid abs_regions
    | _ -> false
  in
  let ty_has_mut_region =
    TypesUtils.ty_has_mut_borrow_for_region_in_pred type_infos keep_region
  in
  let visitor =
    object (self)
      inherit [_] InterpBorrowsCore.iter_tavalue_with_levels as super
      method incr_level (level, ty) = (level + 1, ty)
      method! visit_ELet _ _ = [%internal_error] span

      method! visit_tevalue (level, _) ev =
        (* Remember the type of the current value *)
        super#visit_tevalue (level, ev.ty) ev

      method! visit_adt_evalue (level, ty) av =
        if ty_has_mut_region ty then
          if
            (* TODO: problem with nested borrows *)
            av.borrow_proj
          then set_has_mut_borrows level
          else set_has_mut_loans level;
        super#visit_adt_evalue (level, ty) av

      method! visit_ELoan (level, ty) lc =
        set_has_loans level;
        begin
          match lc with
          | EIgnoredMutLoan _ | EEndedIgnoredMutLoan _ -> ()
          | EMutLoan _ | EEndedMutLoan _ -> set_has_mut_loans level
        end;
        (* Continue exploring as a sanity check: we want to make sure we don't find borrows *)
        super#visit_ELoan (level, ty) lc

      method! visit_EBorrow (level, ty) bc =
        set_has_borrows level;
        begin
          match bc with
          | EIgnoredMutBorrow _ | EEndedIgnoredMutBorrow _ -> ()
          | EMutBorrow _ | EEndedMutBorrow _ -> set_has_mut_borrows level
        end;
        (* Continue exploring as a sanity check: we want to make sure we don't find loans *)
        super#visit_EBorrow (level, ty) bc

      method! visit_EFVar (level, ty) _ =
        if ty_has_mut_region ty then (
          (* It may seem counterintuitive, but we consider the free variables
             as a **loan** (because it binds an input loan). *)
          set_has_loans level;
          set_has_mut_loans level)

      method! visit_EValue (level, _ty) _ _ =
        (* An EValue is essentially an ended loan: it is a concrete value
           consumed by the backward function. We treat it as a mutable loan
           so that the containing ADT is not classified as UnknownProj. *)
        set_has_mut_loans level

      method! visit_ESymbolic (level, ty) pm eproj =
        [%sanity_check] span (pm = PNone);
        match eproj with
        | V.EEndedProjLoans _ ->
            set_has_loans level;
            (* We need to check wether the projected loans are mutable or not *)
            if ty_has_mut_region ty then set_has_mut_loans level;
            (* Continue exploring (same reasons as above) *)
            super#visit_ESymbolic (level, ty) pm eproj
        | EProjLoans _ ->
            set_has_loans level;
            (* We need to check wether the projected loans are mutable or not *)
            if ty_has_mut_region ty then set_has_mut_loans level;
            (* Continue exploring (same reasons as above) *)
            super#visit_ESymbolic (level, ty) pm eproj
        | EEndedProjBorrows _ ->
            set_has_borrows level;
            (* We need to check wether the projected borrows are mutable or not *)
            if ty_has_mut_region ty then set_has_mut_borrows level;
            (* Continue exploring (same reasons as above) *)
            super#visit_ESymbolic (level, ty) pm eproj
        | EProjBorrows _ ->
            set_has_borrows level;
            (* We need to check wether the projected loans are mutable or not *)
            if ty_has_mut_region ty then set_has_mut_borrows level;
            (* Continue exploring (same reasons as above) *)
            super#visit_ESymbolic (level, ty) pm eproj
        | EEmpty ->
            (* Continue exploring (same reasons as above) *)
            super#visit_ESymbolic (level, ty) pm eproj

      method! visit_EMutBorrowInput (level, ty) x =
        let r, _, _ = TypesUtils.ty_get_ref ty in
        if keep_region r then (
          set_has_loans level;
          set_has_mut_loans level);
        self#visit_tevalue (level, ty) x
    end
  in
  visitor#visit_tevalue (current_level, ev.ty) ev;
  [%cassert] span ((not !has_borrows) || not !has_loans) "Unreachable";
  let to_borrow_kind b = if b then BMut else BShared in
  if !has_borrows then BorrowProj (to_borrow_kind !has_mut_borrows)
  else if !has_loans then LoanProj (to_borrow_kind !has_mut_loans)
  else UnknownProj

let abs_fvar_id_to_tpat (ctx : bs_ctx)
    (fvar_to_texpr : texpr V.AbsFVarId.Map.t ref) (rids : T.RegionId.Set.t)
    ~(filter : bool) (fid : V.abs_fvar_id) (ty : T.ty) : bs_ctx * tpat option =
  let type_infos = ctx.type_ctx.type_infos in
  let keep_region (r : T.region) =
    match r with
    | T.RVar (Free rid) -> T.RegionId.Set.mem rid rids
    | _ -> false
  in
  if TypesUtils.ty_has_mut_borrow_for_region_in_pred type_infos keep_region ty
  then (
    let ctx, fvar = fresh_var_llbc_ty None ty ctx in
    fvar_to_texpr :=
      V.AbsFVarId.Map.add fid (mk_texpr_from_fvar fvar) !fvar_to_texpr;
    (ctx, Some (mk_tpat_from_fvar None fvar)))
  else
    let pat =
      if filter then begin
        None
      end
      else begin
        fvar_to_texpr := V.AbsFVarId.Map.add fid mk_unit_texpr !fvar_to_texpr;
        let ty = ctx_translate_fwd_ty ctx ty in
        Some (mk_ignored_pat ty)
      end
    in
    (ctx, pat)

(** Map inputs (i.e., (symbolic) loans) or ouputs (i.e., (symbolic) borrows) to
    free variables. We use this in order to control the order in which we bind
    the inputs (it is fixed by the avalues which act as a signature of the
    function). *)
type bound_borrows_loans = {
  concrete : texpr V.BorrowId.Map.t;
  symbolic : texpr NormSymbProjMap.t;
}

let empty_bound_borrows_loans : bound_borrows_loans =
  { concrete = V.BorrowId.Map.empty; symbolic = NormSymbProjMap.empty }

let bound_borrows_loans_to_string (ctx : bs_ctx) (bound : bound_borrows_loans) :
    string =
  let { concrete; symbolic } = bound in
  "{\n  concrete: "
  ^ V.BorrowId.Map.to_string (Some "    ") (texpr_to_string ctx) concrete
  ^ "\n  symbolic: "
  ^ NormSymbProjMap.to_string (Some "    ") (texpr_to_string ctx) symbolic
  ^ "\n}"

(** We need [fvar_to_texpr] because variables may be bound inside intermediate
    let-bindings inside the region abstraction continuation, so we need to
    remember by which pure expression we translated the abstraction free
    variable, in case it is used later. In case we ignore a free variable (we
    want to eliminate it) we register it in [fvar_to_none] for sanity purposes.
*)
let eoutput_to_pat (ctx : bs_ctx) (fvar_to_texpr : texpr V.AbsFVarId.Map.t ref)
    (rids : T.RegionId.Set.t) (abs_level : abs_level) (output : V.tevalue) :
    bs_ctx * bound_borrows_loans * tpat =
  [%cassert] ctx.span (abs_level = 0) "Unimplemented";
  let span = ctx.span in
  let type_infos = ctx.type_ctx.type_infos in
  let concrete = ref V.BorrowId.Map.empty in
  let symbolic = ref NormSymbProjMap.empty in
  let keep_region (r : T.region) =
    match r with
    | T.RVar (Free rid) -> T.RegionId.Set.mem rid rids
    | _ -> false
  in
  let fresh_fvar ctx (ty : T.ty) : bs_ctx * texpr * tpat =
    let ctx, fvar = fresh_var_llbc_ty None ty ctx in
    (ctx, mk_texpr_from_fvar fvar, mk_tpat_from_fvar None fvar)
  in
  let add_symbolic ctx (sv_id : SymbolicValueId.id) (proj_ty : T.ty) :
      bs_ctx * tpat =
    let ctx, e, pat = fresh_fvar ctx proj_ty in
    let norm_proj_ty = InterpBorrowsCore.normalize_proj_ty rids proj_ty in
    symbolic := NormSymbProjMap.add { sv_id; norm_proj_ty } e !symbolic;
    (ctx, pat)
  in
  let add_concrete ctx (bid : V.BorrowId.id) (ty : T.ty) : bs_ctx * tpat =
    let ctx, e, pat = fresh_fvar ctx ty in
    concrete := V.BorrowId.Map.add bid e !concrete;
    (ctx, pat)
  in
  let rec to_pat ~(filter : bool) (current_level : abs_level) (ctx : bs_ctx)
      (output : V.tevalue) : bs_ctx * tpat option =
    match output.value with
    | V.ELet _
    | V.EJoinMarkers _
    | V.EBVar _
    | V.EApp _
    | V.EBottom
    | V.EValue _
    | V.ELoan _
    | V.EMutBorrowInput _ -> [%internal_error] span
    | V.EFVar afid ->
        abs_fvar_id_to_tpat ctx fvar_to_texpr rids ~filter afid output.ty
    | V.EBorrow bc -> (
        match bc with
        | V.EIgnoredMutBorrow _
        | V.EEndedMutBorrow _
        | V.EEndedIgnoredMutBorrow _ -> [%internal_error] span
        | V.EMutBorrow (pm, bid, child) ->
            [%sanity_check] span (pm = PNone);
            [%sanity_check] span (ValuesUtils.is_eignored child.value);
            let ctx, e = add_concrete ctx bid output.ty in
            (ctx, Some e))
    | V.ESymbolic (pm, proj) ->
        [%sanity_check] span (pm = PNone);
        begin
          match proj with
          | V.EProjLoans _
          | V.EEndedProjLoans _
          | V.EEndedProjBorrows _
          | V.EEmpty -> [%internal_error] span
          | V.EProjBorrows { proj; loans } ->
              [%sanity_check] span (loans = []);
              (* Case disjunction depending on whether we actually need to give back
                 something or not *)
              if
                TypesUtils.ty_has_mut_borrow_for_region_in_pred type_infos
                  keep_region output.ty
              then
                let ctx, e = add_symbolic ctx proj.sv_id output.ty in
                (ctx, Some e)
              else
                let pat =
                  if filter then None
                  else
                    let ty = ctx_translate_fwd_ty ctx output.ty in
                    Some (mk_ignored_pat ty)
                in
                (ctx, pat)
        end
    | V.EAdt { borrow_proj = _; variant_id; fields } -> begin
        let ctx, out =
          gtranslate_adt_fields ~project_borrows:true (tevalue_to_string ctx)
            (tpat_to_string ctx)
            (fun ~filter ctx v ->
              let ctx, pat = to_pat ~filter current_level ctx v in
              ( ctx,
                match pat with
                | None -> None
                | Some x -> Some ((), x) ))
            (compute_tevalue_proj_kind ctx.span ctx.type_ctx.type_infos rids
               abs_level current_level)
            (fun fields ->
              let ty =
                translate_fwd_ty (Some ctx.span) ctx.decls_ctx output.ty
              in
              mk_adt_pat ty variant_id fields)
            mk_simpl_tuple_pat ~filter ctx output output.ty fields
        in
        (ctx, Option.map snd out)
      end
    | V.EIgnored _ ->
        let ty = output.ty in
        if
          filter
          && not
               (TypesUtils.ty_has_mut_borrow_for_region_in_pred type_infos
                  keep_region ty)
        then (ctx, None)
        else
          let ty = ctx_translate_fwd_ty ctx ty in
          (ctx, Some (mk_ignored_pat ty))
  in
  let ctx, pat = to_pat ~filter:true 0 ctx output in
  let pat =
    match pat with
    | None -> mk_ignored_pat mk_unit_ty
    | Some pat -> pat
  in
  let bound = { concrete = !concrete; symbolic = !symbolic } in
  (ctx, bound, pat)

let tepat_to_tpat (ctx : bs_ctx) (fvar_to_texpr : texpr V.AbsFVarId.Map.t ref)
    (rids : T.RegionId.Set.t) (pat : V.tepat) : bs_ctx * tpat =
  let span = ctx.span in
  let type_infos = ctx.type_ctx.type_infos in
  let keep_region (r : T.region) =
    match r with
    | T.RVar (Free rid) -> T.RegionId.Set.mem rid rids
    | _ -> false
  in
  let rec to_pat ~(filter : bool) (ctx : bs_ctx) (pat : V.tepat) :
      bs_ctx * tpat option =
    match pat.pat with
    | V.POpen fid ->
        abs_fvar_id_to_tpat ctx fvar_to_texpr rids ~filter fid pat.ty
    | V.PBound ->
        (* Binders should have been opened *)
        [%internal_error] span
    | V.PAdt (variant_id, fields) -> begin
        (* Note that the [project_borrows] field doesn't matter *)
        let project_borrows = true in
        let compute_proj_kind (pat : V.tepat) : proj_kind =
          if
            TypesUtils.ty_has_mut_borrow_for_region_in_pred type_infos
              keep_region pat.ty
          then BorrowProj BMut
          else BorrowProj BShared
        in
        let ctx, out =
          gtranslate_adt_fields ~project_borrows (tepat_to_string ctx)
            (tpat_to_string ctx)
            (fun ~filter ctx v ->
              let ctx, pat = to_pat ~filter ctx v in
              (ctx, Option.map (fun x -> ((), x)) pat))
            compute_proj_kind
            (fun fields ->
              let ty = translate_fwd_ty (Some ctx.span) ctx.decls_ctx pat.ty in
              mk_adt_pat ty variant_id fields)
            mk_simpl_tuple_pat ~filter ctx pat pat.ty fields
        in
        let out = Option.map snd out in
        (ctx, out)
      end
    | V.PIgnored ->
        let ty = pat.ty in
        if
          filter
          && not
               (TypesUtils.ty_has_mut_borrow_for_region_in_pred type_infos
                  keep_region ty)
        then (ctx, None)
        else
          let ty = ctx_translate_fwd_ty ctx ty in
          (ctx, Some (mk_ignored_pat ty))
  in
  let ctx, pat = to_pat ~filter:true ctx pat in
  let pat =
    match pat with
    | Some pat -> pat
    | None -> mk_ignored_pat mk_unit_ty
  in
  (ctx, pat)

(** The boolean is [can_fail] (i.e., does the expression live in the error
    monad?).

    [bound_inputs]: see the doc of [bound_inputs] *)
let einput_to_texpr (ctx : bs_ctx) (ectx : C.eval_ctx) (rids : T.RegionId.Set.t)
    (bound_inputs : bound_borrows_loans)
    (fvar_to_texpr : texpr V.AbsFVarId.Map.t ref) (abs_level : abs_level)
    (input : V.tevalue) : bs_ctx * bool * texpr =
  [%ldebug
    "- rids: "
    ^ T.RegionId.Set.to_string None rids
    ^ "\n- input: "
    ^ tevalue_to_string ctx input];
  [%cassert] ctx.span (abs_level = 0) "Unimplemented";
  let span = ctx.span in
  let rec to_texpr_aux ~(filter : bool) (rids : T.RegionId.Set.t)
      (current_level : abs_level) (ctx : bs_ctx) (input : V.tevalue) :
      bs_ctx * bool * texpr option =
    [%ldebug
      "- input: "
      ^ tevalue_to_string ctx input
      ^ "\n- fvar_to_texpr:\n"
      ^ V.AbsFVarId.Map.to_string (Some "  ") (texpr_to_string ctx)
          !fvar_to_texpr];
    match input.value with
    | V.ELet (rids', pat, bound, next) ->
        [%ldebug "let"];
        (* Open the binders *)
        let pat, next = ValuesUtils.open_binder span pat next in
        [%ldebug
          "- input after opening the binders: "
          ^ tevalue_to_string ctx
              { input with value = ELet (rids', pat, bound, next) }];
        [%ldebug
          "- pat: " ^ tepat_to_string ctx pat ^ "\n- pat.ty: "
          ^ InterpUtils.ty_to_string ectx pat.ty];
        (* Translate *)
        let ctx, bound_can_fail, bound = to_texpr ~filter rids' ctx bound in
        let llbc_pat = pat in
        let ctx, pat = tepat_to_tpat ctx fvar_to_texpr rids' pat in
        [%ldebug
          "Let-binding:\n- pat: " ^ tpat_to_string ctx pat ^ "\n- LLBC pat.ty: "
          ^ InterpUtils.ty_to_string ectx llbc_pat.ty
          ^ "\n- bound: "
          ^ Print.option_to_string (texpr_to_string ctx) bound];
        let ctx, next_can_fail, next = to_texpr ~filter rids ctx next in
        (* Create the let binding *)
        let bound =
          match bound with
          | None -> mk_unit_texpr
          | Some bound -> bound
        in
        let next =
          match next with
          | None -> mk_unit_texpr
          | Some next -> next
        in
        let next =
          if bound_can_fail && not next_can_fail then
            mk_result_ok_texpr span next
          else next
        in
        let e = [%add_loc] mk_closed_checked_let ctx false pat bound next in
        let can_fail = bound_can_fail || next_can_fail in
        (ctx, can_fail, Some e)
    | V.EJoinMarkers _ | V.EBVar _ ->
        (* - joins should have been eliminated
           - there should be no bound variables *)
        [%internal_error] span
    | V.EFVar fvid ->
        [%ldebug
          "- input is fvar: " ^ V.AbsFVarId.to_string fvid
          ^ "\n- fvar_to_texpr:\n"
          ^ V.AbsFVarId.Map.to_string (Some "  ") (texpr_to_string ctx)
              !fvar_to_texpr];
        let e = V.AbsFVarId.Map.find_opt fvid !fvar_to_texpr in
        (ctx, false, e)
    | V.EApp (f, args) ->
        [%ldebug "app"];
        let update_args ctx args =
          let ctx, args =
            List.fold_left_map
              (fun ctx arg ->
                let ctx, can_fail, arg = to_texpr ~filter rids ctx arg in
                [%sanity_check] span (not can_fail);
                (ctx, arg))
              ctx args
          in
          let args = List.filter_map (fun x -> x) args in
          (ctx, args)
        in
        let ctx, args =
          List.fold_left_map (fun ctx args -> update_args ctx args) ctx args
        in
        [%ldebug
          "- app:\n"
          ^ tevalue_to_string ctx input
          ^ "\n\n- args:\n"
          ^ Print.list_to_string ~sep:"\n"
              (fun args -> Print.list_to_string (texpr_to_string ctx) args)
              args];
        begin
          match f with
          | V.EOutputAbs _ | V.EInputAbs _ ->
              (* Those shouln't get merged *)
              [%internal_error] span
          | V.EFunCall abs_id | V.ELoop (abs_id, _) | V.EJoin abs_id ->
              (* Lookup the variable introduced for the backward function *)
              let e, can_fail =
                match V.AbsId.Map.find_opt abs_id ctx.abs_id_to_info with
                | None ->
                    (* No variable was introduced: it means the abstraction should
                       be ignored (it consumes unit and outputs unit). *)
                    [%sanity_check] span (args = [ [] ]);
                    [%sanity_check] span
                      (V.AbsId.Set.mem abs_id ctx.ignored_abs_ids);
                    (None, false)
                | Some { fvar; can_fail } ->
                    (* The list of lists of arguments is only used in the presence
                     of nested borrows with function applications *)
                    let args =
                      match (f, args) with
                      | (V.ELoop _ | V.EJoin _), [ args ] -> args
                      | V.EFunCall _, _ ->
                          (* Filter out empty argument groups (e.g., from
                             unit return values that got filtered away) *)
                          let args =
                            List.filter_map
                              (fun args ->
                                match args with
                                | [] -> None
                                | _ -> Some (mk_simpl_tuple_texpr span args))
                              args
                          in
                          args
                      | _ -> [%internal_error] span
                    in
                    (Some ([%add_loc] mk_apps span fvar args), can_fail)
              in
              (ctx, can_fail, e)
        end
    | V.EAdt { borrow_proj = _; variant_id; fields } -> begin
        [%ldebug "adt"];
        let ctx, out =
          gtranslate_adt_fields ~project_borrows:false (tevalue_to_string ctx)
            (texpr_to_string ctx)
            (fun ~filter ctx v ->
              let ctx, can_fail, e = to_texpr ~filter rids ctx v in
              (ctx, Option.map (fun x -> (can_fail, x)) e))
            (compute_tevalue_proj_kind ctx.span ctx.type_ctx.type_infos rids
               abs_level current_level)
            (fun fields ->
              let ty =
                translate_fwd_ty (Some ctx.span) ctx.decls_ctx input.ty
              in
              mk_adt_texpr span ty variant_id fields)
            (mk_simpl_tuple_texpr span)
            ~filter ctx input input.ty fields
        in
        match out with
        | None -> (ctx, false, None)
        | Some (can_fail, x) ->
            [%sanity_check] span (List.for_all (fun x -> not x) can_fail);
            (ctx, false, Some x)
      end
    | V.ELoan lc -> (
        [%ldebug "loan"];
        match lc with
        | V.EMutLoan (pm, lid, child) ->
            [%sanity_check] span (pm = PNone);
            [%sanity_check] span (ValuesUtils.is_eignored child.value);
            let e =
              [%unwrap_with_span] span
                (V.BorrowId.Map.find_opt lid bound_inputs.concrete)
                "Unexpected"
            in
            (ctx, false, Some e)
        | V.EEndedMutLoan { child; given_back; given_back_meta } ->
            [%sanity_check] ctx.span (ValuesUtils.is_eignored child.value);
            [%sanity_check] ctx.span (ValuesUtils.is_eignored given_back.value);
            (* Return the meta-value *)
            let out = Some (tvalue_to_texpr ctx ectx given_back_meta) in
            (ctx, false, out)
        | V.EIgnoredMutLoan _ | V.EEndedIgnoredMutLoan _ ->
            [%internal_error] span)
    | V.EBorrow _ -> [%internal_error] span
    | V.ESymbolic (pm, proj) -> begin
        [%ldebug "symbolic"];
        [%sanity_check] span (pm = PNone);
        match proj with
        | V.EProjLoans { proj = { sv_id; proj_ty }; consumed; borrows } ->
            [%sanity_check] span (consumed = []);
            [%sanity_check] span (borrows = []);
            let norm_proj_ty =
              InterpBorrowsCore.normalize_proj_ty rids proj_ty
            in
            let e =
              NormSymbProjMap.find_opt { sv_id; norm_proj_ty }
                bound_inputs.symbolic
            in
            (ctx, false, e)
        | V.EEndedProjLoans { proj = msv; consumed = []; borrows = [] } ->
            (* The symbolic value was left unchanged.

               We're using the projection type as the type of the symbolic value -
               it doesn't really matter. *)
            let msv : V.symbolic_value = { sv_id = msv; sv_ty = input.ty } in
            let out = Some (symbolic_value_to_texpr ctx msv) in
            (ctx, false, out)
        | V.EEndedProjLoans
            { proj = _; consumed = [ (mnv, child_aproj) ]; borrows = [] } ->
            [%sanity_check] ctx.span (child_aproj = EEmpty);
            (* TODO: check that the updated symbolic values covers all the cases
               (part of the symbolic value might have been updated, and the rest
               left unchanged) - it might happen with nested borrows (see the documentation
               of [AProjLoans]). For now we check that there are no nested borrows
               to make sure we have to update this part of the code once we add support
               for nested borrows.
            *)
            [%sanity_check] ctx.span
              (not
                 (TypesUtils.ty_has_nested_borrows (Some ctx.span)
                    ctx.type_ctx.type_infos input.ty));
            (* The symbolic value was updated.

               We're using the projection type as the type of the symbolic value -
               it doesn't really matter. *)
            let mnv : V.symbolic_value =
              { sv_id = mnv.sv_id; sv_ty = input.ty }
            in
            let out = Some (symbolic_value_to_texpr ctx mnv) in
            (ctx, false, out)
        | V.EEndedProjLoans _ ->
            (* The symbolic value was updated, and the given back values come from several
             abstractions *)
            [%craise] ctx.span "Unimplemented"
        | V.EProjBorrows _ | V.EEndedProjBorrows _ | V.EEmpty ->
            [%internal_error] span
      end
    | V.EValue (env, mv) ->
        [%ldebug "value: filter = " ^ string_of_bool filter];
        let e = tvalue_to_texpr ctx { ectx with env } mv in
        (ctx, false, Some e)
    | V.EIgnored None ->
        [%ldebug "ignored (None)"];
        (ctx, false, None)
    | V.EIgnored (Some (env, mv)) ->
        [%ldebug "ignored (Some): filter = " ^ string_of_bool filter];
        if filter then (ctx, false, None)
        else
          let e = tvalue_to_texpr ctx { ectx with env } mv in
          (ctx, false, Some e)
    | V.EBottom ->
        [%ldebug "bottom"];
        [%internal_error] span
    | V.EMutBorrowInput inner ->
        let r, _, _ = TypesUtils.ty_as_ref input.ty in
        let keep_region (r : T.region) =
          match r with
          | T.RVar (Free rid) -> T.RegionId.Set.mem rid rids
          | _ -> false
        in
        let filter = if keep_region r then false else filter in
        to_texpr ~filter rids ctx inner
  and to_texpr ~(filter : bool) (rids : T.RegionId.Set.t) (ctx : bs_ctx)
      (input : V.tevalue) : bs_ctx * bool * texpr option =
    let e = to_texpr_aux ~filter rids 0 ctx input in
    [%ldebug
      let _, _, e = e in
      "- rids: "
      ^ T.RegionId.Set.to_string None rids
      ^ "\n- input:\n"
      ^ tevalue_to_string ctx input
      ^ "\n\n- resulting expr:\n"
      ^ Print.option_to_string (texpr_to_string ctx) e];
    e
  in
  let ctx, can_fail, e = to_texpr ~filter:true rids ctx input in
  let e =
    match e with
    | None -> mk_unit_texpr
    | Some e -> e
  in
  (ctx, can_fail, e)

(** Go through some avalues to register the inputs (i.e., the borrows).

    We need this to fix the order of the *inputs*: the *avalues* act as a
    function signature, while the order of the borrows inside the *evalues* is
    arbitrary. *)
let register_inputs (ctx : bs_ctx) (rids : T.RegionId.Set.t)
    (avl : V.tavalue list) : bound_borrows_loans * fvar list =
  let span = ctx.span in
  let type_infos = ctx.type_ctx.type_infos in
  let concrete = ref V.BorrowId.Map.empty in
  let symbolic = ref NormSymbProjMap.empty in
  let ctx = ref ctx in
  let fvars = ref [] in
  let keep_region (r : T.region) =
    match r with
    | T.RVar (Free rid) -> T.RegionId.Set.mem rid rids
    | _ -> false
  in
  let fresh_fvar (ty : T.ty) : texpr =
    let ctx', fvar = fresh_var_llbc_ty None ty !ctx in
    ctx := ctx';
    fvars := fvar :: !fvars;
    mk_texpr_from_fvar fvar
  in
  let add_symbolic (sv_id : SymbolicValueId.id) (proj_ty : T.ty) : unit =
    let e = fresh_fvar proj_ty in
    let norm_proj_ty = InterpBorrowsCore.normalize_proj_ty rids proj_ty in
    symbolic := NormSymbProjMap.add { sv_id; norm_proj_ty } e !symbolic
  in
  let add_concrete (bid : V.BorrowId.id) (ty : T.ty) : unit =
    let e = fresh_fvar ty in
    concrete := V.BorrowId.Map.add bid e !concrete
  in
  let rec register (av : V.tavalue) : unit =
    match av.value with
    | V.AAdt { borrow_proj = _; variant_id = _; fields } ->
        List.iter register fields
    | V.ALoan lc -> (
        match lc with
        | V.AMutLoan (pm, bid, child) ->
            [%sanity_check] span (pm = PNone);
            add_concrete bid av.ty;
            register child
        | V.ASharedLoan (pm, _, _, child) ->
            [%sanity_check] span (pm = PNone);
            register child
        | V.AIgnoredMutLoan (_, child) | V.AIgnoredSharedLoan child ->
            register child
        | V.AEndedMutLoan _ | V.AEndedSharedLoan _ | V.AEndedIgnoredMutLoan _ ->
            [%internal_error] span)
    | V.ABorrow bc -> (
        match bc with
        | V.AMutBorrow (_, _, child) | V.AIgnoredMutBorrow (_, child) ->
            register child
        | V.ASharedBorrow (pm, _, _) ->
            (* Nothing to do *)
            [%sanity_check] span (pm = PNone)
        | V.AEndedMutBorrow _ | V.AEndedSharedBorrow -> [%internal_error] span
        | V.AEndedIgnoredMutBorrow _ -> [%craise] span "Not implemented yet"
        | V.AProjSharedBorrow _ -> ())
    | V.ASymbolic (pm, proj) -> (
        [%sanity_check] span (pm = PNone);
        match proj with
        | V.AProjLoans { proj = { sv_id; proj_ty }; consumed; borrows } ->
            [%sanity_check] span (consumed = []);
            [%sanity_check] span (borrows = []);
            if
              TypesUtils.ty_has_mut_borrow_for_region_in_pred type_infos
                keep_region av.ty
            then add_symbolic sv_id proj_ty
            else ()
        | V.AProjBorrows { proj = _; loans } ->
            [%sanity_check] span (loans = []);
            ()
        | V.AEndedProjLoans _ | V.AEndedProjBorrows _ | V.AEmpty ->
            [%internal_error] span)
    | V.AIgnored _ -> ()
  in
  List.iter register avl;
  ({ concrete = !concrete; symbolic = !symbolic }, List.rev !fvars)

(** Given some bound outputs, go through some avalues to sort those outputs in
    the order given by the avalues.

    The goal is similar to [register_inputs]: we want to use avalues as a
    function signature.

    TODO: we shouldn't do this. The avalues should be independent from the
    function signature. The signature should be contained within the abstraction
    continuation. *)
let register_outputs (ctx : bs_ctx) (bound_outputs : bound_borrows_loans)
    (rids : T.RegionId.Set.t) (avl : V.tavalue list) : texpr list =
  [%ltrace
    "- bound_outputs:\n"
    ^ bound_borrows_loans_to_string ctx bound_outputs
    ^ "- avl:\n"
    ^ Print.list_to_string (SymbolicToPureCore.tavalue_to_string ctx) avl];
  let span = ctx.span in
  let type_infos = ctx.type_ctx.type_infos in
  let outputs = ref [] in
  let keep_region (r : T.region) =
    match r with
    | T.RVar (Free rid) -> T.RegionId.Set.mem rid rids
    | _ -> false
  in
  let add_symbolic (sv_id : SymbolicValueId.id) (proj_ty : T.ty) : unit =
    let norm_proj_ty = InterpBorrowsCore.normalize_proj_ty rids proj_ty in
    let e =
      NormSymbProjMap.find { sv_id; norm_proj_ty } bound_outputs.symbolic
    in
    outputs := e :: !outputs
  in
  let add_concrete (bid : V.BorrowId.id) : unit =
    let e = V.BorrowId.Map.find bid bound_outputs.concrete in
    outputs := e :: !outputs
  in
  let rec register (av : V.tavalue) : unit =
    match av.value with
    | V.AAdt { borrow_proj = _; variant_id = _; fields } ->
        List.iter register fields
    | V.ALoan lc -> (
        match lc with
        | V.AMutLoan (pm, _, child) | V.ASharedLoan (pm, _, _, child) ->
            [%sanity_check] span (pm = PNone);
            register child
        | V.AIgnoredMutLoan (_, child) | V.AIgnoredSharedLoan child ->
            register child
        | V.AEndedMutLoan _ | V.AEndedSharedLoan _ | V.AEndedIgnoredMutLoan _ ->
            [%internal_error] span)
    | V.ABorrow bc -> (
        match bc with
        | V.AMutBorrow (pm, bid, child) ->
            [%sanity_check] span (pm = PNone);
            add_concrete bid;
            register child
        | V.AIgnoredMutBorrow (_, child) -> register child
        | V.ASharedBorrow (pm, _, _) ->
            (* Nothing to do *)
            [%sanity_check] span (pm = PNone)
        | V.AEndedMutBorrow _ | V.AEndedSharedBorrow -> [%internal_error] span
        | V.AEndedIgnoredMutBorrow _ -> [%craise] span "Not implemented yet"
        | V.AProjSharedBorrow _ -> ())
    | V.ASymbolic (pm, proj) -> (
        [%sanity_check] span (pm = PNone);
        match proj with
        | V.AProjLoans { proj = _; consumed; borrows } ->
            [%sanity_check] span (consumed = []);
            [%sanity_check] span (borrows = [])
        | V.AProjBorrows { proj = { sv_id; proj_ty }; loans } ->
            [%sanity_check] span (loans = []);
            if
              TypesUtils.ty_has_mut_borrow_for_region_in_pred type_infos
                keep_region av.ty
            then add_symbolic sv_id proj_ty
            else ()
        | V.AEndedProjLoans _ | V.AEndedProjBorrows _ | V.AEmpty ->
            [%internal_error] span)
    | V.AIgnored _ -> ()
  in
  List.iter register avl;
  List.rev !outputs

let abs_cont_to_texpr_aux (ctx : bs_ctx) (ectx : C.eval_ctx) (abs : V.abs)
    (output : V.tevalue) (input : V.tevalue) : texpr option =
  let span = ctx.span in
  (* TODO: properly handle the sub-abstractions *)
  let abs_level = 0 in
  (* Go through the *avalues* to introduce free variables for the loans:
     we need to do this to fix the order of the *inputs* (the order given
     by the abstraction expression itself is arbitrary)
  *)
  let bound_inputs, inputs =
    register_inputs ctx abs.regions.owned abs.avalues
  in

  (* Translate the abstraction expression *)
  let fvar_to_texpr = ref V.AbsFVarId.Map.empty in
  let ctx, can_fail, input_e =
    einput_to_texpr ctx ectx abs.regions.owned bound_inputs fvar_to_texpr
      abs_level input
  in
  [%sanity_check] span (not can_fail);
  let ctx, bound_outputs, pat =
    eoutput_to_pat ctx fvar_to_texpr abs.regions.owned abs_level output
  in

  (* Go through the *avalues* to compute the order of the *outputs*. Same remark
     as with the inputs: we do this to fix the order *)
  let outputs =
    register_outputs ctx bound_outputs abs.regions.owned abs.avalues
  in

  if inputs = [] && outputs = [] then None
  else
    (* Put everything together *)
    let output_e = mk_simpl_tuple_texpr span outputs in
    let e =
      mk_closed_checked_let __FILE__ __LINE__ ctx can_fail pat input_e output_e
    in
    let e =
      mk_closed_lambdas span (List.map (mk_tpat_from_fvar None) inputs) e
    in
    Some e

(** Translate the continuation of a region abstraction to a pure continuation.

    Returns [None] if the continuation has type [unit] (i.e., it consumes
    nothing and returns nothing). *)
let translate_abs_to_cont (ctx : bs_ctx) (ectx : C.eval_ctx) (abs : V.abs) :
    texpr option =
  [%ltrace "abs:\n" ^ abs_to_string ctx abs];
  match abs.cont with
  | None -> [%internal_error] ctx.span
  | Some cont -> (
      match (cont.output, cont.input) with
      | Some output, Some input ->
          let abs = abs_cont_to_texpr_aux ctx ectx abs output input in
          [%ltrace
            "translated abs:\n"
            ^ Print.option_to_string (texpr_to_string ctx) abs];
          abs
      | _ -> [%internal_error] ctx.span)

let rec tevalue_to_given_back_aux ~(filter : bool)
    (abs_regions : T.RegionId.Set.t) (abs_level : abs_level)
    (current_level : abs_level) (mp : mplace option) (ev : V.tevalue)
    (ctx : bs_ctx) : bs_ctx * tpat option =
  let (ctx, value) : _ * tpat option =
    match ev.value with
    | EAdt adt_v ->
        adt_evalue_to_given_back_aux ~filter abs_regions abs_level current_level
          ev adt_v ctx
    | ELoan _ ->
        (* The evalue should have been generated by a borrow projector: this case is unreachable *)
        [%craise] ctx.span "Unreachable"
    | EBorrow bc ->
        eborrow_content_to_given_back_aux ~filter abs_level current_level mp bc
          ev.ty ctx
    | ESymbolic (pm, eproj) ->
        [%sanity_check] ctx.span (pm = PNone);
        eproj_to_given_back_aux abs_level current_level mp eproj ev.ty ctx
    | EIgnored _ ->
        (* If we do not filter, we have to create an ignored pattern *)
        if filter then (ctx, None)
        else
          let ty = translate_fwd_ty (Some ctx.span) ctx.decls_ctx ev.ty in
          (ctx, Some (mk_ignored_pat ty))
    | EBottom -> (ctx, None)
    | ELet _
    | EJoinMarkers _
    | EBVar _
    | EFVar _
    | EApp _
    | EValue _
    | EMutBorrowInput _ -> [%internal_error] ctx.span
  in
  (* Sanity checks - Rk.: we do this at every recursive call, which is a bit
   * expansive... *)
  (match value with
  | None -> ()
  | Some value -> type_check_pat ctx value);
  (* Return *)
  (ctx, value)

and adt_evalue_to_given_back_aux ~(filter : bool)
    (abs_regions : T.RegionId.Set.t) (abs_level : abs_level)
    (current_level : abs_level) (av : V.tevalue) (adt_v : V.adt_evalue)
    (ctx : bs_ctx) : bs_ctx * tpat option =
  let ctx, out =
    gtranslate_adt_fields ~project_borrows:true (tevalue_to_string ctx)
      (tpat_to_string ctx)
      (fun ~filter ctx v ->
        let ctx, v =
          tevalue_to_given_back_aux ~filter abs_regions abs_level current_level
            None v ctx
        in
        match v with
        | None -> (ctx, None)
        | Some x -> (ctx, Some ((), x)))
      (compute_tevalue_proj_kind ctx.span ctx.type_ctx.type_infos abs_regions
         abs_level current_level)
      (fun fields ->
        let ty = translate_fwd_ty (Some ctx.span) ctx.decls_ctx av.ty in
        mk_adt_pat ty adt_v.variant_id fields)
      mk_simpl_tuple_pat ~filter ctx av av.ty adt_v.fields
  in
  (ctx, Option.map snd out)

and eborrow_content_to_given_back_aux ~(filter : bool) (_abs_level : abs_level)
    (_current_level : abs_level) (mp : mplace option) (bc : V.eborrow_content)
    (_ty : T.ty) (ctx : bs_ctx) : bs_ctx * tpat option =
  let _ = filter in
  match bc with
  | V.EMutBorrow _ | EIgnoredMutBorrow _ ->
      (* All the borrows should have been ended upon ending the abstraction *)
      [%craise] ctx.span "Unreachable"
  | EEndedMutBorrow (msv, _) ->
      (* Return the meta symbolic-value *)
      let ctx, var = fresh_var_for_symbolic_value msv.given_back ctx in
      let pat = mk_tpat_from_fvar mp var in
      (* Lookup the default value and update the [var_id_to_default] map.
         Note that the default value might be missing, for instance for
         abstractions which were not introduced because of function calls but
         rather because of loops.
      *)
      let ctx =
        match V.BorrowId.Map.find_opt msv.bid ctx.mut_borrow_to_consumed with
        | None -> ctx
        | Some e ->
            {
              ctx with
              var_id_to_default = FVarId.Map.add var.id e ctx.var_id_to_default;
            }
      in
      (* *)
      (ctx, Some pat)
  | EEndedIgnoredMutBorrow _ ->
      (* This happens with nested borrows: we need to dive in *)
      [%craise] ctx.span "Unimplemented"

and eproj_to_given_back_aux (_abs_level : abs_level)
    (_current_level : abs_level) (mp : mplace option) (eproj : V.eproj)
    (ty : T.ty) (ctx : bs_ctx) : bs_ctx * tpat option =
  match eproj with
  | V.EEndedProjLoans _ -> [%craise] ctx.span "Unreachable"
  | EEndedProjBorrows { mvalues = mv; loans } ->
      [%cassert] ctx.span (loans = []) "Unreachable";
      (* Return the meta-value *)
      let ctx, var = fresh_var_for_symbolic_value mv.given_back ctx in
      let pat = mk_tpat_from_fvar mp var in
      (* Register the default value *)
      let ctx =
        (* Using the projection type as the type of the symbolic value - it
           doesn't really matter *)
        let sv : V.symbolic_value = { sv_id = mv.consumed; sv_ty = ty } in
        {
          ctx with
          var_id_to_default =
            FVarId.Map.add var.id
              (symbolic_value_to_texpr ctx sv)
              ctx.var_id_to_default;
        }
      in
      (ctx, Some pat)
  | EEmpty | EProjLoans _ | EProjBorrows _ -> [%craise] ctx.span "Unreachable"

let tevalue_to_given_back (abs_regions : T.RegionId.Set.t)
    (abs_level : abs_level) (mp : mplace option) (v : V.tevalue) (ctx : bs_ctx)
    : bs_ctx * tpat option =
  (* Check if the value was generated from a borrow projector: if yes, and if
     it contains mutable borrows we generate a given back pattern (because
     upon ending the borrow the abstraction gave back a value).
     Otherwise we ignore it. *)
  match
    compute_tevalue_proj_kind ctx.span ctx.type_ctx.type_infos abs_regions
      abs_level 0 v
  with
  | BorrowProj BMut ->
      tevalue_to_given_back_aux abs_regions abs_level 0 mp ~filter:true v ctx
  | BorrowProj BShared | LoanProj _ | UnknownProj ->
      (* If it is a loan proj we ignore it. If it is an unknown projection,
         it means the value doesn't contain loans nor borrows, so nothing
         is given back: we can ignore it as well. *)
      (ctx, None)

let ended_abs_cont_to_texpr_aux (ctx : bs_ctx) (ectx : C.eval_ctx) (abs : V.abs)
    (abs_level : abs_level) (output : V.tevalue) (input : V.tevalue) :
    bs_ctx * bool * tpat * texpr =
  let span = ctx.span in
  [%cassert] span (abs_level = 0) "Unimplemented";
  (* Translate the abstraction expression *)
  let fvar_to_texpr = ref V.AbsFVarId.Map.empty in
  let ctx, can_fail, input_e =
    einput_to_texpr ctx ectx abs.regions.owned empty_bound_borrows_loans
      fvar_to_texpr abs_level input
  in
  [%sanity_check] span (not can_fail);
  let ctx, pat =
    tevalue_to_given_back abs.regions.owned abs_level None output ctx
  in

  let pat =
    match pat with
    | None -> mk_ignored_pat input_e.ty
    | Some pat -> pat
  in
  (ctx, can_fail, pat, input_e)

(** Translate the continuation of an ended region abstraction to a pure
    continuation. *)
let translate_ended_abs_to_texpr (ctx : bs_ctx) (ectx : C.eval_ctx)
    (abs : V.abs) (abs_level : abs_level) : bs_ctx * bool * tpat * texpr =
  [%ltrace "abs:\n" ^ abs_to_string ctx abs];
  match abs.cont with
  | None -> [%internal_error] ctx.span
  | Some cont -> (
      match (cont.output, cont.input) with
      | Some output, Some input ->
          let ctx, can_fail, pat, expr =
            ended_abs_cont_to_texpr_aux ctx ectx abs abs_level output input
          in
          [%ltrace
            "translated abs:\n" ^ tpat_to_string ctx pat ^ " :=\n"
            ^ texpr_to_string ctx expr];
          (ctx, can_fail, pat, expr)
      | _ -> [%internal_error] ctx.span)
