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