From 7e8f17188efcecfdfd1afbbc894a53c65985f836 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 21 Mar 2019 22:13:11 +0300 Subject: [PATCH] diagnostics --- crates/ra_hir/src/code_model_api.rs | 5 +++++ crates/ra_hir/src/diagnostics.rs | 6 ++++++ crates/ra_hir/src/expr.rs | 15 ++++++++++++-- crates/ra_hir/src/lib.rs | 1 + crates/ra_hir/src/ty/infer.rs | 23 ++++++++++++++++++--- crates/ra_ide_api/src/diagnostics.rs | 30 +++++++++++++++++++++++++++- 6 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 crates/ra_hir/src/diagnostics.rs diff --git a/crates/ra_hir/src/code_model_api.rs b/crates/ra_hir/src/code_model_api.rs index 45fa4cd110..58481e715e 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::{ ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId}, impl_block::ImplBlock, resolve::Resolver, + diagnostics::FunctionDiagnostic, }; /// hir::Crate describes a single crate. It's the main interface with which @@ -519,6 +520,10 @@ impl Function { let r = if !p.params.is_empty() { r.push_generic_params_scope(p) } else { r }; r } + + pub fn diagnostics(&self, db: &impl HirDatabase) -> Vec { + self.infer(db).diagnostics() + } } 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 0000000000..82aff9cee0 --- /dev/null +++ b/crates/ra_hir/src/diagnostics.rs @@ -0,0 +1,6 @@ +use crate::{expr::ExprId}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FunctionDiagnostic { + NoSuchField { expr: ExprId, field: usize }, +} diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index c37fd0454b..31af5d2410 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -5,7 +5,7 @@ use rustc_hash::FxHashMap; use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap}; use ra_syntax::{ - SyntaxNodePtr, AstNode, + SyntaxNodePtr, AstPtr, AstNode, ast::{self, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor, TypeAscriptionOwner} }; @@ -54,6 +54,7 @@ pub struct BodySourceMap { expr_map_back: ArenaMap, pat_map: FxHashMap, pat_map_back: ArenaMap, + field_map: FxHashMap<(ExprId, usize), AstPtr>, } impl Body { @@ -138,6 +139,10 @@ impl BodySourceMap { pub fn node_pat(&self, node: &ast::Pat) -> Option { self.pat_map.get(&SyntaxNodePtr::new(node.syntax())).cloned() } + + pub fn field_syntax(&self, expr: ExprId, field: usize) -> Option> { + self.field_map.get(&(expr, field)).cloned() + } } #[derive(Debug, Clone, Eq, PartialEq)] @@ -629,8 +634,10 @@ impl ExprCollector { } ast::ExprKind::StructLit(e) => { let path = e.path().and_then(Path::from_ast); + let mut field_ptrs = Vec::new(); let fields = if let Some(nfl) = e.named_field_list() { nfl.fields() + .inspect(|field| field_ptrs.push(AstPtr::new(*field))) .map(|field| StructLitField { name: field .name_ref() @@ -657,7 +664,11 @@ impl ExprCollector { Vec::new() }; let spread = e.spread().map(|s| self.collect_expr(s)); - self.alloc_expr(Expr::StructLit { path, fields, spread }, syntax_ptr) + let res = self.alloc_expr(Expr::StructLit { path, fields, spread }, syntax_ptr); + for (i, ptr) in field_ptrs.into_iter().enumerate() { + self.source_map.field_map.insert((res, i), ptr); + } + res } ast::ExprKind::FieldExpr(e) => { 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 a89c916f88..390aef0a9c 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -35,6 +35,7 @@ mod expr; mod generics; mod docs; mod resolve; +pub mod diagnostics; mod code_model_api; mod code_model_impl; diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index cff7e74819..269b5162e3 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -36,7 +36,8 @@ use crate::{ path::{GenericArgs, GenericArg}, adt::VariantDef, resolve::{Resolver, Resolution}, - nameres::Namespace + nameres::Namespace, + diagnostics::FunctionDiagnostic, }; use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor}; @@ -96,6 +97,7 @@ pub struct InferenceResult { field_resolutions: FxHashMap, /// For each associated item record what it resolves to assoc_resolutions: FxHashMap, + diagnostics: Vec, pub(super) type_of_expr: ArenaMap, pub(super) type_of_pat: ArenaMap, } @@ -113,6 +115,9 @@ impl InferenceResult { pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option { self.assoc_resolutions.get(&id.into()).map(|it| *it) } + pub(crate) fn diagnostics(&self) -> Vec { + self.diagnostics.clone() + } } impl Index for InferenceResult { @@ -143,6 +148,7 @@ struct InferenceContext<'a, D: HirDatabase> { assoc_resolutions: FxHashMap, type_of_expr: ArenaMap, type_of_pat: ArenaMap, + diagnostics: Vec, /// The return type of the function being inferred. return_ty: Ty, } @@ -155,6 +161,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { assoc_resolutions: FxHashMap::default(), type_of_expr: ArenaMap::default(), type_of_pat: ArenaMap::default(), + diagnostics: Vec::default(), var_unification_table: InPlaceUnificationTable::new(), return_ty: Ty::Unknown, // set in collect_fn_signature db, @@ -181,6 +188,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { assoc_resolutions: self.assoc_resolutions, type_of_expr: expr_types, type_of_pat: pat_types, + diagnostics: self.diagnostics, } } @@ -915,9 +923,18 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Expr::StructLit { path, fields, spread } => { let (ty, def_id) = self.resolve_variant(path.as_ref()); let substs = ty.substs().unwrap_or_else(Substs::empty); - for field in fields { + for (field_idx, field) in fields.into_iter().enumerate() { let field_ty = def_id - .and_then(|it| it.field(self.db, &field.name)) + .and_then(|it| match it.field(self.db, &field.name) { + Some(field) => Some(field), + None => { + self.diagnostics.push(FunctionDiagnostic::NoSuchField { + expr: tgt_expr, + field: field_idx, + }); + None + } + }) .map_or(Ty::Unknown, |field| field.ty(self.db)) .subst(&substs); 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 156f28ca33..f662f7e2f1 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}; use ra_db::SourceDatabase; use ra_syntax::{ Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, - ast::{self, AstNode}, + ast::{self, AstNode, NameOwner}, }; use ra_text_edit::{TextEdit, TextEditBuilder}; @@ -134,6 +134,13 @@ fn check_module( file_id: FileId, module: hir::Module, ) { + for decl in module.declarations(db) { + match decl { + hir::ModuleDef::Function(f) => check_function(acc, db, f), + _ => (), + } + } + let source_root = db.file_source_root(file_id); for (name_node, problem) in module.problems(db) { let diag = match problem { @@ -153,6 +160,27 @@ fn check_module( } } +fn check_function(acc: &mut Vec, db: &RootDatabase, function: hir::Function) { + let (_file_id, fn_def) = function.source(db); + let source_file = fn_def.syntax().ancestors().find_map(ast::SourceFile::cast).unwrap(); + let source_map = function.body_source_map(db); + for d in function.diagnostics(db) { + match d { + hir::diagnostics::FunctionDiagnostic::NoSuchField { expr, field } => { + if let Some(field) = source_map.field_syntax(expr, field) { + let field = field.to_node(&source_file); + acc.push(Diagnostic { + message: "no such field".into(), + range: field.syntax().range(), + severity: Severity::Error, + fix: None, + }) + } + } + } + } +} + #[cfg(test)] mod tests { use test_utils::assert_eq_text;