Skip to content

Commit 3304310

Browse files
committed
Infer template params from default values in function and constructor
calls
1 parent 3383fc3 commit 3304310

2 files changed

Lines changed: 65 additions & 22 deletions

File tree

src/completion/call_resolution.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,23 +2008,29 @@ impl Backend {
20082008
let arg_text = match arg_texts.get(param_idx) {
20092009
Some(text) => text.trim(),
20102010
None => {
2011-
// No argument was provided at the call site. If the
2012-
// parameter has a default value of `null`, resolve the
2013-
// template param to `null`. This handles the common
2014-
// pattern `@template TDefault` with
2015-
// `@param TDefault $default = null` where the caller
2016-
// omits the argument — e.g. `Collection::first()`
2017-
// returns `TValue|TFirstDefault` and `TFirstDefault`
2018-
// should become `null` when no default is passed.
2011+
// No argument was provided at the call site. Fall
2012+
// back to the parameter's default value so that
2013+
// template params can be inferred from defaults
2014+
// (e.g. `app()` with `$name = Application::class`).
20192015
if let Some(param) = method.parameters.get(param_idx)
2020-
&& param.default_value.as_deref().is_some_and(|d| d == "null")
2016+
&& let Some(default) = param.default_value.as_deref()
20212017
&& !subs.contains_key(tpl_name.as_str())
20222018
{
2023-
crate::completion::variable::rhs_resolution::insert_or_union(
2024-
&mut subs,
2025-
tpl_name.to_string(),
2026-
PhpType::null(),
2027-
);
2019+
if default == "null" {
2020+
crate::completion::variable::rhs_resolution::insert_or_union(
2021+
&mut subs,
2022+
tpl_name.to_string(),
2023+
PhpType::null(),
2024+
);
2025+
} else if let Some(resolved) =
2026+
Backend::resolve_arg_text_to_type(default, ctx)
2027+
{
2028+
crate::completion::variable::rhs_resolution::insert_or_union(
2029+
&mut subs,
2030+
tpl_name.to_string(),
2031+
resolved,
2032+
);
2033+
}
20282034
}
20292035
continue;
20302036
}

src/completion/variable/rhs_resolution.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,10 +1164,7 @@ fn build_constructor_template_subs(
11641164
};
11651165

11661166
// Get the corresponding argument text.
1167-
let arg_text = match arg_texts.get(param_idx) {
1168-
Some(text) => text.trim(),
1169-
None => continue,
1170-
};
1167+
let provided_arg = arg_texts.get(param_idx).map(|t| t.trim());
11711168

11721169
// Determine the binding mode by inspecting the parameter's
11731170
// docblock type hint. The type hint tells us how the template
@@ -1178,6 +1175,27 @@ fn build_constructor_template_subs(
11781175
.and_then(|p| p.type_hint.as_ref());
11791176
let binding_mode = classify_template_binding(tpl_name, param_hint);
11801177

1178+
// Fall back to the parameter's default value only for binding
1179+
// modes where the default is meaningful.
1180+
let default_value = ctor
1181+
.parameters
1182+
.get(param_idx)
1183+
.and_then(|p| p.default_value.as_deref());
1184+
let arg_text: &str = match provided_arg {
1185+
Some(text) => text,
1186+
None => match &binding_mode {
1187+
TemplateBindingMode::ClassStringInner => match default_value {
1188+
Some(d) => d,
1189+
None => continue,
1190+
},
1191+
TemplateBindingMode::Direct => match default_value {
1192+
Some(d) if d.ends_with("::class") => d,
1193+
_ => continue,
1194+
},
1195+
_ => continue,
1196+
},
1197+
};
1198+
11811199
match binding_mode {
11821200
TemplateBindingMode::Direct => {
11831201
// `@param T $bar` — the argument resolves directly to T.
@@ -1856,10 +1874,7 @@ pub(crate) fn build_function_template_subs(
18561874
None => continue,
18571875
};
18581876

1859-
let arg_text = match arg_texts.get(param_idx) {
1860-
Some(text) => text.trim(),
1861-
None => continue,
1862-
};
1877+
let provided_arg = arg_texts.get(param_idx).map(|t| t.trim());
18631878

18641879
// Determine the binding mode by inspecting the parameter's
18651880
// docblock type hint. The type hint tells us how the template
@@ -1870,6 +1885,28 @@ pub(crate) fn build_function_template_subs(
18701885
.and_then(|p| p.type_hint.as_ref());
18711886
let binding_mode = classify_template_binding(tpl_name, param_hint);
18721887

1888+
// Fall back to the parameter's default value only for binding
1889+
// modes where the default is meaningful (class-string<T> with
1890+
// a `Foo::class` default, or direct bindings with `::class`).
1891+
let default_value = func_info
1892+
.parameters
1893+
.get(param_idx)
1894+
.and_then(|p| p.default_value.as_deref());
1895+
let arg_text: &str = match provided_arg {
1896+
Some(text) => text,
1897+
None => match &binding_mode {
1898+
TemplateBindingMode::ClassStringInner => match default_value {
1899+
Some(d) => d,
1900+
None => continue,
1901+
},
1902+
TemplateBindingMode::Direct => match default_value {
1903+
Some(d) if d.ends_with("::class") => d,
1904+
_ => continue,
1905+
},
1906+
_ => continue,
1907+
},
1908+
};
1909+
18731910
match binding_mode {
18741911
TemplateBindingMode::Direct => {
18751912
if let Some(resolved_type) = Backend::resolve_arg_text_to_type(arg_text, rctx) {

0 commit comments

Comments
 (0)