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),
}
}