mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 14:51:48 +00:00
diagnostics
This commit is contained in:
parent
4132fbf3a0
commit
7e8f17188e
6 changed files with 74 additions and 6 deletions
|
@ -17,6 +17,7 @@ use crate::{
|
||||||
ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId},
|
ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId},
|
||||||
impl_block::ImplBlock,
|
impl_block::ImplBlock,
|
||||||
resolve::Resolver,
|
resolve::Resolver,
|
||||||
|
diagnostics::FunctionDiagnostic,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// hir::Crate describes a single crate. It's the main interface with which
|
/// 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 };
|
let r = if !p.params.is_empty() { r.push_generic_params_scope(p) } else { r };
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn diagnostics(&self, db: &impl HirDatabase) -> Vec<FunctionDiagnostic> {
|
||||||
|
self.infer(db).diagnostics()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Docs for Function {
|
impl Docs for Function {
|
||||||
|
|
6
crates/ra_hir/src/diagnostics.rs
Normal file
6
crates/ra_hir/src/diagnostics.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use crate::{expr::ExprId};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FunctionDiagnostic {
|
||||||
|
NoSuchField { expr: ExprId, field: usize },
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap};
|
use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap};
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
SyntaxNodePtr, AstNode,
|
SyntaxNodePtr, AstPtr, AstNode,
|
||||||
ast::{self, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor, TypeAscriptionOwner}
|
ast::{self, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor, TypeAscriptionOwner}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ pub struct BodySourceMap {
|
||||||
expr_map_back: ArenaMap<ExprId, SyntaxNodePtr>,
|
expr_map_back: ArenaMap<ExprId, SyntaxNodePtr>,
|
||||||
pat_map: FxHashMap<SyntaxNodePtr, PatId>,
|
pat_map: FxHashMap<SyntaxNodePtr, PatId>,
|
||||||
pat_map_back: ArenaMap<PatId, SyntaxNodePtr>,
|
pat_map_back: ArenaMap<PatId, SyntaxNodePtr>,
|
||||||
|
field_map: FxHashMap<(ExprId, usize), AstPtr<ast::NamedField>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Body {
|
impl Body {
|
||||||
|
@ -138,6 +139,10 @@ impl BodySourceMap {
|
||||||
pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> {
|
pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> {
|
||||||
self.pat_map.get(&SyntaxNodePtr::new(node.syntax())).cloned()
|
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)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
@ -629,8 +634,10 @@ impl ExprCollector {
|
||||||
}
|
}
|
||||||
ast::ExprKind::StructLit(e) => {
|
ast::ExprKind::StructLit(e) => {
|
||||||
let path = e.path().and_then(Path::from_ast);
|
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() {
|
let fields = if let Some(nfl) = e.named_field_list() {
|
||||||
nfl.fields()
|
nfl.fields()
|
||||||
|
.inspect(|field| field_ptrs.push(AstPtr::new(*field)))
|
||||||
.map(|field| StructLitField {
|
.map(|field| StructLitField {
|
||||||
name: field
|
name: field
|
||||||
.name_ref()
|
.name_ref()
|
||||||
|
@ -657,7 +664,11 @@ impl ExprCollector {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
let spread = e.spread().map(|s| self.collect_expr(s));
|
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) => {
|
ast::ExprKind::FieldExpr(e) => {
|
||||||
let expr = self.collect_expr_opt(e.expr());
|
let expr = self.collect_expr_opt(e.expr());
|
||||||
|
|
|
@ -35,6 +35,7 @@ mod expr;
|
||||||
mod generics;
|
mod generics;
|
||||||
mod docs;
|
mod docs;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
|
pub mod diagnostics;
|
||||||
|
|
||||||
mod code_model_api;
|
mod code_model_api;
|
||||||
mod code_model_impl;
|
mod code_model_impl;
|
||||||
|
|
|
@ -36,7 +36,8 @@ use crate::{
|
||||||
path::{GenericArgs, GenericArg},
|
path::{GenericArgs, GenericArg},
|
||||||
adt::VariantDef,
|
adt::VariantDef,
|
||||||
resolve::{Resolver, Resolution},
|
resolve::{Resolver, Resolution},
|
||||||
nameres::Namespace
|
nameres::Namespace,
|
||||||
|
diagnostics::FunctionDiagnostic,
|
||||||
};
|
};
|
||||||
use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor};
|
use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor};
|
||||||
|
|
||||||
|
@ -96,6 +97,7 @@ pub struct InferenceResult {
|
||||||
field_resolutions: FxHashMap<ExprId, StructField>,
|
field_resolutions: FxHashMap<ExprId, StructField>,
|
||||||
/// For each associated item record what it resolves to
|
/// For each associated item record what it resolves to
|
||||||
assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
|
assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
|
||||||
|
diagnostics: Vec<FunctionDiagnostic>,
|
||||||
pub(super) type_of_expr: ArenaMap<ExprId, Ty>,
|
pub(super) type_of_expr: ArenaMap<ExprId, Ty>,
|
||||||
pub(super) type_of_pat: ArenaMap<PatId, 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> {
|
pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<ImplItem> {
|
||||||
self.assoc_resolutions.get(&id.into()).map(|it| *it)
|
self.assoc_resolutions.get(&id.into()).map(|it| *it)
|
||||||
}
|
}
|
||||||
|
pub(crate) fn diagnostics(&self) -> Vec<FunctionDiagnostic> {
|
||||||
|
self.diagnostics.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<ExprId> for InferenceResult {
|
impl Index<ExprId> for InferenceResult {
|
||||||
|
@ -143,6 +148,7 @@ struct InferenceContext<'a, D: HirDatabase> {
|
||||||
assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
|
assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
|
||||||
type_of_expr: ArenaMap<ExprId, Ty>,
|
type_of_expr: ArenaMap<ExprId, Ty>,
|
||||||
type_of_pat: ArenaMap<PatId, Ty>,
|
type_of_pat: ArenaMap<PatId, Ty>,
|
||||||
|
diagnostics: Vec<FunctionDiagnostic>,
|
||||||
/// The return type of the function being inferred.
|
/// The return type of the function being inferred.
|
||||||
return_ty: Ty,
|
return_ty: Ty,
|
||||||
}
|
}
|
||||||
|
@ -155,6 +161,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
||||||
assoc_resolutions: FxHashMap::default(),
|
assoc_resolutions: FxHashMap::default(),
|
||||||
type_of_expr: ArenaMap::default(),
|
type_of_expr: ArenaMap::default(),
|
||||||
type_of_pat: ArenaMap::default(),
|
type_of_pat: ArenaMap::default(),
|
||||||
|
diagnostics: Vec::default(),
|
||||||
var_unification_table: InPlaceUnificationTable::new(),
|
var_unification_table: InPlaceUnificationTable::new(),
|
||||||
return_ty: Ty::Unknown, // set in collect_fn_signature
|
return_ty: Ty::Unknown, // set in collect_fn_signature
|
||||||
db,
|
db,
|
||||||
|
@ -181,6 +188,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
||||||
assoc_resolutions: self.assoc_resolutions,
|
assoc_resolutions: self.assoc_resolutions,
|
||||||
type_of_expr: expr_types,
|
type_of_expr: expr_types,
|
||||||
type_of_pat: pat_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 } => {
|
Expr::StructLit { path, fields, spread } => {
|
||||||
let (ty, def_id) = self.resolve_variant(path.as_ref());
|
let (ty, def_id) = self.resolve_variant(path.as_ref());
|
||||||
let substs = ty.substs().unwrap_or_else(Substs::empty);
|
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
|
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))
|
.map_or(Ty::Unknown, |field| field.ty(self.db))
|
||||||
.subst(&substs);
|
.subst(&substs);
|
||||||
self.infer_expr(field.expr, &Expectation::has_type(field_ty));
|
self.infer_expr(field.expr, &Expectation::has_type(field_ty));
|
||||||
|
|
|
@ -3,7 +3,7 @@ use hir::{Problem, source_binder};
|
||||||
use ra_db::SourceDatabase;
|
use ra_db::SourceDatabase;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
Location, SourceFile, SyntaxKind, TextRange, SyntaxNode,
|
Location, SourceFile, SyntaxKind, TextRange, SyntaxNode,
|
||||||
ast::{self, AstNode},
|
ast::{self, AstNode, NameOwner},
|
||||||
|
|
||||||
};
|
};
|
||||||
use ra_text_edit::{TextEdit, TextEditBuilder};
|
use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||||
|
@ -134,6 +134,13 @@ fn check_module(
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
module: hir::Module,
|
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);
|
let source_root = db.file_source_root(file_id);
|
||||||
for (name_node, problem) in module.problems(db) {
|
for (name_node, problem) in module.problems(db) {
|
||||||
let diag = match problem {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use test_utils::assert_eq_text;
|
use test_utils::assert_eq_text;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue