internal: unified missing fields diagnostic
This commit is contained in:
parent
c6509a4592
commit
6383252cc2
@ -6,6 +6,7 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use cfg::{CfgExpr, CfgOptions, DnfExpr};
|
use cfg::{CfgExpr, CfgOptions, DnfExpr};
|
||||||
|
use either::Either;
|
||||||
use hir_def::path::ModPath;
|
use hir_def::path::ModPath;
|
||||||
use hir_expand::{name::Name, HirFileId, InFile};
|
use hir_expand::{name::Name, HirFileId, InFile};
|
||||||
use stdx::format_to;
|
use stdx::format_to;
|
||||||
@ -324,60 +325,11 @@ fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MissingFields {
|
pub struct MissingFields {
|
||||||
pub file: HirFileId,
|
pub file: HirFileId,
|
||||||
pub field_list_parent: AstPtr<ast::RecordExpr>,
|
pub field_list_parent: Either<AstPtr<ast::RecordExpr>, AstPtr<ast::RecordPat>>,
|
||||||
pub field_list_parent_path: Option<AstPtr<ast::Path>>,
|
pub field_list_parent_path: Option<AstPtr<ast::Path>>,
|
||||||
pub missed_fields: Vec<Name>,
|
pub missed_fields: Vec<Name>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diagnostic: missing-pat-fields
|
|
||||||
//
|
|
||||||
// This diagnostic is triggered if pattern lacks some fields that exist in the corresponding structure.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// ```rust
|
|
||||||
// struct A { a: u8, b: u8 }
|
|
||||||
//
|
|
||||||
// let a = A { a: 10, b: 20 };
|
|
||||||
//
|
|
||||||
// if let A { a } = a {
|
|
||||||
// // ...
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MissingPatFields {
|
|
||||||
pub file: HirFileId,
|
|
||||||
pub field_list_parent: AstPtr<ast::RecordPat>,
|
|
||||||
pub field_list_parent_path: Option<AstPtr<ast::Path>>,
|
|
||||||
pub missed_fields: Vec<Name>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Diagnostic for MissingPatFields {
|
|
||||||
fn code(&self) -> DiagnosticCode {
|
|
||||||
DiagnosticCode("missing-pat-fields")
|
|
||||||
}
|
|
||||||
fn message(&self) -> String {
|
|
||||||
let mut buf = String::from("Missing structure fields:\n");
|
|
||||||
for field in &self.missed_fields {
|
|
||||||
format_to!(buf, "- {}\n", field);
|
|
||||||
}
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
|
||||||
InFile {
|
|
||||||
file_id: self.file,
|
|
||||||
value: self
|
|
||||||
.field_list_parent_path
|
|
||||||
.clone()
|
|
||||||
.map(SyntaxNodePtr::from)
|
|
||||||
.unwrap_or_else(|| self.field_list_parent.clone().into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diagnostic: replace-filter-map-next-with-find-map
|
// Diagnostic: replace-filter-map-next-with-find-map
|
||||||
//
|
//
|
||||||
// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
|
// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
|
||||||
|
@ -88,9 +88,9 @@
|
|||||||
diagnostics::{
|
diagnostics::{
|
||||||
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, InternalBailedOut, MacroError,
|
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, InternalBailedOut, MacroError,
|
||||||
MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
|
MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
|
||||||
MissingPatFields, MissingUnsafe, NoSuchField, RemoveThisSemicolon,
|
MissingUnsafe, NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
|
||||||
ReplaceFilterMapNextWithFindMap, UnimplementedBuiltinMacro, UnresolvedExternCrate,
|
UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
|
||||||
UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro,
|
UnresolvedModule, UnresolvedProcMacro,
|
||||||
},
|
},
|
||||||
has_source::HasSource,
|
has_source::HasSource,
|
||||||
semantics::{PathResolution, Semantics, SemanticsScope},
|
semantics::{PathResolution, Semantics, SemanticsScope},
|
||||||
@ -1098,67 +1098,70 @@ pub fn diagnostics(
|
|||||||
BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics)
|
BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics)
|
||||||
{
|
{
|
||||||
match diagnostic {
|
match diagnostic {
|
||||||
BodyValidationDiagnostic::RecordLiteralMissingFields {
|
BodyValidationDiagnostic::RecordMissingFields {
|
||||||
record_expr,
|
record,
|
||||||
variant,
|
variant,
|
||||||
missed_fields,
|
missed_fields,
|
||||||
} => match source_map.expr_syntax(record_expr) {
|
} => {
|
||||||
Ok(source_ptr) => {
|
let variant_data = variant.variant_data(db.upcast());
|
||||||
let root = source_ptr.file_syntax(db.upcast());
|
let missed_fields = missed_fields
|
||||||
if let ast::Expr::RecordExpr(record_expr) = &source_ptr.value.to_node(&root)
|
.into_iter()
|
||||||
{
|
.map(|idx| variant_data.fields()[idx].name.clone())
|
||||||
if let Some(_) = record_expr.record_expr_field_list() {
|
.collect();
|
||||||
let variant_data = variant.variant_data(db.upcast());
|
|
||||||
let missed_fields = missed_fields
|
match record {
|
||||||
.into_iter()
|
Either::Left(record_expr) => match source_map.expr_syntax(record_expr) {
|
||||||
.map(|idx| variant_data.fields()[idx].name.clone())
|
Ok(source_ptr) => {
|
||||||
.collect();
|
let root = source_ptr.file_syntax(db.upcast());
|
||||||
acc.push(
|
if let ast::Expr::RecordExpr(record_expr) =
|
||||||
MissingFields {
|
&source_ptr.value.to_node(&root)
|
||||||
file: source_ptr.file_id,
|
{
|
||||||
field_list_parent: AstPtr::new(record_expr),
|
if let Some(_) = record_expr.record_expr_field_list() {
|
||||||
field_list_parent_path: record_expr
|
acc.push(
|
||||||
.path()
|
MissingFields {
|
||||||
.map(|path| AstPtr::new(&path)),
|
file: source_ptr.file_id,
|
||||||
missed_fields,
|
field_list_parent: Either::Left(AstPtr::new(
|
||||||
|
record_expr,
|
||||||
|
)),
|
||||||
|
field_list_parent_path: record_expr
|
||||||
|
.path()
|
||||||
|
.map(|path| AstPtr::new(&path)),
|
||||||
|
missed_fields,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(SyntheticSyntax) => (),
|
|
||||||
},
|
|
||||||
BodyValidationDiagnostic::RecordPatMissingFields {
|
|
||||||
record_pat,
|
|
||||||
variant,
|
|
||||||
missed_fields,
|
|
||||||
} => match source_map.pat_syntax(record_pat) {
|
|
||||||
Ok(source_ptr) => {
|
|
||||||
if let Some(expr) = source_ptr.value.as_ref().left() {
|
|
||||||
let root = source_ptr.file_syntax(db.upcast());
|
|
||||||
if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
|
|
||||||
if let Some(_) = record_pat.record_pat_field_list() {
|
|
||||||
let variant_data = variant.variant_data(db.upcast());
|
|
||||||
let missed_fields = missed_fields
|
|
||||||
.into_iter()
|
|
||||||
.map(|idx| variant_data.fields()[idx].name.clone())
|
|
||||||
.collect();
|
|
||||||
sink.push(MissingPatFields {
|
|
||||||
file: source_ptr.file_id,
|
|
||||||
field_list_parent: AstPtr::new(&record_pat),
|
|
||||||
field_list_parent_path: record_pat
|
|
||||||
.path()
|
|
||||||
.map(|path| AstPtr::new(&path)),
|
|
||||||
missed_fields,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Err(SyntheticSyntax) => (),
|
||||||
|
},
|
||||||
|
Either::Right(record_pat) => match source_map.pat_syntax(record_pat) {
|
||||||
|
Ok(source_ptr) => {
|
||||||
|
if let Some(expr) = source_ptr.value.as_ref().left() {
|
||||||
|
let root = source_ptr.file_syntax(db.upcast());
|
||||||
|
if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
|
||||||
|
if let Some(_) = record_pat.record_pat_field_list() {
|
||||||
|
acc.push(
|
||||||
|
MissingFields {
|
||||||
|
file: source_ptr.file_id,
|
||||||
|
field_list_parent: Either::Right(AstPtr::new(
|
||||||
|
&record_pat,
|
||||||
|
)),
|
||||||
|
field_list_parent_path: record_pat
|
||||||
|
.path()
|
||||||
|
.map(|path| AstPtr::new(&path)),
|
||||||
|
missed_fields,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(SyntheticSyntax) => (),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
Err(SyntheticSyntax) => (),
|
}
|
||||||
},
|
|
||||||
|
|
||||||
BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => {
|
BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => {
|
||||||
if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) {
|
if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) {
|
||||||
sink.push(ReplaceFilterMapNextWithFindMap {
|
sink.push(ReplaceFilterMapNextWithFindMap {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule,
|
expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule,
|
||||||
};
|
};
|
||||||
use hir_expand::name;
|
use hir_expand::name;
|
||||||
|
use itertools::Either;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -26,13 +27,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub enum BodyValidationDiagnostic {
|
pub enum BodyValidationDiagnostic {
|
||||||
RecordLiteralMissingFields {
|
RecordMissingFields {
|
||||||
record_expr: ExprId,
|
record: Either<ExprId, PatId>,
|
||||||
variant: VariantId,
|
|
||||||
missed_fields: Vec<LocalFieldId>,
|
|
||||||
},
|
|
||||||
RecordPatMissingFields {
|
|
||||||
record_pat: PatId,
|
|
||||||
variant: VariantId,
|
variant: VariantId,
|
||||||
missed_fields: Vec<LocalFieldId>,
|
missed_fields: Vec<LocalFieldId>,
|
||||||
},
|
},
|
||||||
@ -95,8 +91,8 @@ fn validate_body(&mut self, db: &dyn HirDatabase) {
|
|||||||
if let Some((variant, missed_fields, true)) =
|
if let Some((variant, missed_fields, true)) =
|
||||||
record_literal_missing_fields(db, &self.infer, id, expr)
|
record_literal_missing_fields(db, &self.infer, id, expr)
|
||||||
{
|
{
|
||||||
self.diagnostics.push(BodyValidationDiagnostic::RecordLiteralMissingFields {
|
self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
|
||||||
record_expr: id,
|
record: Either::Left(id),
|
||||||
variant,
|
variant,
|
||||||
missed_fields,
|
missed_fields,
|
||||||
});
|
});
|
||||||
@ -116,8 +112,8 @@ fn validate_body(&mut self, db: &dyn HirDatabase) {
|
|||||||
if let Some((variant, missed_fields, true)) =
|
if let Some((variant, missed_fields, true)) =
|
||||||
record_pattern_missing_fields(db, &self.infer, id, pat)
|
record_pattern_missing_fields(db, &self.infer, id, pat)
|
||||||
{
|
{
|
||||||
self.diagnostics.push(BodyValidationDiagnostic::RecordPatMissingFields {
|
self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
|
||||||
record_pat: id,
|
record: Either::Right(id),
|
||||||
variant,
|
variant,
|
||||||
missed_fields,
|
missed_fields,
|
||||||
});
|
});
|
||||||
|
@ -1055,20 +1055,6 @@ fn foo() {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn missing_record_pat_field_diagnostic() {
|
|
||||||
check_diagnostics(
|
|
||||||
r#"
|
|
||||||
struct S { foo: i32, bar: () }
|
|
||||||
fn baz(s: S) {
|
|
||||||
let S { foo: _ } = s;
|
|
||||||
//^ Missing structure fields:
|
|
||||||
//| - bar
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
|
fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
|
||||||
check_diagnostics(
|
check_diagnostics(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use either::Either;
|
||||||
use hir::{db::AstDatabase, InFile};
|
use hir::{db::AstDatabase, InFile};
|
||||||
use ide_assists::Assist;
|
use ide_assists::Assist;
|
||||||
use ide_db::source_change::SourceChange;
|
use ide_db::source_change::SourceChange;
|
||||||
@ -7,7 +8,7 @@
|
|||||||
|
|
||||||
use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
|
use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
|
||||||
|
|
||||||
// Diagnostic: missing-structure-fields
|
// Diagnostic: missing-fields
|
||||||
//
|
//
|
||||||
// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
|
// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
|
||||||
//
|
//
|
||||||
@ -29,15 +30,11 @@ pub(super) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingField
|
|||||||
d.field_list_parent_path
|
d.field_list_parent_path
|
||||||
.clone()
|
.clone()
|
||||||
.map(SyntaxNodePtr::from)
|
.map(SyntaxNodePtr::from)
|
||||||
.unwrap_or_else(|| d.field_list_parent.clone().into()),
|
.unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
|
||||||
);
|
);
|
||||||
|
|
||||||
Diagnostic::new(
|
Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
|
||||||
"missing-structure-fields",
|
.with_fixes(fixes(ctx, d))
|
||||||
message,
|
|
||||||
ctx.sema.diagnostics_display_range(ptr).range,
|
|
||||||
)
|
|
||||||
.with_fixes(fixes(ctx, d))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
|
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
|
||||||
@ -51,7 +48,11 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
|
|||||||
}
|
}
|
||||||
|
|
||||||
let root = ctx.sema.db.parse_or_expand(d.file)?;
|
let root = ctx.sema.db.parse_or_expand(d.file)?;
|
||||||
let field_list_parent = d.field_list_parent.to_node(&root);
|
let field_list_parent = match &d.field_list_parent {
|
||||||
|
Either::Left(record_expr) => record_expr.to_node(&root),
|
||||||
|
// FIXE: patterns should be fixable as well.
|
||||||
|
Either::Right(_) => return None,
|
||||||
|
};
|
||||||
let old_field_list = field_list_parent.record_expr_field_list()?;
|
let old_field_list = field_list_parent.record_expr_field_list()?;
|
||||||
let new_field_list = old_field_list.clone_for_update();
|
let new_field_list = old_field_list.clone_for_update();
|
||||||
for f in d.missed_fields.iter() {
|
for f in d.missed_fields.iter() {
|
||||||
@ -76,7 +77,21 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
|
use crate::diagnostics::tests::{check_diagnostics, check_fix, check_no_diagnostics};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_record_pat_field_diagnostic() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
struct S { foo: i32, bar: () }
|
||||||
|
fn baz(s: S) {
|
||||||
|
let S { foo: _ } = s;
|
||||||
|
//^ Missing structure fields:
|
||||||
|
//| - bar
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fill_struct_fields_empty() {
|
fn test_fill_struct_fields_empty() {
|
||||||
|
Loading…
Reference in New Issue
Block a user