@@ -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,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+
94171fn 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 }
0 commit comments