aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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.rs215
-rw-r--r--crates/ra_ide/src/diagnostics.rs8
6 files changed, 246 insertions, 13 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..72577d114 100644
--- a/crates/ra_hir_ty/src/expr.rs
+++ b/crates/ra_hir_ty/src/expr.rs
@@ -9,7 +9,9 @@ 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, InferenceResult, Ty, TypeCtor,
15 _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness}, 17 _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
@@ -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,57 @@ 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
166 (callable, args.clone())
167 }
168 Expr::MethodCall { receiver, args, .. } => {
169 let callee = self.infer.method_resolution(call_id)?;
170 let mut args = args.clone();
171 args.insert(0, *receiver);
172 (callee.into(), args)
173 }
174 _ => return None,
175 };
176
177 let sig = db.callable_item_signature(callee);
178 let params = sig.value.params();
179
180 let mut param_count = params.len();
181 let mut arg_count = args.len();
182
183 if arg_count != param_count {
184 let (_, source_map) = db.body_with_source_map(self.func.into());
185 if let Ok(source_ptr) = source_map.expr_syntax(call_id) {
186 if is_method_call {
187 param_count -= 1;
188 arg_count -= 1;
189 }
190 self.sink.push(MismatchedArgCount {
191 file: source_ptr.file_id,
192 call_expr: source_ptr.value,
193 expected: param_count,
194 found: arg_count,
195 });
196 }
197 }
198
199 None
200 }
201
141 fn validate_match( 202 fn validate_match(
142 &mut self, 203 &mut self,
143 id: ExprId, 204 id: ExprId,
@@ -312,3 +373,149 @@ pub fn record_pattern_missing_fields(
312 } 373 }
313 Some((variant_def, missed_fields, exhaustive)) 374 Some((variant_def, missed_fields, exhaustive))
314} 375}
376
377#[cfg(test)]
378mod tests {
379 use expect::{expect, Expect};
380 use ra_db::fixture::WithFixture;
381
382 use crate::{diagnostics::MismatchedArgCount, test_db::TestDB};
383
384 fn check_diagnostic(ra_fixture: &str, expect: Expect) {
385 let msg = TestDB::with_single_file(ra_fixture).0.diagnostic::<MismatchedArgCount>().0;
386 expect.assert_eq(&msg);
387 }
388
389 fn check_no_diagnostic(ra_fixture: &str) {
390 let (s, diagnostic_count) =
391 TestDB::with_single_file(ra_fixture).0.diagnostic::<MismatchedArgCount>();
392
393 assert_eq!(0, diagnostic_count, "expected no diagnostic, found one: {}", s);
394 }
395
396 #[test]
397 fn simple_free_fn_zero() {
398 check_diagnostic(
399 r"
400 fn zero() {}
401 fn f() { zero(1); }
402 ",
403 expect![["\"zero(1)\": Expected 0 arguments, found 1\n"]],
404 );
405
406 check_no_diagnostic(
407 r"
408 fn zero() {}
409 fn f() { zero(); }
410 ",
411 );
412 }
413
414 #[test]
415 fn simple_free_fn_one() {
416 check_diagnostic(
417 r"
418 fn one(arg: u8) {}
419 fn f() { one(); }
420 ",
421 expect![["\"one()\": Expected 1 argument, found 0\n"]],
422 );
423
424 check_no_diagnostic(
425 r"
426 fn one(arg: u8) {}
427 fn f() { one(1); }
428 ",
429 );
430 }
431
432 #[test]
433 fn method_as_fn() {
434 check_diagnostic(
435 r"
436 struct S;
437 impl S {
438 fn method(&self) {}
439 }
440
441 fn f() {
442 S::method();
443 }
444 ",
445 expect![["\"S::method()\": Expected 1 argument, found 0\n"]],
446 );
447
448 check_no_diagnostic(
449 r"
450 struct S;
451 impl S {
452 fn method(&self) {}
453 }
454
455 fn f() {
456 S::method(&S);
457 S.method();
458 }
459 ",
460 );
461 }
462
463 #[test]
464 fn method_with_arg() {
465 check_diagnostic(
466 r"
467 struct S;
468 impl S {
469 fn method(&self, arg: u8) {}
470 }
471
472 fn f() {
473 S.method();
474 }
475 ",
476 expect![["\"S.method()\": Expected 1 argument, found 0\n"]],
477 );
478
479 check_no_diagnostic(
480 r"
481 struct S;
482 impl S {
483 fn method(&self, arg: u8) {}
484 }
485
486 fn f() {
487 S::method(&S, 0);
488 S.method(1);
489 }
490 ",
491 );
492 }
493
494 #[test]
495 fn tuple_struct() {
496 check_diagnostic(
497 r"
498 struct Tup(u8, u16);
499 fn f() {
500 Tup(0);
501 }
502 ",
503 expect![["\"Tup(0)\": Expected 2 arguments, found 1\n"]],
504 )
505 }
506
507 #[test]
508 fn enum_variant() {
509 check_diagnostic(
510 r"
511 enum En {
512 Variant(u8, u16),
513 }
514 fn f() {
515 En::Variant(0);
516 }
517 ",
518 expect![["\"En::Variant(0)\": Expected 2 arguments, found 1\n"]],
519 )
520 }
521}
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());