Make Typing_instantiate only work on decl ty
[hiphop-php.git] / hphp / hack / src / typing / typing_inherit.ml
blob35304256600271d167341a3e4ce59e36a5208888
1 (**
2 * Copyright (c) 2014, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the "hack" directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
9 *)
12 (*****************************************************************************)
13 (* Module dealing with inheritance.
14 * When we want to declare a new class, we first have to retrieve all the
15 * types that were inherited from their parents.
17 (*****************************************************************************)
19 open Utils
20 open Nast
21 open Typing_defs
23 module Env = Typing_env
24 module Inst = Typing_instantiate
25 module TUtils = Typing_utils
26 module Phase = Typing_phase
28 type env = Env.env
30 (*****************************************************************************)
31 (* This is what we are trying to produce for a given class. *)
32 (*****************************************************************************)
34 type inherited = {
35 ih_cstr : class_elt option * bool (* consistency required *);
36 ih_consts : class_elt SMap.t ;
37 ih_typeconsts : typeconst_type SMap.t ;
38 ih_props : class_elt SMap.t ;
39 ih_sprops : class_elt SMap.t ;
40 ih_methods : class_elt SMap.t ;
41 ih_smethods : class_elt SMap.t ;
44 let empty = {
45 ih_cstr = None, false;
46 ih_consts = SMap.empty;
47 ih_typeconsts = SMap.empty;
48 ih_props = SMap.empty;
49 ih_sprops = SMap.empty;
50 ih_methods = SMap.empty;
51 ih_smethods = SMap.empty;
54 (*****************************************************************************)
55 (* Functions used to merge an additional inherited class to the types
56 * we already inherited.
58 (*****************************************************************************)
60 let is_abstract_method x =
61 match x.ce_type with
62 | _, Tfun x when x.ft_abstract -> true
63 | _ -> false
65 let add_method name sig_ methods =
66 match (fst sig_.ce_type), sig_.ce_synthesized with
67 | Reason.Rdynamic_yield _, true ->
68 (* DynamicYield::__call-derived pseudo-methods need to be
69 * rederived at each level *)
70 methods
71 | _ ->
73 match SMap.get name methods with
74 | None ->
75 (* The method didn't exist so far, let's add it *)
76 SMap.add name sig_ methods
77 | Some old_sig ->
78 if ((not (is_abstract_method old_sig) && is_abstract_method sig_)
79 || (is_abstract_method old_sig = is_abstract_method sig_
80 && not (old_sig.ce_synthesized) && sig_.ce_synthesized))
82 (* The then-branch of this if is encountered when the method being
83 * added shouldn't *actually* be added. When's that?
84 * In isolation, we can say that
85 * - We don't want to override a concrete method with
86 * an abstract one.
87 * - We don't want to override a method that's actually
88 * implemented by the programmer with one that's "synthetic",
89 * e.g. arising merely from a require-extends declaration in
90 * a trait.
91 * When these two considerations conflict, we give precedence to
92 * abstractness for determining priority of the method.
94 then methods
96 (* Otherwise, we *are* overwriting a method definition. This is
97 * OK when a naming conflict is parent class vs trait (trait
98 * wins!), but not really OK when the naming conflict is trait vs
99 * trait (we rely on HHVM to catch the error at runtime) *)
100 else SMap.add name {sig_ with ce_override = false} methods
103 let add_methods methods' acc =
104 SMap.fold add_method methods' acc
106 let add_members members acc =
107 SMap.fold SMap.add members acc
109 let is_abstract_typeconst x = x.ttc_type = None
111 let can_override_typeconst x =
112 (is_abstract_typeconst x) || x.ttc_constraint <> None
114 let add_typeconst name sig_ typeconsts =
115 match SMap.get name typeconsts with
116 | None ->
117 (* The type constant didn't exist so far, let's add it *)
118 SMap.add name sig_ typeconsts
119 (* This covers the following case
121 * interface I1 { abstract const type T; }
122 * interface I2 { const type T = int; }
124 * class C implements I1, I2 {}
126 * Then C::T == I2::T since I2::T is not abstract
128 | Some old_sig
129 when not (is_abstract_typeconst old_sig) && (is_abstract_typeconst sig_) ->
130 typeconsts
131 (* This covers the following case
133 * abstract P { const type T as arraykey = arraykey; }
134 * interface I { const type T = int; }
136 * class C extends P implements I {}
138 * Then C::T == I::T since P::T has a constraint and thus can be overridden
139 * by it's child, while I::T cannot be overridden.
141 | Some old_sig
142 when not (can_override_typeconst old_sig) && (can_override_typeconst sig_) ->
143 typeconsts
144 (* When a type constant is declared in multiple parents we need to make a
145 * subtle choice of what type we inherit. For example in:
147 * interface I1 { abstract const type t as Container<int>; }
148 * interface I2 { abstract const type t as KeyedContainer<int, int>; }
149 * abstract class C implements I1, I2 {}
151 * Depending on the order the interfaces are declared, we may report an error.
152 * Since this could be confusing there is special logic in Typing_extends that
153 * checks for this potentially ambiguous situation and warns the programmer to
154 * explicitly declare T in C.
156 | _ ->
157 SMap.add name sig_ typeconsts
159 let add_constructor (cstr, cstr_consist) (acc, acc_consist) =
160 let ce = match cstr, acc with
161 | None, _ -> acc
162 | Some ce, Some acce when ce.ce_synthesized && not acce.ce_synthesized ->
164 | _ -> cstr
165 in ce, cstr_consist || acc_consist
167 let add_inherited inherited acc = {
168 ih_cstr = add_constructor inherited.ih_cstr acc.ih_cstr;
169 ih_consts = add_members inherited.ih_consts acc.ih_consts;
170 ih_typeconsts =
171 SMap.fold add_typeconst inherited.ih_typeconsts acc.ih_typeconsts;
172 ih_props = add_members inherited.ih_props acc.ih_props;
173 ih_sprops = add_members inherited.ih_sprops acc.ih_sprops;
174 ih_methods = add_methods inherited.ih_methods acc.ih_methods;
175 ih_smethods = add_methods inherited.ih_smethods acc.ih_smethods;
178 (*****************************************************************************)
179 (* Helpers *)
180 (*****************************************************************************)
182 let desugar_class_hint = function
183 | (_, Happly ((pos, class_name), type_parameters)) ->
184 pos, class_name, type_parameters
185 | _ -> assert false
187 let check_arity pos class_name class_type class_parameters =
188 let arity = List.length class_type.tc_tparams in
189 if List.length class_parameters <> arity
190 then Errors.class_arity pos class_type.tc_pos class_name arity;
193 let make_substitution pos class_name class_type class_parameters =
194 check_arity pos class_name class_type class_parameters;
195 Inst.make_subst class_type.tc_tparams class_parameters
197 let constructor env subst (cstr, consistent) = match cstr with
198 | None -> env, (None, consistent)
199 | Some ce ->
200 let env, ty = Inst.instantiate subst env ce.ce_type in
201 env, (Some {ce with ce_type = ty}, consistent)
203 let map_inherited f inh =
205 ih_cstr = (opt_map f (fst inh.ih_cstr)), (snd inh.ih_cstr);
206 ih_typeconsts = inh.ih_typeconsts;
207 ih_consts = SMap.map f inh.ih_consts;
208 ih_props = SMap.map f inh.ih_props;
209 ih_sprops = SMap.map f inh.ih_sprops;
210 ih_methods = SMap.map f inh.ih_methods;
211 ih_smethods = SMap.map f inh.ih_smethods;
214 (*****************************************************************************)
215 (* Code filtering the private members (useful for inheritance) *)
216 (*****************************************************************************)
218 let filter_private x =
219 SMap.fold begin fun name class_elt acc ->
220 match class_elt.ce_visibility with
221 | Vprivate _ -> acc
222 | Vpublic | Vprotected _ -> SMap.add name class_elt acc
223 end x SMap.empty
225 let chown_private owner =
226 SMap.map begin fun class_elt ->
227 match class_elt.ce_visibility with
228 | Vprivate _ -> {class_elt with ce_visibility = Vprivate owner}
229 | _ -> class_elt end
231 let apply_fn_to_class_elts fn class_type = {
232 class_type with
233 tc_consts = fn class_type.tc_consts;
234 tc_typeconsts = class_type.tc_typeconsts;
235 tc_props = fn class_type.tc_props;
236 tc_sprops = fn class_type.tc_sprops;
237 tc_methods = fn class_type.tc_methods;
238 tc_smethods = fn class_type.tc_smethods;
241 let filter_privates = apply_fn_to_class_elts filter_private
242 let chown_privates owner = apply_fn_to_class_elts (chown_private owner)
244 (*****************************************************************************)
245 (* Builds the inherited type when the class lives in Hack *)
246 (*****************************************************************************)
248 let inherit_hack_class c env p class_name class_type argl =
249 let subst = make_substitution p class_name class_type argl in
250 let instantiate = SMap.map_env (Inst.instantiate_ce subst) in
251 let class_type =
252 match class_type.tc_kind with
253 | Ast.Ctrait ->
254 (* Change the private visibility to point to the inheriting class *)
255 chown_privates (snd c.c_name) class_type
256 | Ast.Cnormal | Ast.Cabstract | Ast.Cinterface ->
257 filter_privates class_type
258 | Ast.Cenum -> class_type
260 let env, typeconsts = SMap.map_env (Inst.instantiate_typeconst subst)
261 env class_type.tc_typeconsts in
262 let env, consts = instantiate env class_type.tc_consts in
263 let env, props = instantiate env class_type.tc_props in
264 let env, sprops = instantiate env class_type.tc_sprops in
265 let env, methods = instantiate env class_type.tc_methods in
266 let env, smethods = instantiate env class_type.tc_smethods in
267 let cstr = Env.get_construct env class_type in
268 let env, cstr = constructor env subst cstr in
269 let result = {
270 ih_cstr = cstr;
271 ih_consts = consts;
272 ih_typeconsts = typeconsts;
273 ih_props = props;
274 ih_sprops = sprops;
275 ih_methods = methods;
276 ih_smethods = smethods;
277 } in
278 env, result
280 (* mostly copy paste of inherit_hack_class *)
281 let inherit_hack_class_constants_only env p class_name class_type argl =
282 let subst = make_substitution p class_name class_type argl in
283 let instantiate = SMap.map_env (Inst.instantiate_ce subst) in
284 let env, consts = instantiate env class_type.tc_consts in
285 let env, typeconsts = SMap.map_env (Inst.instantiate_typeconst subst)
286 env class_type.tc_typeconsts in
287 let result = { empty with
288 ih_consts = consts;
289 ih_typeconsts = typeconsts;
290 } in
291 env, result
293 (* This logic deals with importing XHP attributes from an XHP class
294 via the "attribute :foo;" syntax. *)
295 let inherit_hack_xhp_attrs_only env p class_name class_type argl =
296 let subst = make_substitution p class_name class_type argl in
297 (* Filter out properties that are not XHP attributes *)
298 let props =
299 SMap.fold begin fun name class_elt acc ->
300 if class_elt.ce_is_xhp_attr then SMap.add name class_elt acc else acc
301 end class_type.tc_props SMap.empty in
302 let env, props = SMap.map_env (Inst.instantiate_ce subst) env props in
303 let result = { empty with ih_props = props; } in
304 env, result
306 (*****************************************************************************)
308 let from_class c env hint =
309 let pos, class_name, class_params = desugar_class_hint hint in
310 let env, class_params = lfold Typing_hint.hint env class_params in
311 let class_type = Env.get_class_dep env class_name in
312 match class_type with
313 | None ->
314 (* The class lives in PHP, we don't know anything about it *)
315 env, empty
316 | Some class_ ->
317 (* The class lives in Hack *)
318 inherit_hack_class c env pos class_name class_ class_params
320 (* mostly copy paste of from_class *)
321 let from_class_constants_only env hint =
322 let pos, class_name, class_params = desugar_class_hint hint in
323 let env, class_params = lfold Typing_hint.hint env class_params in
324 let class_type = Env.get_class_dep env class_name in
325 match class_type with
326 | None ->
327 (* The class lives in PHP, we don't know anything about it *)
328 env, empty
329 | Some class_ ->
330 (* The class lives in Hack *)
331 inherit_hack_class_constants_only env pos class_name class_ class_params
333 let from_class_xhp_attrs_only env hint =
334 let pos, class_name, class_params = desugar_class_hint hint in
335 let env, class_params = lfold Typing_hint.hint env class_params in
336 let class_type = Env.get_class_dep env class_name in
337 match class_type with
338 | None ->
339 (* The class lives in PHP, we don't know anything about it *)
340 env, empty
341 | Some class_ ->
342 (* The class lives in Hack *)
343 inherit_hack_xhp_attrs_only env pos class_name class_ class_params
345 let from_parent env c =
346 let extends =
347 (* In an abstract class or a trait, we assume the interfaces
348 * will be implemented in the future, so we take them as
349 * part of the class (as requested by dependency injection implementers)
351 match c.c_kind with
352 | Ast.Cabstract -> c.c_implements @ c.c_extends
353 | Ast.Ctrait -> c.c_implements @ c.c_extends @ c.c_req_implements
354 | _ -> c.c_extends
356 let env, inherited_l = lfold (from_class c) env extends in
357 env, List.fold_right add_inherited inherited_l empty
359 let from_requirements c (env, acc) reqs =
360 let env, inherited = from_class c env reqs in
361 let inherited = map_inherited
362 (fun ce -> { ce with ce_synthesized = true })
363 inherited in
364 env, add_inherited inherited acc
366 let from_trait c (env, acc) uses =
367 let env, inherited = from_class c env uses in
368 env, add_inherited inherited acc
370 let from_xhp_attr_use (env, acc) uses =
371 let env, inherited = from_class_xhp_attrs_only env uses in
372 env, add_inherited inherited acc
374 let from_interface_constants (env, acc) impls =
375 let env, inherited = from_class_constants_only env impls in
376 env, add_inherited inherited acc
378 (*****************************************************************************)
379 (* The API to the outside *)
380 (*****************************************************************************)
382 let make env c =
383 (* members inherited from parent class ... *)
384 let acc = from_parent env c in
385 let acc = List.fold_left (from_requirements c) acc c.c_req_extends in
386 (* ... are overridden with those inherited from used traits *)
387 let acc = List.fold_left (from_trait c) acc c.c_uses in
388 let acc = List.fold_left from_xhp_attr_use acc c.c_xhp_attr_uses in
389 (* todo: what about the same constant defined in different interfaces
390 * we implement? We should forbid and say "constant already defined".
391 * to julien: where is the logic that check for duplicated things?
392 * todo: improve constant handling, see task #2487051
394 let acc = List.fold_left from_interface_constants acc c.c_req_implements in
395 List.fold_left from_interface_constants acc c.c_implements