in hphp/hack/src/hackc/emitter/emit_expression.rs [2488:2830]
fn emit_special_function<'a, 'arena, 'decl>(
e: &mut Emitter<'arena, 'decl>,
env: &Env<'a, 'arena>,
pos: &Pos,
args: &[(ParamKind, ast::Expr)],
uarg: Option<&ast::Expr>,
lower_fq_name: &str,
) -> Result<Option<InstrSeq<'arena>>> {
use ast::{Expr, Expr_};
let alloc = env.arena;
let nargs = args.len() + uarg.map_or(0, |_| 1);
let fun_and_clsmeth_disabled = e
.options()
.hhvm
.hack_lang
.flags
.contains(LangFlags::DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS);
match (lower_fq_name, args) {
(id, _) if id == special_functions::ECHO => Ok(Some(InstrSeq::gather(
args.iter()
.enumerate()
.map(|(i, arg)| {
Ok(InstrSeq::gather(vec![
emit_expr(e, env, expect_normal_paramkind(arg)?)?,
emit_pos(pos),
instr::print(),
if i == nargs - 1 {
instr::empty()
} else {
instr::popc()
},
]))
})
.collect::<Result<_>>()?,
))),
("HH\\invariant", args) if args.len() >= 2 => {
let l = e.label_gen_mut().next_regular();
let expr_id = ast::Expr(
(),
pos.clone(),
ast::Expr_::mk_id(ast_defs::Id(
pos.clone(),
"\\hh\\invariant_violation".into(),
)),
);
let call = ast::Expr(
(),
pos.clone(),
ast::Expr_::mk_call(expr_id, vec![], args[1..].to_owned(), uarg.cloned()),
);
let ignored_expr = emit_ignored_expr(e, env, &Pos::make_none(), &call)?;
Ok(Some(InstrSeq::gather(vec![
emit_expr(e, env, expect_normal_paramkind(&args[0])?)?,
instr::jmpnz(l),
ignored_expr,
emit_fatal::emit_fatal_runtime(alloc, pos, "invariant_violation"),
instr::label(l),
instr::null(),
])))
}
("class_exists", &[ref arg1, ..])
| ("trait_exists", &[ref arg1, ..])
| ("interface_exists", &[ref arg1, ..])
if nargs == 1 || nargs == 2 =>
{
let class_kind = match lower_fq_name {
"class_exists" => OODeclExistsOp::Class,
"interface_exists" => OODeclExistsOp::Interface,
"trait_exists" => OODeclExistsOp::Trait,
_ => return Err(Error::unrecoverable("emit_special_function: class_kind")),
};
Ok(Some(InstrSeq::gather(vec![
emit_expr(e, env, expect_normal_paramkind(arg1)?)?,
instr::cast_string(),
if nargs == 1 {
instr::true_()
} else {
InstrSeq::gather(vec![
emit_expr(e, env, expect_normal_paramkind(&args[1])?)?,
instr::cast_bool(),
])
},
instr::oodeclexists(class_kind),
])))
}
("exit", _) | ("die", _) if nargs == 0 || nargs == 1 => Ok(Some(emit_exit(
e,
env,
args.first().map(expect_normal_paramkind).transpose()?,
)?)),
("HH\\fun", _) => {
if fun_and_clsmeth_disabled {
match args {
[(_, ast::Expr(_, _, ast::Expr_::String(func_name)))] => {
Err(Error::fatal_parse(
pos,
format!(
"`fun()` is disabled; switch to first-class references like `{}<>`",
func_name
),
))
}
_ => Err(Error::fatal_runtime(
pos,
"Constant string expected in fun()",
)),
}
} else if nargs != 1 {
Err(Error::fatal_runtime(
pos,
format!("fun() expects exactly 1 parameter, {} given", nargs),
))
} else {
match args {
// `inout` is dropped here, but it should be impossible to have an expression
// like: `foo(inout "literal")`
[(_, ast::Expr(_, _, ast::Expr_::String(func_name)))] => {
Ok(Some(emit_hh_fun(
e,
env,
pos,
&[],
// 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(func_name.as_slice()) },
)?))
}
_ => Err(Error::fatal_runtime(
pos,
"Constant string expected in fun()",
)),
}
}
}
("__systemlib\\meth_caller", _) => {
// used by meth_caller() to directly emit func ptr
if nargs != 1 {
return Err(Error::fatal_runtime(
pos,
format!("fun() expects exactly 1 parameter, {} given", nargs),
));
}
match args {
// `inout` is dropped here, but it should be impossible to have an expression
// like: `foo(inout "literal")`
[(_, Expr(_, _, Expr_::String(ref func_name)))] => {
Ok(Some(instr::resolve_meth_caller(
function::FunctionType::new(Str::new_str(
alloc,
string_utils::strip_global_ns(
// 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(func_name.as_slice()) },
),
)),
)))
}
_ => Err(Error::fatal_runtime(
pos,
"Constant string expected in fun()",
)),
}
}
("__systemlib\\__debugger_is_uninit", _) => {
if nargs != 1 {
Err(Error::fatal_runtime(
pos,
format!(
"__debugger_is_uninit() expects exactly 1 parameter {} given",
nargs
),
))
} else {
match args {
// Calling convention is dropped here, but given this is meant for the debugger
// I don't think it particularly matters.
[(_, Expr(_, _, Expr_::Lvar(id)))] => {
Ok(Some(instr::isunsetl(get_local(e, env, pos, id.name())?)))
}
_ => Err(Error::fatal_runtime(
pos,
"Local variable expected in __debugger_is_uninit()",
)),
}
}
}
("__SystemLib\\get_enum_member_by_label", _) if e.systemlib() => {
let local = match args {
[(pk, Expr(_, _, Expr_::Lvar(id)))] => {
ensure_normal_paramkind(pk)?;
get_local(e, env, pos, id.name())
}
_ => Err(Error::fatal_runtime(
pos,
"Argument must be the label argument",
)),
}?;
Ok(Some(InstrSeq::gather(vec![
instr::lateboundcls(),
instr::clscnsl(local),
])))
}
("HH\\inst_meth", _) => match args {
[obj_expr, method_name] => Ok(Some(emit_inst_meth(
e,
env,
expect_normal_paramkind(obj_expr)?,
expect_normal_paramkind(method_name)?,
)?)),
_ => Err(Error::fatal_runtime(
pos,
format!("inst_meth() expects exactly 2 parameters, {} given", nargs),
)),
},
("HH\\class_meth", _) if fun_and_clsmeth_disabled => Err(Error::fatal_parse(
pos,
"`class_meth()` is disabled; switch to first-class references like `C::bar<>`",
)),
("HH\\class_meth", &[(ref pk_cls, ref cls), (ref pk_meth, ref meth), ..]) if nargs == 2 => {
if meth.2.is_string() {
if cls.2.is_string()
|| cls
.2
.as_class_const()
.map_or(false, |(_, (_, id))| string_utils::is_class(id))
|| cls
.2
.as_id()
.map_or(false, |ast_defs::Id(_, id)| id == pseudo_consts::G__CLASS__)
{
ensure_normal_paramkind(pk_cls)?;
ensure_normal_paramkind(pk_meth)?;
return Ok(Some(emit_class_meth(e, env, cls, meth)?));
}
}
Err(Error::fatal_runtime(
pos,
concat!(
"class_meth() expects a literal class name or ::class constant, ",
"followed by a constant string that refers to a static method ",
"on that class"
),
))
}
("HH\\class_meth", _) => Err(Error::fatal_runtime(
pos,
format!("class_meth() expects exactly 2 parameters, {} given", nargs),
)),
("HH\\global_set", _) => match *args {
[ref gkey, ref gvalue] => Ok(Some(InstrSeq::gather(vec![
emit_expr(e, env, expect_normal_paramkind(gkey)?)?,
emit_expr(e, env, expect_normal_paramkind(gvalue)?)?,
emit_pos(pos),
instr::setg(),
instr::popc(),
instr::null(),
]))),
_ => Err(Error::fatal_runtime(
pos,
format!("global_set() expects exactly 2 parameters, {} given", nargs),
)),
},
("HH\\global_unset", _) => match *args {
[ref gkey] => Ok(Some(InstrSeq::gather(vec![
emit_expr(e, env, expect_normal_paramkind(gkey)?)?,
emit_pos(pos),
instr::unsetg(),
instr::null(),
]))),
_ => Err(Error::fatal_runtime(
pos,
format!(
"global_unset() expects exactly 1 parameter, {} given",
nargs
),
)),
},
("__hhvm_internal_whresult", &[(ref pk, Expr(_, _, Expr_::Lvar(ref param)))])
if e.systemlib() =>
{
ensure_normal_paramkind(pk)?;
Ok(Some(InstrSeq::gather(vec![
instr::cgetl(e.named_local(local_id::get_name(¶m.1).into())),
instr::whresult(),
])))
}
("HH\\array_mark_legacy", _) if args.len() == 1 || args.len() == 2 => {
Ok(Some(emit_array_mark_legacy(e, env, pos, args, true)?))
}
("HH\\array_unmark_legacy", _) if args.len() == 1 || args.len() == 2 => {
Ok(Some(emit_array_mark_legacy(e, env, pos, args, false)?))
}
("HH\\tag_provenance_here", _) if args.len() == 1 || args.len() == 2 => {
Ok(Some(emit_tag_provenance_here(e, env, pos, args)?))
}
_ => Ok(
match (args, istype_op(lower_fq_name), is_isexp_op(lower_fq_name)) {
(&[ref arg_expr], _, Some(ref h)) => {
let is_expr = emit_is(e, env, pos, h)?;
Some(InstrSeq::gather(vec![
emit_expr(e, env, expect_normal_paramkind(arg_expr)?)?,
is_expr,
]))
}
(&[(ref pk, Expr(_, _, Expr_::Lvar(ref arg_id)))], Some(i), _)
if superglobals::is_any_global(arg_id.name()) =>
{
ensure_normal_paramkind(pk)?;
Some(InstrSeq::gather(vec![
emit_local(e, env, BareThisOp::NoNotice, arg_id)?,
emit_pos(pos),
instr::istypec(i),
]))
}
(&[(ref pk, Expr(_, _, Expr_::Lvar(ref arg_id)))], Some(i), _)
if !is_local_this(env, &arg_id.1) =>
{
ensure_normal_paramkind(pk)?;
Some(instr::istypel(
get_local(e, env, &arg_id.0, &(arg_id.1).1)?,
i,
))
}
(&[(ref pk, ref arg_expr)], Some(i), _) => {
ensure_normal_paramkind(pk)?;
Some(InstrSeq::gather(vec![
emit_expr(e, env, arg_expr)?,
emit_pos(pos),
instr::istypec(i),
]))
}
_ => match get_call_builtin_func_info(e, lower_fq_name) {
Some((nargs, i)) if nargs == args.len() => Some(InstrSeq::gather(vec![
emit_exprs_and_error_on_inout(e, env, args, lower_fq_name)?,
emit_pos(pos),
instr::instr(i),
])),
_ => None,
},
},
),
}
}