diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_hir/src/diagnostics.rs | 4 | ||||
-rw-r--r-- | crates/ra_hir_ty/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/diagnostics.rs | 30 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/expr.rs | 215 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 8 |
5 files changed, 245 insertions, 13 deletions
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 |
2 | pub use hir_def::diagnostics::UnresolvedModule; | 2 | pub use hir_def::diagnostics::UnresolvedModule; |
3 | pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; | 3 | pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; |
4 | pub use hir_ty::diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField}; | 4 | pub 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] |
34 | insta = "0.16.0" | 34 | insta = "0.16.0" |
35 | expect = { 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)] | ||
202 | pub struct MismatchedArgCount { | ||
203 | pub file: HirFileId, | ||
204 | pub call_expr: AstPtr<ast::Expr>, | ||
205 | pub expected: usize, | ||
206 | pub found: usize, | ||
207 | } | ||
208 | |||
209 | impl 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 | |||
222 | impl 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 | ||
10 | use crate::{ | 10 | use 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 | ||
30 | pub struct ExprValidator<'a, 'b: 'a> { | 33 | pub 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)] | ||
378 | mod 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()); |