Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ pub struct InlayHintsConfig<'a> {
pub sized_bound: bool,
pub discriminant_hints: DiscriminantHints,
pub parameter_hints: bool,
pub parameter_hints_for_missing_arguments: bool,
pub generic_parameter_hints: GenericParameterHints,
pub chaining_hints: bool,
pub adjustment_hints: AdjustmentHints,
Expand Down Expand Up @@ -886,6 +887,7 @@ mod tests {
render_colons: false,
type_hints: false,
parameter_hints: false,
parameter_hints_for_missing_arguments: false,
sized_bound: false,
generic_parameter_hints: GenericParameterHints {
type_hints: false,
Expand Down
195 changes: 195 additions & 0 deletions crates/ide/src/inlay_hints/param_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use hir::{EditionedFileId, Semantics};
use ide_db::{RootDatabase, famous_defs::FamousDefs};

use stdx::to_lower_snake_case;
use syntax::T;
use syntax::ast::{self, AstNode, HasArgList, HasName, UnaryOp};

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

acc.extend(hints);

// Show hint for the next expected (missing) argument if enabled
if config.parameter_hints_for_missing_arguments {
let provided_args_count = arg_list.args().count();
let params = callable.params();
let total_params = params.len();

if provided_args_count < total_params
&& let Some(next_param) = params.get(provided_args_count)
&& let Some(param_name) = next_param.name(sema.db)
{
// Apply heuristics to hide obvious parameter hints
if should_hide_missing_param_hint(unary_function, function_name, param_name.as_str()) {
return Some(());
}

// Determine the position for the hint
if let Some(hint_range) = missing_arg_hint_position(&arg_list) {
let colon = if config.render_colons { ":" } else { "" };
let label = InlayHintLabel::simple(
format!("{}{}", param_name.display(sema.db, krate.edition(sema.db)), colon),
None,
config.lazy_location_opt(|| {
let source = sema.source(next_param.clone())?;
let name_syntax = match source.value.as_ref() {
Either::Left(pat) => pat.name(),
Either::Right(param) => match param.pat()? {
ast::Pat::IdentPat(it) => it.name(),
_ => None,
},
}?;
sema.original_range_opt(name_syntax.syntax()).map(|frange| {
ide_db::FileRange {
file_id: frange.file_id.file_id(sema.db),
range: frange.range,
}
})
}),
);
acc.push(InlayHint {
range: hint_range,
kind: InlayKind::Parameter,
label,
text_edit: None,
position: InlayHintPosition::Before,
pad_left: true,
pad_right: false,
resolve_parent: Some(expr.syntax().text_range()),
});
}
}
}

Some(())
}

/// Determines the position where the hint for a missing argument should be placed.
/// Returns the range of the token where the hint should appear.
fn missing_arg_hint_position(arg_list: &ast::ArgList) -> Option<syntax::TextRange> {
// Always place the hint on the closing paren, so it appears before `)`.
// This way `foo()` becomes `foo(a)` visually with the hint.
arg_list
.syntax()
.children_with_tokens()
.filter_map(|it| it.into_token())
.find(|t| t.kind() == T![')'])
.map(|t| t.text_range())
}

fn get_callable<'db>(
sema: &Semantics<'db, RootDatabase>,
expr: &ast::Expr,
Expand Down Expand Up @@ -153,6 +220,37 @@ fn should_hide_param_name_hint(
is_argument_expr_similar_to_param_name(sema, argument, param_name)
}

/// Determines whether to hide the parameter hint for a missing argument.
/// This is a simplified version of `should_hide_param_name_hint` that doesn't
/// require an actual argument expression.
fn should_hide_missing_param_hint(
unary_function: bool,
function_name: Option<&str>,
param_name: &str,
) -> bool {
let param_name = param_name.trim_matches('_');
if param_name.is_empty() {
return true;
}

if param_name.starts_with("ra_fixture") {
return true;
}

if unary_function {
if let Some(function_name) = function_name
&& is_param_name_suffix_of_fn_name(param_name, function_name)
{
return true;
}
if is_obvious_param(param_name) {
return true;
}
}

false
}

/// Hide the parameter name of a unary function if it is a `_` - prefixed suffix of the function's name, or equal.
///
/// `fn strip_suffix(suffix)` will be hidden.
Expand Down Expand Up @@ -606,6 +704,103 @@ fn main() {
// ^^^^^^ a_d_e
baz(a.d.ec);
// ^^^^^^ a_d_e
}"#,
);
}

#[track_caller]
fn check_missing_params(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
check_with_config(
InlayHintsConfig {
parameter_hints: true,
parameter_hints_for_missing_arguments: true,
..DISABLED_CONFIG
},
ra_fixture,
);
}

#[test]
fn missing_param_hint_empty_call() {
// When calling foo() with no args, show hint for first param on the closing paren
check_missing_params(
r#"
fn foo(a: i32, b: i32) -> i32 { a + b }
fn main() {
foo();
//^ a
}"#,
);
}

#[test]
fn missing_param_hint_after_first_arg() {
// foo(1,) - show hint for 'a' on '1', and 'b' on the trailing comma
check_missing_params(
r#"
fn foo(a: i32, b: i32) -> i32 { a + b }
fn main() {
foo(1,);
//^ a
//^ b
}"#,
);
}

#[test]
fn missing_param_hint_partial_args() {
// foo(1, 2,) - show hints for a, b on args, and c on trailing comma
check_missing_params(
r#"
fn foo(a: i32, b: i32, c: i32) -> i32 { a + b + c }
fn main() {
foo(1, 2,);
//^ a
//^ b
//^ c
}"#,
);
}

#[test]
fn missing_param_hint_method_call() {
// S.foo(1,) - show hint for 'a' on '1', and 'b' on trailing comma
check_missing_params(
r#"
struct S;
impl S {
fn foo(&self, a: i32, b: i32) -> i32 { a + b }
}
fn main() {
S.foo(1,);
//^ a
//^ b
}"#,
);
}

#[test]
fn missing_param_hint_no_hint_when_complete() {
// When all args provided, no missing hint - just regular param hints
check_missing_params(
r#"
fn foo(a: i32, b: i32) -> i32 { a + b }
fn main() {
foo(1, 2);
//^ a
//^ b
}"#,
);
}

#[test]
fn missing_param_hint_respects_heuristics() {
// The hint should be hidden if it matches heuristics (e.g., single param unary fn with same name)
check_missing_params(
r#"
fn foo(foo: i32) -> i32 { foo }
fn main() {
foo();
}"#,
);
}
Expand Down
1 change: 1 addition & 0 deletions crates/ide/src/static_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ impl StaticIndex<'_> {
type_hints: true,
sized_bound: false,
parameter_hints: true,
parameter_hints_for_missing_arguments: false,
generic_parameter_hints: crate::GenericParameterHints {
type_hints: false,
lifetime_hints: false,
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/cli/analysis_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,7 @@ impl flags::AnalysisStats {
sized_bound: false,
discriminant_hints: ide::DiscriminantHints::Always,
parameter_hints: true,
parameter_hints_for_missing_arguments: false,
generic_parameter_hints: ide::GenericParameterHints {
type_hints: true,
lifetime_hints: true,
Expand Down
6 changes: 6 additions & 0 deletions crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ config_data! {
/// Show function parameter name inlay hints at the call site.
inlayHints_parameterHints_enable: bool = true,

/// Show parameter name inlay hints for missing arguments at the call site.
inlayHints_parameterHints_missingArguments_enable: bool = false,

/// Show exclusive range inlay hints.
inlayHints_rangeExclusiveHints_enable: bool = false,

Expand Down Expand Up @@ -1916,6 +1919,9 @@ impl Config {
type_hints: self.inlayHints_typeHints_enable().to_owned(),
sized_bound: self.inlayHints_implicitSizedBoundHints_enable().to_owned(),
parameter_hints: self.inlayHints_parameterHints_enable().to_owned(),
parameter_hints_for_missing_arguments: self
.inlayHints_parameterHints_missingArguments_enable()
.to_owned(),
generic_parameter_hints: GenericParameterHints {
type_hints: self.inlayHints_genericParameterHints_type_enable().to_owned(),
lifetime_hints: self.inlayHints_genericParameterHints_lifetime_enable().to_owned(),
Expand Down
7 changes: 7 additions & 0 deletions docs/book/src/configuration_generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,13 @@ Default: `true`
Show function parameter name inlay hints at the call site.


## rust-analyzer.inlayHints.parameterHints.missingArguments.enable {#inlayHints.parameterHints.missingArguments.enable}

Default: `false`

Show parameter name inlay hints for missing arguments at the call site.


## rust-analyzer.inlayHints.rangeExclusiveHints.enable {#inlayHints.rangeExclusiveHints.enable}

Default: `false`
Expand Down
10 changes: 10 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2396,6 +2396,16 @@
}
}
},
{
"title": "Inlay Hints",
"properties": {
"rust-analyzer.inlayHints.parameterHints.missingArguments.enable": {
"markdownDescription": "Show parameter name inlay hints for missing arguments at the call site.",
"default": false,
"type": "boolean"
}
}
},
{
"title": "Inlay Hints",
"properties": {
Expand Down