Skip to content

Commit 87f13bb

Browse files
committed
feat: show parameter hint for missing arguments
1 parent 7d052bd commit 87f13bb

File tree

7 files changed

+222
-0
lines changed

7 files changed

+222
-0
lines changed

crates/ide/src/inlay_hints.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ pub struct InlayHintsConfig<'a> {
307307
pub sized_bound: bool,
308308
pub discriminant_hints: DiscriminantHints,
309309
pub parameter_hints: bool,
310+
pub parameter_hints_for_missing_arguments: bool,
310311
pub generic_parameter_hints: GenericParameterHints,
311312
pub chaining_hints: bool,
312313
pub adjustment_hints: AdjustmentHints,
@@ -886,6 +887,7 @@ mod tests {
886887
render_colons: false,
887888
type_hints: false,
888889
parameter_hints: false,
890+
parameter_hints_for_missing_arguments: false,
889891
sized_bound: false,
890892
generic_parameter_hints: GenericParameterHints {
891893
type_hints: false,

crates/ide/src/inlay_hints/param_name.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use hir::{EditionedFileId, Semantics};
1111
use ide_db::{RootDatabase, famous_defs::FamousDefs};
1212

1313
use stdx::to_lower_snake_case;
14+
use syntax::T;
1415
use syntax::ast::{self, AstNode, HasArgList, HasName, UnaryOp};
1516

1617
use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
@@ -88,9 +89,75 @@ pub(super) fn hints(
8889
});
8990

9091
acc.extend(hints);
92+
93+
// Show hint for the next expected (missing) argument if enabled
94+
if config.parameter_hints_for_missing_arguments {
95+
let provided_args_count = arg_list.args().count();
96+
let params = callable.params();
97+
let total_params = params.len();
98+
99+
if provided_args_count < total_params
100+
&& let Some(next_param) = params.get(provided_args_count)
101+
&& let Some(param_name) = next_param.name(sema.db)
102+
{
103+
// Apply heuristics to hide obvious parameter hints
104+
if should_hide_missing_param_hint(unary_function, function_name, param_name.as_str()) {
105+
return Some(());
106+
}
107+
108+
// Determine the position for the hint
109+
if let Some(hint_range) = missing_arg_hint_position(&arg_list) {
110+
let colon = if config.render_colons { ":" } else { "" };
111+
let label = InlayHintLabel::simple(
112+
format!("{}{}", param_name.display(sema.db, krate.edition(sema.db)), colon),
113+
None,
114+
config.lazy_location_opt(|| {
115+
let source = sema.source(next_param.clone())?;
116+
let name_syntax = match source.value.as_ref() {
117+
Either::Left(pat) => pat.name(),
118+
Either::Right(param) => match param.pat()? {
119+
ast::Pat::IdentPat(it) => it.name(),
120+
_ => None,
121+
},
122+
}?;
123+
sema.original_range_opt(name_syntax.syntax()).map(|frange| {
124+
ide_db::FileRange {
125+
file_id: frange.file_id.file_id(sema.db),
126+
range: frange.range,
127+
}
128+
})
129+
}),
130+
);
131+
acc.push(InlayHint {
132+
range: hint_range,
133+
kind: InlayKind::Parameter,
134+
label,
135+
text_edit: None,
136+
position: InlayHintPosition::Before,
137+
pad_left: true,
138+
pad_right: false,
139+
resolve_parent: Some(expr.syntax().text_range()),
140+
});
141+
}
142+
}
143+
}
144+
91145
Some(())
92146
}
93147

148+
/// Determines the position where the hint for a missing argument should be placed.
149+
/// Returns the range of the token where the hint should appear.
150+
fn missing_arg_hint_position(arg_list: &ast::ArgList) -> Option<syntax::TextRange> {
151+
// Always place the hint on the closing paren, so it appears before `)`.
152+
// This way `foo()` becomes `foo(a)` visually with the hint.
153+
arg_list
154+
.syntax()
155+
.children_with_tokens()
156+
.filter_map(|it| it.into_token())
157+
.find(|t| t.kind() == T![')'])
158+
.map(|t| t.text_range())
159+
}
160+
94161
fn get_callable<'db>(
95162
sema: &Semantics<'db, RootDatabase>,
96163
expr: &ast::Expr,
@@ -153,6 +220,37 @@ fn should_hide_param_name_hint(
153220
is_argument_expr_similar_to_param_name(sema, argument, param_name)
154221
}
155222

223+
/// Determines whether to hide the parameter hint for a missing argument.
224+
/// This is a simplified version of `should_hide_param_name_hint` that doesn't
225+
/// require an actual argument expression.
226+
fn should_hide_missing_param_hint(
227+
unary_function: bool,
228+
function_name: Option<&str>,
229+
param_name: &str,
230+
) -> bool {
231+
let param_name = param_name.trim_matches('_');
232+
if param_name.is_empty() {
233+
return true;
234+
}
235+
236+
if param_name.starts_with("ra_fixture") {
237+
return true;
238+
}
239+
240+
if unary_function {
241+
if let Some(function_name) = function_name
242+
&& is_param_name_suffix_of_fn_name(param_name, function_name)
243+
{
244+
return true;
245+
}
246+
if is_obvious_param(param_name) {
247+
return true;
248+
}
249+
}
250+
251+
false
252+
}
253+
156254
/// Hide the parameter name of a unary function if it is a `_` - prefixed suffix of the function's name, or equal.
157255
///
158256
/// `fn strip_suffix(suffix)` will be hidden.
@@ -606,6 +704,103 @@ fn main() {
606704
// ^^^^^^ a_d_e
607705
baz(a.d.ec);
608706
// ^^^^^^ a_d_e
707+
}"#,
708+
);
709+
}
710+
711+
#[track_caller]
712+
fn check_missing_params(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
713+
check_with_config(
714+
InlayHintsConfig {
715+
parameter_hints: true,
716+
parameter_hints_for_missing_arguments: true,
717+
..DISABLED_CONFIG
718+
},
719+
ra_fixture,
720+
);
721+
}
722+
723+
#[test]
724+
fn missing_param_hint_empty_call() {
725+
// When calling foo() with no args, show hint for first param on the closing paren
726+
check_missing_params(
727+
r#"
728+
fn foo(a: i32, b: i32) -> i32 { a + b }
729+
fn main() {
730+
foo();
731+
//^ a
732+
}"#,
733+
);
734+
}
735+
736+
#[test]
737+
fn missing_param_hint_after_first_arg() {
738+
// foo(1,) - show hint for 'a' on '1', and 'b' on the trailing comma
739+
check_missing_params(
740+
r#"
741+
fn foo(a: i32, b: i32) -> i32 { a + b }
742+
fn main() {
743+
foo(1,);
744+
//^ a
745+
//^ b
746+
}"#,
747+
);
748+
}
749+
750+
#[test]
751+
fn missing_param_hint_partial_args() {
752+
// foo(1, 2,) - show hints for a, b on args, and c on trailing comma
753+
check_missing_params(
754+
r#"
755+
fn foo(a: i32, b: i32, c: i32) -> i32 { a + b + c }
756+
fn main() {
757+
foo(1, 2,);
758+
//^ a
759+
//^ b
760+
//^ c
761+
}"#,
762+
);
763+
}
764+
765+
#[test]
766+
fn missing_param_hint_method_call() {
767+
// S.foo(1,) - show hint for 'a' on '1', and 'b' on trailing comma
768+
check_missing_params(
769+
r#"
770+
struct S;
771+
impl S {
772+
fn foo(&self, a: i32, b: i32) -> i32 { a + b }
773+
}
774+
fn main() {
775+
S.foo(1,);
776+
//^ a
777+
//^ b
778+
}"#,
779+
);
780+
}
781+
782+
#[test]
783+
fn missing_param_hint_no_hint_when_complete() {
784+
// When all args provided, no missing hint - just regular param hints
785+
check_missing_params(
786+
r#"
787+
fn foo(a: i32, b: i32) -> i32 { a + b }
788+
fn main() {
789+
foo(1, 2);
790+
//^ a
791+
//^ b
792+
}"#,
793+
);
794+
}
795+
796+
#[test]
797+
fn missing_param_hint_respects_heuristics() {
798+
// The hint should be hidden if it matches heuristics (e.g., single param unary fn with same name)
799+
check_missing_params(
800+
r#"
801+
fn foo(foo: i32) -> i32 { foo }
802+
fn main() {
803+
foo();
609804
}"#,
610805
);
611806
}

crates/ide/src/static_index.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ impl StaticIndex<'_> {
169169
type_hints: true,
170170
sized_bound: false,
171171
parameter_hints: true,
172+
parameter_hints_for_missing_arguments: false,
172173
generic_parameter_hints: crate::GenericParameterHints {
173174
type_hints: false,
174175
lifetime_hints: false,

crates/rust-analyzer/src/cli/analysis_stats.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,7 @@ impl flags::AnalysisStats {
12051205
sized_bound: false,
12061206
discriminant_hints: ide::DiscriminantHints::Always,
12071207
parameter_hints: true,
1208+
parameter_hints_for_missing_arguments: false,
12081209
generic_parameter_hints: ide::GenericParameterHints {
12091210
type_hints: true,
12101211
lifetime_hints: true,

crates/rust-analyzer/src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ config_data! {
280280
/// Show function parameter name inlay hints at the call site.
281281
inlayHints_parameterHints_enable: bool = true,
282282

283+
/// Show parameter name inlay hints for missing arguments at the call site.
284+
inlayHints_parameterHints_missingArguments_enable: bool = false,
285+
283286
/// Show exclusive range inlay hints.
284287
inlayHints_rangeExclusiveHints_enable: bool = false,
285288

@@ -1916,6 +1919,9 @@ impl Config {
19161919
type_hints: self.inlayHints_typeHints_enable().to_owned(),
19171920
sized_bound: self.inlayHints_implicitSizedBoundHints_enable().to_owned(),
19181921
parameter_hints: self.inlayHints_parameterHints_enable().to_owned(),
1922+
parameter_hints_for_missing_arguments: self
1923+
.inlayHints_parameterHints_missingArguments_enable()
1924+
.to_owned(),
19191925
generic_parameter_hints: GenericParameterHints {
19201926
type_hints: self.inlayHints_genericParameterHints_type_enable().to_owned(),
19211927
lifetime_hints: self.inlayHints_genericParameterHints_lifetime_enable().to_owned(),

docs/book/src/configuration_generated.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,13 @@ Default: `true`
10701070
Show function parameter name inlay hints at the call site.
10711071

10721072

1073+
## rust-analyzer.inlayHints.parameterHints.missingArguments.enable {#inlayHints.parameterHints.missingArguments.enable}
1074+
1075+
Default: `false`
1076+
1077+
Show parameter name inlay hints for missing arguments at the call site.
1078+
1079+
10731080
## rust-analyzer.inlayHints.rangeExclusiveHints.enable {#inlayHints.rangeExclusiveHints.enable}
10741081

10751082
Default: `false`

editors/code/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,6 +2396,16 @@
23962396
}
23972397
}
23982398
},
2399+
{
2400+
"title": "Inlay Hints",
2401+
"properties": {
2402+
"rust-analyzer.inlayHints.parameterHints.missingArguments.enable": {
2403+
"markdownDescription": "Show parameter name inlay hints for missing arguments at the call site.",
2404+
"default": false,
2405+
"type": "boolean"
2406+
}
2407+
}
2408+
},
23992409
{
24002410
"title": "Inlay Hints",
24012411
"properties": {

0 commit comments

Comments
 (0)