1 // Copyright (c) Facebook, Inc. and its affiliates.
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
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;
15 aast_defs::{Hint, NastShapeInfo, ShapeFieldInfo},
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";
25 (match p.to_lowercase().as_str() {
37 "$$internal$$fun" => 11,
38 "$$internal$$typevar" | "_" => 13, // corresponds to user OF_GENERIC
47 "hh\\vec_or_dict" => 22,
51 "hh\\varray_or_darray" => 26,
57 "$$internal$$typeaccess" => 102,
58 "$$internal$$reifiedtype" => 104,
60 if p.len() > 4 && p.starts_with("xhp_") {
69 fn is_prim(s: &str) -> bool {
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,
78 fn is_resolved_classname(s: &str) -> bool {
82 | "HH\\varray_or_darray"
87 | "HH\\AnyArray" => true,
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::*};
96 SFlitInt((_, s)) => (s.to_string(), false),
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()) },
105 SFclassConst(Id(_, cname), (_, s)) => {
106 let id = class::ClassType::from_ast_name(alloc, &cname);
107 (format!("{}::{}", id.to_raw_string(), s), true)
112 fn shape_field_to_pair<'arena>(
113 alloc: &'arena bumpalo::Bump,
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,
122 let (name, is_class_const) = shape_field_name(alloc, &sfi.name);
123 let mut r = bumpalo::vec![in alloc;];
126 TypedValue::string("is_cls_cns", alloc),
127 TypedValue::Bool(true),
132 TypedValue::string("optional_shape_field", alloc),
133 TypedValue::Bool(true),
137 TypedValue::string("value", alloc),
138 hint_to_type_constant(alloc, opts, tparams, targ_map, &sfi.hint, false, false)?,
141 TypedValue::string(name, alloc),
142 TypedValue::mk_dict(r.into_bump_slice()),
146 fn shape_info_to_typed_value<'arena>(
147 alloc: &'arena bumpalo::Bump,
150 targ_map: &BTreeMap<&str, i64>,
152 ) -> std::result::Result<TypedValue<'arena>, hhbc_by_ref_instruction_sequence::Error> {
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()),
163 fn shape_allows_unknown_fields<'arena>(
164 alloc: &'arena bumpalo::Bump,
166 ) -> Option<Pair<TypedValue<'arena>, TypedValue<'arena>>> {
167 if si.allows_unknown_fields {
169 TypedValue::string("allows_unknown_fields", alloc),
170 TypedValue::Bool(true),
177 fn type_constant_access_list<'arena>(
178 alloc: &'arena bumpalo::Bump,
180 ) -> TypedValue<'arena> {
181 let sl_ = alloc.alloc_slice_fill_iter(
183 .map(|ast_defs::Id(_, s)| TypedValue::string(s, alloc)),
185 TypedValue::mk_vec(sl_)
188 fn resolve_classname<'arena>(
189 alloc: &'arena bumpalo::Bump,
192 ) -> (Option<Pair<TypedValue<'arena>, TypedValue<'arena>>>, String) {
193 let is_tparam = s == "_" || tparams.contains(&s.as_str());
195 s = class::ClassType::from_ast_name(alloc, s.as_str()).into()
197 if is_prim(&s) || is_resolved_classname(&s) {
200 let id = if is_tparam { "name" } else { "classname" };
203 TypedValue::string(id, alloc),
204 TypedValue::string(s.as_str(), alloc),
211 fn get_generic_types<'arena>(
212 alloc: &'arena bumpalo::Bump,
215 targ_map: &BTreeMap<&str, i64>,
217 ) -> std::result::Result<
218 bumpalo::collections::vec::Vec<'arena, Pair<TypedValue<'arena>, TypedValue<'arena>>>,
219 hhbc_by_ref_instruction_sequence::Error,
221 Ok(if hints.is_empty() {
222 bumpalo::vec![in alloc;]
224 bumpalo::vec![in alloc; Pair(
225 TypedValue::string("generic_types", alloc),
226 hints_to_type_constant(alloc, opts, tparams, targ_map, hints)?,
232 alloc: &'arena bumpalo::Bump,
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)),
242 #[allow(clippy::needless_lifetimes)]
243 fn root_to_string<'arena>(alloc: &'arena bumpalo::Bump, s: &str) -> String {
245 string_utils::prefix_namespace("HH", s)
247 class::ClassType::from_ast_name(alloc, s).into()
251 fn get_typevars<'arena>(
252 alloc: &'arena bumpalo::Bump,
254 ) -> bumpalo::collections::Vec<'arena, Pair<TypedValue<'arena>, TypedValue<'arena>>> {
255 if tparams.is_empty() {
256 bumpalo::vec![in alloc;]
258 bumpalo::vec![in alloc; (
259 TypedValue::string("typevars", alloc),
260 TypedValue::string(tparams.join(","), alloc),
265 fn hint_to_type_constant_list<'arena>(
266 alloc: &'arena bumpalo::Bump,
269 targ_map: &BTreeMap<&str, i64>,
271 ) -> std::result::Result<
272 bumpalo::collections::Vec<'arena, Pair<TypedValue<'arena>, TypedValue<'arena>>>,
273 hhbc_by_ref_instruction_sequence::Error,
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)));
287 let (classname, s_res) = resolve_classname(alloc, tparams, name.to_owned());
289 if s_res.eq_ignore_ascii_case("tuple") || s_res.eq_ignore_ascii_case("shape") {
290 get_kind(alloc, tparams, "unresolved")
292 get_kind(alloc, tparams, s_res.as_str())
294 if let Some(c) = classname {
297 if !(name.eq_ignore_ascii_case(classes::CLASS_NAME)
298 || name.eq_ignore_ascii_case(classes::TYPE_NAME))
300 r.append(&mut get_generic_types(
301 alloc, opts, tparams, targ_map, hints,
307 let mut r = bumpalo::vec![in alloc;];
308 if let Some(v) = shape_allows_unknown_fields(alloc, si) {
311 r.append(&mut get_kind(alloc, tparams, "shape"));
313 TypedValue::string("fields", alloc),
314 shape_info_to_typed_value(alloc, opts, tparams, targ_map, si)?,
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");
322 TypedValue::string("root_name", alloc),
323 TypedValue::string(root_to_string(alloc, &root_id.1).as_str(), alloc),
326 TypedValue::string("access_list", alloc),
327 type_constant_access_list(alloc, ids),
332 return Err(unrecoverable(
333 "Structure not translated according to ast_to_nast",
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)]))
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),
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)?,
352 param_types.append(&mut variadic_type);
353 return_type.append(&mut param_types);
354 kind.append(&mut return_type);
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)?,
363 kind.append(&mut elem_types);
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,
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,
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,
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")),
392 _ => return Err(unrecoverable("Hints not available on the original AST")),
396 pub fn hint_to_type_constant<'arena>(
397 alloc: &'arena bumpalo::Bump,
400 targ_map: &BTreeMap<&str, i64>,
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)?;
407 tconsts.append(&mut get_typevars(alloc, tparams));
410 tconsts.push((TypedValue::string("opaque", alloc), TypedValue::Bool(true)).into())
412 Ok(TypedValue::mk_dict(tconsts.into_bump_slice()))
415 fn hints_to_type_constant<'arena>(
416 alloc: &'arena bumpalo::Bump,
419 targ_map: &BTreeMap<&str, i64>,
421 ) -> std::result::Result<TypedValue<'arena>, hhbc_by_ref_instruction_sequence::Error> {
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())))