php_lsp/analysis/
callable_info.rs1pub(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#[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}