diff options
-rw-r--r-- | crates/ra_hir/src/code_model_api.rs | 5 | ||||
-rw-r--r-- | crates/ra_hir/src/diagnostics.rs | 6 | ||||
-rw-r--r-- | crates/ra_hir/src/expr.rs | 15 | ||||
-rw-r--r-- | crates/ra_hir/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/infer.rs | 23 | ||||
-rw-r--r-- | crates/ra_ide_api/src/diagnostics.rs | 30 |
6 files changed, 74 insertions, 6 deletions
diff --git a/crates/ra_hir/src/code_model_api.rs b/crates/ra_hir/src/code_model_api.rs index 45fa4cd11..58481e715 100644 --- a/crates/ra_hir/src/code_model_api.rs +++ b/crates/ra_hir/src/code_model_api.rs | |||
@@ -17,6 +17,7 @@ use crate::{ | |||
17 | ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId}, | 17 | ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId}, |
18 | impl_block::ImplBlock, | 18 | impl_block::ImplBlock, |
19 | resolve::Resolver, | 19 | resolve::Resolver, |
20 | diagnostics::FunctionDiagnostic, | ||
20 | }; | 21 | }; |
21 | 22 | ||
22 | /// hir::Crate describes a single crate. It's the main interface with which | 23 | /// hir::Crate describes a single crate. It's the main interface with which |
@@ -519,6 +520,10 @@ impl Function { | |||
519 | let r = if !p.params.is_empty() { r.push_generic_params_scope(p) } else { r }; | 520 | let r = if !p.params.is_empty() { r.push_generic_params_scope(p) } else { r }; |
520 | r | 521 | r |
521 | } | 522 | } |
523 | |||
524 | pub fn diagnostics(&self, db: &impl HirDatabase) -> Vec<FunctionDiagnostic> { | ||
525 | self.infer(db).diagnostics() | ||
526 | } | ||
522 | } | 527 | } |
523 | 528 | ||
524 | impl Docs for Function { | 529 | impl Docs for Function { |
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs new file mode 100644 index 000000000..82aff9cee --- /dev/null +++ b/crates/ra_hir/src/diagnostics.rs | |||
@@ -0,0 +1,6 @@ | |||
1 | use crate::{expr::ExprId}; | ||
2 | |||
3 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
4 | pub enum FunctionDiagnostic { | ||
5 | NoSuchField { expr: ExprId, field: usize }, | ||
6 | } | ||
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index c37fd0454..31af5d241 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs | |||
@@ -5,7 +5,7 @@ use rustc_hash::FxHashMap; | |||
5 | 5 | ||
6 | use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap}; | 6 | use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap}; |
7 | use ra_syntax::{ | 7 | use ra_syntax::{ |
8 | SyntaxNodePtr, AstNode, | 8 | SyntaxNodePtr, AstPtr, AstNode, |
9 | ast::{self, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor, TypeAscriptionOwner} | 9 | ast::{self, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor, TypeAscriptionOwner} |
10 | }; | 10 | }; |
11 | 11 | ||
@@ -54,6 +54,7 @@ pub struct BodySourceMap { | |||
54 | expr_map_back: ArenaMap<ExprId, SyntaxNodePtr>, | 54 | expr_map_back: ArenaMap<ExprId, SyntaxNodePtr>, |
55 | pat_map: FxHashMap<SyntaxNodePtr, PatId>, | 55 | pat_map: FxHashMap<SyntaxNodePtr, PatId>, |
56 | pat_map_back: ArenaMap<PatId, SyntaxNodePtr>, | 56 | pat_map_back: ArenaMap<PatId, SyntaxNodePtr>, |
57 | field_map: FxHashMap<(ExprId, usize), AstPtr<ast::NamedField>>, | ||
57 | } | 58 | } |
58 | 59 | ||
59 | impl Body { | 60 | impl Body { |
@@ -138,6 +139,10 @@ impl BodySourceMap { | |||
138 | pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> { | 139 | pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> { |
139 | self.pat_map.get(&SyntaxNodePtr::new(node.syntax())).cloned() | 140 | self.pat_map.get(&SyntaxNodePtr::new(node.syntax())).cloned() |
140 | } | 141 | } |
142 | |||
143 | pub fn field_syntax(&self, expr: ExprId, field: usize) -> Option<AstPtr<ast::NamedField>> { | ||
144 | self.field_map.get(&(expr, field)).cloned() | ||
145 | } | ||
141 | } | 146 | } |
142 | 147 | ||
143 | #[derive(Debug, Clone, Eq, PartialEq)] | 148 | #[derive(Debug, Clone, Eq, PartialEq)] |
@@ -629,8 +634,10 @@ impl ExprCollector { | |||
629 | } | 634 | } |
630 | ast::ExprKind::StructLit(e) => { | 635 | ast::ExprKind::StructLit(e) => { |
631 | let path = e.path().and_then(Path::from_ast); | 636 | let path = e.path().and_then(Path::from_ast); |
637 | let mut field_ptrs = Vec::new(); | ||
632 | let fields = if let Some(nfl) = e.named_field_list() { | 638 | let fields = if let Some(nfl) = e.named_field_list() { |
633 | nfl.fields() | 639 | nfl.fields() |
640 | .inspect(|field| field_ptrs.push(AstPtr::new(*field))) | ||
634 | .map(|field| StructLitField { | 641 | .map(|field| StructLitField { |
635 | name: field | 642 | name: field |
636 | .name_ref() | 643 | .name_ref() |
@@ -657,7 +664,11 @@ impl ExprCollector { | |||
657 | Vec::new() | 664 | Vec::new() |
658 | }; | 665 | }; |
659 | let spread = e.spread().map(|s| self.collect_expr(s)); | 666 | let spread = e.spread().map(|s| self.collect_expr(s)); |
660 | self.alloc_expr(Expr::StructLit { path, fields, spread }, syntax_ptr) | 667 | let res = self.alloc_expr(Expr::StructLit { path, fields, spread }, syntax_ptr); |
668 | for (i, ptr) in field_ptrs.into_iter().enumerate() { | ||
669 | self.source_map.field_map.insert((res, i), ptr); | ||
670 | } | ||
671 | res | ||
661 | } | 672 | } |
662 | ast::ExprKind::FieldExpr(e) => { | 673 | ast::ExprKind::FieldExpr(e) => { |
663 | let expr = self.collect_expr_opt(e.expr()); | 674 | let expr = self.collect_expr_opt(e.expr()); |
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index a89c916f8..390aef0a9 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs | |||
@@ -35,6 +35,7 @@ mod expr; | |||
35 | mod generics; | 35 | mod generics; |
36 | mod docs; | 36 | mod docs; |
37 | mod resolve; | 37 | mod resolve; |
38 | pub mod diagnostics; | ||
38 | 39 | ||
39 | mod code_model_api; | 40 | mod code_model_api; |
40 | mod code_model_impl; | 41 | mod code_model_impl; |
diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index cff7e7481..269b5162e 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs | |||
@@ -36,7 +36,8 @@ use crate::{ | |||
36 | path::{GenericArgs, GenericArg}, | 36 | path::{GenericArgs, GenericArg}, |
37 | adt::VariantDef, | 37 | adt::VariantDef, |
38 | resolve::{Resolver, Resolution}, | 38 | resolve::{Resolver, Resolution}, |
39 | nameres::Namespace | 39 | nameres::Namespace, |
40 | diagnostics::FunctionDiagnostic, | ||
40 | }; | 41 | }; |
41 | use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor}; | 42 | use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor}; |
42 | 43 | ||
@@ -96,6 +97,7 @@ pub struct InferenceResult { | |||
96 | field_resolutions: FxHashMap<ExprId, StructField>, | 97 | field_resolutions: FxHashMap<ExprId, StructField>, |
97 | /// For each associated item record what it resolves to | 98 | /// For each associated item record what it resolves to |
98 | assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>, | 99 | assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>, |
100 | diagnostics: Vec<FunctionDiagnostic>, | ||
99 | pub(super) type_of_expr: ArenaMap<ExprId, Ty>, | 101 | pub(super) type_of_expr: ArenaMap<ExprId, Ty>, |
100 | pub(super) type_of_pat: ArenaMap<PatId, Ty>, | 102 | pub(super) type_of_pat: ArenaMap<PatId, Ty>, |
101 | } | 103 | } |
@@ -113,6 +115,9 @@ impl InferenceResult { | |||
113 | pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<ImplItem> { | 115 | pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<ImplItem> { |
114 | self.assoc_resolutions.get(&id.into()).map(|it| *it) | 116 | self.assoc_resolutions.get(&id.into()).map(|it| *it) |
115 | } | 117 | } |
118 | pub(crate) fn diagnostics(&self) -> Vec<FunctionDiagnostic> { | ||
119 | self.diagnostics.clone() | ||
120 | } | ||
116 | } | 121 | } |
117 | 122 | ||
118 | impl Index<ExprId> for InferenceResult { | 123 | impl Index<ExprId> for InferenceResult { |
@@ -143,6 +148,7 @@ struct InferenceContext<'a, D: HirDatabase> { | |||
143 | assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>, | 148 | assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>, |
144 | type_of_expr: ArenaMap<ExprId, Ty>, | 149 | type_of_expr: ArenaMap<ExprId, Ty>, |
145 | type_of_pat: ArenaMap<PatId, Ty>, | 150 | type_of_pat: ArenaMap<PatId, Ty>, |
151 | diagnostics: Vec<FunctionDiagnostic>, | ||
146 | /// The return type of the function being inferred. | 152 | /// The return type of the function being inferred. |
147 | return_ty: Ty, | 153 | return_ty: Ty, |
148 | } | 154 | } |
@@ -155,6 +161,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
155 | assoc_resolutions: FxHashMap::default(), | 161 | assoc_resolutions: FxHashMap::default(), |
156 | type_of_expr: ArenaMap::default(), | 162 | type_of_expr: ArenaMap::default(), |
157 | type_of_pat: ArenaMap::default(), | 163 | type_of_pat: ArenaMap::default(), |
164 | diagnostics: Vec::default(), | ||
158 | var_unification_table: InPlaceUnificationTable::new(), | 165 | var_unification_table: InPlaceUnificationTable::new(), |
159 | return_ty: Ty::Unknown, // set in collect_fn_signature | 166 | return_ty: Ty::Unknown, // set in collect_fn_signature |
160 | db, | 167 | db, |
@@ -181,6 +188,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
181 | assoc_resolutions: self.assoc_resolutions, | 188 | assoc_resolutions: self.assoc_resolutions, |
182 | type_of_expr: expr_types, | 189 | type_of_expr: expr_types, |
183 | type_of_pat: pat_types, | 190 | type_of_pat: pat_types, |
191 | diagnostics: self.diagnostics, | ||
184 | } | 192 | } |
185 | } | 193 | } |
186 | 194 | ||
@@ -915,9 +923,18 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { | |||
915 | Expr::StructLit { path, fields, spread } => { | 923 | Expr::StructLit { path, fields, spread } => { |
916 | let (ty, def_id) = self.resolve_variant(path.as_ref()); | 924 | let (ty, def_id) = self.resolve_variant(path.as_ref()); |
917 | let substs = ty.substs().unwrap_or_else(Substs::empty); | 925 | let substs = ty.substs().unwrap_or_else(Substs::empty); |
918 | for field in fields { | 926 | for (field_idx, field) in fields.into_iter().enumerate() { |
919 | let field_ty = def_id | 927 | let field_ty = def_id |
920 | .and_then(|it| it.field(self.db, &field.name)) | 928 | .and_then(|it| match it.field(self.db, &field.name) { |
929 | Some(field) => Some(field), | ||
930 | None => { | ||
931 | self.diagnostics.push(FunctionDiagnostic::NoSuchField { | ||
932 | expr: tgt_expr, | ||
933 | field: field_idx, | ||
934 | }); | ||
935 | None | ||
936 | } | ||
937 | }) | ||
921 | .map_or(Ty::Unknown, |field| field.ty(self.db)) | 938 | .map_or(Ty::Unknown, |field| field.ty(self.db)) |
922 | .subst(&substs); | 939 | .subst(&substs); |
923 | self.infer_expr(field.expr, &Expectation::has_type(field_ty)); | 940 | self.infer_expr(field.expr, &Expectation::has_type(field_ty)); |
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index 156f28ca3..f662f7e2f 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs | |||
@@ -3,7 +3,7 @@ use hir::{Problem, source_binder}; | |||
3 | use ra_db::SourceDatabase; | 3 | use ra_db::SourceDatabase; |
4 | use ra_syntax::{ | 4 | use ra_syntax::{ |
5 | Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, | 5 | Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, |
6 | ast::{self, AstNode}, | 6 | ast::{self, AstNode, NameOwner}, |
7 | 7 | ||
8 | }; | 8 | }; |
9 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 9 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
@@ -134,6 +134,13 @@ fn check_module( | |||
134 | file_id: FileId, | 134 | file_id: FileId, |
135 | module: hir::Module, | 135 | module: hir::Module, |
136 | ) { | 136 | ) { |
137 | for decl in module.declarations(db) { | ||
138 | match decl { | ||
139 | hir::ModuleDef::Function(f) => check_function(acc, db, f), | ||
140 | _ => (), | ||
141 | } | ||
142 | } | ||
143 | |||
137 | let source_root = db.file_source_root(file_id); | 144 | let source_root = db.file_source_root(file_id); |
138 | for (name_node, problem) in module.problems(db) { | 145 | for (name_node, problem) in module.problems(db) { |
139 | let diag = match problem { | 146 | let diag = match problem { |
@@ -153,6 +160,27 @@ fn check_module( | |||
153 | } | 160 | } |
154 | } | 161 | } |
155 | 162 | ||
163 | fn check_function(acc: &mut Vec<Diagnostic>, db: &RootDatabase, function: hir::Function) { | ||
164 | let (_file_id, fn_def) = function.source(db); | ||
165 | let source_file = fn_def.syntax().ancestors().find_map(ast::SourceFile::cast).unwrap(); | ||
166 | let source_map = function.body_source_map(db); | ||
167 | for d in function.diagnostics(db) { | ||
168 | match d { | ||
169 | hir::diagnostics::FunctionDiagnostic::NoSuchField { expr, field } => { | ||
170 | if let Some(field) = source_map.field_syntax(expr, field) { | ||
171 | let field = field.to_node(&source_file); | ||
172 | acc.push(Diagnostic { | ||
173 | message: "no such field".into(), | ||
174 | range: field.syntax().range(), | ||
175 | severity: Severity::Error, | ||
176 | fix: None, | ||
177 | }) | ||
178 | } | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
156 | #[cfg(test)] | 184 | #[cfg(test)] |
157 | mod tests { | 185 | mod tests { |
158 | use test_utils::assert_eq_text; | 186 | use test_utils::assert_eq_text; |