aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_ty/src/diagnostics/expr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_ty/src/diagnostics/expr.rs')
-rw-r--r--crates/ra_hir_ty/src/diagnostics/expr.rs565
1 files changed, 565 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..f0e0f2988
--- /dev/null
+++ b/crates/ra_hir_ty/src/diagnostics/expr.rs
@@ -0,0 +1,565 @@
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::RecordExpr(record_lit) = &source_ptr.value.to_node(&root) {
104 if let Some(field_list) = record_lit.record_expr_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 (sig, args) = match expr {
162 Expr::Call { callee, args } => {
163 let callee = &self.infer.type_of_expr[*callee];
164 let sig = callee.callable_sig(db)?;
165 (sig, args.clone())
166 }
167 Expr::MethodCall { receiver, args, .. } => {
168 let mut args = args.clone();
169 args.insert(0, *receiver);
170
171 // FIXME: note that we erase information about substs here. This
172 // is not right, but, luckily, doesn't matter as we care only
173 // about the number of params
174 let callee = self.infer.method_resolution(call_id)?;
175 let sig = db.callable_item_signature(callee.into()).value;
176
177 (sig, args)
178 }
179 _ => return None,
180 };
181
182 if sig.is_varargs {
183 return None;
184 }
185
186 let params = sig.params();
187
188 let mut param_count = params.len();
189 let mut arg_count = args.len();
190
191 if arg_count != param_count {
192 let (_, source_map) = db.body_with_source_map(self.owner.into());
193 if let Ok(source_ptr) = source_map.expr_syntax(call_id) {
194 if is_method_call {
195 param_count -= 1;
196 arg_count -= 1;
197 }
198 self.sink.push(MismatchedArgCount {
199 file: source_ptr.file_id,
200 call_expr: source_ptr.value,
201 expected: param_count,
202 found: arg_count,
203 });
204 }
205 }
206
207 None
208 }
209
210 fn validate_match(
211 &mut self,
212 id: ExprId,
213 match_expr: ExprId,
214 arms: &[MatchArm],
215 db: &dyn HirDatabase,
216 infer: Arc<InferenceResult>,
217 ) {
218 let (body, source_map): (Arc<Body>, Arc<BodySourceMap>) =
219 db.body_with_source_map(self.owner.into());
220
221 let match_expr_ty = match infer.type_of_expr.get(match_expr) {
222 Some(ty) => ty,
223 // If we can't resolve the type of the match expression
224 // we cannot perform exhaustiveness checks.
225 None => return,
226 };
227
228 let cx = MatchCheckCtx { match_expr, body, infer: infer.clone(), db };
229 let pats = arms.iter().map(|arm| arm.pat);
230
231 let mut seen = Matrix::empty();
232 for pat in pats {
233 if let Some(pat_ty) = infer.type_of_pat.get(pat) {
234 // We only include patterns whose type matches the type
235 // of the match expression. If we had a InvalidMatchArmPattern
236 // diagnostic or similar we could raise that in an else
237 // block here.
238 //
239 // When comparing the types, we also have to consider that rustc
240 // will automatically de-reference the match expression type if
241 // necessary.
242 //
243 // FIXME we should use the type checker for this.
244 if pat_ty == match_expr_ty
245 || match_expr_ty
246 .as_reference()
247 .map(|(match_expr_ty, _)| match_expr_ty == pat_ty)
248 .unwrap_or(false)
249 {
250 // If we had a NotUsefulMatchArm diagnostic, we could
251 // check the usefulness of each pattern as we added it
252 // to the matrix here.
253 let v = PatStack::from_pattern(pat);
254 seen.push(&cx, v);
255 continue;
256 }
257 }
258
259 // If we can't resolve the type of a pattern, or the pattern type doesn't
260 // fit the match expression, we skip this diagnostic. Skipping the entire
261 // diagnostic rather than just not including this match arm is preferred
262 // to avoid the chance of false positives.
263 return;
264 }
265
266 match is_useful(&cx, &seen, &PatStack::from_wild()) {
267 Ok(Usefulness::Useful) => (),
268 // if a wildcard pattern is not useful, then all patterns are covered
269 Ok(Usefulness::NotUseful) => return,
270 // this path is for unimplemented checks, so we err on the side of not
271 // reporting any errors
272 _ => return,
273 }
274
275 if let Ok(source_ptr) = source_map.expr_syntax(id) {
276 let root = source_ptr.file_syntax(db.upcast());
277 if let ast::Expr::MatchExpr(match_expr) = &source_ptr.value.to_node(&root) {
278 if let (Some(match_expr), Some(arms)) =
279 (match_expr.expr(), match_expr.match_arm_list())
280 {
281 self.sink.push(MissingMatchArms {
282 file: source_ptr.file_id,
283 match_expr: AstPtr::new(&match_expr),
284 arms: AstPtr::new(&arms),
285 })
286 }
287 }
288 }
289 }
290
291 fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) {
292 // the mismatch will be on the whole block currently
293 let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
294 Some(m) => m,
295 None => return,
296 };
297
298 let core_result_path = path![core::result::Result];
299
300 let resolver = self.owner.resolver(db.upcast());
301 let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) {
302 Some(it) => it,
303 _ => return,
304 };
305
306 let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum));
307 let params = match &mismatch.expected {
308 Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => {
309 parameters
310 }
311 _ => return,
312 };
313
314 if params.len() == 2 && params[0] == mismatch.actual {
315 let (_, source_map) = db.body_with_source_map(self.owner.into());
316
317 if let Ok(source_ptr) = source_map.expr_syntax(id) {
318 self.sink
319 .push(MissingOkInTailExpr { file: source_ptr.file_id, expr: source_ptr.value });
320 }
321 }
322 }
323}
324
325pub fn record_literal_missing_fields(
326 db: &dyn HirDatabase,
327 infer: &InferenceResult,
328 id: ExprId,
329 expr: &Expr,
330) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
331 let (fields, exhausitve) = match expr {
332 Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
333 _ => return None,
334 };
335
336 let variant_def = infer.variant_resolution_for_expr(id)?;
337 if let VariantId::UnionId(_) = variant_def {
338 return None;
339 }
340
341 let variant_data = variant_data(db.upcast(), variant_def);
342
343 let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
344 let missed_fields: Vec<LocalFieldId> = variant_data
345 .fields()
346 .iter()
347 .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
348 .collect();
349 if missed_fields.is_empty() {
350 return None;
351 }
352 Some((variant_def, missed_fields, exhausitve))
353}
354
355pub fn record_pattern_missing_fields(
356 db: &dyn HirDatabase,
357 infer: &InferenceResult,
358 id: PatId,
359 pat: &Pat,
360) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
361 let (fields, exhaustive) = match pat {
362 Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
363 _ => return None,
364 };
365
366 let variant_def = infer.variant_resolution_for_pat(id)?;
367 if let VariantId::UnionId(_) = variant_def {
368 return None;
369 }
370
371 let variant_data = variant_data(db.upcast(), variant_def);
372
373 let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
374 let missed_fields: Vec<LocalFieldId> = variant_data
375 .fields()
376 .iter()
377 .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
378 .collect();
379 if missed_fields.is_empty() {
380 return None;
381 }
382 Some((variant_def, missed_fields, exhaustive))
383}
384
385#[cfg(test)]
386mod tests {
387 use crate::diagnostics::tests::check_diagnostics;
388
389 #[test]
390 fn simple_free_fn_zero() {
391 check_diagnostics(
392 r#"
393fn zero() {}
394fn f() { zero(1); }
395 //^^^^^^^ Expected 0 arguments, found 1
396"#,
397 );
398
399 check_diagnostics(
400 r#"
401fn zero() {}
402fn f() { zero(); }
403"#,
404 );
405 }
406
407 #[test]
408 fn simple_free_fn_one() {
409 check_diagnostics(
410 r#"
411fn one(arg: u8) {}
412fn f() { one(); }
413 //^^^^^ Expected 1 argument, found 0
414"#,
415 );
416
417 check_diagnostics(
418 r#"
419fn one(arg: u8) {}
420fn f() { one(1); }
421"#,
422 );
423 }
424
425 #[test]
426 fn method_as_fn() {
427 check_diagnostics(
428 r#"
429struct S;
430impl S { fn method(&self) {} }
431
432fn f() {
433 S::method();
434} //^^^^^^^^^^^ Expected 1 argument, found 0
435"#,
436 );
437
438 check_diagnostics(
439 r#"
440struct S;
441impl S { fn method(&self) {} }
442
443fn f() {
444 S::method(&S);
445 S.method();
446}
447"#,
448 );
449 }
450
451 #[test]
452 fn method_with_arg() {
453 check_diagnostics(
454 r#"
455struct S;
456impl S { fn method(&self, arg: u8) {} }
457
458 fn f() {
459 S.method();
460 } //^^^^^^^^^^ Expected 1 argument, found 0
461 "#,
462 );
463
464 check_diagnostics(
465 r#"
466struct S;
467impl S { fn method(&self, arg: u8) {} }
468
469fn f() {
470 S::method(&S, 0);
471 S.method(1);
472}
473"#,
474 );
475 }
476
477 #[test]
478 fn tuple_struct() {
479 check_diagnostics(
480 r#"
481struct Tup(u8, u16);
482fn f() {
483 Tup(0);
484} //^^^^^^ Expected 2 arguments, found 1
485"#,
486 )
487 }
488
489 #[test]
490 fn enum_variant() {
491 check_diagnostics(
492 r#"
493enum En { Variant(u8, u16), }
494fn f() {
495 En::Variant(0);
496} //^^^^^^^^^^^^^^ Expected 2 arguments, found 1
497"#,
498 )
499 }
500
501 #[test]
502 fn enum_variant_type_macro() {
503 check_diagnostics(
504 r#"
505macro_rules! Type {
506 () => { u32 };
507}
508enum Foo {
509 Bar(Type![])
510}
511impl Foo {
512 fn new() {
513 Foo::Bar(0);
514 Foo::Bar(0, 1);
515 //^^^^^^^^^^^^^^ Expected 1 argument, found 2
516 Foo::Bar();
517 //^^^^^^^^^^ Expected 1 argument, found 0
518 }
519}
520 "#,
521 );
522 }
523
524 #[test]
525 fn varargs() {
526 check_diagnostics(
527 r#"
528extern "C" {
529 fn fixed(fixed: u8);
530 fn varargs(fixed: u8, ...);
531 fn varargs2(...);
532}
533
534fn f() {
535 unsafe {
536 fixed(0);
537 fixed(0, 1);
538 //^^^^^^^^^^^ Expected 1 argument, found 2
539 varargs(0);
540 varargs(0, 1);
541 varargs2();
542 varargs2(0);
543 varargs2(0, 1);
544 }
545}
546 "#,
547 )
548 }
549
550 #[test]
551 fn arg_count_lambda() {
552 check_diagnostics(
553 r#"
554fn main() {
555 let f = |()| ();
556 f();
557 //^^^ Expected 1 argument, found 0
558 f(());
559 f((), ());
560 //^^^^^^^^^ Expected 1 argument, found 2
561}
562"#,
563 )
564 }
565}