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 env::emitter::Emitter;
7 use error::{Error, Result};
8 use global_state::{ClosureEnclosingClassInfo, GlobalState};
9 use hack_macro::hack_expr;
11 use hhbc::hhas_coeffects::HhasCoeffects;
12 use hhbc_string_utils as string_utils;
13 use itertools::Itertools;
14 use naming_special_names_rust::{
15 fb, pseudo_consts, pseudo_functions, special_idents, superglobals,
17 use ocamlrep::rc::RcOc;
18 use options::{HhvmFlags, Options};
20 aast_visitor::{self, visit_mut, AstParams, NodeMut, VisitorMut},
22 Abstraction, ClassGetExpr, ClassHint, ClassId, ClassId_, ClassName, ClassVar, Class_,
23 ClassishKind, Contexts, Def, EmitId, Expr, Expr_, FunDef, FunKind, FunParam, Fun_,
24 FuncBody, Hint, Hint_, Id, Lid, LocalId, Method_, Pos, ReifyKind, Sid, Stmt, Stmt_, Targ,
25 Tparam, TypeHint, UserAttribute, Visibility,
29 local_id, namespace_env,
33 use unique_id_builder::{
34 get_unique_id_for_function, get_unique_id_for_main, get_unique_id_for_method,
39 /// all variables declared/used in the scope
40 all_vars: IndexSet<String>,
41 /// names of parameters if scope correspond to a function
42 parameter_names: IndexSet<String>,
43 /// If this is a long lambda then the list of explicitly captured vars (if
45 explicit_capture: IndexSet<String>,
48 struct ClassSummary<'b> {
49 extends: &'b [ClassHint],
54 tparams: &'b [Tparam],
57 struct FunctionSummary<'b, 'arena> {
58 // Unfortunately HhasCoeffects has to be owned for now.
59 coeffects: HhasCoeffects<'arena>,
64 tparams: &'b [Tparam],
67 struct LambdaSummary<'b, 'arena> {
68 // Unfortunately HhasCoeffects has to be owned for now.
69 coeffects: HhasCoeffects<'arena>,
70 explicit_capture: Option<&'b [Lid]>,
75 struct MethodSummary<'b, 'arena> {
76 // Unfortunately HhasCoeffects has to be owned for now.
77 coeffects: HhasCoeffects<'arena>,
82 tparams: &'b [Tparam],
85 enum ScopeSummary<'b, 'arena> {
87 Class(ClassSummary<'b>),
88 Function(FunctionSummary<'b, 'arena>),
89 Method(MethodSummary<'b, 'arena>),
90 Lambda(LambdaSummary<'b, 'arena>),
93 impl<'b, 'arena> ScopeSummary<'b, 'arena> {
94 fn span(&self) -> Option<&Pos> {
96 ScopeSummary::TopLevel => None,
97 ScopeSummary::Class(cd) => Some(cd.span),
98 ScopeSummary::Function(fd) => Some(fd.span),
99 ScopeSummary::Method(md) => Some(md.span),
100 ScopeSummary::Lambda(ld) => Some(ld.span),
105 /// The environment for a scope. This tracks the summary information and
106 /// variables used within a scope.
107 struct Scope<'b, 'arena> {
108 parent: Option<&'b Scope<'b, 'arena>>,
110 /// Number of closures created in the current function
111 closure_cnt_per_fun: u32,
112 /// Are we immediately in a using statement?
114 /// What is the current context?
115 summary: ScopeSummary<'b, 'arena>,
116 /// What variables are defined in this scope?
117 variables: Variables,
120 impl<'b, 'arena> Scope<'b, 'arena> {
121 fn toplevel(defs: &[Def]) -> Result<Self> {
122 let all_vars = compute_vars(&[], &defs)?;
126 closure_cnt_per_fun: 0,
128 summary: ScopeSummary::TopLevel,
129 variables: Variables {
131 ..Variables::default()
136 fn as_class_summary(&self) -> Option<&ClassSummary<'_>> {
137 self.walk_scope().find_map(|scope| match &scope.summary {
138 ScopeSummary::Class(cd) => Some(cd),
143 fn check_if_in_async_context(&self) -> Result<()> {
144 let check_valid_fun_kind = |name, kind: FunKind| {
145 if !kind.is_async() {
146 Err(Error::fatal_parse(
147 &self.span_or_none(),
149 "Function '{}' contains 'await' but is not declared as async.",
150 string_utils::strip_global_ns(name)
157 let check_lambda = |is_async: bool| {
159 Err(Error::fatal_parse(
160 &self.span_or_none(),
161 "Await may only appear in an async function",
167 match &self.summary {
168 ScopeSummary::TopLevel => Err(Error::fatal_parse(
169 &self.span_or_none(),
170 "'await' can only be used inside a function",
172 ScopeSummary::Lambda(ld) => check_lambda(ld.fun_kind.is_async()),
173 ScopeSummary::Class(_) => Ok(()), /* Syntax error, wont get here */
174 ScopeSummary::Function(fd) => check_valid_fun_kind(&fd.name.1, fd.fun_kind),
175 ScopeSummary::Method(md) => check_valid_fun_kind(&md.name.1, md.fun_kind),
179 fn class_tparams(&self) -> &[Tparam] {
180 self.as_class_summary().map_or(&[], |cd| cd.tparams)
183 fn coeffects_of_scope<'c>(&'c self) -> Option<&'c HhasCoeffects<'arena>> {
184 for scope in self.walk_scope() {
185 match &scope.summary {
186 ScopeSummary::Class(_) => break,
187 ScopeSummary::Method(md) => return Some(&md.coeffects),
188 ScopeSummary::Function(fd) => return Some(&fd.coeffects),
189 ScopeSummary::Lambda(ld) => {
190 if !ld.coeffects.get_static_coeffects().is_empty() {
191 return Some(&ld.coeffects);
194 ScopeSummary::TopLevel => {}
200 fn fun_tparams(&self) -> &[Tparam] {
201 for scope in self.walk_scope() {
202 match &scope.summary {
203 ScopeSummary::Class(_) => break,
204 ScopeSummary::Function(fd) => return fd.tparams,
205 ScopeSummary::Method(md) => return md.tparams,
212 fn is_in_debugger_eval_fun(&self) -> bool {
213 let mut cur = Some(self);
214 while let Some(scope) = cur {
215 match &scope.summary {
216 ScopeSummary::Lambda(_) => {}
217 ScopeSummary::Function(fd) => return fd.name.1 == "include",
225 fn is_static(&self) -> bool {
226 for scope in self.walk_scope() {
227 match &scope.summary {
228 ScopeSummary::Function(_) => return true,
229 ScopeSummary::Method(md) => return md.static_,
230 ScopeSummary::Lambda(_) => {}
231 ScopeSummary::Class(_) | ScopeSummary::TopLevel => unreachable!(),
237 fn make_scope_name(&self, ns: &RcOc<namespace_env::Env>) -> String {
238 let mut parts = Vec::new();
239 let mut iter = self.walk_scope();
240 while let Some(scope) = iter.next() {
241 match &scope.summary {
242 ScopeSummary::Class(cd) => {
243 parts.push(make_class_name(cd.name));
246 ScopeSummary::Function(fd) => {
247 let fname = strip_id(fd.name);
248 parts.push(fname.into());
249 for sub_scope in iter {
250 match &sub_scope.summary {
251 ScopeSummary::Class(cd) => {
252 parts.push("::".into());
253 parts.push(make_class_name(cd.name));
261 ScopeSummary::Method(x) => {
262 parts.push(strip_id(x.name).to_string());
263 if !parts.last().map_or(false, |x| x.ends_with("::")) {
264 parts.push("::".into())
267 ScopeSummary::Lambda(_) | ScopeSummary::TopLevel => {}
271 if parts.is_empty() {
272 if let Some(n) = &ns.name {
283 /// Create a new Env which uses `self` as its parent.
286 summary: ScopeSummary<'s, 'arena>,
287 variables: Variables,
288 ) -> Result<Scope<'s, 'arena>> {
290 in_using: self.in_using,
291 closure_cnt_per_fun: self.closure_cnt_per_fun,
298 fn scope_fmode(&self) -> Mode {
299 for scope in self.walk_scope() {
300 match &scope.summary {
301 ScopeSummary::Class(cd) => return cd.mode,
302 ScopeSummary::Function(fd) => return fd.mode,
309 fn should_capture_var(&self, var: &str) -> bool {
310 // variable used in lambda should be captured if is:
311 // - not contained in lambda parameter list
312 if self.variables.parameter_names.contains(var) {
316 // - it exists in one of enclosing scopes
317 for scope in self.walk_scope().skip(1) {
318 let vars = &scope.variables;
319 if vars.all_vars.contains(var)
320 || vars.parameter_names.contains(var)
321 || vars.explicit_capture.contains(var)
326 match &scope.summary {
327 ScopeSummary::Lambda(ld) => {
328 // A lambda contained within an anonymous function (a 'long'
329 // lambda) shouldn't capture variables from outside the
330 // anonymous function unless they're explicitly mentioned in
331 // the function's use clause.
332 if ld.explicit_capture.is_some() {
336 ScopeSummary::TopLevel => {}
344 fn span(&self) -> Option<&Pos> {
348 fn span_or_none<'s>(&'s self) -> Cow<'s, Pos> {
349 if let Some(pos) = self.span() {
352 Cow::Owned(Pos::make_none())
356 fn walk_scope<'s>(&'s self) -> ScopeIter<'b, 's, 'arena> {
357 ScopeIter(Some(self))
360 fn with_in_using<F, R>(&mut self, in_using: bool, mut f: F) -> R
362 F: FnMut(&mut Self) -> R,
364 let old_in_using = self.in_using;
365 self.in_using = in_using;
367 self.in_using = old_in_using;
372 struct ScopeIter<'b, 'c, 'arena>(Option<&'c Scope<'b, 'arena>>);
374 impl<'b, 'c, 'arena> Iterator for ScopeIter<'b, 'c, 'arena> {
375 type Item = &'c Scope<'c, 'arena>;
377 fn next(&mut self) -> Option<Self::Item> {
378 if let Some(cur) = self.0.take() {
387 #[derive(Clone, Default)]
388 struct CaptureState {
390 // Free variables computed so far
391 vars: IndexSet<String>,
392 generics: IndexSet<String>,
395 /// ReadOnlyState is split from State because it can be a simple ref in
397 struct ReadOnlyState<'a> {
398 /// How many existing classes are there?
400 // Empty namespace as constructed by parser
401 empty_namespace: RcOc<namespace_env::Env>,
402 /// For debugger eval
403 for_debugger_eval: bool,
404 /// Global compiler/hack options
405 options: &'a Options,
408 /// Mutable state used during visiting in ClosureVisitor. It's mutable and owned
409 /// so it needs to be moved as we push and pop scopes.
410 struct State<'arena> {
411 capture_state: CaptureState,
413 closures: Vec<Class_>,
414 // accumulated information about program
415 global_state: GlobalState<'arena>,
416 /// Hoisted meth_caller functions
417 named_hoisted_functions: SMap<FunDef>,
418 // The current namespace environment
419 namespace: RcOc<namespace_env::Env>,
422 impl<'arena> State<'arena> {
423 fn initial_state(empty_namespace: RcOc<namespace_env::Env>) -> Self {
425 capture_state: Default::default(),
427 global_state: GlobalState::default(),
428 named_hoisted_functions: SMap::new(),
429 namespace: empty_namespace,
433 fn record_function_state(&mut self, key: String, coeffects_of_scope: HhasCoeffects<'arena>) {
434 if !coeffects_of_scope.get_static_coeffects().is_empty() {
436 .lambda_coeffects_of_scope
437 .insert(key.clone(), coeffects_of_scope);
441 /// Clear the variables, upon entering a lambda
442 fn enter_lambda(&mut self) {
443 self.capture_state.vars = Default::default();
444 self.capture_state.this_ = false;
445 self.capture_state.generics = Default::default();
448 fn set_namespace(&mut self, namespace: RcOc<namespace_env::Env>) {
449 self.namespace = namespace;
452 /// Add a variable to the captured variables
453 fn add_var<'s>(&mut self, scope: &Scope<'_, '_>, var: impl Into<Cow<'s, str>>) {
454 let var = var.into();
456 // Don't bother if it's $this, as this is captured implicitly
457 if var == special_idents::THIS {
458 self.capture_state.this_ = true;
459 } else if scope.should_capture_var(&var)
460 && (var != special_idents::DOLLAR_DOLLAR)
461 && !superglobals::is_superglobal(&var)
463 // If it's bound as a parameter or definite assignment, don't add it
464 // Also don't add the pipe variable and superglobals
465 self.capture_state.vars.insert(var.into_owned());
469 fn add_generic(&mut self, scope: &mut Scope<'_, '_>, var: &str) {
470 let reified_var_position = |is_fun| {
472 |param: &Tparam| param.reified != ReifyKind::Erased && param.name.1 == var;
474 scope.fun_tparams().iter().position(is_reified_var)
476 scope.class_tparams().iter().position(is_reified_var)
480 if let Some(i) = reified_var_position(true) {
481 let var = string_utils::reified::captured_name(true, i);
482 self.capture_state.generics.insert(var);
483 } else if let Some(i) = reified_var_position(false) {
484 let var = string_utils::reified::captured_name(false, i);
485 self.capture_state.generics.insert(var);
492 body: &impl aast_visitor::Node<AstParams<(), String>>,
493 ) -> Result<IndexSet<String>> {
494 hhbc::decl_vars::vars_from_ast(params, &body).map_err(Error::unrecoverable)
497 fn get_parameter_names(params: &[FunParam]) -> IndexSet<String> {
498 params.iter().map(|p| p.name.to_string()).collect()
501 fn strip_id(id: &Id) -> &str {
502 string_utils::strip_global_ns(&id.1)
505 fn make_class_name(name: &ClassName) -> String {
506 string_utils::mangle_xhp_id(strip_id(name).to_string())
509 fn make_closure_name(scope: &Scope<'_, '_>, state: &State<'_>) -> String {
510 let per_fun_idx = scope.closure_cnt_per_fun;
511 let name = scope.make_scope_name(&state.namespace);
512 string_utils::closures::mangle_closure(&name, per_fun_idx)
518 scope: &Scope<'_, '_>,
520 ro_state: &ReadOnlyState<'_>,
521 lambda_vars: Vec<String>,
522 fun_tparams: Vec<Tparam>,
523 class_tparams: Vec<Tparam>,
527 ) -> (Fun_, Class_) {
529 span: fd.span.clone(),
530 annotation: fd.annotation,
534 readonly_this: fd.readonly_this.is_some(),
535 visibility: Visibility::Public,
536 name: Id(fd.name.0.clone(), "__invoke".into()),
537 tparams: fun_tparams,
538 where_constraints: fd.where_constraints.clone(),
539 params: fd.params.clone(),
540 ctxs: fd.ctxs.clone(),
541 unsafe_ctxs: None, // TODO(T70095684)
542 body: fd.body.clone(),
543 fun_kind: fd.fun_kind,
544 user_attributes: fd.user_attributes.clone(),
545 readonly_ret: fd.readonly_ret,
548 doc_comment: fd.doc_comment.clone(),
551 let make_class_var = |name: &str| ClassVar {
555 readonly: false, // readonly on closure_convert
556 visibility: Visibility::Private,
557 type_: TypeHint((), None),
558 id: Id(p.clone(), name.into()),
560 user_attributes: vec![],
562 is_promoted_variadic: false,
567 let cvl = lambda_vars
569 .map(|name| make_class_var(string_utils::locals::strip_dollar(name)));
573 annotation: fd.annotation,
575 user_attributes: vec![],
576 file_attributes: vec![],
579 has_xhp_keyword: false,
580 kind: ClassishKind::Cclass(Abstraction::Concrete),
581 name: Id(p.clone(), make_closure_name(scope, state)),
582 tparams: class_tparams,
585 Box::new(Hint_::Happly(Id(p.clone(), "Closure".into()), vec![])),
588 xhp_attr_uses: vec![],
592 where_constraints: vec![],
598 xhp_children: vec![],
600 namespace: RcOc::clone(&ro_state.empty_namespace),
603 emit_id: Some(EmitId::Anonymous),
604 // TODO(T116039119): Populate value with presence of internal attribute
606 // TODO: closures should have the visibility of the module they are defined in
610 // TODO(hrust): can we reconstruct fd here from the scratch?
611 fd.name = Id(p.clone(), class_num.to_string());
615 /// Translate special identifiers `__CLASS__`, `__METHOD__` and `__FUNCTION__`
616 /// into literal strings. It's necessary to do this before closure conversion
617 /// because the enclosing class will be changed.
618 fn convert_id(scope: &Scope<'_, '_>, Id(p, s): Id) -> Expr_ {
619 let ret = Expr_::mk_string;
620 let name = |c: &ClassName| {
621 Expr_::mk_string(string_utils::mangle_xhp_id(strip_id(c).to_string()).into())
625 _ if s.eq_ignore_ascii_case(pseudo_consts::G__TRAIT__) => match scope.as_class_summary() {
626 Some(cd) if cd.kind == ClassishKind::Ctrait => name(cd.name),
629 _ if s.eq_ignore_ascii_case(pseudo_consts::G__CLASS__) => match scope.as_class_summary() {
630 Some(cd) if cd.kind != ClassishKind::Ctrait => name(cd.name),
631 Some(_) => Expr_::mk_id(Id(p, s)),
632 None => ret("".into()),
634 _ if s.eq_ignore_ascii_case(pseudo_consts::G__METHOD__) => {
635 let (prefix, is_trait) = match scope.as_class_summary() {
636 None => ("".into(), false),
638 string_utils::mangle_xhp_id(strip_id(cd.name).to_string()) + "::",
639 cd.kind == ClassishKind::Ctrait,
642 // for lambdas nested in trait methods HHVM replaces __METHOD__
643 // with enclosing method name - do the same and bubble up from lambdas *
644 let id_scope = if is_trait {
645 scope.walk_scope().find(|x| {
646 let scope_is_in_lambda = match &x.summary {
647 ScopeSummary::Lambda(_) => true,
653 scope.walk_scope().next()
656 match id_scope.map(|x| &x.summary) {
657 Some(ScopeSummary::Function(fd)) => ret((prefix + strip_id(fd.name)).into()),
658 Some(ScopeSummary::Method(md)) => ret((prefix + strip_id(md.name)).into()),
659 Some(ScopeSummary::Lambda(_)) => ret((prefix + "{closure}").into()),
660 // PHP weirdness: __METHOD__ inside a class outside a method returns class name
661 Some(ScopeSummary::Class(cd)) => ret(strip_id(cd.name).into()),
665 _ if s.eq_ignore_ascii_case(pseudo_consts::G__FUNCTION__) => match &scope.summary {
666 ScopeSummary::Function(fd) => ret(strip_id(fd.name).into()),
667 ScopeSummary::Method(md) => ret(strip_id(md.name).into()),
668 ScopeSummary::Lambda(_) => ret("{closure}".into()),
671 _ if s.eq_ignore_ascii_case(pseudo_consts::G__LINE__) => {
672 // If the expression goes on multi lines, we return the last line
673 let (_, line, _, _) = p.info_pos_extended();
674 Expr_::mk_int(line.to_string())
676 _ => Expr_::mk_id(Id(p, s)),
680 fn make_class_info(c: &ClassSummary<'_>) -> ClosureEnclosingClassInfo {
681 ClosureEnclosingClassInfo {
683 name: c.name.1.clone(),
684 parent_class_name: match c.extends {
685 [x] => x.as_happly().map(|(id, _args)| id.1.clone()),
691 pub fn make_fn_param(pos: Pos, lid: &LocalId, is_variadic: bool, is_inout: bool) -> FunParam {
694 type_hint: TypeHint((), None),
697 name: local_id::get_name(lid).clone(),
699 callconv: if is_inout {
700 ParamKind::Pinout(pos)
704 readonly: None, // TODO
705 user_attributes: vec![],
710 fn make_dyn_meth_caller_lambda(pos: &Pos, cexpr: &Expr, fexpr: &Expr, force: bool) -> Expr_ {
711 let pos = || pos.clone();
712 let obj_var = Box::new(Lid(pos(), local_id::make_unscoped("$o")));
713 let meth_var = Box::new(Lid(pos(), local_id::make_unscoped("$m")));
714 let obj_lvar = Expr((), pos(), Expr_::Lvar(obj_var.clone()));
715 let meth_lvar = Expr((), pos(), Expr_::Lvar(meth_var.clone()));
716 // AST for: return $o-><func>(...$args);
717 let args_var = local_id::make_unscoped("$args");
718 let variadic_param = make_fn_param(pos(), &args_var, true, false);
719 let invoke_method = hack_expr!(
721 r#"#obj_lvar->#meth_lvar(...#{lvar(args_var)})"#
723 let attrs = if force {
725 name: Id(pos(), "__DynamicMethCallerForce".into()),
731 let ctxs = Some(Contexts(
735 Hint_::mk_happly(Id(pos(), string_utils::coeffects::CALLER.into()), vec![]),
742 readonly_this: None, // TODO: readonly_this in closure_convert
743 readonly_ret: None, // TODO: readonly_ret in closure convert
744 ret: TypeHint((), None),
745 name: Id(pos(), ";anonymous".to_string()),
747 where_constraints: vec![],
749 make_fn_param(pos(), &obj_var.1, false, false),
750 make_fn_param(pos(), &meth_var.1, false, false),
756 fb_ast: vec![Stmt(pos(), Stmt_::Return(Box::new(Some(invoke_method))))],
758 fun_kind: FunKind::FSync,
759 user_attributes: attrs,
763 let force_val = if force { Expr_::True } else { Expr_::False };
764 let force_val_expr = Expr((), pos(), force_val);
765 let efun = Expr((), pos(), Expr_::mk_efun(fd, vec![]));
766 let fun_handle = hack_expr!(
768 r#"\__systemlib\dynamic_meth_caller(#{clone(cexpr)}, #{clone(fexpr)}, #efun, #force_val_expr)"#
773 fn add_reified_property(tparams: &[Tparam], vars: &mut Vec<ClassVar>) {
774 if !tparams.iter().all(|t| t.reified == ReifyKind::Erased) {
775 let p = Pos::make_none();
776 // varray/vec that holds a list of type structures
777 // this prop will be initilized during runtime
780 Box::new(Hint_::Happly(Id(p.clone(), "\\HH\\varray".into()), vec![])),
787 is_promoted_variadic: false,
791 visibility: Visibility::Private,
792 type_: TypeHint((), Some(hint)),
793 id: Id(p.clone(), string_utils::reified::PROP_NAME.into()),
795 user_attributes: vec![],
803 struct ClosureVisitor<'a, 'b, 'arena> {
804 alloc: &'arena bumpalo::Bump,
805 state: Option<State<'arena>>,
806 ro_state: &'a ReadOnlyState<'a>,
807 // We need 'b to be a real lifetime so that our `type Params` can refer to
808 // it - but we don't actually have any fields that use it - so we need a
810 phantom: std::marker::PhantomData<&'b ()>,
813 impl<'ast, 'a: 'b, 'b, 'arena: 'a> VisitorMut<'ast> for ClosureVisitor<'a, 'b, 'arena> {
814 type Params = AstParams<Scope<'b, 'arena>, Error>;
816 fn object(&mut self) -> &mut dyn VisitorMut<'ast, Params = Self::Params> {
820 fn visit_method_(&mut self, scope: &mut Scope<'b, 'arena>, md: &mut Method_) -> Result<()> {
821 let cd = scope.as_class_summary().ok_or_else(|| {
822 Error::unrecoverable("unexpected scope shape - method is not inside the class")
824 let variables = Self::compute_variables_from_fun(&md.params, &md.body.fb_ast, None)?;
825 let coeffects = HhasCoeffects::from_ast(
830 scope.class_tparams(),
832 let si = ScopeSummary::Method(MethodSummary {
834 fun_kind: md.fun_kind,
838 tparams: &md.tparams,
840 self.with_subscope(scope, si, variables, |self_, scope| {
841 md.body.recurse(scope, self_)?;
842 let uid = get_unique_id_for_method(&cd.name.1, &md.name.1);
845 .record_function_state(uid, HhasCoeffects::default());
846 visit_mut(self_, scope, &mut md.params)?;
852 fn visit_class_(&mut self, scope: &mut Scope<'b, 'arena>, cd: &mut Class_) -> Result<()> {
853 let variables = Variables::default();
854 let si = ScopeSummary::Class(ClassSummary {
855 extends: &cd.extends,
860 tparams: &cd.tparams,
862 self.with_subscope(scope, si, variables, |self_, scope| -> Result<()> {
863 visit_mut(self_, scope, &mut cd.methods)?;
864 visit_mut(self_, scope, &mut cd.consts)?;
865 visit_mut(self_, scope, &mut cd.vars)?;
866 visit_mut(self_, scope, &mut cd.xhp_attrs)?;
867 visit_mut(self_, scope, &mut cd.user_attributes)?;
868 add_reified_property(&cd.tparams, &mut cd.vars);
874 fn visit_def(&mut self, scope: &mut Scope<'b, 'arena>, def: &mut Def) -> Result<()> {
876 // need to handle it ourselvses, because visit_fun_ is
877 // called both for toplevel functions and lambdas
880 Self::compute_variables_from_fun(&fd.fun.params, &fd.fun.body.fb_ast, None)?;
881 let coeffects = HhasCoeffects::from_ast(
883 fd.fun.ctxs.as_ref(),
888 let si = ScopeSummary::Function(FunctionSummary {
890 fun_kind: fd.fun.fun_kind,
894 tparams: &fd.fun.tparams,
896 self.with_subscope(scope, si, variables, |self_, scope| {
897 fd.fun.body.recurse(scope, self_)?;
898 let uid = get_unique_id_for_function(&fd.fun.name.1);
901 .record_function_state(uid, HhasCoeffects::default());
902 visit_mut(self_, scope, &mut fd.fun.params)?;
903 visit_mut(self_, scope, &mut fd.fun.user_attributes)?;
908 _ => def.recurse(scope, self),
912 fn visit_hint_(&mut self, scope: &mut Scope<'b, 'arena>, hint: &mut Hint_) -> Result<()> {
913 if let Hint_::Happly(id, _) = hint {
914 self.state_mut().add_generic(scope, id.name())
916 hint.recurse(scope, self)
919 fn visit_stmt_(&mut self, scope: &mut Scope<'b, 'arena>, stmt: &mut Stmt_) -> Result<()> {
921 Stmt_::Awaitall(x) => {
922 scope.check_if_in_async_context()?;
923 x.recurse(scope, self)
926 let (b, e) = &mut **x;
927 scope.with_in_using(false, |scope| visit_mut(self, scope, b))?;
928 self.visit_expr(scope, e)
931 let (e, b) = &mut **x;
932 self.visit_expr(scope, e)?;
933 scope.with_in_using(false, |scope| visit_mut(self, scope, b))
935 Stmt_::Foreach(x) => {
936 if x.1.is_await_as_v() || x.1.is_await_as_kv() {
937 scope.check_if_in_async_context()?
939 x.recurse(scope, self)
942 let (e1, e2, e3, b) = &mut **x;
945 self.visit_expr(scope, e)?;
947 if let Some(e) = e2 {
948 self.visit_expr(scope, e)?;
950 scope.with_in_using(false, |scope| visit_mut(self, scope, b))?;
952 self.visit_expr(scope, e)?;
956 Stmt_::Switch(x) => {
957 let (e, cl, dfl) = &mut **x;
958 self.visit_expr(scope, e)?;
959 scope.with_in_using(false, |scope| visit_mut(self, scope, cl))?;
962 Some(dfl) => scope.with_in_using(false, |scope| visit_mut(self, scope, dfl)),
967 scope.check_if_in_async_context()?;
969 for e in &mut x.exprs.1 {
970 self.visit_expr(scope, e)?;
972 scope.with_in_using(true, |scope| visit_mut(self, scope, &mut x.block))?;
975 _ => stmt.recurse(scope, self),
981 scope: &mut Scope<'b, 'arena>,
982 Expr(_, pos, e): &mut Expr,
984 stack_limit::maybe_grow(|| {
985 *e = match strip_unsafe_casts(e) {
986 Expr_::Efun(x) => self.convert_lambda(scope, x.0, Some(x.1))?,
987 Expr_::Lfun(x) => self.convert_lambda(scope, x.0, None)?,
988 Expr_::Lvar(id_orig) => {
989 let id = if self.ro_state.for_debugger_eval
990 && local_id::get_name(&id_orig.1) == special_idents::THIS
991 && scope.is_in_debugger_eval_fun()
993 Box::new(Lid(id_orig.0, (0, "$__debugger$this".to_string())))
997 self.state_mut().add_var(scope, local_id::get_name(&id.1));
1000 Expr_::Id(id) if id.name().starts_with('$') => {
1001 let state = self.state_mut();
1002 state.add_var(scope, id.name());
1003 state.add_generic(scope, id.name());
1007 self.state_mut().add_generic(scope, id.name());
1008 convert_id(scope, *id)
1010 Expr_::Call(x) if is_dyn_meth_caller(&x) => {
1011 self.visit_dyn_meth_caller(scope, x, &*pos)?
1014 if is_meth_caller(&x)
1020 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS) =>
1022 self.visit_meth_caller_funcptr(scope, x, &*pos)?
1024 Expr_::Call(x) if is_meth_caller(&x) => self.visit_meth_caller(scope, x)?,
1028 .and_then(|(id, _, _)| id.as_ciexpr())
1029 .and_then(|x| x.as_id())
1030 .map_or(false, string_utils::is_parent)
1033 .and_then(|(id, _)| id.as_ciexpr())
1034 .and_then(|x| x.as_id())
1035 .map_or(false, string_utils::is_parent) =>
1037 self.state_mut().add_var(scope, "$this");
1038 let mut res = Expr_::Call(x);
1039 res.recurse(scope, self)?;
1042 Expr_::As(x) if (x.1).is_hlike() => {
1044 res.recurse(scope, self)?;
1052 (id.name() == fb::INCORRECT_TYPE
1053 || id.name() == fb::INCORRECT_TYPE_NO_NS)
1056 .unwrap_or_default() =>
1059 res.recurse(scope, self)?;
1063 Expr_::ClassGet(mut x) => {
1064 if let ClassGetExpr::CGstring(id) = &x.1 {
1065 // T43412864 claims that this does not need to be added into the
1066 // closure and can be removed. There are no relevant HHVM tests
1067 // checking for it, but there are flib test failures when you try
1069 self.state_mut().add_var(scope, &id.1);
1071 x.recurse(scope, self)?;
1074 Expr_::Await(mut x) => {
1075 scope.check_if_in_async_context()?;
1076 x.recurse(scope, self)?;
1079 Expr_::ReadonlyExpr(mut x) => {
1080 x.recurse(scope, self)?;
1081 Expr_::ReadonlyExpr(x)
1083 Expr_::ExpressionTree(mut x) => {
1084 x.runtime_expr.recurse(scope, self)?;
1085 Expr_::ExpressionTree(x)
1088 x.recurse(scope, self)?;
1097 impl<'a: 'b, 'b, 'arena: 'a + 'b> ClosureVisitor<'a, 'b, 'arena> {
1098 /// Calls a function in the scope of a sub-Scope as a child of `scope`.
1099 fn with_subscope<'s, F, R>(
1101 scope: &'s Scope<'b, 'arena>,
1102 si: ScopeSummary<'s, 'arena>,
1103 variables: Variables,
1105 ) -> Result<(R, u32)>
1108 F: FnOnce(&mut ClosureVisitor<'a, 's, 'arena>, &mut Scope<'s, 'arena>) -> Result<R>,
1110 let mut scope = scope.new_child(si, variables)?;
1112 let mut self_ = ClosureVisitor {
1114 ro_state: self.ro_state,
1115 state: self.state.take(),
1116 phantom: Default::default(),
1118 let res = f(&mut self_, &mut scope);
1119 self.state = self_.state;
1120 Ok((res?, scope.closure_cnt_per_fun))
1123 fn state(&self) -> &State<'arena> {
1124 self.state.as_ref().unwrap()
1127 fn state_mut(&mut self) -> &mut State<'arena> {
1128 self.state.as_mut().unwrap()
1132 fn visit_dyn_meth_caller(
1134 scope: &mut Scope<'b, 'arena>,
1137 ) -> Result<Expr_> {
1138 let force = if let Expr_::Id(ref id) = (x.0).2 {
1139 strip_id(id).eq_ignore_ascii_case("hh\\dynamic_meth_caller_force")
1143 if let [(pk_c, cexpr), (pk_f, fexpr)] = &mut *x.2 {
1144 error::ensure_normal_paramkind(pk_c)?;
1145 error::ensure_normal_paramkind(pk_f)?;
1146 let mut res = make_dyn_meth_caller_lambda(pos, cexpr, fexpr, force);
1147 res.recurse(scope, self)?;
1150 let mut res = Expr_::Call(x);
1151 res.recurse(scope, self)?;
1157 fn visit_meth_caller_funcptr(
1159 scope: &mut Scope<'b, 'arena>,
1162 ) -> Result<Expr_> {
1163 if let [(pk_cls, Expr(_, pc, cls)), (pk_f, Expr(_, pf, func))] = &mut *x.2 {
1164 error::ensure_normal_paramkind(pk_cls)?;
1165 error::ensure_normal_paramkind(pk_f)?;
1166 match (&cls, func.as_string()) {
1167 (Expr_::ClassConst(cc), Some(fname)) if string_utils::is_class(&(cc.1).1) => {
1168 let mut cls_const = cls.as_class_const_mut();
1169 let (cid, _) = match cls_const {
1170 None => unreachable!(),
1171 Some((ref mut cid, (_, cs))) => (cid, cs),
1173 self.visit_class_id(scope, cid)?;
1177 .and_then(Expr::as_id)
1178 .map_or(false, |id| !is_selflike_keyword(id)) =>
1180 let alloc = bumpalo::Bump::new();
1181 let id = cid.as_ciexpr().unwrap().as_id().unwrap();
1182 let mangled_class_name =
1183 hhbc::ClassName::from_ast_name_and_mangle(&alloc, id.as_ref());
1184 let mangled_class_name = mangled_class_name.unsafe_as_str();
1185 Ok(self.convert_meth_caller_to_func_ptr(
1191 // FIXME: This is not safe--string literals are binary
1192 // strings. There's no guarantee that they're valid UTF-8.
1193 unsafe { std::str::from_utf8_unchecked(fname.as_slice()) },
1196 _ => Err(Error::fatal_parse(pc, "Invalid class")),
1199 (Expr_::String(cls_name), Some(fname)) => Ok(self.convert_meth_caller_to_func_ptr(
1203 // FIXME: This is not safe--string literals are binary strings.
1204 // There's no guarantee that they're valid UTF-8.
1205 unsafe { std::str::from_utf8_unchecked(cls_name.as_slice()) },
1207 // FIXME: This is not safe--string literals are binary strings.
1208 // There's no guarantee that they're valid UTF-8.
1209 unsafe { std::str::from_utf8_unchecked(fname.as_slice()) },
1211 (_, Some(_)) => Err(Error::fatal_parse(
1213 "Class must be a Class or string type",
1215 (_, _) => Err(Error::fatal_parse(
1217 "Method name must be a literal string",
1221 let mut res = Expr_::Call(x);
1222 res.recurse(scope, self)?;
1228 fn visit_meth_caller(
1230 scope: &mut Scope<'b, 'arena>,
1232 ) -> Result<Expr_> {
1233 if let [(pk_cls, Expr(_, pc, cls)), (pk_f, Expr(_, pf, func))] = &mut *x.2 {
1234 error::ensure_normal_paramkind(pk_cls)?;
1235 error::ensure_normal_paramkind(pk_f)?;
1236 match (&cls, func.as_string()) {
1237 (Expr_::ClassConst(cc), Some(_)) if string_utils::is_class(&(cc.1).1) => {
1238 let mut cls_const = cls.as_class_const_mut();
1239 let cid = match cls_const {
1240 None => unreachable!(),
1241 Some((ref mut cid, (_, _))) => cid,
1245 .and_then(Expr::as_id)
1246 .map_or(false, |id| !is_selflike_keyword(id))
1248 let mut res = Expr_::Call(x);
1249 res.recurse(scope, self)?;
1252 Err(Error::fatal_parse(pc, "Invalid class"))
1255 (Expr_::String(_), Some(_)) => {
1256 let mut res = Expr_::Call(x);
1257 res.recurse(scope, self)?;
1260 (_, Some(_)) => Err(Error::fatal_parse(
1262 "Class must be a Class or string type",
1264 (_, _) => Err(Error::fatal_parse(
1266 "Method name must be a literal string",
1270 let mut res = Expr_::Call(x);
1271 res.recurse(scope, self)?;
1276 fn visit_class_id(&mut self, scope: &mut Scope<'b, 'arena>, cid: &mut ClassId) -> Result<()> {
1277 if let ClassId(_, _, ClassId_::CIexpr(e)) = cid {
1278 self.visit_expr(scope, e)?;
1283 // Closure-convert a lambda expression, with use_vars_opt = Some vars
1284 // if there is an explicit `use` clause.
1287 scope: &mut Scope<'b, 'arena>,
1289 use_vars_opt: Option<Vec<Lid>>,
1290 ) -> Result<Expr_> {
1291 let is_long_lambda = use_vars_opt.is_some();
1292 let state = self.state_mut();
1294 // Remember the current capture and defined set across the lambda
1295 let capture_state = state.capture_state.clone();
1296 let coeffects_of_scope = scope
1297 .coeffects_of_scope()
1298 .map_or_else(Default::default, |co| co.clone());
1299 state.enter_lambda();
1300 if let Some(user_vars) = &use_vars_opt {
1301 for Lid(p, id) in user_vars.iter() {
1302 if local_id::get_name(id) == special_idents::THIS {
1303 return Err(Error::fatal_parse(
1305 "Cannot use $this as lexical variable",
1311 let explicit_capture: Option<IndexSet<String>> = use_vars_opt.as_ref().map(|vars| {
1313 .map(|Lid(_, (_, name))| name.to_string())
1317 Self::compute_variables_from_fun(&fd.params, &fd.body.fb_ast, explicit_capture)?;
1319 HhasCoeffects::from_ast(self.alloc, fd.ctxs.as_ref(), &fd.params, &fd.tparams, &[]);
1320 let si = ScopeSummary::Lambda(LambdaSummary {
1322 explicit_capture: use_vars_opt.as_deref(),
1323 fun_kind: fd.fun_kind,
1326 let (_, closure_cnt_per_fun) =
1327 self.with_subscope(scope, si, variables, |self_, scope| {
1328 fd.body.recurse(scope, self_)?;
1329 for param in &mut fd.params {
1330 visit_mut(self_, scope, &mut param.type_hint)?;
1332 visit_mut(self_, scope, &mut fd.ret)?;
1336 scope.closure_cnt_per_fun = closure_cnt_per_fun + 1;
1338 let state = self.state.as_mut().unwrap();
1339 let current_generics = state.capture_state.generics.clone();
1341 // TODO(hrust): produce real unique local ids
1342 let fresh_lid = |name: String| Lid(Pos::make_none(), (12345, name));
1344 let lambda_vars: Vec<&String> = state
1348 .chain(current_generics.iter())
1349 // HHVM lists lambda vars in descending order - do the same
1354 // Remove duplicates, (not efficient, but unlikely to be large),
1355 // remove variables that are actually just parameters
1356 let use_vars_opt: Option<Vec<Lid>> = use_vars_opt.map(|use_vars| {
1357 let params = &fd.params;
1361 .unique_by(|lid| lid.name().to_string())
1362 .filter(|x| !params.iter().any(|y| x.name() == &y.name))
1363 .collect::<Vec<_>>()
1369 // For lambdas with explicit `use` variables, we ignore the computed
1370 // capture set and instead use the explicit set
1371 let (lambda_vars, use_vars): (Vec<String>, Vec<Lid>) = match use_vars_opt {
1373 lambda_vars.iter().map(|x| x.to_string()).collect(),
1376 .map(|x| fresh_lid(x.to_string()))
1380 // We still need to append the generics
1385 .chain(current_generics.iter())
1386 .map(|x| x.to_string())
1391 .chain(current_generics.iter().map(|x| fresh_lid(x.to_string())))
1397 let fun_tparams = scope.fun_tparams().to_vec(); // hiddden .clone()
1398 let class_tparams = scope.class_tparams().to_vec(); // hiddden .clone()
1399 let class_num = state.closures.len() + self.ro_state.class_count;
1401 let is_static = if is_long_lambda {
1402 // long lambdas are never static
1405 // short lambdas can be made static if they don't capture this in
1406 // any form (including any nested lambdas)
1407 !state.capture_state.this_
1410 // check if something can be promoted to static based on enclosing scope
1411 let is_static = is_static || scope.is_static();
1413 let pos = fd.span.clone();
1414 let lambda_vars_clone = lambda_vars.clone();
1415 let (inline_fundef, cd) = make_closure(
1425 scope.scope_fmode(),
1433 .insert(inline_fundef.name.1.clone());
1436 let closure_class_name = &cd.name.1;
1437 if let Some(cd) = scope.as_class_summary() {
1440 .closure_enclosing_classes
1441 .insert(closure_class_name.clone(), make_class_info(cd));
1444 // Restore capture and defined set
1445 // - adjust captured $this information if lambda that was just processed was
1446 // converted into non-static one
1447 state.capture_state = CaptureState {
1448 this_: capture_state.this_ || !is_static,
1454 .insert(closure_class_name.clone(), state.namespace.clone());
1455 state.record_function_state(
1456 get_unique_id_for_method(&cd.name.1, &cd.methods.first().unwrap().name.1),
1460 // Add lambda captured vars to current captured vars
1461 for var in lambda_vars_clone.into_iter() {
1462 state.add_var(scope, var)
1464 for x in current_generics.iter() {
1465 state.capture_state.generics.insert(x.to_string());
1468 state.closures.push(cd);
1470 Ok(Expr_::mk_efun(inline_fundef, use_vars))
1473 fn convert_meth_caller_to_func_ptr(
1475 scope: &Scope<'_, '_>,
1482 let pos = || pos.clone();
1483 let cname = match scope.as_class_summary() {
1484 Some(cd) => &cd.name.1,
1487 let mangle_name = string_utils::mangle_meth_caller(cls, fname);
1488 let fun_handle = hack_expr!(
1490 r#"\__systemlib\meth_caller(#{str(clone(mangle_name))})"#
1494 .named_hoisted_functions
1495 .contains_key(&mangle_name)
1497 return fun_handle.2;
1499 // AST for: invariant(is_a($o, <cls>), 'object must be an instance of <cls>');
1500 let obj_var = Box::new(Lid(pos(), local_id::make_unscoped("$o")));
1501 let obj_lvar = Expr((), pos(), Expr_::Lvar(obj_var.clone()));
1502 let msg = format!("object must be an instance of ({})", cls);
1503 let assert_invariant = hack_expr!(
1505 r#"\HH\invariant(\is_a(#{clone(obj_lvar)}, #{str(clone(cls), pc)}), #{str(msg)})"#
1507 // AST for: return $o-><func>(...$args);
1508 let args_var = local_id::make_unscoped("$args");
1509 let variadic_param = make_fn_param(pos(), &args_var, true, false);
1510 let meth_caller_handle = hack_expr!(
1512 r#"#obj_lvar->#{id(clone(fname), pf)}(...#{lvar(args_var)})"#
1518 readonly_this: None, // TODO(readonly): readonly_this in closure_convert
1520 ret: TypeHint((), None),
1521 name: Id(pos(), mangle_name.clone()),
1523 where_constraints: vec![],
1525 make_fn_param(pos(), &obj_var.1, false, false),
1532 Stmt(pos(), Stmt_::Expr(Box::new(assert_invariant))),
1533 Stmt(pos(), Stmt_::Return(Box::new(Some(meth_caller_handle)))),
1536 fun_kind: FunKind::FSync,
1537 user_attributes: vec![UserAttribute {
1538 name: Id(pos(), "__MethCaller".into()),
1539 params: vec![Expr((), pos(), Expr_::String(cname.into()))],
1545 file_attributes: vec![],
1546 namespace: RcOc::clone(&self.ro_state.empty_namespace),
1547 mode: scope.scope_fmode(),
1549 // TODO(T116039119): Populate value with presence of internal attribute
1551 // TODO: meth_caller should have the visibility of the module it is defined in
1555 .named_hoisted_functions
1556 .insert(mangle_name, fd);
1560 fn compute_variables_from_fun(
1561 params: &[FunParam],
1563 explicit_capture: Option<IndexSet<String>>,
1564 ) -> Result<Variables> {
1565 let parameter_names = get_parameter_names(params);
1566 let all_vars = compute_vars(params, &body)?;
1567 let explicit_capture = explicit_capture.unwrap_or_default();
1576 /// Swap *e with Expr_::Null, then return it with UNSAFE_CAST stripped off.
1577 fn strip_unsafe_casts(e: &mut Expr_) -> Expr_ {
1578 let null = Expr_::mk_null();
1579 let mut e_owned = std::mem::replace(e, null);
1581 If this is a call of the form
1582 HH\FIXME\UNSAFE_CAST(e, ...)
1583 then treat as a no-op by transforming it to
1585 Repeat in case there are nested occurrences
1589 // Must have at least one argument
1591 if !x.2.is_empty() && {
1592 // Function name should be HH\FIXME\UNSAFE_CAST
1593 if let Expr_::Id(ref id) = (x.0).2 {
1594 id.1 == pseudo_functions::UNSAFE_CAST
1600 // Select first argument
1601 let Expr(_, _, e) = x.2.swap_remove(0).1;
1609 type CallExpr = Box<(Expr, Vec<Targ>, Vec<(ParamKind, Expr)>, Option<Expr>)>;
1611 fn is_dyn_meth_caller(x: &CallExpr) -> bool {
1612 if let Expr_::Id(ref id) = (x.0).2 {
1613 let name = strip_id(id);
1614 name.eq_ignore_ascii_case("hh\\dynamic_meth_caller")
1615 || name.eq_ignore_ascii_case("hh\\dynamic_meth_caller_force")
1621 fn is_meth_caller(x: &CallExpr) -> bool {
1622 if let Expr_::Id(ref id) = (x.0).2 {
1623 let name = strip_id(id);
1624 name.eq_ignore_ascii_case("hh\\meth_caller") || name.eq_ignore_ascii_case("meth_caller")
1630 fn is_selflike_keyword(id: &Id) -> bool {
1631 string_utils::is_self(id) || string_utils::is_parent(id) || string_utils::is_static(id)
1634 fn hoist_toplevel_functions(defs: &mut Vec<Def>) {
1635 // Reorder the functions so that they appear first.
1636 let (funs, nonfuns): (Vec<Def>, Vec<Def>) = defs.drain(..).partition(|x| x.is_fun());
1638 defs.extend(nonfuns);
1641 fn prepare_defs(defs: &mut [Def]) -> usize {
1642 let mut class_count = 0;
1643 let mut typedef_count = 0;
1644 let mut const_count = 0;
1646 for def in defs.iter_mut() {
1649 x.emit_id = Some(EmitId::EmitId(class_count));
1652 Def::Typedef(x) => {
1653 x.emit_id = Some(EmitId::EmitId(typedef_count));
1656 Def::Constant(x) => {
1657 x.emit_id = Some(EmitId::EmitId(const_count));
1660 Def::Namespace(_) => {
1661 // This should have already been flattened by rewrite_program.
1664 Def::FileAttributes(_)
1668 | Def::NamespaceUse(_)
1669 | Def::SetNamespaceEnv(_)
1670 | Def::Stmt(_) => {}
1674 class_count as usize
1677 pub fn convert_toplevel_prog<'arena, 'decl>(
1678 e: &mut Emitter<'arena, 'decl>,
1679 defs: &mut Vec<Def>,
1680 namespace_env: RcOc<namespace_env::Env>,
1682 let class_count = prepare_defs(defs);
1684 let mut scope = Scope::toplevel(defs.as_slice())?;
1685 let ro_state = ReadOnlyState {
1687 empty_namespace: RcOc::clone(&namespace_env),
1688 for_debugger_eval: e.for_debugger_eval,
1689 options: e.options(),
1691 let state = State::initial_state(namespace_env);
1693 let mut visitor = ClosureVisitor {
1696 ro_state: &ro_state,
1697 phantom: Default::default(),
1700 for def in defs.iter_mut() {
1701 visitor.visit_def(&mut scope, def)?;
1703 Def::SetNamespaceEnv(x) => {
1704 visitor.state_mut().set_namespace(RcOc::clone(&*x));
1708 | Def::FileAttributes(_)
1713 | Def::NamespaceUse(_)
1715 | Def::Typedef(_) => {}
1719 let mut state = visitor.state.take().unwrap();
1720 state.record_function_state(get_unique_id_for_main(), HhasCoeffects::default());
1721 hoist_toplevel_functions(defs);
1722 let named_fun_defs = state
1723 .named_hoisted_functions
1725 .map(|(_, fd)| Def::mk_fun(fd));
1726 defs.splice(0..0, named_fun_defs);
1727 for class in state.closures.into_iter() {
1728 defs.push(Def::mk_class(class));
1730 *e.emit_global_state_mut() = state.global_state;