2 * Copyright (c) 2019, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
12 open Reordered_argument_collections
15 let class_names_from_deps ~ctx ~get_classes_in_file deps
=
16 let filenames = Naming_provider.get_files ctx deps
in
17 Relative_path.Set.fold
filenames ~init
:SSet.empty ~f
:(fun file acc
->
18 SSet.fold
(get_classes_in_file file
) ~init
:acc ~f
:(fun cid acc
->
19 if DepSet.mem deps
Dep.(make
(Type cid
)) then
24 let get_minor_change_fanout
25 ~
(ctx
: Provider_context.t
)
27 (minor_change
: ClassDiff.minor_change
) : AffectedDeps.t
=
28 let mode = Provider_context.get_deps_mode ctx
in
29 let changed = DepSet.singleton
(Dep.make
(Dep.Type class_name
)) in
30 let acc = AffectedDeps.empty
() in
31 let acc = AffectedDeps.mark_changed
acc changed in
32 let acc = AffectedDeps.mark_as_needing_recheck
acc changed in
34 mro_positions_changed
;
36 { consts
; typeconsts
; props
; sprops
; methods
; smethods
; constructor
};
40 let changed_and_descendants =
41 lazy (Typing_deps.add_extend_deps
mode changed)
44 (* If positions affecting the linearization have changed, we need to update
45 positions in the linearization of this class and all its descendants.
46 We mark those linearizations as invalidated here. We don't need to
47 recheck the fanout of the invalidated classes--since only positions
48 changed, there will be no change in the fanout except in the positions
49 in error messages, and we recheck all files with errors anyway. *)
50 if mro_positions_changed
then
51 let changed_and_descendants = Lazy.force
changed_and_descendants in
52 AffectedDeps.mark_mro_invalidated
acc changed_and_descendants
56 (* Recheck any file with a dependency on the provided member
57 in the changed class and in each of its descendants,
58 even if the member was overridden in a subclass. This deals with the case
59 where adding, removing, or changing the abstract-ness of a member causes
60 some subclass which inherits a member of that name from multiple parents
61 to resolve the conflict in a different way than it did previously. *)
62 let recheck_descendants_and_their_member_dependents acc member
=
63 let changed_and_descendants = Lazy.force
changed_and_descendants in
64 DepSet.fold
changed_and_descendants ~init
:acc ~f
:(fun dep
acc ->
66 AffectedDeps.mark_as_needing_recheck
acc (DepSet.singleton dep
)
68 AffectedDeps.mark_all_dependents_as_needing_recheck_from_hash
71 (Typing_deps.Dep.make_member_dep_from_type_dep dep member
))
73 let add_member_fanout ~is_const member change
acc =
74 (* Consts and typeconsts have their types copied into descendant classes in
75 folded decl (rather than being stored in a separate heap as methods and
76 properties are). As a result, when using a const, we register a
77 dependency only upon the class type at the use site. In contrast, if we
78 use a method or property B::f which was inherited from A, we register a
79 dependency both upon B::f and A::f, which is what allows us to avoid the
80 more expensive use of recheck_descendants_and_their_member_dependents
81 here. Since we don't register a dependency upon the const at its class of
82 origin in this way, we must always take the more expensive path for
85 is_const
|| ClassDiff.method_or_property_change_affects_descendants change
87 recheck_descendants_and_their_member_dependents acc member
89 AffectedDeps.mark_all_dependents_as_needing_recheck_from_hash
92 (Typing_deps.Dep.make_member_dep_from_type_dep
93 (Typing_deps.Dep.make
(Typing_deps.Dep.Type class_name
))
96 let add_member_fanouts ~is_const changes make_member
acc =
97 SMap.fold changes ~init
:acc ~f
:(fun name
->
98 add_member_fanout ~is_const
(make_member name
))
101 SMap.fold consts ~init
:acc ~f
:(fun name change
acc ->
103 (* If a const has been added or removed in an enum type, we must recheck
104 all switch statements which need to have a case for each variant
105 (exhaustive switch statements add an AllMembers dependency).
106 We don't bother to test whether the class is an enum type because
107 non-enum classes will have no AllMembers dependents anyway. *)
111 recheck_descendants_and_their_member_dependents acc Dep.Member.all
114 add_member_fanout ~is_const
:true (Dep.Member.const name
) change
acc)
118 |> add_member_fanouts ~is_const
:true typeconsts
Dep.Member.const
119 |> add_member_fanouts ~is_const
:false props
Dep.Member.prop
120 |> add_member_fanouts ~is_const
:false sprops
Dep.Member.sprop
121 |> add_member_fanouts ~is_const
:false methods
Dep.Member.method_
122 |> add_member_fanouts ~is_const
:false smethods
Dep.Member.smethod
125 Option.value_map constructor ~default
:acc ~f
:(fun change
->
126 add_member_fanout ~is_const
:false Dep.Member.constructor change
acc)
130 let get_maximum_fanout (ctx
: Provider_context.t
) (class_name
: string) =
131 let mode = Provider_context.get_deps_mode ctx
in
132 AffectedDeps.get_maximum_fanout mode (Dep.make
(Dep.Type class_name
))
134 let get_fanout ~
(ctx
: Provider_context.t
) (class_name
, diff
) : AffectedDeps.t
=
136 | Unchanged
-> AffectedDeps.empty
()
137 | Major_change _major_change
-> get_maximum_fanout ctx class_name
138 | Minor_change minor_change
->
139 get_minor_change_fanout ~ctx class_name minor_change
141 let direct_references_cardinal mode class_name
: int =
142 Typing_deps.get_ideps
mode (Dep.Type class_name
) |> DepSet.cardinal
144 let descendants_cardinal mode class_name
: int =
145 (Typing_deps.add_extend_deps
147 (DepSet.singleton
@@ Dep.make
@@ Dep.Type class_name
)
151 let children_cardinal mode class_name
: int =
152 Typing_deps.get_ideps
mode (Dep.Extends class_name
) |> DepSet.cardinal
154 let get_fanout_cardinal (fanout
: AffectedDeps.t
) : int =
155 DepSet.cardinal fanout
.AffectedDeps.needs_recheck
158 let do_log ctx ~fanout_cardinal
=
159 TypecheckerOptions.log_fanout ~fanout_cardinal
160 @@ Provider_context.get_tcopt ctx
164 ((class_name
: string), (diff
: ClassDiff.t
))
165 (fanout
: AffectedDeps.t
) : unit =
166 let fanout_cardinal = get_fanout_cardinal fanout
in
167 if do_log ~
fanout_cardinal ctx
then
168 let mode = Provider_context.get_deps_mode ctx
in
169 HackEventLogger.Fanouts.log_class
171 ~class_diff
:(ClassDiff.show diff
)
173 ~class_diff_category
:(ClassDiff.to_category_json diff
)
174 ~
direct_references_cardinal:(direct_references_cardinal mode class_name
)
175 ~
descendants_cardinal:(descendants_cardinal mode class_name
)
176 ~
children_cardinal:(children_cardinal mode class_name
)
181 (fanout
: AffectedDeps.t
)
182 ~max_class_fanout_cardinal
: unit =
183 let fanout_cardinal = get_fanout_cardinal fanout
in
184 if do_log ~
fanout_cardinal ctx
then
185 HackEventLogger.Fanouts.log
186 ~changes_cardinal
:(List.length changes
)
187 ~max_class_fanout_cardinal
191 let add_fanout ~ctx
(fanout_acc
, max_class_fanout_cardinal
) diff
=
192 let fanout = get_fanout ~ctx diff
in
193 Log.log_class_fanout ctx diff
fanout;
194 let fanout_acc = AffectedDeps.union
fanout_acc fanout in
195 let max_class_fanout_cardinal =
196 Int.max
max_class_fanout_cardinal (get_fanout_cardinal fanout)
198 (fanout_acc, max_class_fanout_cardinal)
200 let fanout_of_changes
201 ~
(ctx
: Provider_context.t
) (changes
: (string * ClassDiff.t
) list
) :
203 let (fanout, max_class_fanout_cardinal) =
204 List.fold changes ~init
:(AffectedDeps.empty
(), 0) ~f
:(add_fanout ~ctx
)
206 Log.log_fanout ctx changes
fanout ~
max_class_fanout_cardinal;