fn emit_call_lhs_and_fcall()

in hphp/hack/src/hackc/emitter/emit_expression.rs [1872:2241]


fn emit_call_lhs_and_fcall<'a, 'arena, 'decl>(
    e: &mut Emitter<'arena, 'decl>,
    env: &Env<'a, 'arena>,
    expr: &ast::Expr,
    mut fcall_args: FCallArgs<'arena>,
    targs: &[ast::Targ],
    caller_readonly_opt: Option<&Pos>,
) -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
    let ast::Expr(_, pos, expr_) = expr;
    use ast::{Expr, Expr_};
    let alloc = env.arena;
    let emit_generics =
        |e: &mut Emitter<'arena, 'decl>, env, fcall_args: &mut FCallArgs<'arena>| {
            let does_not_have_non_tparam_generics = !has_non_tparam_generics_targs(env, targs);
            if does_not_have_non_tparam_generics {
                Ok(instr::empty())
            } else {
                fcall_args.flags |= FCallArgsFlags::HasGenerics;
                emit_reified_targs(
                    e,
                    env,
                    pos,
                    targs
                        .iter()
                        .map(|targ| &targ.1)
                        .collect::<Vec<_>>()
                        .as_slice(),
                )
            }
        };

    let emit_fcall_func = |e: &mut Emitter<'arena, 'decl>,
                           env,
                           expr: &ast::Expr,
                           fcall_args: FCallArgs<'arena>,
                           caller_readonly_opt: Option<&Pos>|
     -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
        let tmp = e.local_gen_mut().get_unnamed();
        // if the original expression was wrapped in readonly, emit a readonly expression here
        let res = if let Some(p) = caller_readonly_opt {
            emit_readonly_expr(e, env, p, expr)?
        } else {
            emit_expr(e, env, expr)?
        };
        Ok((
            InstrSeq::gather(vec![
                instr::nulluninit(),
                instr::nulluninit(),
                res,
                instr::popl(tmp),
            ]),
            InstrSeq::gather(vec![instr::pushl(tmp), instr::fcallfunc(fcall_args)]),
        ))
    };

    match expr_ {
        Expr_::ReadonlyExpr(r) => {
            // If calling a Readonly expression, first recurse inside to
            // handle ObjGet and ClassGet prop call cases. Keep track of the position of the
            // outer readonly expression for use later.
            // TODO: use the fact that this is a readonly call in HHVM enforcement
            emit_call_lhs_and_fcall(e, env, r, fcall_args, targs, Some(pos))
        }
        Expr_::ObjGet(o) if o.as_ref().3 == ast::PropOrMethod::IsMethod => {
            // Case $x->foo(...).
            // TODO: utilize caller_readonly_opt here for method calls
            let emit_id = |e: &mut Emitter<'arena, 'decl>,
                           obj,
                           id,
                           null_flavor: &ast::OgNullFlavor,
                           mut fcall_args| {
                let name =
                    method::MethodType::new(Str::new_str(alloc, string_utils::strip_global_ns(id)));
                let obj = emit_object_expr(e, env, obj)?;
                let generics = emit_generics(e, env, &mut fcall_args)?;
                let null_flavor = from_ast_null_flavor(*null_flavor);
                Ok((
                    InstrSeq::gather(vec![obj, instr::nulluninit()]),
                    InstrSeq::gather(vec![
                        generics,
                        instr::fcallobjmethodd(fcall_args, name, null_flavor),
                    ]),
                ))
            };
            match o.as_ref() {
                (obj, Expr(_, _, Expr_::String(id)), null_flavor, _) => {
                    emit_id(
                        e,
                        obj,
                        // FIXME: This is not safe--string literals are binary strings.
                        // There's no guarantee that they're valid UTF-8.
                        unsafe { std::str::from_utf8_unchecked(id.as_slice()) },
                        null_flavor,
                        fcall_args,
                    )
                }
                (Expr(_, pos, Expr_::New(new_exp)), Expr(_, _, Expr_::Id(id)), null_flavor, _)
                    if fcall_args.num_args == 0 =>
                {
                    let cexpr =
                        ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, &new_exp.0);
                    match &cexpr {
                        ClassExpr::Id(ast_defs::Id(_, name))
                            if string_utils::strip_global_ns(name) == "ReflectionClass" =>
                        {
                            let fid = match string_utils::strip_global_ns(&id.1) {
                                "isAbstract" => Some("__SystemLib\\reflection_class_is_abstract"),
                                "isInterface" => Some("__SystemLib\\reflection_class_is_interface"),
                                "isFinal" => Some("__SystemLib\\reflection_class_is_final"),
                                "getName" => Some("__SystemLib\\reflection_class_get_name"),
                                _ => None,
                            };
                            match fid {
                                None => emit_id(e, &o.as_ref().0, &id.1, null_flavor, fcall_args),
                                Some(fid) => {
                                    let fcall_args = FCallArgs::new(
                                        FCallArgsFlags::default(),
                                        1,
                                        1,
                                        Slice::empty(),
                                        Slice::empty(),
                                        None,
                                        None,
                                    );
                                    let newobj_instrs = emit_new(e, env, pos, new_exp, true);
                                    Ok((
                                        InstrSeq::gather(vec![
                                            instr::nulluninit(),
                                            instr::nulluninit(),
                                            newobj_instrs?,
                                        ]),
                                        InstrSeq::gather(vec![instr::fcallfuncd(
                                            fcall_args,
                                            function::FunctionType::<'arena>::from_ast_name(
                                                alloc, fid,
                                            ),
                                        )]),
                                    ))
                                }
                            }
                        }
                        _ => emit_id(e, &o.as_ref().0, &id.1, null_flavor, fcall_args),
                    }
                }
                (obj, Expr(_, _, Expr_::Id(id)), null_flavor, _) => {
                    emit_id(e, obj, &id.1, null_flavor, fcall_args)
                }
                (obj, method_expr, null_flavor, _) => {
                    let obj = emit_object_expr(e, env, obj)?;
                    let tmp = e.local_gen_mut().get_unnamed();
                    let null_flavor = from_ast_null_flavor(*null_flavor);
                    Ok((
                        InstrSeq::gather(vec![
                            obj,
                            instr::nulluninit(),
                            emit_expr(e, env, method_expr)?,
                            instr::popl(tmp),
                        ]),
                        InstrSeq::gather(vec![
                            instr::pushl(tmp),
                            instr::fcallobjmethod(fcall_args, null_flavor),
                        ]),
                    ))
                }
            }
        }
        Expr_::ClassConst(cls_const) => {
            let (cid, (_, id)) = &**cls_const;
            let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
            if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
                if let Some(reified_var_cexpr) = get_reified_var_cexpr(e, env, pos, name)? {
                    cexpr = reified_var_cexpr;
                }
            }
            let method_id =
                method::MethodType::new(Str::new_str(alloc, string_utils::strip_global_ns(id)));
            Ok(match cexpr {
                // Statically known
                ClassExpr::Id(ast_defs::Id(_, cname)) => {
                    let cid = class::ClassType::<'arena>::from_ast_name_and_mangle(alloc, &cname);
                    emit_symbol_refs::add_class(e, cid.clone());
                    let generics = emit_generics(e, env, &mut fcall_args)?;
                    (
                        InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
                        InstrSeq::gather(vec![
                            generics,
                            instr::fcallclsmethodd(fcall_args, method_id, cid),
                        ]),
                    )
                }
                ClassExpr::Special(clsref) => {
                    let generics = emit_generics(e, env, &mut fcall_args)?;
                    (
                        InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
                        InstrSeq::gather(vec![
                            generics,
                            instr::fcallclsmethodsd(fcall_args, clsref, method_id),
                        ]),
                    )
                }
                ClassExpr::Expr(expr) => {
                    let generics = emit_generics(e, env, &mut fcall_args)?;
                    (
                        InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
                        InstrSeq::gather(vec![
                            generics,
                            instr::string(alloc, method_id.unsafe_as_str()),
                            emit_expr(e, env, &expr)?,
                            instr::classgetc(),
                            instr::fcallclsmethod(
                                IsLogAsDynamicCallOp::DontLogAsDynamicCall,
                                fcall_args,
                            ),
                        ]),
                    )
                }
                ClassExpr::Reified(instrs) => {
                    let tmp = e.local_gen_mut().get_unnamed();
                    (
                        InstrSeq::gather(vec![
                            instr::nulluninit(),
                            instr::nulluninit(),
                            instrs,
                            instr::popl(tmp),
                        ]),
                        InstrSeq::gather(vec![
                            instr::string(alloc, method_id.unsafe_as_str()),
                            instr::pushl(tmp),
                            instr::classgetc(),
                            instr::fcallclsmethod(
                                IsLogAsDynamicCallOp::DontLogAsDynamicCall,
                                fcall_args,
                            ),
                        ]),
                    )
                }
            })
        }
        Expr_::ClassGet(c) if c.as_ref().2 == ast::PropOrMethod::IsMethod => {
            // Case Foo::bar(...).
            let (cid, cls_get_expr, _) = &**c;
            let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
            if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
                if let Some(reified_var_cexpr) = get_reified_var_cexpr(e, env, pos, name)? {
                    cexpr = reified_var_cexpr;
                }
            }
            let emit_meth_name = |e: &mut Emitter<'arena, 'decl>| match &cls_get_expr {
                ast::ClassGetExpr::CGstring((pos, id)) => {
                    Ok(emit_pos_then(pos, instr::cgetl(e.named_local(id.into()))))
                }
                ast::ClassGetExpr::CGexpr(expr) => emit_expr(e, env, expr),
            };
            Ok(match cexpr {
                ClassExpr::Id(cid) => {
                    let tmp = e.local_gen_mut().get_unnamed();
                    (
                        InstrSeq::gather(vec![
                            instr::nulluninit(),
                            instr::nulluninit(),
                            emit_meth_name(e)?,
                            instr::popl(tmp),
                        ]),
                        InstrSeq::gather(vec![
                            instr::pushl(tmp),
                            emit_known_class_id(alloc, e, &cid),
                            instr::fcallclsmethod(
                                IsLogAsDynamicCallOp::LogAsDynamicCall,
                                fcall_args,
                            ),
                        ]),
                    )
                }
                ClassExpr::Special(clsref) => {
                    let tmp = e.local_gen_mut().get_unnamed();
                    (
                        InstrSeq::gather(vec![
                            instr::nulluninit(),
                            instr::nulluninit(),
                            emit_meth_name(e)?,
                            instr::popl(tmp),
                        ]),
                        InstrSeq::gather(vec![
                            instr::pushl(tmp),
                            instr::fcallclsmethods(fcall_args, clsref),
                        ]),
                    )
                }
                ClassExpr::Expr(expr) => {
                    let cls = e.local_gen_mut().get_unnamed();
                    let meth = e.local_gen_mut().get_unnamed();
                    (
                        InstrSeq::gather(vec![
                            instr::nulluninit(),
                            instr::nulluninit(),
                            emit_expr(e, env, &expr)?,
                            instr::popl(cls),
                            emit_meth_name(e)?,
                            instr::popl(meth),
                        ]),
                        InstrSeq::gather(vec![
                            instr::pushl(meth),
                            instr::pushl(cls),
                            instr::classgetc(),
                            instr::fcallclsmethod(
                                IsLogAsDynamicCallOp::LogAsDynamicCall,
                                fcall_args,
                            ),
                        ]),
                    )
                }
                ClassExpr::Reified(instrs) => {
                    let cls = e.local_gen_mut().get_unnamed();
                    let meth = e.local_gen_mut().get_unnamed();
                    (
                        InstrSeq::gather(vec![
                            instr::nulluninit(),
                            instr::nulluninit(),
                            instrs,
                            instr::popl(cls),
                            emit_meth_name(e)?,
                            instr::popl(meth),
                        ]),
                        InstrSeq::gather(vec![
                            instr::pushl(meth),
                            instr::pushl(cls),
                            instr::classgetc(),
                            instr::fcallclsmethod(
                                IsLogAsDynamicCallOp::LogAsDynamicCall,
                                fcall_args,
                            ),
                        ]),
                    )
                }
            })
        }
        Expr_::Id(id) => {
            let FCallArgs {
                flags, num_args, ..
            } = fcall_args;
            let fq_id = match string_utils::strip_global_ns(&id.1) {
                "min" if num_args == 2 && !flags.contains(FCallArgsFlags::HasUnpack) => {
                    function::FunctionType::<'arena>::from_ast_name(alloc, "__SystemLib\\min2")
                }
                "max" if num_args == 2 && !flags.contains(FCallArgsFlags::HasUnpack) => {
                    function::FunctionType::<'arena>::from_ast_name(alloc, "__SystemLib\\max2")
                }
                _ => function::FunctionType::new(Str::new_str(
                    alloc,
                    string_utils::strip_global_ns(&id.1),
                )),
            };
            let generics = emit_generics(e, env, &mut fcall_args)?;
            Ok((
                InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
                InstrSeq::gather(vec![generics, instr::fcallfuncd(fcall_args, fq_id)]),
            ))
        }
        Expr_::String(s) => {
            // TODO(hrust) should be able to accept `let fq_id = function::from_raw_string(s);`
            let fq_id = function::FunctionType::new(Str::new_str(alloc, s.to_string().as_str()));
            let generics = emit_generics(e, env, &mut fcall_args)?;
            Ok((
                InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
                InstrSeq::gather(vec![generics, instr::fcallfuncd(fcall_args, fq_id)]),
            ))
        }
        _ => emit_fcall_func(e, env, expr, fcall_args, caller_readonly_opt),
    }
}