php-lsp 0.29.0

A PHP Language Server Protocol implementation
Documentation
pub(crate) struct CallableInfo {
    pub params: String,
    #[cfg_attr(target_family = "wasm", allow(dead_code))]
    pub return_type: Option<String>,
    pub documentation: Option<String>,
}

pub(crate) fn format_declared_params(params: &[mir_analyzer::DeclaredParam]) -> String {
    params
        .iter()
        .map(|p| {
            let mut s = String::new();
            if let Some(ty) = &p.ty {
                s.push_str(&format!("{ty} "));
            }
            if p.is_variadic {
                s.push_str("...");
            }
            s.push_str(&format!("${}", p.name.as_str().trim_start_matches('$')));
            if let Some(text) = &p.default_text {
                s.push_str(&format!(" = {text}"));
            } else if p.has_default {
                s.push_str(" = ...");
            }
            s
        })
        .collect::<Vec<_>>()
        .join(", ")
}

pub(crate) fn callable_info_for_name(
    db: &mir_analyzer::db::MirDbStorage,
    symbol: &mir_analyzer::Name,
) -> Option<CallableInfo> {
    match symbol {
        mir_analyzer::Name::Function(fqn) => {
            let short = fqn.rsplit('\\').next().unwrap_or(fqn.as_ref());
            if mir_analyzer::is_builtin_function(short) {
                return None;
            }
            let f = mir_analyzer::db::find_function(
                db,
                mir_analyzer::db::Fqcn::from_str(db, fqn.as_ref()),
            )?;
            Some(CallableInfo {
                params: format_declared_params(&f.params),
                return_type: f.effective_return_type().map(ToString::to_string),
                documentation: f.docstring.as_deref().map(str::to_string),
            })
        }
        mir_analyzer::Name::Method { class, name } => {
            let (_, m) = mir_analyzer::db::find_method_in_chain(
                db,
                mir_analyzer::db::Fqcn::from_str(db, class.as_ref()),
                name.as_ref(),
            )?;
            Some(CallableInfo {
                params: format_declared_params(&m.params),
                return_type: m
                    .return_type
                    .as_deref()
                    .or(m.inferred_return_type.as_deref())
                    .map(ToString::to_string),
                documentation: m.docstring.as_deref().map(str::to_string),
            })
        }
        mir_analyzer::Name::Class(fqcn) => {
            let (_, m) = mir_analyzer::db::find_method_in_chain(
                db,
                mir_analyzer::db::Fqcn::from_str(db, fqcn.as_ref()),
                "__construct",
            )?;
            Some(CallableInfo {
                params: format_declared_params(&m.params),
                return_type: None,
                documentation: m.docstring.as_deref().map(str::to_string),
            })
        }
        _ => None,
    }
}

/// Whether the parameter of `symbol` that takes the `position`-th (or `name`d) argument is `&`.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
pub(crate) fn param_is_byref(
    db: &mir_analyzer::db::MirDbStorage,
    symbol: &mir_analyzer::Name,
    position: usize,
    name: Option<&str>,
) -> bool {
    let params = match symbol {
        mir_analyzer::Name::Function(fqn) => {
            mir_analyzer::db::find_function(db, mir_analyzer::db::Fqcn::from_str(db, fqn.as_ref()))
                .map(|f| f.params.clone())
        }
        mir_analyzer::Name::Method { class, name } => mir_analyzer::db::find_method_in_chain(
            db,
            mir_analyzer::db::Fqcn::from_str(db, class.as_ref()),
            name.as_ref(),
        )
        .map(|(_, m)| m.params.clone()),
        mir_analyzer::Name::Class(fqcn) => mir_analyzer::db::find_method_in_chain(
            db,
            mir_analyzer::db::Fqcn::from_str(db, fqcn.as_ref()),
            "__construct",
        )
        .map(|(_, m)| m.params.clone()),
        _ => None,
    };
    let Some(params) = params else {
        return false;
    };
    let param = match name {
        Some(name) => params
            .iter()
            .find(|p| p.name.as_str().trim_start_matches('$') == name),
        None => params
            .get(position)
            .or_else(|| params.last().filter(|p| p.is_variadic)),
    };
    param.is_some_and(|p| p.is_byref)
}