src/analysis/scope_api.ml (212 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
module Make (L : Loc_sig.S) = struct
module L = L
type scope = int
type use = L.t
type uses = L.LSet.t
module Def = struct
type t = {
locs: L.t Nel.t;
name: int;
actual_name: string;
kind: Bindings.kind;
}
[@@deriving show]
let compare =
let rec iter locs1 locs2 =
match (locs1, locs2) with
| ([], []) -> 0
| ([], _) -> -1
| (_, []) -> 1
| (loc1 :: locs1, loc2 :: locs2) ->
let i = L.compare loc1 loc2 in
if i = 0 then
iter locs1 locs2
else
i
in
(fun t1 t2 -> iter (Nel.to_list t1.locs) (Nel.to_list t2.locs))
let is x t = Nel.exists (L.equal x) t.locs
end
module DefMap = WrappedMap.Make (Def)
type use_def_map = Def.t L.LMap.t [@@deriving show]
module Scope = struct
type t = {
lexical: bool;
parent: int option;
defs: Def.t SMap.t;
locals: use_def_map;
globals: SSet.t;
loc: L.t;
}
[@@deriving show]
end
type info = {
(* number of distinct name ids *)
max_distinct: int;
(* map of scope ids to local scopes *)
scopes: Scope.t IMap.t;
}
[@@deriving show]
let all_uses { scopes; _ } =
IMap.fold
(fun _ scope acc ->
L.LMap.fold (fun use _ uses -> L.LSet.add use uses) scope.Scope.locals acc)
scopes
L.LSet.empty
let defs_of_all_uses { scopes; _ } =
IMap.fold (fun _ scope acc -> L.LMap.union scope.Scope.locals acc) scopes L.LMap.empty
let uses_of_all_defs info =
let use_def_map = defs_of_all_uses info in
L.LMap.fold
(fun use def def_uses_map ->
match DefMap.find_opt def def_uses_map with
| None -> DefMap.add def (L.LSet.singleton use) def_uses_map
| Some uses -> DefMap.add def (L.LSet.add use uses) def_uses_map)
use_def_map
DefMap.empty
exception Missing_def of info * use
let def_of_use_opt { scopes; _ } use =
IMap.fold
(fun _ scope acc ->
match acc with
| Some _ -> acc
| None -> L.LMap.find_opt use scope.Scope.locals)
scopes
None
let def_of_use info use =
match def_of_use_opt info use with
| Some def -> def
| None -> raise (Missing_def (info, use))
let use_is_def info use =
let def = def_of_use info use in
Def.is use def
let uses_of_def { scopes; _ } ?(exclude_def = false) def =
IMap.fold
(fun _ scope acc ->
L.LMap.fold
(fun use def' uses ->
if exclude_def && Def.is use def' then
uses
else if Def.compare def def' = 0 then
L.LSet.add use uses
else
uses)
scope.Scope.locals
acc)
scopes
L.LSet.empty
let uses_of_use info ?exclude_def use =
try
let def = def_of_use info use in
uses_of_def info ?exclude_def def
with
| Missing_def _ -> L.LSet.empty
let def_is_unused info def = L.LSet.is_empty (uses_of_def info ~exclude_def:true def)
let toplevel_scopes = [0]
let scope info scope_id =
try IMap.find scope_id info.scopes with
| Not_found -> failwith ("Scope " ^ string_of_int scope_id ^ " not found")
let rec scope_within info scope_id s =
match s.Scope.parent with
| None -> false
| Some p ->
if p = scope_id then
true
else
scope_within info scope_id (scope info p)
let scope_of_loc info scope_loc =
let scopes =
IMap.fold
(fun scope_id scope acc ->
if scope.Scope.loc = scope_loc then
scope_id :: acc
else
acc)
info.scopes
[]
in
List.rev scopes
let is_local_use { scopes; _ } use =
IMap.exists (fun _ scope -> L.LMap.mem use scope.Scope.locals) scopes
let rec fold_scope_chain info f scope_id acc =
let s = scope info scope_id in
let acc = f scope_id s acc in
match s.Scope.parent with
| Some parent_id -> fold_scope_chain info f parent_id acc
| None -> acc
let rev_scope_pointers scopes =
IMap.fold
(fun id scope acc ->
match scope.Scope.parent with
| Some scope_id ->
let children' =
match IMap.find_opt scope_id acc with
| Some children -> children
| None -> []
in
IMap.add scope_id (id :: children') acc
| None -> acc)
scopes
IMap.empty
let build_scope_tree info =
let scopes = info.scopes in
let children_map = rev_scope_pointers scopes in
let rec build_scope_tree scope_id =
let children =
match IMap.find_opt scope_id children_map with
| None -> []
| Some children_scope_ids -> List.rev_map build_scope_tree children_scope_ids
in
Tree.Node (IMap.find scope_id scopes, children)
in
build_scope_tree 0
(* Let D be the declared names of some scope.
The free variables F of the scope are the names in G + F' + L - D, where:
* G contains the global names used in that scope
* L contains the local names used in that scope
* F' contains the free variables of its children
The bound variables B of the scope are the names in B' + D, where:
* B' contains the bound variables of its children
*)
let rec compute_free_and_bound_variables = function
| Tree.Node (scope, children) ->
let children' = Base.List.map ~f:compute_free_and_bound_variables children in
let (free_children, bound_children) =
List.fold_left
(fun (facc, bacc) -> function
| Tree.Node ((_, free, bound), _) -> (SSet.union free facc, SSet.union bound bacc))
(SSet.empty, SSet.empty)
children'
in
let def_locals = scope.Scope.defs in
let is_def_local use_name = SMap.exists (fun def_name _ -> def_name = use_name) def_locals in
let free =
scope.Scope.globals
|> L.LMap.fold
(fun _loc use_def acc ->
let use_name = use_def.Def.actual_name in
if is_def_local use_name then
acc
else
SSet.add use_name acc)
scope.Scope.locals
|> SSet.fold
(fun use_name acc ->
if is_def_local use_name then
acc
else
SSet.add use_name acc)
free_children
in
let bound = SMap.fold (fun name _def acc -> SSet.add name acc) def_locals bound_children in
Tree.Node ((def_locals, free, bound), children')
end
module With_Loc = Make (Loc_sig.LocS)
module With_ALoc = Make (Loc_sig.ALocS)
module With_ILoc = Make (Loc_sig.ILocS)