Skip to content

Commit b3b4b01

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

File tree

7 files changed

+232
-0
lines changed

7 files changed

+232
-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: 205 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,85 @@ 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+
// Get the next expected parameter
101+
if let Some(next_param) = params.get(provided_args_count) {
102+
if let Some(param_name) = next_param.name(sema.db) {
103+
// Apply heuristics to hide obvious parameter hints
104+
if should_hide_missing_param_hint(
105+
unary_function,
106+
function_name,
107+
param_name.as_str(),
108+
) {
109+
return Some(());
110+
}
111+
112+
// Determine the position for the hint
113+
if let Some(hint_range) = missing_arg_hint_position(&arg_list) {
114+
let colon = if config.render_colons { ":" } else { "" };
115+
let label = InlayHintLabel::simple(
116+
format!(
117+
"{}{}",
118+
param_name.display(sema.db, krate.edition(sema.db)),
119+
colon
120+
),
121+
None,
122+
config.lazy_location_opt(|| {
123+
let source = sema.source(next_param.clone())?;
124+
let name_syntax = match source.value.as_ref() {
125+
Either::Left(pat) => pat.name(),
126+
Either::Right(param) => match param.pat()? {
127+
ast::Pat::IdentPat(it) => it.name(),
128+
_ => None,
129+
},
130+
}?;
131+
sema.original_range_opt(name_syntax.syntax()).map(|frange| {
132+
ide_db::FileRange {
133+
file_id: frange.file_id.file_id(sema.db),
134+
range: frange.range,
135+
}
136+
})
137+
}),
138+
);
139+
acc.push(InlayHint {
140+
range: hint_range,
141+
kind: InlayKind::Parameter,
142+
label,
143+
text_edit: None,
144+
position: InlayHintPosition::Before,
145+
pad_left: true,
146+
pad_right: false,
147+
resolve_parent: Some(expr.syntax().text_range()),
148+
});
149+
}
150+
}
151+
}
152+
}
153+
}
154+
91155
Some(())
92156
}
93157

158+
/// Determines the position where the hint for a missing argument should be placed.
159+
/// Returns the range of the token where the hint should appear.
160+
fn missing_arg_hint_position(arg_list: &ast::ArgList) -> Option<syntax::TextRange> {
161+
// Always place the hint on the closing paren, so it appears before `)`.
162+
// This way `foo()` becomes `foo(a)` visually with the hint.
163+
arg_list
164+
.syntax()
165+
.children_with_tokens()
166+
.filter_map(|it| it.into_token())
167+
.find(|t| t.kind() == T![')'])
168+
.map(|t| t.text_range())
169+
}
170+
94171
fn get_callable<'db>(
95172
sema: &Semantics<'db, RootDatabase>,
96173
expr: &ast::Expr,
@@ -153,6 +230,37 @@ fn should_hide_param_name_hint(
153230
is_argument_expr_similar_to_param_name(sema, argument, param_name)
154231
}
155232

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

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)