diagnostics

This commit is contained in:
Aleksey Kladov 2019-03-21 22:13:11 +03:00
parent 4132fbf3a0
commit 7e8f17188e
6 changed files with 74 additions and 6 deletions

View File

@ -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<FunctionDiagnostic> {
self.infer(db).diagnostics()
}
}
impl Docs for Function {

View File

@ -0,0 +1,6 @@
use crate::{expr::ExprId};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FunctionDiagnostic {
NoSuchField { expr: ExprId, field: usize },
}

View File

@ -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<ExprId, SyntaxNodePtr>,
pat_map: FxHashMap<SyntaxNodePtr, PatId>,
pat_map_back: ArenaMap<PatId, SyntaxNodePtr>,
field_map: FxHashMap<(ExprId, usize), AstPtr<ast::NamedField>>,
}
impl Body {
@ -138,6 +139,10 @@ impl BodySourceMap {
pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> {
self.pat_map.get(&SyntaxNodePtr::new(node.syntax())).cloned()
}
pub fn field_syntax(&self, expr: ExprId, field: usize) -> Option<AstPtr<ast::NamedField>> {
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());

View File

@ -35,6 +35,7 @@ mod expr;
mod generics;
mod docs;
mod resolve;
pub mod diagnostics;
mod code_model_api;
mod code_model_impl;

View File

@ -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<ExprId, StructField>,
/// For each associated item record what it resolves to
assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
diagnostics: Vec<FunctionDiagnostic>,
pub(super) type_of_expr: ArenaMap<ExprId, Ty>,
pub(super) type_of_pat: ArenaMap<PatId, Ty>,
}
@ -113,6 +115,9 @@ impl InferenceResult {
pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<ImplItem> {
self.assoc_resolutions.get(&id.into()).map(|it| *it)
}
pub(crate) fn diagnostics(&self) -> Vec<FunctionDiagnostic> {
self.diagnostics.clone()
}
}
impl Index<ExprId> for InferenceResult {
@ -143,6 +148,7 @@ struct InferenceContext<'a, D: HirDatabase> {
assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
type_of_expr: ArenaMap<ExprId, Ty>,
type_of_pat: ArenaMap<PatId, Ty>,
diagnostics: Vec<FunctionDiagnostic>,
/// 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));

View File

@ -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<Diagnostic>, 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;