Tweak `HhasTypeConstant` C++: Replace Option with Maybe
[hiphop-php.git] / hphp / hack / src / hhbc / hhbc_by_ref / emit_type_constant.rs
blob1a8d9fa4be796a3f492ed3fd672d09c189e53df4
1 // Copyright (c) Facebook, Inc. and its affiliates.
2 //
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
6 use ffi::Pair;
7 use hhbc_by_ref_hhbc_id::{class, Id};
8 use hhbc_by_ref_hhbc_string_utils as string_utils;
9 use hhbc_by_ref_instruction_sequence::{unrecoverable, Result};
10 use hhbc_by_ref_options::Options;
11 use hhbc_by_ref_runtime::TypedValue;
12 use naming_special_names_rust::classes;
13 use oxidized::{
14     aast, aast_defs,
15     aast_defs::{Hint, NastShapeInfo, ShapeFieldInfo},
16     ast_defs,
17     ast_defs::ShapeFieldName,
19 use std::collections::BTreeMap;
21 fn get_kind_num(tparams: &[&str], mut p: &str) -> i64 {
22     if tparams.contains(&p) {
23         p = "$$internal$$typevar";
24     };
25     (match p.to_lowercase().as_str() {
26         "hh\\void" => 0,
27         "hh\\int" => 1,
28         "hh\\bool" => 2,
29         "hh\\float" => 3,
30         "hh\\string" => 4,
31         "hh\\resource" => 5,
32         "hh\\num" => 6,
33         "hh\\noreturn" => 8,
34         "hh\\arraykey" => 7,
35         "hh\\mixed" => 9,
36         "tuple" => 10,
37         "$$internal$$fun" => 11,
38         "$$internal$$typevar" | "_" => 13, // corresponds to user OF_GENERIC
39         "shape" => 14,
40         "class" => 15,
41         "interface" => 16,
42         "trait" => 17,
43         "enum" => 18,
44         "hh\\dict" => 19,
45         "hh\\vec" => 20,
46         "hh\\keyset" => 21,
47         "hh\\vec_or_dict" => 22,
48         "hh\\nonnull" => 23,
49         "hh\\darray" => 24,
50         "hh\\varray" => 25,
51         "hh\\varray_or_darray" => 26,
52         "hh\\anyarray" => 27,
53         "hh\\null" => 28,
54         "hh\\nothing" => 29,
55         "hh\\dynamic" => 30,
56         "unresolved" => 101,
57         "$$internal$$typeaccess" => 102,
58         "$$internal$$reifiedtype" => 104,
59         _ => {
60             if p.len() > 4 && p.starts_with("xhp_") {
61                 103
62             } else {
63                 101
64             }
65         }
66     }) as i64
69 fn is_prim(s: &str) -> bool {
70     match s {
71         "HH\\void" | "HH\\int" | "HH\\bool" | "HH\\float" | "HH\\string" | "HH\\resource"
72         | "HH\\num" | "HH\\noreturn" | "HH\\arraykey" | "HH\\mixed" | "HH\\nonnull"
73         | "HH\\null" | "HH\\nothing" | "HH\\dynamic" => true,
74         _ => false,
75     }
78 fn is_resolved_classname(s: &str) -> bool {
79     match s {
80         "HH\\darray"
81         | "HH\\varray"
82         | "HH\\varray_or_darray"
83         | "HH\\vec"
84         | "HH\\dict"
85         | "HH\\keyset"
86         | "HH\\vec_or_dict"
87         | "HH\\AnyArray" => true,
88         _ => false,
89     }
92 #[allow(clippy::needless_lifetimes)]
93 fn shape_field_name<'arena>(alloc: &'arena bumpalo::Bump, sf: &ShapeFieldName) -> (String, bool) {
94     use oxidized::ast_defs::{Id, ShapeFieldName::*};
95     match sf {
96         SFlitInt((_, s)) => (s.to_string(), false),
97         SFlitStr((_, s)) => {
98             (
99                 // FIXME: This is not safe--string literals are binary strings.
100                 // There's no guarantee that they're valid UTF-8.
101                 unsafe { String::from_utf8_unchecked(s.clone().into()) },
102                 false,
103             )
104         }
105         SFclassConst(Id(_, cname), (_, s)) => {
106             let id = class::ClassType::from_ast_name(alloc, &cname);
107             (format!("{}::{}", id.to_raw_string(), s), true)
108         }
109     }
112 fn shape_field_to_pair<'arena>(
113     alloc: &'arena bumpalo::Bump,
114     opts: &Options,
115     tparams: &[&str],
116     targ_map: &BTreeMap<&str, i64>,
117     sfi: &ShapeFieldInfo,
118 ) -> std::result::Result<
119     Pair<TypedValue<'arena>, TypedValue<'arena>>,
120     hhbc_by_ref_instruction_sequence::Error,
121 > {
122     let (name, is_class_const) = shape_field_name(alloc, &sfi.name);
123     let mut r = bumpalo::vec![in alloc;];
124     if is_class_const {
125         r.push(Pair(
126             TypedValue::string("is_cls_cns", alloc),
127             TypedValue::Bool(true),
128         ));
129     };
130     if sfi.optional {
131         r.push(Pair(
132             TypedValue::string("optional_shape_field", alloc),
133             TypedValue::Bool(true),
134         ));
135     };
136     r.push(Pair(
137         TypedValue::string("value", alloc),
138         hint_to_type_constant(alloc, opts, tparams, targ_map, &sfi.hint, false, false)?,
139     ));
140     Ok(Pair(
141         TypedValue::string(name, alloc),
142         TypedValue::mk_dict(r.into_bump_slice()),
143     ))
146 fn shape_info_to_typed_value<'arena>(
147     alloc: &'arena bumpalo::Bump,
148     opts: &Options,
149     tparams: &[&str],
150     targ_map: &BTreeMap<&str, i64>,
151     si: &NastShapeInfo,
152 ) -> std::result::Result<TypedValue<'arena>, hhbc_by_ref_instruction_sequence::Error> {
153     let info = si
154         .field_map
155         .iter()
156         .map(|sfi| shape_field_to_pair(alloc, opts, tparams, targ_map, &sfi))
157         .collect::<Result<Vec<_>>>()?;
158     Ok(TypedValue::mk_dict(
159         alloc.alloc_slice_fill_iter(info.into_iter()),
160     ))
163 fn shape_allows_unknown_fields<'arena>(
164     alloc: &'arena bumpalo::Bump,
165     si: &NastShapeInfo,
166 ) -> Option<Pair<TypedValue<'arena>, TypedValue<'arena>>> {
167     if si.allows_unknown_fields {
168         Some(Pair(
169             TypedValue::string("allows_unknown_fields", alloc),
170             TypedValue::Bool(true),
171         ))
172     } else {
173         None
174     }
177 fn type_constant_access_list<'arena>(
178     alloc: &'arena bumpalo::Bump,
179     sl: &[aast::Sid],
180 ) -> TypedValue<'arena> {
181     let sl_ = alloc.alloc_slice_fill_iter(
182         sl.iter()
183             .map(|ast_defs::Id(_, s)| TypedValue::string(s, alloc)),
184     );
185     TypedValue::mk_vec(sl_)
188 fn resolve_classname<'arena>(
189     alloc: &'arena bumpalo::Bump,
190     tparams: &[&str],
191     mut s: String,
192 ) -> (Option<Pair<TypedValue<'arena>, TypedValue<'arena>>>, String) {
193     let is_tparam = s == "_" || tparams.contains(&s.as_str());
194     if !is_tparam {
195         s = class::ClassType::from_ast_name(alloc, s.as_str()).into()
196     };
197     if is_prim(&s) || is_resolved_classname(&s) {
198         (None, s)
199     } else {
200         let id = if is_tparam { "name" } else { "classname" };
201         (
202             Some(Pair(
203                 TypedValue::string(id, alloc),
204                 TypedValue::string(s.as_str(), alloc),
205             )),
206             s,
207         )
208     }
211 fn get_generic_types<'arena>(
212     alloc: &'arena bumpalo::Bump,
213     opts: &Options,
214     tparams: &[&str],
215     targ_map: &BTreeMap<&str, i64>,
216     hints: &[Hint],
217 ) -> std::result::Result<
218     bumpalo::collections::vec::Vec<'arena, Pair<TypedValue<'arena>, TypedValue<'arena>>>,
219     hhbc_by_ref_instruction_sequence::Error,
220 > {
221     Ok(if hints.is_empty() {
222         bumpalo::vec![in alloc;]
223     } else {
224         bumpalo::vec![in alloc; Pair(
225             TypedValue::string("generic_types", alloc),
226             hints_to_type_constant(alloc, opts, tparams, targ_map, hints)?,
227         )]
228     })
231 fn get_kind<'arena>(
232     alloc: &'arena bumpalo::Bump,
233     tparams: &[&str],
234     s: &str,
235 ) -> bumpalo::collections::vec::Vec<'arena, Pair<TypedValue<'arena>, TypedValue<'arena>>> {
236     bumpalo::vec![in alloc; Pair(
237         TypedValue::string("kind", alloc),
238         TypedValue::Int(get_kind_num(tparams, s)),
239     )]
242 #[allow(clippy::needless_lifetimes)]
243 fn root_to_string<'arena>(alloc: &'arena bumpalo::Bump, s: &str) -> String {
244     if s == "this" {
245         string_utils::prefix_namespace("HH", s)
246     } else {
247         class::ClassType::from_ast_name(alloc, s).into()
248     }
251 fn get_typevars<'arena>(
252     alloc: &'arena bumpalo::Bump,
253     tparams: &[&str],
254 ) -> bumpalo::collections::Vec<'arena, Pair<TypedValue<'arena>, TypedValue<'arena>>> {
255     if tparams.is_empty() {
256         bumpalo::vec![in alloc;]
257     } else {
258         bumpalo::vec![in alloc; (
259             TypedValue::string("typevars", alloc),
260             TypedValue::string(tparams.join(","), alloc),
261         ).into()]
262     }
265 fn hint_to_type_constant_list<'arena>(
266     alloc: &'arena bumpalo::Bump,
267     opts: &Options,
268     tparams: &[&str],
269     targ_map: &BTreeMap<&str, i64>,
270     hint: &Hint,
271 ) -> std::result::Result<
272     bumpalo::collections::Vec<'arena, Pair<TypedValue<'arena>, TypedValue<'arena>>>,
273     hhbc_by_ref_instruction_sequence::Error,
274 > {
275     use aast_defs::Hint_::*;
276     let Hint(_, h) = hint;
277     Ok(match h.as_ref() {
278         Happly(s, hints) => {
279             let ast_defs::Id(_, name) = s;
280             if hints.is_empty() {
281                 if let Some(id) = targ_map.get(name.as_str()) {
282                     let mut r = get_kind(alloc, tparams, "$$internal$$reifiedtype");
283                     r.push(Pair(TypedValue::string("id", alloc), TypedValue::Int(*id)));
284                     return Ok(r);
285                 }
286             }
287             let (classname, s_res) = resolve_classname(alloc, tparams, name.to_owned());
288             let mut r =
289                 if s_res.eq_ignore_ascii_case("tuple") || s_res.eq_ignore_ascii_case("shape") {
290                     get_kind(alloc, tparams, "unresolved")
291                 } else {
292                     get_kind(alloc, tparams, s_res.as_str())
293                 };
294             if let Some(c) = classname {
295                 r.push(c);
296             }
297             if !(name.eq_ignore_ascii_case(classes::CLASS_NAME)
298                 || name.eq_ignore_ascii_case(classes::TYPE_NAME))
299             {
300                 r.append(&mut get_generic_types(
301                     alloc, opts, tparams, targ_map, hints,
302                 )?);
303             };
304             r
305         }
306         Hshape(si) => {
307             let mut r = bumpalo::vec![in alloc;];
308             if let Some(v) = shape_allows_unknown_fields(alloc, si) {
309                 r.push(v);
310             }
311             r.append(&mut get_kind(alloc, tparams, "shape"));
312             r.push(Pair(
313                 TypedValue::string("fields", alloc),
314                 shape_info_to_typed_value(alloc, opts, tparams, targ_map, si)?,
315             ));
316             r
317         }
318         Haccess(Hint(_, h), ids) => match h.as_happly() {
319             Some((root_id, hs)) if hs.is_empty() => {
320                 let mut r = get_kind(alloc, tparams, "$$internal$$typeaccess");
321                 r.push(Pair(
322                     TypedValue::string("root_name", alloc),
323                     TypedValue::string(root_to_string(alloc, &root_id.1).as_str(), alloc),
324                 ));
325                 r.push(Pair(
326                     TypedValue::string("access_list", alloc),
327                     type_constant_access_list(alloc, ids),
328                 ));
329                 r
330             }
331             _ => {
332                 return Err(unrecoverable(
333                     "Structure not translated according to ast_to_nast",
334                 ));
335             }
336         },
337         Hfun(hf) => {
338             let mut kind = get_kind(alloc, tparams, "$$internal$$fun");
339             let single_hint = |name: &str, h| {
340                 hint_to_type_constant(alloc, opts, tparams, targ_map, h, false, false)
341                     .map(|tc| (bumpalo::vec![in alloc; Pair(TypedValue::string(name, alloc), tc)]))
342             };
343             let mut return_type = single_hint("return_type", &hf.return_ty)?;
344             let mut variadic_type = hf.variadic_ty.as_ref().map_or_else(
345                 || Ok(bumpalo::vec![in alloc;]),
346                 |h| single_hint("variadic_type", &h),
347             )?;
348             let mut param_types = bumpalo::vec![in alloc; Pair(
349                 TypedValue::string("param_types", alloc),
350                 hints_to_type_constant(alloc, opts, tparams, targ_map, &hf.param_tys)?,
351             )];
352             param_types.append(&mut variadic_type);
353             return_type.append(&mut param_types);
354             kind.append(&mut return_type);
355             kind
356         }
357         Htuple(hints) => {
358             let mut kind = get_kind(alloc, tparams, "tuple");
359             let mut elem_types = bumpalo::vec![in alloc; Pair(
360                 TypedValue::string("elem_types", alloc),
361                 hints_to_type_constant(alloc, opts, tparams, targ_map, hints)?,
362             )];
363             kind.append(&mut elem_types);
364             kind
365         }
366         Hoption(h) => {
367             let mut r = bumpalo::vec![in alloc; Pair(TypedValue::string("nullable", alloc), TypedValue::Bool(true))];
368             r.append(&mut hint_to_type_constant_list(
369                 alloc, opts, tparams, targ_map, h,
370             )?);
371             r
372         }
373         Hsoft(h) => {
374             let mut r = bumpalo::vec![in alloc; Pair(TypedValue::string("soft", alloc), TypedValue::Bool(true))];
375             r.append(&mut hint_to_type_constant_list(
376                 alloc, opts, tparams, targ_map, h,
377             )?);
378             r
379         }
380         Hlike(h) => {
381             let mut r = bumpalo::vec![in alloc; Pair(TypedValue::string("like", alloc), TypedValue::Bool(true))];
382             r.append(&mut hint_to_type_constant_list(
383                 alloc, opts, tparams, targ_map, h,
384             )?);
385             r
386         }
387         // TODO(coeffects) actually handle emission of context constants
388         Hintersection(_) => bumpalo::vec![in alloc; (
389             TypedValue::string("kind", alloc),
390             TypedValue::Int(get_kind_num(tparams, "HH\\mixed")),
391         ).into()],
392         _ => return Err(unrecoverable("Hints not available on the original AST")),
393     })
396 pub fn hint_to_type_constant<'arena>(
397     alloc: &'arena bumpalo::Bump,
398     opts: &Options,
399     tparams: &[&str],
400     targ_map: &BTreeMap<&str, i64>,
401     hint: &Hint,
402     is_typedef: bool,
403     is_opaque: bool,
404 ) -> std::result::Result<TypedValue<'arena>, hhbc_by_ref_instruction_sequence::Error> {
405     let mut tconsts = hint_to_type_constant_list(alloc, opts, tparams, targ_map, &hint)?;
406     if is_typedef {
407         tconsts.append(&mut get_typevars(alloc, tparams));
408     };
409     if is_opaque {
410         tconsts.push((TypedValue::string("opaque", alloc), TypedValue::Bool(true)).into())
411     };
412     Ok(TypedValue::mk_dict(tconsts.into_bump_slice()))
415 fn hints_to_type_constant<'arena>(
416     alloc: &'arena bumpalo::Bump,
417     opts: &Options,
418     tparams: &[&str],
419     targ_map: &BTreeMap<&str, i64>,
420     hints: &[Hint],
421 ) -> std::result::Result<TypedValue<'arena>, hhbc_by_ref_instruction_sequence::Error> {
422     hints
423         .iter()
424         .map(|h| hint_to_type_constant(alloc, opts, tparams, targ_map, h, false, false))
425         .collect::<Result<Vec<_>>>()
426         .map(|hs| TypedValue::mk_vec(alloc.alloc_slice_fill_iter(hs.into_iter())))