mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 22:54:58 +00:00
fill struct fields diagnostic
This commit is contained in:
parent
32db5884ad
commit
26ed925685
9 changed files with 269 additions and 18 deletions
|
@ -4,6 +4,7 @@ use arrayvec::ArrayVec;
|
||||||
use ra_text_edit::TextEditBuilder;
|
use ra_text_edit::TextEditBuilder;
|
||||||
use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction};
|
use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction};
|
||||||
use ra_fmt::leading_indent;
|
use ra_fmt::leading_indent;
|
||||||
|
use hir::Name;
|
||||||
|
|
||||||
pub struct AstEditor<N: AstNode> {
|
pub struct AstEditor<N: AstNode> {
|
||||||
original_ast: TreeArc<N>,
|
original_ast: TreeArc<N>,
|
||||||
|
@ -235,6 +236,10 @@ pub struct AstBuilder<N: AstNode> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AstBuilder<ast::NamedField> {
|
impl AstBuilder<ast::NamedField> {
|
||||||
|
pub fn from_name(name: &Name) -> TreeArc<ast::NamedField> {
|
||||||
|
ast_node_from_file_text(&format!("fn f() {{ S {{ {}: (), }} }}", name))
|
||||||
|
}
|
||||||
|
|
||||||
fn from_text(text: &str) -> TreeArc<ast::NamedField> {
|
fn from_text(text: &str) -> TreeArc<ast::NamedField> {
|
||||||
ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text))
|
ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
HirDatabase, DefDatabase,
|
HirDatabase, DefDatabase,
|
||||||
type_ref::TypeRef,
|
type_ref::TypeRef,
|
||||||
nameres::{ModuleScope, Namespace, ImportId, CrateModuleId},
|
nameres::{ModuleScope, Namespace, ImportId, CrateModuleId},
|
||||||
expr::{Body, BodySourceMap},
|
expr::{Body, BodySourceMap, validation::ExprValidator},
|
||||||
ty::{ TraitRef, InferenceResult},
|
ty::{ TraitRef, InferenceResult},
|
||||||
adt::{EnumVariantId, StructFieldId, VariantDef},
|
adt::{EnumVariantId, StructFieldId, VariantDef},
|
||||||
generics::HasGenericParams,
|
generics::HasGenericParams,
|
||||||
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeAliasId},
|
ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeAliasId},
|
||||||
impl_block::ImplBlock,
|
impl_block::ImplBlock,
|
||||||
resolve::Resolver,
|
resolve::Resolver,
|
||||||
diagnostics::DiagnosticSink,
|
diagnostics::{DiagnosticSink},
|
||||||
traits::{TraitItem, TraitData},
|
traits::{TraitItem, TraitData},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -431,8 +431,8 @@ impl Docs for EnumVariant {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum DefWithBody {
|
pub enum DefWithBody {
|
||||||
Function(Function),
|
Function(Function),
|
||||||
Const(Const),
|
|
||||||
Static(Static),
|
Static(Static),
|
||||||
|
Const(Const),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_froms!(DefWithBody: Function, Const, Static);
|
impl_froms!(DefWithBody: Function, Const, Static);
|
||||||
|
@ -562,7 +562,10 @@ impl Function {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diagnostics(&self, db: &impl HirDatabase, sink: &mut DiagnosticSink) {
|
pub fn diagnostics(&self, db: &impl HirDatabase, sink: &mut DiagnosticSink) {
|
||||||
self.infer(db).add_diagnostics(db, *self, sink);
|
let infer = self.infer(db);
|
||||||
|
infer.add_diagnostics(db, *self, sink);
|
||||||
|
let mut validator = ExprValidator::new(*self, infer, sink);
|
||||||
|
validator.validate_body(db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{fmt, any::Any};
|
||||||
use ra_syntax::{SyntaxNodePtr, TreeArc, AstPtr, TextRange, ast, SyntaxNode};
|
use ra_syntax::{SyntaxNodePtr, TreeArc, AstPtr, TextRange, ast, SyntaxNode};
|
||||||
use relative_path::RelativePathBuf;
|
use relative_path::RelativePathBuf;
|
||||||
|
|
||||||
use crate::{HirFileId, HirDatabase};
|
use crate::{HirFileId, HirDatabase, Name};
|
||||||
|
|
||||||
/// Diagnostic defines hir API for errors and warnings.
|
/// Diagnostic defines hir API for errors and warnings.
|
||||||
///
|
///
|
||||||
|
@ -113,3 +113,25 @@ impl Diagnostic for UnresolvedModule {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MissingFields {
|
||||||
|
pub file: HirFileId,
|
||||||
|
pub field_list: AstPtr<ast::NamedFieldList>,
|
||||||
|
pub missed_fields: Vec<Name>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic for MissingFields {
|
||||||
|
fn message(&self) -> String {
|
||||||
|
"fill structure fields".to_string()
|
||||||
|
}
|
||||||
|
fn file(&self) -> HirFileId {
|
||||||
|
self.file
|
||||||
|
}
|
||||||
|
fn syntax_node_ptr(&self) -> SyntaxNodePtr {
|
||||||
|
self.field_list.into()
|
||||||
|
}
|
||||||
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ use crate::{path::GenericArgs, ty::primitive::{IntTy, UncertainIntTy, FloatTy, U
|
||||||
pub use self::scope::ExprScopes;
|
pub use self::scope::ExprScopes;
|
||||||
|
|
||||||
pub(crate) mod scope;
|
pub(crate) mod scope;
|
||||||
|
pub(crate) mod validation;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ExprId(RawId);
|
pub struct ExprId(RawId);
|
||||||
|
@ -670,8 +671,9 @@ where
|
||||||
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 mut field_ptrs = Vec::new();
|
||||||
let fields = if let Some(nfl) = e.named_field_list() {
|
let struct_lit = if let Some(nfl) = e.named_field_list() {
|
||||||
nfl.fields()
|
let fields = nfl
|
||||||
|
.fields()
|
||||||
.inspect(|field| field_ptrs.push(AstPtr::new(*field)))
|
.inspect(|field| field_ptrs.push(AstPtr::new(*field)))
|
||||||
.map(|field| StructLitField {
|
.map(|field| StructLitField {
|
||||||
name: field
|
name: field
|
||||||
|
@ -694,12 +696,14 @@ where
|
||||||
self.exprs.alloc(Expr::Missing)
|
self.exprs.alloc(Expr::Missing)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.collect()
|
.collect();
|
||||||
|
let spread = nfl.spread().map(|s| self.collect_expr(s));
|
||||||
|
Expr::StructLit { path, fields, spread }
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Expr::StructLit { path, fields: Vec::new(), spread: None }
|
||||||
};
|
};
|
||||||
let spread = e.spread().map(|s| self.collect_expr(s));
|
|
||||||
let res = self.alloc_expr(Expr::StructLit { path, fields, spread }, syntax_ptr);
|
let res = self.alloc_expr(struct_lit, syntax_ptr);
|
||||||
for (i, ptr) in field_ptrs.into_iter().enumerate() {
|
for (i, ptr) in field_ptrs.into_iter().enumerate() {
|
||||||
self.source_map.field_map.insert((res, i), ptr);
|
self.source_map.field_map.insert((res, i), ptr);
|
||||||
}
|
}
|
||||||
|
|
92
crates/ra_hir/src/expr/validation.rs
Normal file
92
crates/ra_hir/src/expr/validation.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
|
use ra_syntax::ast::{AstNode, StructLit};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
expr::AstPtr,
|
||||||
|
HirDatabase,
|
||||||
|
Function,
|
||||||
|
Name,
|
||||||
|
diagnostics::{DiagnosticSink, MissingFields},
|
||||||
|
adt::AdtDef,
|
||||||
|
Path,
|
||||||
|
ty::InferenceResult
|
||||||
|
};
|
||||||
|
use super::{Expr, StructLitField, ExprId};
|
||||||
|
|
||||||
|
pub(crate) struct ExprValidator<'a, 'b: 'a> {
|
||||||
|
func: Function,
|
||||||
|
infer: Arc<InferenceResult>,
|
||||||
|
sink: &'a mut DiagnosticSink<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> ExprValidator<'a, 'b> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
func: Function,
|
||||||
|
infer: Arc<InferenceResult>,
|
||||||
|
sink: &'a mut DiagnosticSink<'b>,
|
||||||
|
) -> ExprValidator<'a, 'b> {
|
||||||
|
ExprValidator { func, infer, sink }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) {
|
||||||
|
let body = self.func.body(db);
|
||||||
|
for e in body.exprs() {
|
||||||
|
match e {
|
||||||
|
(id, Expr::StructLit { path, fields, spread }) => {
|
||||||
|
self.validate_struct_literal(id, path, fields, spread, db)
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_struct_literal(
|
||||||
|
&mut self,
|
||||||
|
id: ExprId,
|
||||||
|
_path: &Option<Path>,
|
||||||
|
fields: &Vec<StructLitField>,
|
||||||
|
spread: &Option<ExprId>,
|
||||||
|
db: &impl HirDatabase,
|
||||||
|
) {
|
||||||
|
if let Some(_) = spread {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let lit_fields: FxHashSet<_> = fields.into_iter().map(|f| &f.name).collect();
|
||||||
|
let struct_ty = &self.infer[id];
|
||||||
|
if let Some((AdtDef::Struct(s), _)) = struct_ty.as_adt() {
|
||||||
|
let missed_fields: Vec<Name> = s
|
||||||
|
.fields(db)
|
||||||
|
.iter()
|
||||||
|
.filter_map(|f| {
|
||||||
|
let name = f.name(db);
|
||||||
|
if lit_fields.contains(&name) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if missed_fields.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let source_map = self.func.body_source_map(db);
|
||||||
|
let file_id = self.func.source(db).0;
|
||||||
|
let source_file = db.parse(file_id.original_file(db));
|
||||||
|
if let Some(field_list_node) = source_map
|
||||||
|
.expr_syntax(id)
|
||||||
|
.map(|ptr| ptr.to_node(&source_file))
|
||||||
|
.and_then(StructLit::cast)
|
||||||
|
.and_then(|lit| lit.named_field_list())
|
||||||
|
{
|
||||||
|
let field_list_ptr = AstPtr::new(field_list_node);
|
||||||
|
self.sink.push(MissingFields {
|
||||||
|
file: file_id,
|
||||||
|
field_list: field_list_ptr,
|
||||||
|
missed_fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2662,6 +2662,7 @@ fn no_such_field_diagnostics() {
|
||||||
|
|
||||||
assert_snapshot_matches!(diagnostics, @r###"
|
assert_snapshot_matches!(diagnostics, @r###"
|
||||||
"baz: 62": no such field
|
"baz: 62": no such field
|
||||||
|
"{\n foo: 92,\n baz: 62,\n }": fill structure fields
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,9 @@ use hir::{source_binder, diagnostics::{Diagnostic as _, DiagnosticSink}};
|
||||||
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, NamedFieldList, NamedField},
|
||||||
};
|
};
|
||||||
|
use ra_assists::ast_editor::{AstEditor, AstBuilder};
|
||||||
use ra_text_edit::{TextEdit, TextEditBuilder};
|
use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
|
|
||||||
|
@ -48,6 +49,27 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
fix: Some(fix),
|
fix: Some(fix),
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
.on::<hir::diagnostics::MissingFields, _>(|d| {
|
||||||
|
let file_id = d.file().original_file(db);
|
||||||
|
let source_file = db.parse(file_id);
|
||||||
|
let syntax_node = d.syntax_node_ptr();
|
||||||
|
let node = NamedFieldList::cast(syntax_node.to_node(&source_file)).unwrap();
|
||||||
|
let mut ast_editor = AstEditor::new(node);
|
||||||
|
for f in d.missed_fields.iter() {
|
||||||
|
ast_editor.append_field(&AstBuilder::<NamedField>::from_name(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut builder = TextEditBuilder::default();
|
||||||
|
ast_editor.into_text_edit(&mut builder);
|
||||||
|
let fix =
|
||||||
|
SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish());
|
||||||
|
res.borrow_mut().push(Diagnostic {
|
||||||
|
range: d.highlight_range(),
|
||||||
|
message: d.message(),
|
||||||
|
severity: Severity::Error,
|
||||||
|
fix: Some(fix),
|
||||||
|
})
|
||||||
});
|
});
|
||||||
if let Some(m) = source_binder::module_from_file_id(db, file_id) {
|
if let Some(m) = source_binder::module_from_file_id(db, file_id) {
|
||||||
m.diagnostics(db, &mut sink);
|
m.diagnostics(db, &mut sink);
|
||||||
|
@ -187,6 +209,105 @@ mod tests {
|
||||||
assert_eq_text!(after, &actual);
|
assert_eq_text!(after, &actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_apply_diagnostic_fix(before: &str, after: &str) {
|
||||||
|
let (analysis, file_id) = single_file(before);
|
||||||
|
let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
|
||||||
|
let mut fix = diagnostic.fix.unwrap();
|
||||||
|
let edit = fix.source_file_edits.pop().unwrap().edit;
|
||||||
|
let actual = edit.apply(&before);
|
||||||
|
assert_eq_text!(after, &actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_no_diagnostic(content: &str) {
|
||||||
|
let (analysis, file_id) = single_file(content);
|
||||||
|
let diagnostics = analysis.diagnostics(file_id).unwrap();
|
||||||
|
assert_eq!(diagnostics.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fill_struct_fields_empty() {
|
||||||
|
let before = r"
|
||||||
|
struct TestStruct {
|
||||||
|
one: i32,
|
||||||
|
two: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fn() {
|
||||||
|
let s = TestStruct{};
|
||||||
|
}
|
||||||
|
";
|
||||||
|
let after = r"
|
||||||
|
struct TestStruct {
|
||||||
|
one: i32,
|
||||||
|
two: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fn() {
|
||||||
|
let s = TestStruct{ one: (), two: ()};
|
||||||
|
}
|
||||||
|
";
|
||||||
|
check_apply_diagnostic_fix(before, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fill_struct_fields_partial() {
|
||||||
|
let before = r"
|
||||||
|
struct TestStruct {
|
||||||
|
one: i32,
|
||||||
|
two: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fn() {
|
||||||
|
let s = TestStruct{ two: 2 };
|
||||||
|
}
|
||||||
|
";
|
||||||
|
let after = r"
|
||||||
|
struct TestStruct {
|
||||||
|
one: i32,
|
||||||
|
two: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fn() {
|
||||||
|
let s = TestStruct{ two: 2, one: () };
|
||||||
|
}
|
||||||
|
";
|
||||||
|
check_apply_diagnostic_fix(before, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fill_struct_fields_no_diagnostic() {
|
||||||
|
let content = r"
|
||||||
|
struct TestStruct {
|
||||||
|
one: i32,
|
||||||
|
two: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fn() {
|
||||||
|
let one = 1;
|
||||||
|
let s = TestStruct{ one, two: 2 };
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
check_no_diagnostic(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fill_struct_fields_no_diagnostic_on_spread() {
|
||||||
|
let content = r"
|
||||||
|
struct TestStruct {
|
||||||
|
one: i32,
|
||||||
|
two: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fn() {
|
||||||
|
let one = 1;
|
||||||
|
let s = TestStruct{ ..a };
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
check_no_diagnostic(content);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unresolved_module_diagnostic() {
|
fn test_unresolved_module_diagnostic() {
|
||||||
let (analysis, file_id) = single_file("mod foo;");
|
let (analysis, file_id) = single_file("mod foo;");
|
||||||
|
|
|
@ -2371,6 +2371,10 @@ impl NamedFieldList {
|
||||||
pub fn fields(&self) -> impl Iterator<Item = &NamedField> {
|
pub fn fields(&self) -> impl Iterator<Item = &NamedField> {
|
||||||
super::children(self)
|
super::children(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spread(&self) -> Option<&Expr> {
|
||||||
|
super::child_opt(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NeverType
|
// NeverType
|
||||||
|
@ -3564,10 +3568,6 @@ impl StructLit {
|
||||||
pub fn named_field_list(&self) -> Option<&NamedFieldList> {
|
pub fn named_field_list(&self) -> Option<&NamedFieldList> {
|
||||||
super::child_opt(self)
|
super::child_opt(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spread(&self) -> Option<&Expr> {
|
|
||||||
super::child_opt(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StructPat
|
// StructPat
|
||||||
|
|
|
@ -451,8 +451,11 @@ Grammar(
|
||||||
traits: [ "AttrsOwner" ]
|
traits: [ "AttrsOwner" ]
|
||||||
),
|
),
|
||||||
"MatchGuard": (options: ["Expr"]),
|
"MatchGuard": (options: ["Expr"]),
|
||||||
"StructLit": (options: ["Path", "NamedFieldList", ["spread", "Expr"]]),
|
"StructLit": (options: ["Path", "NamedFieldList"]),
|
||||||
"NamedFieldList": (collections: [ ["fields", "NamedField"] ]),
|
"NamedFieldList": (
|
||||||
|
collections: [ ["fields", "NamedField"] ],
|
||||||
|
options: [["spread", "Expr"]]
|
||||||
|
),
|
||||||
"NamedField": (options: ["NameRef", "Expr"]),
|
"NamedField": (options: ["NameRef", "Expr"]),
|
||||||
"CallExpr": (
|
"CallExpr": (
|
||||||
traits: ["ArgListOwner"],
|
traits: ["ArgListOwner"],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue