fn emit_special_function()

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(&param.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,
                },
            },
        ),
    }
}