2 * Copyright (c) 2017, 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.
11 open Hhbc_string_utils
13 module SN
= Naming_special_names
14 module TC
= Hhas_type_constraint
22 (* Produce the "userType" bit of the annotation *)
23 let rec fmt_name_or_prim ~tparams ~namespace x
=
25 if List.mem ~equal
:( = ) tparams
name || is_self
name || is_parent
name then
28 let needs_unmangling = Xhp.is_xhp
(strip_ns
name) in
29 let fq_id = Hhbc_id.Class.elaborate_id namespace x
in
30 if needs_unmangling then
31 Hhbc_id.Class.to_unmangled_string
fq_id
33 Hhbc_id.Class.to_raw_string
fq_id
35 and prim_to_string prim
=
37 | Aast.Tnull
-> SN.Typehints.null
38 | Aast.Tvoid
-> SN.Typehints.void
39 | Aast.Tint
-> SN.Typehints.int
40 | Aast.Tbool
-> SN.Typehints.bool
41 | Aast.Tfloat
-> SN.Typehints.float
42 | Aast.Tstring
-> SN.Typehints.string
43 | Aast.Tresource
-> SN.Typehints.resource
44 | Aast.Tnum
-> SN.Typehints.num
45 | Aast.Tarraykey
-> SN.Typehints.arraykey
46 | Aast.Tnoreturn
-> SN.Typehints.noreturn
47 | Aast.Tatom s
-> ":@" ^ s
49 and fmt_hint ~tparams ~namespace ?
(strip_tparams
= false) (pos
, h
) =
50 let fmt_name_or_prim = fmt_name_or_prim ~tparams ~namespace
in
52 | Aast.Happly
(id
, []) -> fmt_name_or_prim id
53 | Aast.Happly
(id
, args
) ->
54 let name = fmt_name_or_prim id
in
58 name ^
"<" ^ fmt_hints ~tparams ~namespace args ^
">"
59 | Aast.Hfun
{ is_coroutine
= true; _
} ->
60 failwith
"Codegen for coroutine functions is not supported"
70 is_mutable_return
= _
;
72 (* TODO(mqian): Implement for inout parameters *)
74 ^ fmt_hints ~tparams ~namespace args
76 ^ fmt_hint ~tparams ~namespace ret
78 | Aast.Htuple hs
-> "(" ^ fmt_hints ~tparams ~namespace hs ^
")"
79 | Aast.Haccess
((_
, Aast.Happly
(id
, _
)), accesses
) ->
82 ^
String.concat ~sep
:"::" (List.map accesses snd
)
83 | Aast.Haccess _
-> failwith
"ast_to_nast error. Should be Haccess(Happly())"
84 (* Follow HHVM order: soft -> option *)
85 (* Can we fix this eventually? *)
86 | Aast.Hoption
(_
, Aast.Hsoft t
) -> "@?" ^ fmt_hint ~tparams ~namespace t
87 | Aast.Hoption t
-> "?" ^ fmt_hint ~tparams ~namespace t
88 | Aast.Hlike t
-> "~" ^ fmt_hint ~tparams ~namespace t
89 | Aast.Hsoft h
-> "@" ^ fmt_hint ~tparams ~namespace h
90 (* No guarantee that this is in the correct order when using map instead of list
91 * TODO: Check whether shape fields need to retain order *)
92 | Aast.Hshape
{ Aast.nsi_field_map
; _
} ->
93 let fmt_field_name name =
95 | A.SFlit_int
(_
, s_i
) -> s_i
96 | A.SFlit_str
(_
, s
) -> "'" ^ s ^
"'"
97 | A.SFclass_const
(cid
, (_
, s2
)) -> fmt_name_or_prim cid ^
"::" ^ s2
99 let format { Aast.sfi_hint
; Aast.sfi_optional
; Aast.sfi_name
} =
107 ^
fmt_field_name sfi_name
109 ^ fmt_hint ~tparams ~namespace sfi_hint
111 let shape_fields = List.map ~f
:format nsi_field_map
in
112 prefix_namespace
"HH" "shape(" ^
String.concat ~sep
:", " shape_fields ^
")"
113 | Aast.Hprim p
-> fmt_name_or_prim (pos
, prim_to_string p
)
114 (* Didn't exist in the AST *)
117 failwith
"I'm convinced that this should be an error caught in naming"
118 | Aast.Hmixed
-> fmt_name_or_prim (pos
, SN.Typehints.mixed
)
119 | Aast.Hnonnull
-> fmt_name_or_prim (pos
, SN.Typehints.nonnull
)
120 | Aast.Habstr s
-> fmt_name_or_prim (pos
, s
)
121 | Aast.Harray _
-> fmt_name_or_prim (pos
, SN.Typehints.array
)
122 | Aast.Hdarray _
-> fmt_name_or_prim (pos
, SN.Typehints.darray
)
123 | Aast.Hvarray _
-> fmt_name_or_prim (pos
, SN.Typehints.varray
)
124 | Aast.Hvarray_or_darray _
->
125 fmt_name_or_prim (pos
, SN.Typehints.varray_or_darray
)
126 | Aast.Hthis
-> fmt_name_or_prim (pos
, SN.Typehints.this
)
127 | Aast.Hdynamic
-> fmt_name_or_prim (pos
, SN.Typehints.dynamic
)
128 | Aast.Hnothing
-> fmt_name_or_prim (pos
, SN.Typehints.nothing
)
129 | Aast.Hpu_access
(h
, sid
) ->
130 "(" ^ fmt_hint ~tparams ~namespace h ^
":@" ^ snd sid ^
")"
132 and fmt_hints ~tparams ~namespace hints
=
133 String.concat ~sep
:", " (List.map hints
(fmt_hint ~tparams ~namespace
))
135 (* Differs from above in that this assumes that naming has occurred *)
136 let can_be_nullable (_
, h
) =
139 | Aast.Hoption
(_
, Aast.Hfun _
)
140 | Aast.Happly
((_
, "dynamic"), _
)
141 | Aast.Hoption
(_
, Aast.Happly
((_
, "dynamic"), _
))
142 | Aast.Happly
((_
, "nonnull"), _
)
143 | Aast.Hoption
(_
, Aast.Happly
((_
, "nonnull"), _
))
144 | Aast.Happly
((_
, "mixed"), _
)
145 | Aast.Hoption
(_
, Aast.Happly
((_
, "mixed"), _
))
149 | Aast.Hoption
(_
, Aast.Hdynamic
)
150 | Aast.Hoption
(_
, Aast.Hnonnull
)
151 | Aast.Hoption
(_
, Aast.Hmixed
) ->
153 | Aast.Haccess _
-> false
154 (* HHVM does not emit nullable for type consts that are set to null by default
155 * function(Class::Type $a = null) unless it is explicitly marked as nullable
159 failwith
"I'm convinced that this should be an error caught in naming"
160 (* Naming converted the following from Happly's so assuming it should be true *)
165 | Aast.Hvarray_or_darray _
170 let rec hint_to_type_constraint ~kind ~tparams ~skipawaitable ~namespace
(p
, h
)
172 let happly_helper ((pos
, name) as id
) =
173 if List.mem ~equal
:( = ) tparams
name then
174 let tc_name = Some
"" in
175 let tc_flags = [TC.HHType
; TC.ExtendedHint
; TC.TypeVar
] in
176 TC.make
tc_name tc_flags
177 else if kind
= TypeDef
&& (is_self
name || is_parent
name) then
178 Emit_fatal.raise_fatal_runtime
180 (Printf.sprintf
"Cannot access %s when no class scope is active" name)
183 if is_self
name || is_parent
name then
186 let fq_id = Hhbc_id.Class.elaborate_id namespace id
in
187 Hhbc_id.Class.to_raw_string
fq_id
189 let tc_flags = [TC.HHType
] in
190 TC.make
(Some
tc_name) tc_flags
193 (* The dynamic and nonnull types are treated by the runtime as mixed *)
194 | Aast.Happly
((_
, "dynamic"), [])
195 | Aast.Happly
((_
, "mixed"), [])
201 | Aast.Hprim
Aast.Tvoid
when kind
<> TypeDef
-> TC.make None
[]
202 | Aast.Happly
((_
, s
), [])
203 when String.lowercase s
= "void" && kind
<> TypeDef
->
206 let tc_name = Some
"" in
207 let tc_flags = [TC.HHType
; TC.ExtendedHint
; TC.TypeConstant
] in
208 TC.make
tc_name tc_flags
209 (* Elide the Awaitable class for async return types only *)
210 | Aast.Happly
((_
, "Awaitable"), [(_
, Aast.Hprim
Aast.Tvoid
)])
211 | Aast.Happly
((_
, "Awaitable"), [(_
, Aast.Happly
((_
, "void"), []))])
212 when skipawaitable
->
214 | Aast.Happly
((_
, "Awaitable"), [h
])
215 | Aast.Hoption
(_
, Aast.Happly
((_
, "Awaitable"), [h
]))
216 when skipawaitable
->
217 hint_to_type_constraint ~kind ~tparams ~skipawaitable
:false ~namespace h
218 | Aast.Hoption
(_
, Aast.Hsoft
(_
, Aast.Happly
((_
, "Awaitable"), [h
])))
219 when skipawaitable
->
220 make_tc_with_flags_if_non_empty_flags
226 [TC.Soft
; TC.HHType
; TC.ExtendedHint
]
227 | Aast.Happly
((_
, "Awaitable"), [])
228 | Aast.Hoption
(_
, Aast.Happly
((_
, "Awaitable"), []))
229 when skipawaitable
->
231 (* Need to differentiate between type params and classes *)
232 | Aast.Happly
((pos
, name), _
) -> happly_helper (pos
, name)
233 (* Shapes and tuples are just arrays *)
235 let tc_name = Some
"HH\\darray" in
236 let tc_flags = [TC.HHType
; TC.ExtendedHint
] in
237 TC.make
tc_name tc_flags
239 let tc_name = Some
"HH\\varray" in
240 let tc_flags = [TC.HHType
; TC.ExtendedHint
] in
241 TC.make
tc_name tc_flags
243 make_tc_with_flags_if_non_empty_flags
249 [TC.Nullable
; TC.DisplayNullable
; TC.HHType
; TC.ExtendedHint
]
251 make_tc_with_flags_if_non_empty_flags
257 [TC.Soft
; TC.HHType
; TC.ExtendedHint
]
260 failwith
"I'm convinced that this should be an error caught in naming"
261 (* Naming converted the following from Happly's so use the Happly logic here*)
262 | Aast.Hnonnull
-> happly_helper (p
, SN.Typehints.nonnull
)
263 | Aast.Harray _
-> happly_helper (p
, SN.Typehints.array
)
264 | Aast.Hdarray _
-> happly_helper (p
, SN.Typehints.darray
)
265 | Aast.Hvarray _
-> happly_helper (p
, SN.Typehints.varray
)
266 | Aast.Hvarray_or_darray _
-> happly_helper (p
, SN.Typehints.varray_or_darray
)
267 | Aast.Hprim prim
-> happly_helper (p
, prim_to_string prim
)
268 | Aast.Hthis
-> happly_helper (p
, SN.Typehints.this
)
269 | Aast.Hnothing
-> happly_helper (p
, SN.Typehints.nothing
)
270 | Aast.Habstr s
-> happly_helper (p
, s
)
271 | Aast.Hpu_access _
-> TC.make None
[]
273 and make_tc_with_flags_if_non_empty_flags
274 ~kind ~tparams ~skipawaitable ~namespace t flags
=
276 hint_to_type_constraint ~kind ~tparams ~skipawaitable ~namespace t
278 let tc_name = TC.name tc in
279 let tc_flags = TC.flags
tc in
280 match (tc_name, tc_flags) with
283 let tc_flags = List.stable_dedup
(flags
@ tc_flags) in
284 TC.make
tc_name tc_flags
286 let add_nullable ~nullable flags
=
288 List.stable_dedup
(TC.Nullable
:: TC.DisplayNullable
:: flags
)
292 let try_add_nullable ~nullable h flags
=
293 add_nullable ~nullable
:(nullable
&& can_be_nullable h
) flags
295 let make_type_info ~tparams ~namespace h
tc_name tc_flags =
296 let type_info_user_type = Some
(fmt_hint ~tparams ~namespace h
) in
297 let type_info_type_constraint = TC.make
tc_name tc_flags in
298 Hhas_type_info.make
type_info_user_type type_info_type_constraint
300 let param_hint_to_type_info
301 ~kind ~skipawaitable ~nullable ~tparams ~namespace h
=
308 | Aast.Happly
(_
, _
:: _
)
309 | Aast.Happly
((_
, "dynamic"), [])
310 | Aast.Happly
((_
, "nonnull"), [])
311 | Aast.Happly
((_
, "mixed"), [])
316 (* I think Happly where id is in tparams is translated into Habstr *)
317 | Aast.Habstr s
when List.mem ~equal
:( = ) tparams s
-> false
318 | Aast.Happly
((_
, id
), _
) when List.mem ~equal
:( = ) tparams id
-> false
321 failwith
"Expected error on Tany in naming: param_hint_to_type_info"
322 (* The following are based on Happly conversions in naming *)
323 | Aast.Harray
(Some _
, Some _
) -> false
324 | Aast.Harray _
-> true
325 | Aast.Hdarray _
-> false
326 | Aast.Hvarray _
-> true
327 | Aast.Hvarray_or_darray _
-> true
328 | Aast.Hprim _
-> true
333 hint_to_type_constraint ~kind ~tparams ~skipawaitable ~namespace h
335 let tc_name = TC.name tc in
336 if is_simple_hint then
337 let tc_flags = try_add_nullable ~nullable h
[TC.HHType
] in
338 make_type_info ~tparams ~namespace h
tc_name tc_flags
340 let tc_flags = TC.flags
tc in
341 let tc_flags = try_add_nullable ~nullable h
tc_flags in
342 make_type_info ~tparams ~namespace h
tc_name tc_flags
344 let hint_to_type_info ~kind ~skipawaitable ~nullable ~tparams ~namespace h
=
347 param_hint_to_type_info
356 hint_to_type_constraint ~kind ~tparams ~skipawaitable ~namespace h
358 let tc_name = TC.name tc in
359 let tc_flags = TC.flags
tc in
361 if (kind
= Return
|| kind
= Property
) && tc_name <> None
then
362 List.stable_dedup
(TC.ExtendedHint
:: tc_flags)
367 if kind
= TypeDef
then
368 add_nullable ~nullable
tc_flags
370 try_add_nullable ~nullable h
tc_flags
372 make_type_info ~tparams ~namespace h
tc_name tc_flags
374 let hint_to_class ~namespace
(h
: Aast.hint
) =
376 | (_
, Aast.Happly
(id
, _
)) ->
377 let fq_id = Hhbc_id.Class.elaborate_id namespace id
in
379 | _
-> Hhbc_id.Class.from_raw_string
"__type_is_not_class__"
381 let emit_type_constraint_for_native_function tparams ret ti
=
382 let user_type = Hhas_type_info.user_type ti
in
384 match (user_type, ret
) with
387 (Some
"HH\\void", [TC.HHType
; TC.ExtendedHint
])
388 | (Some t
, _
) when t
= "HH\\mixed" || t
= "callable" -> (None
, [])
389 | (Some t
, Some ret
) ->
390 let strip_nullable n
= String_utils.lstrip n
"?" in
391 let strip_soft n
= String_utils.lstrip n
"@" in
392 let vanilla_name n
= Hhbc_string_utils.strip_type_list n
in
394 (* Strip twice since we don't know which one is coming first *)
395 Some
(vanilla_name @@ strip_nullable @@ strip_soft @@ strip_nullable t
)
397 let flags = [TC.HHType
; TC.ExtendedHint
] in
398 let rec get_flags (_
, t
) flags =
401 TC.Nullable
:: TC.DisplayNullable
:: get_flags x
flags
402 | Aast.Hsoft x
-> TC.Soft
:: get_flags x
flags
403 | Aast.Haccess _
-> TC.TypeConstant
:: flags
404 | Aast.Happly
((_
, name), _
) when List.mem ~equal
:( = ) tparams
name ->
408 let flags = get_flags ret
flags in
411 let tc = Hhas_type_constraint.make
name flags in
412 Hhas_type_info.make
user_type tc