source/interprocedural/classHierarchyGraph.ml (108 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. *) open Core open Ast open Statement open Analysis type class_name = string module ClassNameSet = Caml.Set.Make (String) module ClassNameMap = struct include Caml.Map.Make (String) let pp_map pp_value formatter map = let pp_pairs pp_value formatter pairs = let pp_pair formatter key value = Format.fprintf formatter "@,%s -> [%a]" key pp_value value in iter (pp_pair formatter) pairs in Format.fprintf formatter "{@[<v 2>%a@]@,}" (pp_pairs pp_value) map let show ~pp_value map = Format.asprintf "%a" (pp_map pp_value) map end type t = { roots: ClassNameSet.t; edges: ClassNameSet.t ClassNameMap.t; } [@@deriving eq] let pp_set formatter set = ClassNameSet.iter (fun element -> Format.fprintf formatter "@[%s,@]" element) set let pp formatter { roots; edges } = Format.fprintf formatter "roots: @[[%a]@]@\nedges: {@[<v 2>%a@]@,}" pp_set roots (ClassNameMap.pp_map pp_set) edges let show = Format.asprintf "%a" pp let empty = { roots = ClassNameSet.empty; edges = ClassNameMap.empty } let set_of_children = function | None -> ClassNameSet.empty | Some children -> children let children { edges; _ } parent = set_of_children (ClassNameMap.find_opt parent edges) let add { roots; edges } ~parent:parent_class ~child:child_class = let new_roots = let new_roots = if ClassNameMap.mem parent_class edges then roots else ClassNameSet.add parent_class roots in ClassNameSet.remove child_class new_roots in let new_edges = let add_child parent = Some (ClassNameSet.add child_class (set_of_children parent)) in let update_children ~parent ~update edges = ClassNameMap.update parent update edges in edges |> update_children ~parent:parent_class ~update:add_child |> update_children ~parent:child_class ~update:(fun key -> Some (set_of_children key)) in { roots = new_roots; edges = new_edges } let roots { roots; _ } = roots let from_source ~environment ~source = let resolution = TypeEnvironment.ReadOnly.global_resolution environment in if GlobalResolution.source_is_unit_test resolution ~source then empty else let register_immediate_subclasses accumulator { Node.value = { Class.name = class_name; _ }; _ } = let class_name = Reference.show class_name in let parents = GlobalResolution.immediate_parents ~resolution class_name in List.fold ~init:accumulator parents ~f:(fun accumulator parent -> add accumulator ~parent ~child:class_name) in Preprocessing.classes source |> List.fold ~init:empty ~f:register_immediate_subclasses let create ~roots ~edges = let roots = ClassNameSet.of_list roots in let edges = List.fold edges ~init:ClassNameMap.empty ~f:(fun accumulator (parent, children) -> ClassNameMap.add parent (ClassNameSet.of_list children) accumulator) in { roots; edges } let join ({ roots = _; edges = edges_left } as left) { roots = roots_right; edges = edges_right } = let add_edges parent_right children_right { roots; edges } = if ClassNameSet.is_empty children_right && not (ClassNameMap.mem parent_right edges) then let edges = ClassNameMap.add parent_right ClassNameSet.empty edges in { roots; edges } else let update = function | Some children_left -> Some (ClassNameSet.union children_left children_right) | None -> Some children_right in let edges = ClassNameMap.update parent_right update edges in (* Remove roots that now have incoming edges *) let roots = ClassNameSet.diff roots children_right in { roots; edges } in let { roots; edges } = ClassNameMap.fold add_edges edges_right left in let add_root root_right accumulator = if ClassNameMap.mem root_right edges_left then accumulator else ClassNameSet.add root_right accumulator in let roots = ClassNameSet.fold add_root roots_right roots in { roots; edges }