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