(**************************************************************************)
(*                                                                        *)
(*                                 OCaml                                  *)
(*                                                                        *)
(*                       Pierre Chambart, OCamlPro                        *)
(*           Mark Shinwell and Leo White, Jane Street Europe              *)
(*                                                                        *)
(*   Copyright 2013--2016 OCamlPro SAS                                    *)
(*   Copyright 2014--2016 Jane Street Group LLC                           *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

[@@@ocaml.warning "+a-4-9-30-40-41-42"]

(** Freshening of various identifiers. *)

(** A table used for freshening variables and static exception identifiers. *)
type t
type subst = t

(** The freshening that does nothing.  This is the unique inactive
    freshening. *)
val empty : t

val is_empty : t -> bool

(** Activate the freshening.  Without activation, operations to request
    freshenings have no effect (cf. the documentation below for
    [add_variable]).  As such, the inactive renaming is unique. *)
val activate : t -> t

(** Given the inactive freshening, return the same; otherwise, return an
    empty active freshening. *)
val empty_preserving_activation_state : t -> t

(** [add_variable t var]
    If [t] is active:
      It returns a fresh variable [new_var] and adds [var] -> [new_var]
      to the freshening.
      If a renaming [other_var] -> [var] or [symbol] -> [var] was already
      present in [t], it will also add [other_var] -> [new_var] and
      [symbol] -> [new_var].
    If [t] is inactive, this is the identity.
*)
val add_variable : t -> Variable.t -> Variable.t * t

(** Like [add_variable], but for multiple variables, each freshened
    separately. *)
val add_variables'
   : t
  -> Variable.t list
  -> Variable.t list * t

(** Like [add_variables'], but passes through the second component of the
    input list unchanged. *)
val add_variables
   : t
  -> (Variable.t * 'a) list
  -> (Variable.t * 'a) list * t

(** Like [add_variable], but for mutable variables. *)
val add_mutable_variable : t -> Mutable_variable.t -> Mutable_variable.t * t

(** As for [add_variable], but for static exception identifiers. *)
val add_static_exception : t -> Static_exception.t -> Static_exception.t * t

(** [apply_variable t var] applies the freshening [t] to [var].
    If no renaming is specified in [t] for [var] it is returned unchanged. *)
val apply_variable : t -> Variable.t -> Variable.t

(** As for [apply_variable], but for mutable variables. *)
val apply_mutable_variable : t -> Mutable_variable.t -> Mutable_variable.t

(** As for [apply_variable], but for static exception identifiers. *)
val apply_static_exception : t -> Static_exception.t -> Static_exception.t

(** Replace recursive accesses to the closures in the set through
    [Symbol] by the corresponding [Var]. This is used to recover
    the recursive call when importing code from another compilation unit.

    If the renaming is inactive, this is the identity.
*)
val rewrite_recursive_calls_with_symbols
   : t
  -> Flambda.function_declarations
  -> make_closure_symbol:(Closure_id.t -> Symbol.t)
  -> Flambda.function_declarations

(* CR-soon mshinwell for mshinwell: maybe inaccurate module name, it freshens
   closure IDs as well.  Check use points though *)
module Project_var : sig
  (** A table used for freshening of identifiers in [Project_closure] and
      [Move_within_set_of_closures] ("ids of closures"); and [Project_var]
      ("bound vars of closures") expressions.

      This information is propagated bottom up and populated when inlining a
      function containing a closure declaration.

      For instance,
        [let f x =
           let g y = ... x ... in
           ... g.x ...           (Project_var x)
           ... g 1 ...           (Apply (Project_closure g ...))
           ]

      If f is inlined, g is renamed. The approximation of g will carry this
      table such that later the access to the field x of g and selection of
      g in the closure can be substituted.
   *)
  type t

  (* The freshening that does nothing. *)
  val empty : t

  (** Composition of two freshenings. *)
  val compose : earlier:t -> later:t -> t

  (** Freshen a closure ID based on the given renaming.  The same ID is
      returned if the renaming does not affect it.
      If dealing with approximations, you probably want to use
      [Simple_value_approx.freshen_and_check_closure_id] instead of this
      function.
  *)
  val apply_closure_id : t -> Closure_id.t -> Closure_id.t

  (** Like [apply_closure_id], but for variables within closures. *)
  val apply_var_within_closure
     : t
    -> Var_within_closure.t
    -> Var_within_closure.t

  val print : Format.formatter -> t -> unit
end

(* CR-soon mshinwell for mshinwell: add comment *)
val apply_function_decls_and_free_vars
   : t
  -> (Flambda.specialised_to * 'a) Variable.Map.t
  -> Flambda.function_declarations
  -> only_freshen_parameters:bool
  -> (Flambda.specialised_to * 'a) Variable.Map.t
    * Flambda.function_declarations
    * t
    * Project_var.t

val does_not_freshen : t -> Variable.t list -> bool

val print : Format.formatter -> t -> unit

(** N.B. This does not freshen the domain of the supplied map, only the
    range. *)
(* CR-someday mshinwell: consider fixing that *)
val freshen_projection_relation
   : Flambda.specialised_to Variable.Map.t
  -> freshening:t
  -> closure_freshening:Project_var.t
  -> Flambda.specialised_to Variable.Map.t

val freshen_projection_relation'
   : (Flambda.specialised_to * 'a) Variable.Map.t
  -> freshening:t
  -> closure_freshening:Project_var.t
  -> (Flambda.specialised_to * 'a) Variable.Map.t
