don't pass namespace_env to ast_constant_folder
[hiphop-php.git] / hphp / hack / src / hhbc / hhbc_by_ref / emit_xhp.rs
blob5a5abf55eb5d23485a0ac25f98e50f9f8db47f2c
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 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>,
20     class: &'a Class_,
21     class_is_const: bool,
22 ) -> Result<Option<HhasProperty<'arena>>> {
23     let initial_value = Some(Expr(Pos::make_none(), Expr_::mk_null()));
24     let property = emit_property::from_ast(
25         alloc,
26         emitter,
27         class,
28         &[],
29         class_is_const,
30         emit_property::FromAstArgs {
31             initial_value: &initial_value,
32             visibility: Visibility::Private,
33             is_static: true,
34             is_abstract: false,
35             is_readonly: false,
36             typehint: None,
37             doc_comment: None,
38             user_attributes: &[],
39             id: &ast_defs::Id(Pos::make_none(), "__xhpAttributeDeclarationCache".into()),
40         },
41     )?;
42     Ok(Some(property))
45 pub fn from_attribute_declaration<'a, 'arena>(
46     alloc: &'arena bumpalo::Bump,
47     emitter: &mut Emitter<'arena>,
48     class: &'a Class_,
49     xal: &[HhasXhpAttribute],
50     xual: &[Hint],
51 ) -> Result<HhasMethod<'arena>> {
52     let id_from_str = |s: &str| Expr_::mk_id(ast_defs::Id(Pos::make_none(), s.into()));
54     let mk_var_r = || {
55         mk_expr(Expr_::mk_lvar(Lid(
56             Pos::make_none(),
57             local_id::make_unscoped("$r"),
58         )))
59     };
60     let mk_cache = || {
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())),
65             false,
66         ))
67     };
68     let token1 = mk_stmt(Stmt_::mk_expr(mk_expr(Expr_::mk_binop(
69         Bop::Eq(None),
70         mk_var_r(),
71         mk_cache(),
72     ))));
73     // if ($r === null) {
74     //   self::$__xhpAttributeDeclarationCache =
75     //       __SystemLib\\merge_xhp_attr_declarations(
76     //          parent::__xhpAttributeDeclaration(),
77     //          attributes
78     //        );
79     //   $r = self::$__xhpAttributeDeclarationCache;
80     // }
81     let cond = mk_expr(Expr_::mk_binop(
82         Bop::Eqeqeq,
83         mk_var_r(),
84         mk_expr(Expr_::mk_null()),
85     ));
86     let mut args = vec![mk_expr(Expr_::mk_call(
87         mk_expr(Expr_::mk_class_const(
88             ClassId(
89                 Pos::make_none(),
90                 ClassId_::CIexpr(mk_expr(id_from_str("parent"))),
91             ),
92             (Pos::make_none(), "__xhpAttributeDeclaration".into()),
93         )),
94         vec![],
95         vec![],
96         None,
97     ))];
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(
104                         ClassId(
105                             Pos::make_none(),
106                             ClassId_::CIexpr(mk_expr(Expr_::mk_id(ast_defs::Id(
107                                 Pos::make_none(),
108                                 s,
109                             )))),
110                         ),
111                         (Pos::make_none(), "__xhpAttributeDeclaration".into()),
112                     )),
113                     vec![],
114                     vec![],
115                     None,
116                 ));
117                 args.push(arg);
118             }
119             _ => return Err(unrecoverable("Xhp use attribute - unexpected attribute")),
120         }
121     }
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")),
125         vec![],
126         args,
127         None,
128     ));
129     let set_cache = mk_stmt(Stmt_::mk_expr(mk_expr(Expr_::mk_binop(
130         Bop::Eq(None),
131         mk_cache(),
132         array_merge_call,
133     ))));
134     let set_r = mk_stmt(Stmt_::mk_expr(mk_expr(Expr_::mk_binop(
135         Bop::Eq(None),
136         mk_var_r(),
137         mk_cache(),
138     ))));
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(
143         alloc,
144         emitter,
145         class,
146         None,
147         "__xhpAttributeDeclaration",
148         false,
149         false,
150         true,
151         Visibility::Protected,
152         body,
153     )
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(
165         alloc,
166         emitter,
167         ast_class,
168         Some((*pos).clone()),
169         "__xhpChildrenDeclaration",
170         false,
171         false,
172         false,
173         Visibility::Protected,
174         body,
175     )
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(
187         alloc,
188         emitter,
189         ast_class,
190         Some((*pos).clone()),
191         "__xhpCategoryDeclaration",
192         false,
193         false,
194         false,
195         Visibility::Protected,
196         body,
197     )
200 fn get_category_array(categories: &[&String]) -> Expr_ {
201     // TODO: is this always 1?
202     Expr_::mk_darray(
203         None,
204         categories
205             .iter()
206             .map(|&s| {
207                 (
208                     mk_expr(Expr_::String(s.clone().into())),
209                     mk_expr(Expr_::Int("1".into())),
210                 )
211             })
212             .collect(),
213     )
216 fn emit_xhp_children_array(children: &[&XhpChild]) -> Result<Expr_> {
217     match children {
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()))
225                 } else {
226                     emit_xhp_children_paren_expr(c)
227                 }
228             }
229             None => emit_xhp_children_paren_expr(c),
230         },
231         _ => Err(unrecoverable(
232             "HHVM does not support multiple children declarations",
233         )),
234     }
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
241         .as_child_unary()
242         .map(|(c, op)| (c.as_child_list(), op))
243     {
244         (l, xhp_child_op_to_int(Some(op)))
245     } else {
246         return Err(unrecoverable(concat!(
247             "Xhp children declarations cannot be plain id, ",
248             "plain binary or unary without an inside list"
249         )));
250     };
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_> {
256     match children {
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)?,
264             );
265             cs.iter().fold(first_two, |acc, c| {
266                 get_array3(Expr_::Int("4".into()), acc?, emit_xhp_child_decl(unary, c)?)
267             })
268         }
269     }
272 fn emit_xhp_child_decl(unary: &str, child: &XhpChild) -> Result<Expr_> {
273     match child {
274         XhpChild::ChildList(l) => get_array3(
275             Expr_::Int(unary.into()),
276             Expr_::Int("5".into()),
277             emit_xhp_children_decl_expr("0", l)?,
278         ),
279         XhpChild::ChildName(ast_defs::Id(_, name)) => {
280             if name.eq_ignore_ascii_case("any") {
281                 get_array3(
282                     Expr_::Int(unary.into()),
283                     Expr_::Int("1".into()),
284                     Expr_::Null,
285                 )
286             } else if name.eq_ignore_ascii_case("pcdata") {
287                 get_array3(
288                     Expr_::Int(unary.into()),
289                     Expr_::Int("2".into()),
290                     Expr_::Null,
291                 )
292             } else if let Some(end) = name.strip_prefix('%') {
293                 get_array3(
294                     Expr_::Int(unary.into()),
295                     Expr_::Int("4".into()),
296                     Expr_::String(string_utils::mangle(end.into()).into()),
297                 )
298             } else {
299                 get_array3(
300                     Expr_::Int(unary.into()),
301                     Expr_::Int("3".into()),
302                     Expr_::String(string_utils::mangle(name.into()).into()),
303                 )
304             }
305         }
306         XhpChild::ChildUnary(c, op) => {
307             emit_xhp_child_decl(xhp_child_op_to_int(Some(op)).to_string().as_str(), &**c)
308         }
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)?,
313         ),
314     }
317 fn get_array3(i0: Expr_, i1: Expr_, i2: Expr_) -> Result<Expr_> {
318     Ok(Expr_::mk_varray(
319         None,
320         vec![mk_expr(i0), mk_expr(i1), mk_expr(i2)],
321     ))
324 fn xhp_child_op_to_int(op: Option<&XhpChildOp>) -> usize {
325     match op {
326         None => 0,
327         Some(XhpChildOp::ChildStar) => 1,
328         Some(XhpChildOp::ChildQuestion) => 2,
329         Some(XhpChildOp::ChildPlus) => 3,
330     }
333 fn emit_xhp_attribute_array<'arena>(
334     alloc: &'arena bumpalo::Bump,
335     xal: &[HhasXhpAttribute],
336 ) -> Result<Expr> {
337     fn hint_to_num(id: &str) -> usize {
338         match id {
339             "HH\\string" => 1,
340             "HH\\bool" => 2,
341             "HH\\int" => 3,
342             "array" => 4,
343             "var" | "HH\\mixed" => 6,
344             "enum" => 7,
345             "HH\\float" => 8,
346             "callable" => 9,
347             // Regular class names are type 5
348             _ => 5,
349         }
350     }
351     fn get_enum_attributes(enum_opt: Option<&Vec<Expr>>) -> Result<Expr> {
352         match enum_opt {
353             None => Err(unrecoverable(
354                 "Xhp attribute that's supposed to be an enum but not really",
355             )),
356             Some(es) => Ok(mk_expr(Expr_::mk_varray(None, es.to_vec()))),
357         }
358     }
359     fn get_attribute_array_values<'arena>(
360         alloc: &'arena bumpalo::Bump,
361         id: &str,
362         enum_opt: Option<&Vec<Expr>>,
363     ) -> Result<(Expr, Expr)> {
364         let id = class::Type::from_ast_name_and_mangle(alloc, id)
365             .to_raw_string()
366             .to_string();
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),
373         };
374         Ok((class_name, type_ident))
375     }
376     fn extract_from_hint<'arena>(
377         alloc: &'arena bumpalo::Bump,
378         hint: &Hint,
379         enum_opt: Option<&Vec<Expr>>,
380     ) -> Result<(Expr, Expr)> {
381         use naming_special_names_rust::fb;
382         match &*(hint.1) {
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),
386             },
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)
390             }
391             _ => Err(unrecoverable(
392                 "There are no other possible xhp attribute hints",
393             )),
394         }
395     }
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),
404         };
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)
409             }
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),
413         }?;
414         let is_required = mk_expr(Expr_::Int(
415             (if xa.is_required() { "1" } else { "0" }).into(),
416         ));
417         Ok(vec![hint, class_name, expr, is_required])
418     }
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(),
425         ));
426         let v = mk_expr(Expr_::mk_varray(None, inner_array(alloc, xa)?));
427         Ok((k, v))
428     }
429     let xal_arr = xal
430         .iter()
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>,
439     class: &'a Class_,
440     pos: Option<Pos>,
441     name: &str,
442     final_: bool,
443     abstract_: bool,
444     static_: bool,
445     visibility: Visibility,
446     ast: Block,
447 ) -> Result<HhasMethod<'arena>> {
448     let meth = Method_ {
449         span: pos.unwrap_or_else(Pos::make_none),
450         annotation: (),
451         final_,
452         abstract_,
453         static_,
454         readonly_this: false, // TODO readonly emitter
455         visibility,
456         name: ast_defs::Id(Pos::make_none(), name.into()),
457         tparams: vec![],
458         where_constraints: vec![],
459         variadic: FunVariadicity::FVnonVariadic,
460         params: vec![],
461         ctxs: None,        // TODO(T70095684)
462         unsafe_ctxs: None, // TODO(T70095684)
463         body: FuncBody {
464             ast,
465             annotation: (),
466         },
467         fun_kind: ast_defs::FunKind::FSync,
468         user_attributes: vec![],
469         readonly_ret: None, // TODO readonly emitter
470         ret: TypeHint((), None),
471         external: false,
472         doc_comment: None,
473     };
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_)