diff options
Diffstat (limited to 'crates/ra_hir_ty/src/expr.rs')
-rw-r--r-- | crates/ra_hir_ty/src/expr.rs | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs new file mode 100644 index 000000000..5c65f9370 --- /dev/null +++ b/crates/ra_hir_ty/src/expr.rs | |||
@@ -0,0 +1,151 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::sync::Arc; | ||
4 | |||
5 | use hir_def::{ | ||
6 | path::{known, Path}, | ||
7 | resolver::HasResolver, | ||
8 | AdtId, FunctionId, | ||
9 | }; | ||
10 | use hir_expand::{diagnostics::DiagnosticSink, name::Name}; | ||
11 | use ra_syntax::ast; | ||
12 | use ra_syntax::AstPtr; | ||
13 | use rustc_hash::FxHashSet; | ||
14 | |||
15 | use crate::{ | ||
16 | db::HirDatabase, | ||
17 | diagnostics::{MissingFields, MissingOkInTailExpr}, | ||
18 | ApplicationTy, InferenceResult, Ty, TypeCtor, | ||
19 | }; | ||
20 | |||
21 | pub use hir_def::{ | ||
22 | body::{ | ||
23 | scope::{ExprScopes, ScopeEntry, ScopeId}, | ||
24 | Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource, | ||
25 | }, | ||
26 | expr::{ | ||
27 | ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp, | ||
28 | MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp, | ||
29 | }, | ||
30 | }; | ||
31 | |||
32 | pub struct ExprValidator<'a, 'b: 'a> { | ||
33 | func: FunctionId, | ||
34 | infer: Arc<InferenceResult>, | ||
35 | sink: &'a mut DiagnosticSink<'b>, | ||
36 | } | ||
37 | |||
38 | impl<'a, 'b> ExprValidator<'a, 'b> { | ||
39 | pub fn new( | ||
40 | func: FunctionId, | ||
41 | infer: Arc<InferenceResult>, | ||
42 | sink: &'a mut DiagnosticSink<'b>, | ||
43 | ) -> ExprValidator<'a, 'b> { | ||
44 | ExprValidator { func, infer, sink } | ||
45 | } | ||
46 | |||
47 | pub fn validate_body(&mut self, db: &impl HirDatabase) { | ||
48 | let body = db.body(self.func.into()); | ||
49 | |||
50 | for e in body.exprs.iter() { | ||
51 | if let (id, Expr::RecordLit { path, fields, spread }) = e { | ||
52 | self.validate_record_literal(id, path, fields, *spread, db); | ||
53 | } | ||
54 | } | ||
55 | |||
56 | let body_expr = &body[body.body_expr]; | ||
57 | if let Expr::Block { statements: _, tail: Some(t) } = body_expr { | ||
58 | self.validate_results_in_tail_expr(body.body_expr, *t, db); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | fn validate_record_literal( | ||
63 | &mut self, | ||
64 | id: ExprId, | ||
65 | _path: &Option<Path>, | ||
66 | fields: &[RecordLitField], | ||
67 | spread: Option<ExprId>, | ||
68 | db: &impl HirDatabase, | ||
69 | ) { | ||
70 | if spread.is_some() { | ||
71 | return; | ||
72 | } | ||
73 | |||
74 | let struct_def = match self.infer[id].as_adt() { | ||
75 | Some((AdtId::StructId(s), _)) => s, | ||
76 | _ => return, | ||
77 | }; | ||
78 | let struct_data = db.struct_data(struct_def); | ||
79 | |||
80 | let lit_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); | ||
81 | let missed_fields: Vec<Name> = struct_data | ||
82 | .variant_data | ||
83 | .fields() | ||
84 | .iter() | ||
85 | .filter_map(|(_f, d)| { | ||
86 | let name = d.name.clone(); | ||
87 | if lit_fields.contains(&name) { | ||
88 | None | ||
89 | } else { | ||
90 | Some(name) | ||
91 | } | ||
92 | }) | ||
93 | .collect(); | ||
94 | if missed_fields.is_empty() { | ||
95 | return; | ||
96 | } | ||
97 | let (_, source_map) = db.body_with_source_map(self.func.into()); | ||
98 | |||
99 | if let Some(source_ptr) = source_map.expr_syntax(id) { | ||
100 | if let Some(expr) = source_ptr.value.a() { | ||
101 | let root = source_ptr.file_syntax(db); | ||
102 | if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) { | ||
103 | if let Some(field_list) = record_lit.record_field_list() { | ||
104 | self.sink.push(MissingFields { | ||
105 | file: source_ptr.file_id, | ||
106 | field_list: AstPtr::new(&field_list), | ||
107 | missed_fields, | ||
108 | }) | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | |||
115 | fn validate_results_in_tail_expr( | ||
116 | &mut self, | ||
117 | body_id: ExprId, | ||
118 | id: ExprId, | ||
119 | db: &impl HirDatabase, | ||
120 | ) { | ||
121 | // the mismatch will be on the whole block currently | ||
122 | let mismatch = match self.infer.type_mismatch_for_expr(body_id) { | ||
123 | Some(m) => m, | ||
124 | None => return, | ||
125 | }; | ||
126 | |||
127 | let std_result_path = known::std_result_result(); | ||
128 | |||
129 | let resolver = self.func.resolver(db); | ||
130 | let std_result_enum = match resolver.resolve_known_enum(db, &std_result_path) { | ||
131 | Some(it) => it, | ||
132 | _ => return, | ||
133 | }; | ||
134 | |||
135 | let std_result_ctor = TypeCtor::Adt(AdtId::EnumId(std_result_enum)); | ||
136 | let params = match &mismatch.expected { | ||
137 | Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters, | ||
138 | _ => return, | ||
139 | }; | ||
140 | |||
141 | if params.len() == 2 && ¶ms[0] == &mismatch.actual { | ||
142 | let (_, source_map) = db.body_with_source_map(self.func.into()); | ||
143 | |||
144 | if let Some(source_ptr) = source_map.expr_syntax(id) { | ||
145 | if let Some(expr) = source_ptr.value.a() { | ||
146 | self.sink.push(MissingOkInTailExpr { file: source_ptr.file_id, expr }); | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | } | ||