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.
6 use hhbc_by_ref_emit_method as emit_method;
7 use hhbc_by_ref_emit_property as emit_property;
8 use hhbc_by_ref_env::emitter::Emitter;
9 use hhbc_by_ref_hhas_method::HhasMethod;
10 use hhbc_by_ref_hhas_property::HhasProperty;
11 use hhbc_by_ref_hhas_xhp_attribute::HhasXhpAttribute;
12 use hhbc_by_ref_hhbc_id::{class, Id};
13 use hhbc_by_ref_hhbc_string_utils as string_utils;
14 use hhbc_by_ref_instruction_sequence::{unrecoverable, Result};
15 use oxidized::{ast::*, ast_defs, local_id, pos::Pos};
17 pub fn properties_for_cache<'a, 'arena>(
18 alloc: &'arena bumpalo::Bump,
19 emitter: &mut Emitter<'arena>,
22 ) -> Result<Option<HhasProperty<'arena>>> {
23 let initial_value = Some(Expr(Pos::make_none(), Expr_::mk_null()));
24 let property = emit_property::from_ast(
30 emit_property::FromAstArgs {
31 initial_value: &initial_value,
32 visibility: Visibility::Private,
39 id: &ast_defs::Id(Pos::make_none(), "__xhpAttributeDeclarationCache".into()),
45 pub fn from_attribute_declaration<'a, 'arena>(
46 alloc: &'arena bumpalo::Bump,
47 emitter: &mut Emitter<'arena>,
49 xal: &[HhasXhpAttribute],
51 ) -> Result<HhasMethod<'arena>> {
52 let id_from_str = |s: &str| Expr_::mk_id(ast_defs::Id(Pos::make_none(), s.into()));
55 mk_expr(Expr_::mk_lvar(Lid(
57 local_id::make_unscoped("$r"),
61 let self_ = mk_expr(id_from_str("self"));
62 mk_expr(Expr_::mk_class_get(
63 ClassId(Pos::make_none(), ClassId_::CIexpr(self_)),
64 ClassGetExpr::CGstring((Pos::make_none(), "$__xhpAttributeDeclarationCache".into())),
68 let token1 = mk_stmt(Stmt_::mk_expr(mk_expr(Expr_::mk_binop(
74 // self::$__xhpAttributeDeclarationCache =
75 // __SystemLib\\merge_xhp_attr_declarations(
76 // parent::__xhpAttributeDeclaration(),
79 // $r = self::$__xhpAttributeDeclarationCache;
81 let cond = mk_expr(Expr_::mk_binop(
84 mk_expr(Expr_::mk_null()),
86 let mut args = vec![mk_expr(Expr_::mk_call(
87 mk_expr(Expr_::mk_class_const(
90 ClassId_::CIexpr(mk_expr(id_from_str("parent"))),
92 (Pos::make_none(), "__xhpAttributeDeclaration".into()),
98 for xua in xual.iter() {
99 match xua.1.as_happly() {
100 Some((ast_defs::Id(_, s), hints)) if hints.is_empty() => {
101 let s = string_utils::mangle(string_utils::strip_global_ns(s).into());
102 let arg = mk_expr(Expr_::mk_call(
103 mk_expr(Expr_::mk_class_const(
106 ClassId_::CIexpr(mk_expr(Expr_::mk_id(ast_defs::Id(
111 (Pos::make_none(), "__xhpAttributeDeclaration".into()),
119 _ => return Err(unrecoverable("Xhp use attribute - unexpected attribute")),
122 args.push(emit_xhp_attribute_array(alloc, xal)?);
123 let array_merge_call = mk_expr(Expr_::mk_call(
124 mk_expr(id_from_str("__SystemLib\\merge_xhp_attr_declarations")),
129 let set_cache = mk_stmt(Stmt_::mk_expr(mk_expr(Expr_::mk_binop(
134 let set_r = mk_stmt(Stmt_::mk_expr(mk_expr(Expr_::mk_binop(
139 let token2 = mk_stmt(Stmt_::mk_if(cond, vec![set_cache, set_r], vec![]));
140 let token3 = mk_stmt(Stmt_::mk_return(Some(mk_var_r()))); // return $r;
141 let body = vec![token1, token2, token3];
142 from_xhp_attribute_declaration_method(
147 "__xhpAttributeDeclaration",
151 Visibility::Protected,
156 pub fn from_children_declaration<'a, 'arena>(
157 alloc: &'arena bumpalo::Bump,
158 emitter: &mut Emitter<'arena>,
159 ast_class: &'a Class_,
160 (pos, children): &(&ast_defs::Pos, Vec<&XhpChild>),
161 ) -> Result<HhasMethod<'arena>> {
162 let children_arr = mk_expr(emit_xhp_children_array(children)?);
163 let body = vec![Stmt((*pos).clone(), Stmt_::mk_return(Some(children_arr)))];
164 from_xhp_attribute_declaration_method(
168 Some((*pos).clone()),
169 "__xhpChildrenDeclaration",
173 Visibility::Protected,
178 pub fn from_category_declaration<'a, 'arena>(
179 alloc: &'arena bumpalo::Bump,
180 emitter: &mut Emitter<'arena>,
181 ast_class: &'a Class_,
182 (pos, categories): &(&ast_defs::Pos, Vec<&String>),
183 ) -> Result<HhasMethod<'arena>> {
184 let category_arr = mk_expr(get_category_array(categories));
185 let body = vec![mk_stmt(Stmt_::mk_return(Some(category_arr)))];
186 from_xhp_attribute_declaration_method(
190 Some((*pos).clone()),
191 "__xhpCategoryDeclaration",
195 Visibility::Protected,
200 fn get_category_array(categories: &[&String]) -> Expr_ {
201 // TODO: is this always 1?
208 mk_expr(Expr_::String(s.clone().into())),
209 mk_expr(Expr_::Int("1".into())),
216 fn emit_xhp_children_array(children: &[&XhpChild]) -> Result<Expr_> {
218 [] => Ok(Expr_::mk_int("0".into())),
219 [c] => match c.as_child_name() {
220 Some(ast_defs::Id(_, n)) => {
221 if n.eq_ignore_ascii_case("empty") {
222 Ok(Expr_::mk_int("0".into()))
223 } else if n.eq_ignore_ascii_case("any") {
224 Ok(Expr_::mk_int("1".into()))
226 emit_xhp_children_paren_expr(c)
229 None => emit_xhp_children_paren_expr(c),
231 _ => Err(unrecoverable(
232 "HHVM does not support multiple children declarations",
237 fn emit_xhp_children_paren_expr(child: &XhpChild) -> Result<Expr_> {
238 let (children, op_num) = if let Some(l) = child.as_child_list() {
239 (l, xhp_child_op_to_int(None))
240 } else if let Some((Some(l), op)) = child
242 .map(|(c, op)| (c.as_child_list(), op))
244 (l, xhp_child_op_to_int(Some(op)))
246 return Err(unrecoverable(concat!(
247 "Xhp children declarations cannot be plain id, ",
248 "plain binary or unary without an inside list"
251 let arr = emit_xhp_children_decl_expr("0", children)?;
252 get_array3(Expr_::Int(op_num.to_string()), Expr_::Int("5".into()), arr)
255 fn emit_xhp_children_decl_expr(unary: &str, children: &[XhpChild]) -> Result<Expr_> {
257 [] => Err(unrecoverable("xhp children: unexpected empty list")),
258 [c] => emit_xhp_child_decl(unary, c),
259 [c1, c2, cs @ ..] => {
260 let first_two = get_array3(
261 Expr_::Int("4".into()),
262 emit_xhp_child_decl(unary, c1)?,
263 emit_xhp_child_decl(unary, c2)?,
265 cs.iter().fold(first_two, |acc, c| {
266 get_array3(Expr_::Int("4".into()), acc?, emit_xhp_child_decl(unary, c)?)
272 fn emit_xhp_child_decl(unary: &str, child: &XhpChild) -> Result<Expr_> {
274 XhpChild::ChildList(l) => get_array3(
275 Expr_::Int(unary.into()),
276 Expr_::Int("5".into()),
277 emit_xhp_children_decl_expr("0", l)?,
279 XhpChild::ChildName(ast_defs::Id(_, name)) => {
280 if name.eq_ignore_ascii_case("any") {
282 Expr_::Int(unary.into()),
283 Expr_::Int("1".into()),
286 } else if name.eq_ignore_ascii_case("pcdata") {
288 Expr_::Int(unary.into()),
289 Expr_::Int("2".into()),
292 } else if let Some(end) = name.strip_prefix('%') {
294 Expr_::Int(unary.into()),
295 Expr_::Int("4".into()),
296 Expr_::String(string_utils::mangle(end.into()).into()),
300 Expr_::Int(unary.into()),
301 Expr_::Int("3".into()),
302 Expr_::String(string_utils::mangle(name.into()).into()),
306 XhpChild::ChildUnary(c, op) => {
307 emit_xhp_child_decl(xhp_child_op_to_int(Some(op)).to_string().as_str(), &**c)
309 XhpChild::ChildBinary(c1, c2) => get_array3(
310 Expr_::Int("5".into()),
311 emit_xhp_child_decl(unary, &**c1)?,
312 emit_xhp_child_decl(unary, &**c2)?,
317 fn get_array3(i0: Expr_, i1: Expr_, i2: Expr_) -> Result<Expr_> {
320 vec![mk_expr(i0), mk_expr(i1), mk_expr(i2)],
324 fn xhp_child_op_to_int(op: Option<&XhpChildOp>) -> usize {
327 Some(XhpChildOp::ChildStar) => 1,
328 Some(XhpChildOp::ChildQuestion) => 2,
329 Some(XhpChildOp::ChildPlus) => 3,
333 fn emit_xhp_attribute_array<'arena>(
334 alloc: &'arena bumpalo::Bump,
335 xal: &[HhasXhpAttribute],
337 fn hint_to_num(id: &str) -> usize {
343 "var" | "HH\\mixed" => 6,
347 // Regular class names are type 5
351 fn get_enum_attributes(enum_opt: Option<&Vec<Expr>>) -> Result<Expr> {
353 None => Err(unrecoverable(
354 "Xhp attribute that's supposed to be an enum but not really",
356 Some(es) => Ok(mk_expr(Expr_::mk_varray(None, es.to_vec()))),
359 fn get_attribute_array_values<'arena>(
360 alloc: &'arena bumpalo::Bump,
362 enum_opt: Option<&Vec<Expr>>,
363 ) -> Result<(Expr, Expr)> {
364 let id = class::Type::from_ast_name_and_mangle(alloc, id)
367 let type_ = hint_to_num(&id);
368 let type_ident = mk_expr(Expr_::Int(type_.to_string()));
369 let class_name = match type_ {
370 5 => mk_expr(Expr_::String(id.into())),
371 7 => get_enum_attributes(enum_opt)?,
372 _ => mk_expr(Expr_::Null),
374 Ok((class_name, type_ident))
376 fn extract_from_hint<'arena>(
377 alloc: &'arena bumpalo::Bump,
379 enum_opt: Option<&Vec<Expr>>,
380 ) -> Result<(Expr, Expr)> {
381 use naming_special_names_rust::fb;
383 Hint_::Happly(ast_defs::Id(_, inc), hs) if inc == fb::INCORRECT_TYPE => match &hs[..] {
384 [h] => extract_from_hint(alloc, h, enum_opt),
385 _ => get_attribute_array_values(alloc, inc, enum_opt),
387 Hint_::Hlike(h) | Hint_::Hoption(h) => extract_from_hint(alloc, h, enum_opt),
388 Hint_::Happly(ast_defs::Id(_, id), _) => {
389 get_attribute_array_values(alloc, id, enum_opt)
391 _ => Err(unrecoverable(
392 "There are no other possible xhp attribute hints",
396 fn inner_array<'arena>(
397 alloc: &'arena bumpalo::Bump,
398 xa: &HhasXhpAttribute,
399 ) -> Result<Vec<Expr>> {
400 let enum_opt = xa.maybe_enum.map(|(_, es)| es);
401 let expr = match &(xa.class_var).expr {
402 Some(e) => e.clone(),
403 None => mk_expr(Expr_::Null),
405 let (class_name, hint) = match &xa.type_ {
406 // attribute declared with the var identifier - we treat it as mixed
407 None if enum_opt.is_none() => {
408 get_attribute_array_values(alloc, "\\HH\\mixed", enum_opt)
410 None => get_attribute_array_values(alloc, "enum", enum_opt),
411 // As it turns out, if there is a type list, HHVM discards it
412 Some(h) => extract_from_hint(alloc, h, enum_opt),
414 let is_required = mk_expr(Expr_::Int(
415 (if xa.is_required() { "1" } else { "0" }).into(),
417 Ok(vec![hint, class_name, expr, is_required])
419 fn emit_xhp_attribute<'arena>(
420 alloc: &'arena bumpalo::Bump,
421 xa: &HhasXhpAttribute,
422 ) -> Result<(Expr, Expr)> {
423 let k = mk_expr(Expr_::String(
424 string_utils::clean(&((xa.class_var).id).1).into(),
426 let v = mk_expr(Expr_::mk_varray(None, inner_array(alloc, xa)?));
431 .map(|x| emit_xhp_attribute(alloc, x))
432 .collect::<Result<Vec<_>>>()?;
433 Ok(mk_expr(Expr_::mk_darray(None, xal_arr)))
436 fn from_xhp_attribute_declaration_method<'a, 'arena>(
437 alloc: &'arena bumpalo::Bump,
438 emitter: &mut Emitter<'arena>,
445 visibility: Visibility,
447 ) -> Result<HhasMethod<'arena>> {
449 span: pos.unwrap_or_else(Pos::make_none),
454 readonly_this: false, // TODO readonly emitter
456 name: ast_defs::Id(Pos::make_none(), name.into()),
458 where_constraints: vec![],
459 variadic: FunVariadicity::FVnonVariadic,
461 ctxs: None, // TODO(T70095684)
462 unsafe_ctxs: None, // TODO(T70095684)
467 fun_kind: ast_defs::FunKind::FSync,
468 user_attributes: vec![],
469 readonly_ret: None, // TODO readonly emitter
470 ret: TypeHint((), None),
474 emit_method::from_ast(alloc, emitter, class, meth)
477 fn mk_expr(expr_: Expr_) -> Expr {
478 Expr(Pos::make_none(), expr_)
481 fn mk_stmt(stmt_: Stmt_) -> Stmt {
482 Stmt(Pos::make_none(), stmt_)