11use std:: collections:: { HashMap , HashSet } ;
22
33use crate :: analyzer:: Analyzer ;
4- use crate :: diagnostic:: { Diagnostic , code} ;
4+ use crate :: diagnostic:: { Diagnostic , Span , code} ;
55use crate :: syntax:: ast:: { Block , CallArg , Callee , DataEffect , Expr , Item , LetKind , Stmt } ;
66
77pub ( crate ) fn check ( analyzer : & mut Analyzer < ' _ > ) {
@@ -53,8 +53,10 @@ fn check_block(analyzer: &mut Analyzer<'_>, block: &Block, locals: &HashSet<Stri
5353
5454fn check_expr ( analyzer : & mut Analyzer < ' _ > , expr : & Expr , locals : & HashSet < String > ) {
5555 match expr {
56- Expr :: Call { callee, args, .. } => {
57- check_call_args ( analyzer, callee, args, locals) ;
56+ Expr :: Call {
57+ callee, args, span, ..
58+ } => {
59+ check_call_args ( analyzer, callee, args, span, locals) ;
5860 for arg in args {
5961 check_expr ( analyzer, & arg. value , locals) ;
6062 }
@@ -72,6 +74,7 @@ fn check_call_args(
7274 analyzer : & mut Analyzer < ' _ > ,
7375 callee : & Callee ,
7476 args : & [ CallArg ] ,
77+ call_span : & Span ,
7578 locals : & HashSet < String > ,
7679) {
7780 let call_name = callee_name ( callee) ;
@@ -101,6 +104,7 @@ fn check_call_args(
101104 let Some ( signature) = analyzer. resolve_callee ( callee) else {
102105 return ;
103106 } ;
107+ let signature_params = signature. params . clone ( ) ;
104108 let param_effects: HashMap < String , & ' static str > = signature
105109 . params
106110 . iter ( )
@@ -111,6 +115,63 @@ fn check_call_args(
111115 } )
112116 . collect ( ) ;
113117 let retained_params = signature. retained_params . clone ( ) ;
118+ let param_names: HashSet < String > = signature_params
119+ . iter ( )
120+ . map ( |param| param. name . clone ( ) )
121+ . collect ( ) ;
122+
123+ for arg in args {
124+ let Some ( name) = & arg. name else {
125+ continue ;
126+ } ;
127+ if !param_names. contains ( name) {
128+ analyzer. diagnostics . push (
129+ Diagnostic :: error (
130+ code:: UNKNOWN_ARGUMENT ,
131+ format ! ( "call to `{call_name}` has no argument named `{name}`." ) ,
132+ arg. span . clone ( ) ,
133+ "unknown argument" ,
134+ )
135+ . with_cause ( format ! (
136+ "`{call_name}` does not declare a parameter named `{name}`."
137+ ) )
138+ . with_fix (
139+ "rename_argument" ,
140+ format ! ( "Use one of: {}." , join_param_names( & signature_params) ) ,
141+ "manual" ,
142+ ) ,
143+ ) ;
144+ }
145+ }
146+
147+ if args. iter ( ) . all ( |arg| arg. name . is_some ( ) ) {
148+ let provided_names: HashSet < & str > =
149+ args. iter ( ) . filter_map ( |arg| arg. name . as_deref ( ) ) . collect ( ) ;
150+ for param in & signature_params {
151+ if !provided_names. contains ( param. name . as_str ( ) ) {
152+ analyzer. diagnostics . push (
153+ Diagnostic :: error (
154+ code:: MISSING_ARGUMENT ,
155+ format ! (
156+ "call to `{call_name}` is missing required argument `{}`." ,
157+ param. name
158+ ) ,
159+ call_span. clone ( ) ,
160+ "missing argument" ,
161+ )
162+ . with_cause ( format ! (
163+ "`{call_name}` requires a named argument `{}`." ,
164+ param. name
165+ ) )
166+ . with_fix (
167+ "add_argument" ,
168+ format ! ( "Add `{}: ...` to the call." , param. name) ,
169+ "manual" ,
170+ ) ,
171+ ) ;
172+ }
173+ }
174+ }
114175
115176 for arg in args {
116177 let Some ( name) = & arg. name else {
@@ -178,6 +239,14 @@ fn is_enum_variant_call(name: &str) -> bool {
178239 matches ! ( name, "Ok" | "Err" | "Some" | "None" | "Result" | "Option" )
179240}
180241
242+ fn join_param_names ( params : & [ crate :: hir:: ParamSig ] ) -> String {
243+ params
244+ . iter ( )
245+ . map ( |param| param. name . as_str ( ) )
246+ . collect :: < Vec < _ > > ( )
247+ . join ( ", " )
248+ }
249+
181250fn callee_name ( callee : & Callee ) -> String {
182251 match callee {
183252 Callee :: Name ( name) => name. clone ( ) ,
0 commit comments