aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_ty/src/diagnostics/expr.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-07-14 09:30:07 +0100
committerGitHub <[email protected]>2020-07-14 09:30:07 +0100
commit612112a2c26ec61bf341f99e9e7753bb41873fc4 (patch)
treec2becab63418e6ffd88af4bfdd1b129b305cff6d /crates/ra_hir_ty/src/diagnostics/expr.rs
parenta34f79b17412f83242283fae6e7a6275b4056611 (diff)
parent19450534cf308eff30ea7de1a40ab77dca4e6014 (diff)
Merge #5358
5358: Cleanup hir diagnostics API r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_hir_ty/src/diagnostics/expr.rs')
-rw-r--r--crates/ra_hir_ty/src/diagnostics/expr.rs521
1 files changed, 521 insertions, 0 deletions
diff --git a/crates/ra_hir_ty/src/diagnostics/expr.rs b/crates/ra_hir_ty/src/diagnostics/expr.rs
new file mode 100644
index 000000000..239be779f
--- /dev/null
+++ b/crates/ra_hir_ty/src/diagnostics/expr.rs
@@ -0,0 +1,521 @@
1//! FIXME: write short doc here
2
3use std::sync::Arc;
4
5use hir_def::{path::path, resolver::HasResolver, AdtId, DefWithBodyId};
6use hir_expand::diagnostics::DiagnosticSink;
7use ra_syntax::{ast, AstPtr};
8use rustc_hash::FxHashSet;
9
10use crate::{
11 db::HirDatabase,
12 diagnostics::{
13 match_check::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
14 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields,
15 },
16 utils::variant_data,
17 ApplicationTy, InferenceResult, Ty, TypeCtor,
18};
19
20pub use hir_def::{
21 body::{
22 scope::{ExprScopes, ScopeEntry, ScopeId},
23 Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource,
24 },
25 expr::{
26 ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
27 MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp,
28 },
29 src::HasSource,
30 LocalFieldId, Lookup, VariantId,
31};
32
33pub(super) struct ExprValidator<'a, 'b: 'a> {
34 owner: DefWithBodyId,
35 infer: Arc<InferenceResult>,
36 sink: &'a mut DiagnosticSink<'b>,
37}
38
39impl<'a, 'b> ExprValidator<'a, 'b> {
40 pub(super) fn new(
41 owner: DefWithBodyId,
42 infer: Arc<InferenceResult>,
43 sink: &'a mut DiagnosticSink<'b>,
44 ) -> ExprValidator<'a, 'b> {
45 ExprValidator { owner, infer, sink }
46 }
47
48 pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) {
49 let body = db.body(self.owner.into());
50
51 for (id, expr) in body.exprs.iter() {
52 if let Some((variant_def, missed_fields, true)) =
53 record_literal_missing_fields(db, &self.infer, id, expr)
54 {
55 self.create_record_literal_missing_fields_diagnostic(
56 id,
57 db,
58 variant_def,
59 missed_fields,
60 );
61 }
62
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 _ => {}
71 }
72 }
73 for (id, pat) in body.pats.iter() {
74 if let Some((variant_def, missed_fields, true)) =
75 record_pattern_missing_fields(db, &self.infer, id, pat)
76 {
77 self.create_record_pattern_missing_fields_diagnostic(
78 id,
79 db,
80 variant_def,
81 missed_fields,
82 );
83 }
84 }
85 let body_expr = &body[body.body_expr];
86 if let Expr::Block { tail: Some(t), .. } = body_expr {
87 self.validate_results_in_tail_expr(body.body_expr, *t, db);
88 }
89 }
90
91 fn create_record_literal_missing_fields_diagnostic(
92 &mut self,
93 id: ExprId,
94 db: &dyn HirDatabase,
95 variant_def: VariantId,
96 missed_fields: Vec<LocalFieldId>,
97 ) {
98 // XXX: only look at source_map if we do have missing fields
99 let (_, source_map) = db.body_with_source_map(self.owner.into());
100
101 if let Ok(source_ptr) = source_map.expr_syntax(id) {
102 let root = source_ptr.file_syntax(db.upcast());
103 if let ast::Expr::RecordLit(record_lit) = &source_ptr.value.to_node(&root) {
104 if let Some(field_list) = record_lit.record_field_list() {
105 let variant_data = variant_data(db.upcast(), variant_def);
106 let missed_fields = missed_fields
107 .into_iter()
108 .map(|idx| variant_data.fields()[idx].name.clone())
109 .collect();
110 self.sink.push(MissingFields {
111 file: source_ptr.file_id,
112 field_list: AstPtr::new(&field_list),
113 missed_fields,
114 })
115 }
116 }
117 }
118 }
119
120 fn create_record_pattern_missing_fields_diagnostic(
121 &mut self,
122 id: PatId,
123 db: &dyn HirDatabase,
124 variant_def: VariantId,
125 missed_fields: Vec<LocalFieldId>,
126 ) {
127 // XXX: only look at source_map if we do have missing fields
128 let (_, source_map) = db.body_with_source_map(self.owner.into());
129
130 if let Ok(source_ptr) = source_map.pat_syntax(id) {
131 if let Some(expr) = source_ptr.value.as_ref().left() {
132 let root = source_ptr.file_syntax(db.upcast());
133 if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
134 if let Some(field_list) = record_pat.record_field_pat_list() {
135 let variant_data = variant_data(db.upcast(), variant_def);
136 let missed_fields = missed_fields
137 .into_iter()
138 .map(|idx| variant_data.fields()[idx].name.clone())
139 .collect();
140 self.sink.push(MissingPatFields {
141 file: source_ptr.file_id,
142 field_list: AstPtr::new(&field_list),
143 missed_fields,
144 })
145 }
146 }
147 }
148 }
149 }
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.owner.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
202 fn validate_match(
203 &mut self,
204 id: ExprId,
205 match_expr: ExprId,
206 arms: &[MatchArm],
207 db: &dyn HirDatabase,
208 infer: Arc<InferenceResult>,
209 ) {
210 let (body, source_map): (Arc<Body>, Arc<BodySourceMap>) =
211 db.body_with_source_map(self.owner.into());
212
213 let match_expr_ty = match infer.type_of_expr.get(match_expr) {
214 Some(ty) => ty,
215 // If we can't resolve the type of the match expression
216 // we cannot perform exhaustiveness checks.
217 None => return,
218 };
219
220 let cx = MatchCheckCtx { match_expr, body, infer: infer.clone(), db };
221 let pats = arms.iter().map(|arm| arm.pat);
222
223 let mut seen = Matrix::empty();
224 for pat in pats {
225 if let Some(pat_ty) = infer.type_of_pat.get(pat) {
226 // We only include patterns whose type matches the type
227 // of the match expression. If we had a InvalidMatchArmPattern
228 // diagnostic or similar we could raise that in an else
229 // block here.
230 //
231 // When comparing the types, we also have to consider that rustc
232 // will automatically de-reference the match expression type if
233 // necessary.
234 //
235 // FIXME we should use the type checker for this.
236 if pat_ty == match_expr_ty
237 || match_expr_ty
238 .as_reference()
239 .map(|(match_expr_ty, _)| match_expr_ty == pat_ty)
240 .unwrap_or(false)
241 {
242 // If we had a NotUsefulMatchArm diagnostic, we could
243 // check the usefulness of each pattern as we added it
244 // to the matrix here.
245 let v = PatStack::from_pattern(pat);
246 seen.push(&cx, v);
247 continue;
248 }
249 }
250
251 // If we can't resolve the type of a pattern, or the pattern type doesn't
252 // fit the match expression, we skip this diagnostic. Skipping the entire
253 // diagnostic rather than just not including this match arm is preferred
254 // to avoid the chance of false positives.
255 return;
256 }
257
258 match is_useful(&cx, &seen, &PatStack::from_wild()) {
259 Ok(Usefulness::Useful) => (),
260 // if a wildcard pattern is not useful, then all patterns are covered
261 Ok(Usefulness::NotUseful) => return,
262 // this path is for unimplemented checks, so we err on the side of not
263 // reporting any errors
264 _ => return,
265 }
266
267 if let Ok(source_ptr) = source_map.expr_syntax(id) {
268 let root = source_ptr.file_syntax(db.upcast());
269 if let ast::Expr::MatchExpr(match_expr) = &source_ptr.value.to_node(&root) {
270 if let (Some(match_expr), Some(arms)) =
271 (match_expr.expr(), match_expr.match_arm_list())
272 {
273 self.sink.push(MissingMatchArms {
274 file: source_ptr.file_id,
275 match_expr: AstPtr::new(&match_expr),
276 arms: AstPtr::new(&arms),
277 })
278 }
279 }
280 }
281 }
282
283 fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) {
284 // the mismatch will be on the whole block currently
285 let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
286 Some(m) => m,
287 None => return,
288 };
289
290 let core_result_path = path![core::result::Result];
291
292 let resolver = self.owner.resolver(db.upcast());
293 let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) {
294 Some(it) => it,
295 _ => return,
296 };
297
298 let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum));
299 let params = match &mismatch.expected {
300 Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => {
301 parameters
302 }
303 _ => return,
304 };
305
306 if params.len() == 2 && params[0] == mismatch.actual {
307 let (_, source_map) = db.body_with_source_map(self.owner.into());
308
309 if let Ok(source_ptr) = source_map.expr_syntax(id) {
310 self.sink
311 .push(MissingOkInTailExpr { file: source_ptr.file_id, expr: source_ptr.value });
312 }
313 }
314 }
315}
316
317pub fn record_literal_missing_fields(
318 db: &dyn HirDatabase,
319 infer: &InferenceResult,
320 id: ExprId,
321 expr: &Expr,
322) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
323 let (fields, exhausitve) = match expr {
324 Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
325 _ => return None,
326 };
327
328 let variant_def = infer.variant_resolution_for_expr(id)?;
329 if let VariantId::UnionId(_) = variant_def {
330 return None;
331 }
332
333 let variant_data = variant_data(db.upcast(), variant_def);
334
335 let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
336 let missed_fields: Vec<LocalFieldId> = variant_data
337 .fields()
338 .iter()
339 .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
340 .collect();
341 if missed_fields.is_empty() {
342 return None;
343 }
344 Some((variant_def, missed_fields, exhausitve))
345}
346
347pub fn record_pattern_missing_fields(
348 db: &dyn HirDatabase,
349 infer: &InferenceResult,
350 id: PatId,
351 pat: &Pat,
352) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
353 let (fields, exhaustive) = match pat {
354 Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
355 _ => return None,
356 };
357
358 let variant_def = infer.variant_resolution_for_pat(id)?;
359 if let VariantId::UnionId(_) = variant_def {
360 return None;
361 }
362
363 let variant_data = variant_data(db.upcast(), variant_def);
364
365 let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
366 let missed_fields: Vec<LocalFieldId> = variant_data
367 .fields()
368 .iter()
369 .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
370 .collect();
371 if missed_fields.is_empty() {
372 return None;
373 }
374 Some((variant_def, missed_fields, exhaustive))
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}