internal: move inference diagnostics to hir

This commit is contained in:
Aleksey Kladov 2021-06-12 17:17:23 +03:00
parent 409f5fb563
commit f8009666be
6 changed files with 222 additions and 244 deletions

View File

@ -14,8 +14,7 @@ use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
pub use hir_ty::{ pub use hir_ty::{
diagnostics::{ diagnostics::{
IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms,
MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon, MissingOkOrSomeInTailExpr, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
ReplaceFilterMapNextWithFindMap,
}, },
diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder}, diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder},
}; };
@ -251,3 +250,54 @@ impl Diagnostic for UnimplementedBuiltinMacro {
self self
} }
} }
// Diagnostic: no-such-field
//
// This diagnostic is triggered if created structure does not have field provided in record.
#[derive(Debug)]
pub struct NoSuchField {
pub file: HirFileId,
pub field: AstPtr<ast::RecordExprField>,
}
impl Diagnostic for NoSuchField {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("no-such-field")
}
fn message(&self) -> String {
"no such field".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.field.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: break-outside-of-loop
//
// This diagnostic is triggered if the `break` keyword is used outside of a loop.
#[derive(Debug)]
pub struct BreakOutsideOfLoop {
pub file: HirFileId,
pub expr: AstPtr<ast::Expr>,
}
impl Diagnostic for BreakOutsideOfLoop {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("break-outside-of-loop")
}
fn message(&self) -> String {
"break outside of loop".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile { file_id: self.file, value: self.expr.clone().into() }
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}

View File

@ -36,8 +36,9 @@ use std::{iter, sync::Arc};
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, Edition, FileId}; use base_db::{CrateDisplayName, CrateId, Edition, FileId};
use diagnostics::{ use diagnostics::{
InactiveCode, MacroError, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, BreakOutsideOfLoop, InactiveCode, MacroError, NoSuchField, UnimplementedBuiltinMacro,
UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
UnresolvedProcMacro,
}; };
use either::Either; use either::Either;
use hir_def::{ use hir_def::{
@ -1042,6 +1043,23 @@ impl Function {
} }
} }
let infer = db.infer(self.id.into());
let (_, source_map) = db.body_with_source_map(self.id.into());
for d in &infer.diagnostics {
match d {
hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
let field = source_map.field_syntax(*expr);
sink.push(NoSuchField { file: field.file_id, field: field.value })
}
hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
let ptr = source_map
.expr_syntax(*expr)
.expect("break outside of loop in synthetic syntax");
sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value })
}
}
}
hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink); hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
hir_ty::diagnostics::validate_body(db, self.id.into(), sink); hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
} }

View File

@ -33,40 +33,12 @@ pub fn validate_module_item(
pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
let _p = profile::span("validate_body"); let _p = profile::span("validate_body");
let infer = db.infer(owner); let infer = db.infer(owner);
infer.add_diagnostics(db, owner, sink);
let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink); let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink);
validator.validate_body(db); validator.validate_body(db);
let mut validator = unsafe_check::UnsafeValidator::new(owner, infer, sink); let mut validator = unsafe_check::UnsafeValidator::new(owner, infer, sink);
validator.validate_body(db); validator.validate_body(db);
} }
// Diagnostic: no-such-field
//
// This diagnostic is triggered if created structure does not have field provided in record.
#[derive(Debug)]
pub struct NoSuchField {
pub file: HirFileId,
pub field: AstPtr<ast::RecordExprField>,
}
impl Diagnostic for NoSuchField {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("no-such-field")
}
fn message(&self) -> String {
"no such field".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.field.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: missing-structure-fields // Diagnostic: missing-structure-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.
@ -247,30 +219,6 @@ impl Diagnostic for RemoveThisSemicolon {
} }
} }
// Diagnostic: break-outside-of-loop
//
// This diagnostic is triggered if the `break` keyword is used outside of a loop.
#[derive(Debug)]
pub struct BreakOutsideOfLoop {
pub file: HirFileId,
pub expr: AstPtr<ast::Expr>,
}
impl Diagnostic for BreakOutsideOfLoop {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("break-outside-of-loop")
}
fn message(&self) -> String {
"break outside of loop".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile { file_id: self.file, value: self.expr.clone().into() }
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: missing-unsafe // Diagnostic: missing-unsafe
// //
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block. // This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
@ -530,129 +478,6 @@ mod tests {
assert_eq!(annotations, actual); assert_eq!(annotations, actual);
} }
#[test]
fn no_such_field_diagnostics() {
check_diagnostics(
r#"
struct S { foo: i32, bar: () }
impl S {
fn new() -> S {
S {
//^ Missing structure fields:
//| - bar
foo: 92,
baz: 62,
//^^^^^^^ no such field
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct MyStruct {
my_val: usize,
#[cfg(feature = "foo")]
bar: bool,
}
impl MyStruct {
#[cfg(feature = "foo")]
pub(crate) fn new(my_val: usize, bar: bool) -> Self {
Self { my_val, bar }
}
#[cfg(not(feature = "foo"))]
pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
Self { my_val }
}
}
"#,
);
}
#[test]
fn no_such_field_enum_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
enum Foo {
#[cfg(not(feature = "foo"))]
Buz,
#[cfg(feature = "foo")]
Bar,
Baz
}
fn test_fn(f: Foo) {
match f {
Foo::Bar => {},
Foo::Baz => {},
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct S {
#[cfg(feature = "foo")]
foo: u32,
#[cfg(not(feature = "foo"))]
bar: u32,
}
impl S {
#[cfg(feature = "foo")]
fn new(foo: u32) -> Self {
Self { foo }
}
#[cfg(not(feature = "foo"))]
fn new(bar: u32) -> Self {
Self { bar }
}
fn new2(bar: u32) -> Self {
#[cfg(feature = "foo")]
{ Self { foo: bar } }
#[cfg(not(feature = "foo"))]
{ Self { bar } }
}
fn new2(val: u32) -> Self {
Self {
#[cfg(feature = "foo")]
foo: val,
#[cfg(not(feature = "foo"))]
bar: val,
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_type_macro() {
check_diagnostics(
r#"
macro_rules! Type { () => { u32 }; }
struct Foo { bar: Type![] }
impl Foo {
fn new() -> Self {
Foo { bar: 0 }
}
}
"#,
);
}
#[test] #[test]
fn missing_record_pat_field_diagnostic() { fn missing_record_pat_field_diagnostic() {
check_diagnostics( check_diagnostics(
@ -734,16 +559,6 @@ pub struct Claims {
); );
} }
#[test]
fn break_outside_of_loop() {
check_diagnostics(
r#"
fn foo() { break; }
//^^^^^ break outside of loop
"#,
);
}
#[test] #[test]
fn missing_semicolon() { fn missing_semicolon() {
check_diagnostics( check_diagnostics(

View File

@ -35,11 +35,9 @@ use stdx::impl_from;
use syntax::SmolStr; use syntax::SmolStr;
use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty};
use crate::diagnostics_sink::DiagnosticSink;
use crate::{ use crate::{
db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic, db::HirDatabase, fold_tys, lower::ImplTraitLoweringMode,
lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution, TyBuilder, TyExt, TyKind,
TyBuilder, TyExt, TyKind,
}; };
// This lint has a false positive here. See the link below for details. // This lint has a false positive here. See the link below for details.
@ -111,6 +109,12 @@ pub(crate) struct InferOk {
pub(crate) struct TypeError; pub(crate) struct TypeError;
pub(crate) type InferResult = Result<InferOk, TypeError>; pub(crate) type InferResult = Result<InferOk, TypeError>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum InferenceDiagnostic {
NoSuchField { expr: ExprId },
BreakOutsideOfLoop { expr: ExprId },
}
/// A mismatch between an expected and an inferred type. /// A mismatch between an expected and an inferred type.
#[derive(Clone, PartialEq, Eq, Debug, Hash)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct TypeMismatch { pub struct TypeMismatch {
@ -140,7 +144,7 @@ pub struct InferenceResult {
variant_resolutions: FxHashMap<ExprOrPatId, VariantId>, variant_resolutions: FxHashMap<ExprOrPatId, VariantId>,
/// For each associated item record what it resolves to /// For each associated item record what it resolves to
assoc_resolutions: FxHashMap<ExprOrPatId, AssocItemId>, assoc_resolutions: FxHashMap<ExprOrPatId, AssocItemId>,
diagnostics: Vec<InferenceDiagnostic>, pub diagnostics: Vec<InferenceDiagnostic>,
pub type_of_expr: ArenaMap<ExprId, Ty>, pub type_of_expr: ArenaMap<ExprId, Ty>,
/// For each pattern record the type it resolves to. /// For each pattern record the type it resolves to.
/// ///
@ -191,14 +195,6 @@ impl InferenceResult {
_ => None, _ => None,
}) })
} }
pub fn add_diagnostics(
&self,
db: &dyn HirDatabase,
owner: DefWithBodyId,
sink: &mut DiagnosticSink,
) {
self.diagnostics.iter().for_each(|it| it.add_to(db, owner, sink))
}
} }
impl Index<ExprId> for InferenceResult { impl Index<ExprId> for InferenceResult {
@ -804,43 +800,3 @@ impl std::ops::BitOrAssign for Diverges {
*self = *self | other; *self = *self | other;
} }
} }
mod diagnostics {
use hir_def::{expr::ExprId, DefWithBodyId};
use crate::{
db::HirDatabase,
diagnostics::{BreakOutsideOfLoop, NoSuchField},
diagnostics_sink::DiagnosticSink,
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub(super) enum InferenceDiagnostic {
NoSuchField { expr: ExprId },
BreakOutsideOfLoop { expr: ExprId },
}
impl InferenceDiagnostic {
pub(super) fn add_to(
&self,
db: &dyn HirDatabase,
owner: DefWithBodyId,
sink: &mut DiagnosticSink,
) {
match self {
InferenceDiagnostic::NoSuchField { expr } => {
let (_, source_map) = db.body_with_source_map(owner);
let field = source_map.field_syntax(*expr);
sink.push(NoSuchField { file: field.file_id, field: field.value })
}
InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
let (_, source_map) = db.body_with_source_map(owner);
let ptr = source_map
.expr_syntax(*expr)
.expect("break outside of loop in synthetic syntax");
sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value })
}
}
}
}
}

View File

@ -50,7 +50,7 @@ use crate::{db::HirDatabase, utils::generics};
pub use autoderef::autoderef; pub use autoderef::autoderef;
pub use builder::TyBuilder; pub use builder::TyBuilder;
pub use chalk_ext::*; pub use chalk_ext::*;
pub use infer::{could_unify, InferenceResult}; pub use infer::{could_unify, InferenceDiagnostic, InferenceResult};
pub use interner::Interner; pub use interner::Interner;
pub use lower::{ pub use lower::{
associated_type_shorthand_candidates, callable_item_sig, CallableDefId, ImplTraitLoweringMode, associated_type_shorthand_candidates, callable_item_sig, CallableDefId, ImplTraitLoweringMode,

View File

@ -305,6 +305,7 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use expect_test::Expect; use expect_test::Expect;
use hir::diagnostics::DiagnosticCode;
use ide_assists::AssistResolveStrategy; use ide_assists::AssistResolveStrategy;
use stdx::trim_indent; use stdx::trim_indent;
use test_utils::{assert_eq_text, extract_annotations}; use test_utils::{assert_eq_text, extract_annotations};
@ -410,7 +411,12 @@ mod tests {
.unwrap(); .unwrap();
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); let mut actual = diagnostics
.into_iter()
.filter(|d| d.code != Some(DiagnosticCode("inactive-code")))
.map(|d| (d.range, d.message))
.collect::<Vec<_>>();
actual.sort_by_key(|(range, _)| range.start());
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
@ -716,6 +722,139 @@ $0
mod foo; mod foo;
//- /foo.rs //- /foo.rs
"#,
);
}
#[test]
fn break_outside_of_loop() {
check_diagnostics(
r#"
fn foo() { break; }
//^^^^^ break outside of loop
"#,
);
}
#[test]
fn no_such_field_diagnostics() {
check_diagnostics(
r#"
struct S { foo: i32, bar: () }
impl S {
fn new() -> S {
S {
//^ Missing structure fields:
//| - bar
foo: 92,
baz: 62,
//^^^^^^^ no such field
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct MyStruct {
my_val: usize,
#[cfg(feature = "foo")]
bar: bool,
}
impl MyStruct {
#[cfg(feature = "foo")]
pub(crate) fn new(my_val: usize, bar: bool) -> Self {
Self { my_val, bar }
}
#[cfg(not(feature = "foo"))]
pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
Self { my_val }
}
}
"#,
);
}
#[test]
fn no_such_field_enum_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
enum Foo {
#[cfg(not(feature = "foo"))]
Buz,
#[cfg(feature = "foo")]
Bar,
Baz
}
fn test_fn(f: Foo) {
match f {
Foo::Bar => {},
Foo::Baz => {},
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct S {
#[cfg(feature = "foo")]
foo: u32,
#[cfg(not(feature = "foo"))]
bar: u32,
}
impl S {
#[cfg(feature = "foo")]
fn new(foo: u32) -> Self {
Self { foo }
}
#[cfg(not(feature = "foo"))]
fn new(bar: u32) -> Self {
Self { bar }
}
fn new2(bar: u32) -> Self {
#[cfg(feature = "foo")]
{ Self { foo: bar } }
#[cfg(not(feature = "foo"))]
{ Self { bar } }
}
fn new2(val: u32) -> Self {
Self {
#[cfg(feature = "foo")]
foo: val,
#[cfg(not(feature = "foo"))]
bar: val,
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_type_macro() {
check_diagnostics(
r#"
macro_rules! Type { () => { u32 }; }
struct Foo { bar: Type![] }
impl Foo {
fn new() -> Self {
Foo { bar: 0 }
}
}
"#, "#,
); );
} }