Add a builder for DiagnosticSink

This commit is contained in:
Jonas Schievink 2020-07-24 16:30:12 +02:00
parent c3defe2532
commit 6f02befee4
4 changed files with 101 additions and 87 deletions

View file

@ -1,6 +1,8 @@
//! FIXME: write short doc here //! FIXME: write short doc here
pub use hir_def::diagnostics::UnresolvedModule; pub use hir_def::diagnostics::UnresolvedModule;
pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; pub use hir_expand::diagnostics::{
AstDiagnostic, Diagnostic, DiagnosticSink, DiagnosticSinkBuilder,
};
pub use hir_ty::diagnostics::{ pub use hir_ty::diagnostics::{
MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField,
}; };

View file

@ -48,23 +48,6 @@ pub struct DiagnosticSink<'a> {
} }
impl<'a> DiagnosticSink<'a> { impl<'a> DiagnosticSink<'a> {
/// FIXME: split `new` and `on` into a separate builder type
pub fn new(cb: impl FnMut(&dyn Diagnostic) + 'a) -> DiagnosticSink<'a> {
DiagnosticSink { callbacks: Vec::new(), default_callback: Box::new(cb) }
}
pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> DiagnosticSink<'a> {
let cb = move |diag: &dyn Diagnostic| match diag.downcast_ref::<D>() {
Some(d) => {
cb(d);
Ok(())
}
None => Err(()),
};
self.callbacks.push(Box::new(cb));
self
}
pub fn push(&mut self, d: impl Diagnostic) { pub fn push(&mut self, d: impl Diagnostic) {
let d: &dyn Diagnostic = &d; let d: &dyn Diagnostic = &d;
self._push(d); self._push(d);
@ -80,3 +63,29 @@ impl<'a> DiagnosticSink<'a> {
(self.default_callback)(d) (self.default_callback)(d)
} }
} }
pub struct DiagnosticSinkBuilder<'a> {
callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>,
}
impl<'a> DiagnosticSinkBuilder<'a> {
pub fn new() -> Self {
Self { callbacks: Vec::new() }
}
pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> Self {
let cb = move |diag: &dyn Diagnostic| match diag.downcast_ref::<D>() {
Some(d) => {
cb(d);
Ok(())
}
None => Err(()),
};
self.callbacks.push(Box::new(cb));
self
}
pub fn build<F: FnMut(&dyn Diagnostic) + 'a>(self, default_callback: F) -> DiagnosticSink<'a> {
DiagnosticSink { callbacks: self.callbacks, default_callback: Box::new(default_callback) }
}
}

View file

@ -248,7 +248,7 @@ impl AstDiagnostic for MismatchedArgCount {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId}; use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
use hir_expand::diagnostics::{Diagnostic, DiagnosticSink}; use hir_expand::diagnostics::{Diagnostic, DiagnosticSinkBuilder};
use ra_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; use ra_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
use ra_syntax::{TextRange, TextSize}; use ra_syntax::{TextRange, TextSize};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -280,7 +280,7 @@ mod tests {
} }
for f in fns { for f in fns {
let mut sink = DiagnosticSink::new(&mut cb); let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
validate_body(self, f.into(), &mut sink); validate_body(self, f.into(), &mut sink);
} }
} }

View file

@ -7,7 +7,7 @@
use std::cell::RefCell; use std::cell::RefCell;
use hir::{ use hir::{
diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder},
HasSource, HirDisplay, Semantics, VariantDef, HasSource, HirDisplay, Semantics, VariantDef,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -48,79 +48,82 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
check_struct_shorthand_initialization(&mut res, file_id, &node); check_struct_shorthand_initialization(&mut res, file_id, &node);
} }
let res = RefCell::new(res); let res = RefCell::new(res);
let mut sink = DiagnosticSink::new(|d| { let mut sink = DiagnosticSinkBuilder::new()
res.borrow_mut().push(Diagnostic { .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
message: d.message(), let original_file = d.source().file_id.original_file(db);
range: sema.diagnostics_range(d).range, let fix = Fix::new(
severity: Severity::Error, "Create module",
fix: None, FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }
.into(),
);
res.borrow_mut().push(Diagnostic {
range: sema.diagnostics_range(d).range,
message: d.message(),
severity: Severity::Error,
fix: Some(fix),
})
}) })
}) .on::<hir::diagnostics::MissingFields, _>(|d| {
.on::<hir::diagnostics::UnresolvedModule, _>(|d| { // Note that although we could add a diagnostics to
let original_file = d.source().file_id.original_file(db); // fill the missing tuple field, e.g :
let fix = Fix::new( // `struct A(usize);`
"Create module", // `let a = A { 0: () }`
FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }.into(), // but it is uncommon usage and it should not be encouraged.
); let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
res.borrow_mut().push(Diagnostic { None
range: sema.diagnostics_range(d).range, } else {
message: d.message(), let mut field_list = d.ast(db);
severity: Severity::Error, for f in d.missed_fields.iter() {
fix: Some(fix), let field =
}) make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
}) field_list = field_list.append_field(&field);
.on::<hir::diagnostics::MissingFields, _>(|d| { }
// Note that although we could add a diagnostics to
// fill the missing tuple field, e.g :
// `struct A(usize);`
// `let a = A { 0: () }`
// but it is uncommon usage and it should not be encouraged.
let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
None
} else {
let mut field_list = d.ast(db);
for f in d.missed_fields.iter() {
let field =
make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
field_list = field_list.append_field(&field);
}
let edit = { let edit = {
let mut builder = TextEditBuilder::default(); let mut builder = TextEditBuilder::default();
algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); algo::diff(&d.ast(db).syntax(), &field_list.syntax())
builder.finish() .into_text_edit(&mut builder);
builder.finish()
};
Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
}; };
Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
};
res.borrow_mut().push(Diagnostic { res.borrow_mut().push(Diagnostic {
range: sema.diagnostics_range(d).range, range: sema.diagnostics_range(d).range,
message: d.message(), message: d.message(),
severity: Severity::Error, severity: Severity::Error,
fix, fix,
})
}) })
}) .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
.on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { let node = d.ast(db);
let node = d.ast(db); let replacement = format!("Ok({})", node.syntax());
let replacement = format!("Ok({})", node.syntax()); let edit = TextEdit::replace(node.syntax().text_range(), replacement);
let edit = TextEdit::replace(node.syntax().text_range(), replacement); let source_change = SourceFileEdit { file_id, edit }.into();
let source_change = SourceFileEdit { file_id, edit }.into(); let fix = Fix::new("Wrap with ok", source_change);
let fix = Fix::new("Wrap with ok", source_change); res.borrow_mut().push(Diagnostic {
res.borrow_mut().push(Diagnostic { range: sema.diagnostics_range(d).range,
range: sema.diagnostics_range(d).range, message: d.message(),
message: d.message(), severity: Severity::Error,
severity: Severity::Error, fix: Some(fix),
fix: Some(fix), })
}) })
}) .on::<hir::diagnostics::NoSuchField, _>(|d| {
.on::<hir::diagnostics::NoSuchField, _>(|d| { res.borrow_mut().push(Diagnostic {
res.borrow_mut().push(Diagnostic { range: sema.diagnostics_range(d).range,
range: sema.diagnostics_range(d).range, message: d.message(),
message: d.message(), severity: Severity::Error,
severity: Severity::Error, fix: missing_struct_field_fix(&sema, file_id, d),
fix: missing_struct_field_fix(&sema, file_id, d), })
}) })
}); .build(|d| {
res.borrow_mut().push(Diagnostic {
message: d.message(),
range: sema.diagnostics_range(d).range,
severity: Severity::Error,
fix: None,
})
});
if let Some(m) = sema.to_module_def(file_id) { if let Some(m) = sema.to_module_def(file_id) {
m.diagnostics(db, &mut sink); m.diagnostics(db, &mut sink);