aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-07-09 16:34:43 +0100
committerGitHub <[email protected]>2020-07-09 16:34:43 +0100
commit89c7c559953f103f224caacad6c9497d36660c76 (patch)
treefff7cbf2b489053af801e075c51288d7d7d6afca
parentf1d084fbd9504497aed24b907b03247bfcd31675 (diff)
parentf4a9d9a00f0462bce92ddbac24cb91825c8ab192 (diff)
Merge #5270
5270: Add argument count mismatch diagnostic r=matklad a=jonas-schievink Closes https://github.com/rust-analyzer/rust-analyzer/issues/4025. This currently has one false positive on this line, where `max` is resolved to `Iterator::max` instead of `Ord::max`: https://github.com/rust-analyzer/rust-analyzer/blob/8aa10c00a4c5b957d459fac5a103cd9688e8dcdd/crates/expect/src/lib.rs#L263 (I have no idea why it thinks that `usize` is an `Iterator`) TODO: * [x] Tests * [x] Improve diagnostic text for method calls Co-authored-by: Jonas Schievink <[email protected]>
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_hir/src/diagnostics.rs4
-rw-r--r--crates/ra_hir_ty/Cargo.toml1
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs30
-rw-r--r--crates/ra_hir_ty/src/expr.rs200
-rw-r--r--crates/ra_ide/src/diagnostics.rs8
6 files changed, 230 insertions, 14 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b429aae01..752edddd6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1104,6 +1104,7 @@ dependencies = [
1104 "chalk-ir", 1104 "chalk-ir",
1105 "chalk-solve", 1105 "chalk-solve",
1106 "ena", 1106 "ena",
1107 "expect",
1107 "insta", 1108 "insta",
1108 "itertools", 1109 "itertools",
1109 "log", 1110 "log",
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs
index c82883d0c..11a0ecb8b 100644
--- a/crates/ra_hir/src/diagnostics.rs
+++ b/crates/ra_hir/src/diagnostics.rs
@@ -1,4 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2pub use hir_def::diagnostics::UnresolvedModule; 2pub use hir_def::diagnostics::UnresolvedModule;
3pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; 3pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};
4pub use hir_ty::diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField}; 4pub use hir_ty::diagnostics::{
5 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField,
6};
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml
index d6df48db2..ce257dc0b 100644
--- a/crates/ra_hir_ty/Cargo.toml
+++ b/crates/ra_hir_ty/Cargo.toml
@@ -32,3 +32,4 @@ chalk-ir = { version = "0.15.0" }
32 32
33[dev-dependencies] 33[dev-dependencies]
34insta = "0.16.0" 34insta = "0.16.0"
35expect = { path = "../expect" }
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index 0289911de..5b0dda634 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -197,3 +197,33 @@ impl AstDiagnostic for MissingUnsafe {
197 ast::Expr::cast(node).unwrap() 197 ast::Expr::cast(node).unwrap()
198 } 198 }
199} 199}
200
201#[derive(Debug)]
202pub struct MismatchedArgCount {
203 pub file: HirFileId,
204 pub call_expr: AstPtr<ast::Expr>,
205 pub expected: usize,
206 pub found: usize,
207}
208
209impl Diagnostic for MismatchedArgCount {
210 fn message(&self) -> String {
211 let s = if self.expected == 1 { "" } else { "s" };
212 format!("Expected {} argument{}, found {}", self.expected, s, self.found)
213 }
214 fn source(&self) -> InFile<SyntaxNodePtr> {
215 InFile { file_id: self.file, value: self.call_expr.clone().into() }
216 }
217 fn as_any(&self) -> &(dyn Any + Send + 'static) {
218 self
219 }
220}
221
222impl AstDiagnostic for MismatchedArgCount {
223 type AST = ast::CallExpr;
224 fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
225 let root = db.parse_or_expand(self.source().file_id).unwrap();
226 let node = self.source().value.to_node(&root);
227 ast::CallExpr::cast(node).unwrap()
228 }
229}
diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs
index 7db928dde..6f34aaf17 100644
--- a/crates/ra_hir_ty/src/expr.rs
+++ b/crates/ra_hir_ty/src/expr.rs
@@ -9,9 +9,11 @@ use rustc_hash::FxHashSet;
9 9
10use crate::{ 10use crate::{
11 db::HirDatabase, 11 db::HirDatabase,
12 diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields}, 12 diagnostics::{
13 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields,
14 },
13 utils::variant_data, 15 utils::variant_data,
14 ApplicationTy, InferenceResult, Ty, TypeCtor, 16 ApplicationTy, CallableDef, InferenceResult, Ty, TypeCtor,
15 _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness}, 17 _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
16}; 18};
17 19
@@ -24,7 +26,8 @@ pub use hir_def::{
24 ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp, 26 ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
25 MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp, 27 MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp,
26 }, 28 },
27 LocalFieldId, VariantId, 29 src::HasSource,
30 LocalFieldId, Lookup, VariantId,
28}; 31};
29 32
30pub struct ExprValidator<'a, 'b: 'a> { 33pub struct ExprValidator<'a, 'b: 'a> {
@@ -56,8 +59,15 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
56 missed_fields, 59 missed_fields,
57 ); 60 );
58 } 61 }
59 if let Expr::Match { expr, arms } = expr { 62
60 self.validate_match(id, *expr, arms, db, self.infer.clone()); 63 match expr {
64 Expr::Match { expr, arms } => {
65 self.validate_match(id, *expr, arms, db, self.infer.clone());
66 }
67 Expr::Call { .. } | Expr::MethodCall { .. } => {
68 self.validate_call(db, id, expr);
69 }
70 _ => {}
61 } 71 }
62 } 72 }
63 for (id, pat) in body.pats.iter() { 73 for (id, pat) in body.pats.iter() {
@@ -138,6 +148,68 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
138 } 148 }
139 } 149 }
140 150
151 fn validate_call(&mut self, db: &dyn HirDatabase, call_id: ExprId, expr: &Expr) -> Option<()> {
152 // Check that the number of arguments matches the number of parameters.
153
154 // FIXME: Due to shortcomings in the current type system implementation, only emit this
155 // diagnostic if there are no type mismatches in the containing function.
156 if self.infer.type_mismatches.iter().next().is_some() {
157 return Some(());
158 }
159
160 let is_method_call = matches!(expr, Expr::MethodCall { .. });
161 let (callee, args) = match expr {
162 Expr::Call { callee, args } => {
163 let callee = &self.infer.type_of_expr[*callee];
164 let (callable, _) = callee.as_callable()?;
165 let callee = match callable {
166 CallableDef::FunctionId(func) => func,
167
168 // FIXME: Handle tuple struct/variant constructor calls.
169 _ => return None,
170 };
171
172 (callee, args.clone())
173 }
174 Expr::MethodCall { receiver, args, .. } => {
175 let callee = self.infer.method_resolution(call_id)?;
176 let mut args = args.clone();
177 args.insert(0, *receiver);
178 (callee, args)
179 }
180 _ => return None,
181 };
182
183 let loc = callee.lookup(db.upcast());
184 let ast = loc.source(db.upcast());
185 let params = ast.value.param_list()?;
186
187 let mut param_count = params.params().count();
188 let mut arg_count = args.len();
189
190 if params.self_param().is_some() {
191 param_count += 1;
192 }
193
194 if arg_count != param_count {
195 let (_, source_map) = db.body_with_source_map(self.func.into());
196 if let Ok(source_ptr) = source_map.expr_syntax(call_id) {
197 if is_method_call {
198 param_count -= 1;
199 arg_count -= 1;
200 }
201 self.sink.push(MismatchedArgCount {
202 file: source_ptr.file_id,
203 call_expr: source_ptr.value,
204 expected: param_count,
205 found: arg_count,
206 });
207 }
208 }
209
210 None
211 }
212
141 fn validate_match( 213 fn validate_match(
142 &mut self, 214 &mut self,
143 id: ExprId, 215 id: ExprId,
@@ -312,3 +384,121 @@ pub fn record_pattern_missing_fields(
312 } 384 }
313 Some((variant_def, missed_fields, exhaustive)) 385 Some((variant_def, missed_fields, exhaustive))
314} 386}
387
388#[cfg(test)]
389mod tests {
390 use expect::{expect, Expect};
391 use ra_db::fixture::WithFixture;
392
393 use crate::{diagnostics::MismatchedArgCount, test_db::TestDB};
394
395 fn check_diagnostic(ra_fixture: &str, expect: Expect) {
396 let msg = TestDB::with_single_file(ra_fixture).0.diagnostic::<MismatchedArgCount>().0;
397 expect.assert_eq(&msg);
398 }
399
400 fn check_no_diagnostic(ra_fixture: &str) {
401 let (s, diagnostic_count) =
402 TestDB::with_single_file(ra_fixture).0.diagnostic::<MismatchedArgCount>();
403
404 assert_eq!(0, diagnostic_count, "expected no diagnostic, found one: {}", s);
405 }
406
407 #[test]
408 fn simple_free_fn_zero() {
409 check_diagnostic(
410 r"
411 fn zero() {}
412 fn f() { zero(1); }
413 ",
414 expect![["\"zero(1)\": Expected 0 arguments, found 1\n"]],
415 );
416
417 check_no_diagnostic(
418 r"
419 fn zero() {}
420 fn f() { zero(); }
421 ",
422 );
423 }
424
425 #[test]
426 fn simple_free_fn_one() {
427 check_diagnostic(
428 r"
429 fn one(arg: u8) {}
430 fn f() { one(); }
431 ",
432 expect![["\"one()\": Expected 1 argument, found 0\n"]],
433 );
434
435 check_no_diagnostic(
436 r"
437 fn one(arg: u8) {}
438 fn f() { one(1); }
439 ",
440 );
441 }
442
443 #[test]
444 fn method_as_fn() {
445 check_diagnostic(
446 r"
447 struct S;
448 impl S {
449 fn method(&self) {}
450 }
451
452 fn f() {
453 S::method();
454 }
455 ",
456 expect![["\"S::method()\": Expected 1 argument, found 0\n"]],
457 );
458
459 check_no_diagnostic(
460 r"
461 struct S;
462 impl S {
463 fn method(&self) {}
464 }
465
466 fn f() {
467 S::method(&S);
468 S.method();
469 }
470 ",
471 );
472 }
473
474 #[test]
475 fn method_with_arg() {
476 check_diagnostic(
477 r"
478 struct S;
479 impl S {
480 fn method(&self, arg: u8) {}
481 }
482
483 fn f() {
484 S.method();
485 }
486 ",
487 expect![["\"S.method()\": Expected 1 argument, found 0\n"]],
488 );
489
490 check_no_diagnostic(
491 r"
492 struct S;
493 impl S {
494 fn method(&self, arg: u8) {}
495 }
496
497 fn f() {
498 S::method(&S, 0);
499 S.method(1);
500 }
501 ",
502 );
503 }
504}
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 00f6bb186..e69e9b4ec 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -99,14 +99,6 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
99 fix, 99 fix,
100 }) 100 })
101 }) 101 })
102 .on::<hir::diagnostics::MissingMatchArms, _>(|d| {
103 res.borrow_mut().push(Diagnostic {
104 range: sema.diagnostics_range(d).range,
105 message: d.message(),
106 severity: Severity::Error,
107 fix: None,
108 })
109 })
110 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { 102 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
111 let node = d.ast(db); 103 let node = d.ast(db);
112 let replacement = format!("Ok({})", node.syntax()); 104 let replacement = format!("Ok({})", node.syntax());