Skip to main content

php_lsp/analysis/
callable_info.rs

1pub(crate) struct CallableInfo {
2    pub params: String,
3    #[cfg_attr(target_family = "wasm", allow(dead_code))]
4    pub return_type: Option<String>,
5    pub documentation: Option<String>,
6}
7
8pub(crate) fn format_declared_params(params: &[mir_analyzer::DeclaredParam]) -> String {
9    params
10        .iter()
11        .map(|p| {
12            let mut s = String::new();
13            if let Some(ty) = &p.ty {
14                s.push_str(&format!("{ty} "));
15            }
16            if p.is_variadic {
17                s.push_str("...");
18            }
19            s.push_str(&format!("${}", p.name.as_str().trim_start_matches('$')));
20            if let Some(text) = &p.default_text {
21                s.push_str(&format!(" = {text}"));
22            } else if p.has_default {
23                s.push_str(" = ...");
24            }
25            s
26        })
27        .collect::<Vec<_>>()
28        .join(", ")
29}
30
31pub(crate) fn callable_info_for_name(
32    db: &mir_analyzer::db::MirDbStorage,
33    symbol: &mir_analyzer::Name,
34) -> Option<CallableInfo> {
35    match symbol {
36        mir_analyzer::Name::Function(fqn) => {
37            let short = fqn.rsplit('\\').next().unwrap_or(fqn.as_ref());
38            if mir_analyzer::is_builtin_function(short) {
39                return None;
40            }
41            let f = mir_analyzer::db::find_function(
42                db,
43                mir_analyzer::db::Fqcn::from_str(db, fqn.as_ref()),
44            )?;
45            Some(CallableInfo {
46                params: format_declared_params(&f.params),
47                return_type: f.effective_return_type().map(ToString::to_string),
48                documentation: f.docstring.as_deref().map(str::to_string),
49            })
50        }
51        mir_analyzer::Name::Method { class, name } => {
52            let (_, m) = mir_analyzer::db::find_method_in_chain(
53                db,
54                mir_analyzer::db::Fqcn::from_str(db, class.as_ref()),
55                name.as_ref(),
56            )?;
57            Some(CallableInfo {
58                params: format_declared_params(&m.params),
59                return_type: m
60                    .return_type
61                    .as_deref()
62                    .or(m.inferred_return_type.as_deref())
63                    .map(ToString::to_string),
64                documentation: m.docstring.as_deref().map(str::to_string),
65            })
66        }
67        mir_analyzer::Name::Class(fqcn) => {
68            let (_, m) = mir_analyzer::db::find_method_in_chain(
69                db,
70                mir_analyzer::db::Fqcn::from_str(db, fqcn.as_ref()),
71                "__construct",
72            )?;
73            Some(CallableInfo {
74                params: format_declared_params(&m.params),
75                return_type: None,
76                documentation: m.docstring.as_deref().map(str::to_string),
77            })
78        }
79        _ => None,
80    }
81}
82
83/// Whether the parameter of `symbol` that takes the `position`-th (or `name`d) argument is `&`.
84#[cfg_attr(target_family = "wasm", allow(dead_code))]
85pub(crate) fn param_is_byref(
86    db: &mir_analyzer::db::MirDbStorage,
87    symbol: &mir_analyzer::Name,
88    position: usize,
89    name: Option<&str>,
90) -> bool {
91    let params = match symbol {
92        mir_analyzer::Name::Function(fqn) => {
93            mir_analyzer::db::find_function(db, mir_analyzer::db::Fqcn::from_str(db, fqn.as_ref()))
94                .map(|f| f.params.clone())
95        }
96        mir_analyzer::Name::Method { class, name } => mir_analyzer::db::find_method_in_chain(
97            db,
98            mir_analyzer::db::Fqcn::from_str(db, class.as_ref()),
99            name.as_ref(),
100        )
101        .map(|(_, m)| m.params.clone()),
102        mir_analyzer::Name::Class(fqcn) => mir_analyzer::db::find_method_in_chain(
103            db,
104            mir_analyzer::db::Fqcn::from_str(db, fqcn.as_ref()),
105            "__construct",
106        )
107        .map(|(_, m)| m.params.clone()),
108        _ => None,
109    };
110    let Some(params) = params else {
111        return false;
112    };
113    let param = match name {
114        Some(name) => params
115            .iter()
116            .find(|p| p.name.as_str().trim_start_matches('$') == name),
117        None => params
118            .get(position)
119            .or_else(|| params.last().filter(|p| p.is_variadic)),
120    };
121    param.is_some_and(|p| p.is_byref)
122}