Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/inlay_hints/param_name.rs')
| -rw-r--r-- | crates/ide/src/inlay_hints/param_name.rs | 198 |
1 files changed, 197 insertions, 1 deletions
diff --git a/crates/ide/src/inlay_hints/param_name.rs b/crates/ide/src/inlay_hints/param_name.rs index 4122c16d3e..f1e62a5ab8 100644 --- a/crates/ide/src/inlay_hints/param_name.rs +++ b/crates/ide/src/inlay_hints/param_name.rs @@ -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}; @@ -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, @@ -111,7 +178,8 @@ fn get_callable<'db>( } const INSIGNIFICANT_METHOD_NAMES: &[&str] = &["clone", "as_ref", "into"]; -const INSIGNIFICANT_PARAMETER_NAMES: &[&str] = &["predicate", "value", "pat", "rhs", "other"]; +const INSIGNIFICANT_PARAMETER_NAMES: &[&str] = + &["predicate", "value", "pat", "rhs", "other", "msg", "op"]; fn should_hide_param_name_hint( sema: &Semantics<'_, RootDatabase>, @@ -152,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. @@ -608,4 +707,101 @@ fn main() { }"#, ); } + + #[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(); +}"#, + ); + } } |