@@ -11,6 +11,7 @@ use hir::{EditionedFileId, Semantics};
1111use ide_db:: { RootDatabase , famous_defs:: FamousDefs } ;
1212
1313use stdx:: to_lower_snake_case;
14+ use syntax:: T ;
1415use syntax:: ast:: { self , AstNode , HasArgList , HasName , UnaryOp } ;
1516
1617use 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+
94161fn 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 }
0 commit comments