mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-03 10:33:34 +00:00
Merge #9230
9230: internal: move inference diagnostics to hir r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
f9e67d692d
@ -14,8 +14,7 @@ use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
|
||||
pub use hir_ty::{
|
||||
diagnostics::{
|
||||
IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms,
|
||||
MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon,
|
||||
ReplaceFilterMapNextWithFindMap,
|
||||
MissingOkOrSomeInTailExpr, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
|
||||
},
|
||||
diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder},
|
||||
};
|
||||
@ -251,3 +250,78 @@ impl Diagnostic for UnimplementedBuiltinMacro {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostic: missing-unsafe
|
||||
//
|
||||
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
|
||||
#[derive(Debug)]
|
||||
pub struct MissingUnsafe {
|
||||
pub file: HirFileId,
|
||||
pub expr: AstPtr<ast::Expr>,
|
||||
}
|
||||
|
||||
impl Diagnostic for MissingUnsafe {
|
||||
fn code(&self) -> DiagnosticCode {
|
||||
DiagnosticCode("missing-unsafe")
|
||||
}
|
||||
fn message(&self) -> String {
|
||||
format!("This operation is unsafe and requires an unsafe function or block")
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -36,13 +36,14 @@ use std::{iter, sync::Arc};
|
||||
use arrayvec::ArrayVec;
|
||||
use base_db::{CrateDisplayName, CrateId, Edition, FileId};
|
||||
use diagnostics::{
|
||||
InactiveCode, MacroError, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport,
|
||||
UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro,
|
||||
BreakOutsideOfLoop, InactiveCode, MacroError, MissingUnsafe, NoSuchField,
|
||||
UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
|
||||
UnresolvedModule, UnresolvedProcMacro,
|
||||
};
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
adt::{ReprKind, VariantData},
|
||||
body::BodyDiagnostic,
|
||||
body::{BodyDiagnostic, SyntheticSyntax},
|
||||
expr::{BindingAnnotation, LabelId, Pat, PatId},
|
||||
item_tree::ItemTreeNode,
|
||||
lang_item::LangItemTarget,
|
||||
@ -1042,6 +1043,35 @@ 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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for expr in hir_ty::diagnostics::missing_unsafe(db, self.id.into()) {
|
||||
match source_map.as_ref().expr_syntax(expr) {
|
||||
Ok(in_file) => {
|
||||
sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
|
||||
}
|
||||
Err(SyntheticSyntax) => {
|
||||
// FIXME: The `expr` was desugared, report or assert that
|
||||
// this dosen't happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
|
||||
hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
|
||||
}
|
||||
|
@ -17,7 +17,10 @@ use crate::{
|
||||
diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
|
||||
};
|
||||
|
||||
pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
|
||||
pub use crate::diagnostics::{
|
||||
expr::{record_literal_missing_fields, record_pattern_missing_fields},
|
||||
unsafe_check::missing_unsafe,
|
||||
};
|
||||
|
||||
pub fn validate_module_item(
|
||||
db: &dyn HirDatabase,
|
||||
@ -33,38 +36,8 @@ pub fn validate_module_item(
|
||||
pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
|
||||
let _p = profile::span("validate_body");
|
||||
let infer = db.infer(owner);
|
||||
infer.add_diagnostics(db, owner, sink);
|
||||
let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink);
|
||||
validator.validate_body(db);
|
||||
let mut validator = unsafe_check::UnsafeValidator::new(owner, infer, sink);
|
||||
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
|
||||
@ -247,54 +220,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
|
||||
//
|
||||
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
|
||||
#[derive(Debug)]
|
||||
pub struct MissingUnsafe {
|
||||
pub file: HirFileId,
|
||||
pub expr: AstPtr<ast::Expr>,
|
||||
}
|
||||
|
||||
impl Diagnostic for MissingUnsafe {
|
||||
fn code(&self) -> DiagnosticCode {
|
||||
DiagnosticCode("missing-unsafe")
|
||||
}
|
||||
fn message(&self) -> String {
|
||||
format!("This operation is unsafe and requires an unsafe function or block")
|
||||
}
|
||||
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: mismatched-arg-count
|
||||
//
|
||||
// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
|
||||
@ -530,129 +455,6 @@ mod tests {
|
||||
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]
|
||||
fn missing_record_pat_field_diagnostic() {
|
||||
check_diagnostics(
|
||||
@ -734,16 +536,6 @@ pub struct Claims {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_outside_of_loop() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn foo() { break; }
|
||||
//^^^^^ break outside of loop
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_semicolon() {
|
||||
check_diagnostics(
|
||||
|
@ -1,8 +1,6 @@
|
||||
//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
|
||||
//! unsafe blocks.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hir_def::{
|
||||
body::Body,
|
||||
expr::{Expr, ExprId, UnaryOp},
|
||||
@ -10,52 +8,25 @@ use hir_def::{
|
||||
DefWithBodyId,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult,
|
||||
Interner, TyExt, TyKind,
|
||||
};
|
||||
use crate::{db::HirDatabase, InferenceResult, Interner, TyExt, TyKind};
|
||||
|
||||
pub(super) struct UnsafeValidator<'a, 'b: 'a> {
|
||||
owner: DefWithBodyId,
|
||||
infer: Arc<InferenceResult>,
|
||||
sink: &'a mut DiagnosticSink<'b>,
|
||||
}
|
||||
pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
|
||||
let infer = db.infer(def);
|
||||
|
||||
impl<'a, 'b> UnsafeValidator<'a, 'b> {
|
||||
pub(super) fn new(
|
||||
owner: DefWithBodyId,
|
||||
infer: Arc<InferenceResult>,
|
||||
sink: &'a mut DiagnosticSink<'b>,
|
||||
) -> UnsafeValidator<'a, 'b> {
|
||||
UnsafeValidator { owner, infer, sink }
|
||||
// let unsafe_expressions = ;
|
||||
let is_unsafe = match def {
|
||||
DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
|
||||
DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
|
||||
};
|
||||
if is_unsafe {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) {
|
||||
let def = self.owner;
|
||||
let unsafe_expressions = unsafe_expressions(db, self.infer.as_ref(), def);
|
||||
let is_unsafe = match self.owner {
|
||||
DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
|
||||
DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
|
||||
};
|
||||
if is_unsafe
|
||||
|| unsafe_expressions
|
||||
.iter()
|
||||
.filter(|unsafe_expr| !unsafe_expr.inside_unsafe_block)
|
||||
.count()
|
||||
== 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let (_, body_source) = db.body_with_source_map(def);
|
||||
for unsafe_expr in unsafe_expressions {
|
||||
if !unsafe_expr.inside_unsafe_block {
|
||||
if let Ok(in_file) = body_source.as_ref().expr_syntax(unsafe_expr.expr) {
|
||||
self.sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe_expressions(db, &infer, def)
|
||||
.into_iter()
|
||||
.filter(|it| !it.inside_unsafe_block)
|
||||
.map(|it| it.expr)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) struct UnsafeExpr {
|
||||
@ -126,92 +97,3 @@ fn walk_unsafe(
|
||||
walk_unsafe(unsafe_exprs, db, infer, def, body, child, inside_unsafe_block);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostics::tests::check_diagnostics;
|
||||
|
||||
#[test]
|
||||
fn missing_unsafe_diagnostic_with_raw_ptr() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn main() {
|
||||
let x = &5 as *const usize;
|
||||
unsafe { let y = *x; }
|
||||
let z = *x;
|
||||
} //^^ This operation is unsafe and requires an unsafe function or block
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_unsafe_diagnostic_with_unsafe_call() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
struct HasUnsafe;
|
||||
|
||||
impl HasUnsafe {
|
||||
unsafe fn unsafe_fn(&self) {
|
||||
let x = &5 as *const usize;
|
||||
let y = *x;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn unsafe_fn() {
|
||||
let x = &5 as *const usize;
|
||||
let y = *x;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe_fn();
|
||||
//^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
||||
HasUnsafe.unsafe_fn();
|
||||
//^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
||||
unsafe {
|
||||
unsafe_fn();
|
||||
HasUnsafe.unsafe_fn();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_unsafe_diagnostic_with_static_mut() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
struct Ty {
|
||||
a: u8,
|
||||
}
|
||||
|
||||
static mut STATIC_MUT: Ty = Ty { a: 0 };
|
||||
|
||||
fn main() {
|
||||
let x = STATIC_MUT.a;
|
||||
//^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
||||
unsafe {
|
||||
let x = STATIC_MUT.a;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
extern "rust-intrinsic" {
|
||||
pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
|
||||
pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = bitreverse(12);
|
||||
let _ = floorf32(12.0);
|
||||
//^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -35,11 +35,9 @@ use stdx::impl_from;
|
||||
use syntax::SmolStr;
|
||||
|
||||
use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty};
|
||||
use crate::diagnostics_sink::DiagnosticSink;
|
||||
use crate::{
|
||||
db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic,
|
||||
lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution,
|
||||
TyBuilder, TyExt, TyKind,
|
||||
db::HirDatabase, fold_tys, lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy,
|
||||
Goal, Interner, Substitution, TyBuilder, TyExt, TyKind,
|
||||
};
|
||||
|
||||
// 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) 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.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct TypeMismatch {
|
||||
@ -140,7 +144,7 @@ pub struct InferenceResult {
|
||||
variant_resolutions: FxHashMap<ExprOrPatId, VariantId>,
|
||||
/// For each associated item record what it resolves to
|
||||
assoc_resolutions: FxHashMap<ExprOrPatId, AssocItemId>,
|
||||
diagnostics: Vec<InferenceDiagnostic>,
|
||||
pub diagnostics: Vec<InferenceDiagnostic>,
|
||||
pub type_of_expr: ArenaMap<ExprId, Ty>,
|
||||
/// For each pattern record the type it resolves to.
|
||||
///
|
||||
@ -191,14 +195,6 @@ impl InferenceResult {
|
||||
_ => 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 {
|
||||
@ -804,43 +800,3 @@ impl std::ops::BitOrAssign for Diverges {
|
||||
*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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ use crate::{db::HirDatabase, utils::generics};
|
||||
pub use autoderef::autoderef;
|
||||
pub use builder::TyBuilder;
|
||||
pub use chalk_ext::*;
|
||||
pub use infer::{could_unify, InferenceResult};
|
||||
pub use infer::{could_unify, InferenceDiagnostic, InferenceResult};
|
||||
pub use interner::Interner;
|
||||
pub use lower::{
|
||||
associated_type_shorthand_candidates, callable_item_sig, CallableDefId, ImplTraitLoweringMode,
|
||||
|
@ -305,6 +305,7 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::Expect;
|
||||
use hir::diagnostics::DiagnosticCode;
|
||||
use ide_assists::AssistResolveStrategy;
|
||||
use stdx::trim_indent;
|
||||
use test_utils::{assert_eq_text, extract_annotations};
|
||||
@ -410,7 +411,12 @@ mod tests {
|
||||
.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);
|
||||
}
|
||||
|
||||
@ -716,6 +722,223 @@ $0
|
||||
mod foo;
|
||||
|
||||
//- /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 }
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_unsafe_diagnostic_with_raw_ptr() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn main() {
|
||||
let x = &5 as *const usize;
|
||||
unsafe { let y = *x; }
|
||||
let z = *x;
|
||||
} //^^ This operation is unsafe and requires an unsafe function or block
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_unsafe_diagnostic_with_unsafe_call() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
struct HasUnsafe;
|
||||
|
||||
impl HasUnsafe {
|
||||
unsafe fn unsafe_fn(&self) {
|
||||
let x = &5 as *const usize;
|
||||
let y = *x;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn unsafe_fn() {
|
||||
let x = &5 as *const usize;
|
||||
let y = *x;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe_fn();
|
||||
//^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
||||
HasUnsafe.unsafe_fn();
|
||||
//^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
||||
unsafe {
|
||||
unsafe_fn();
|
||||
HasUnsafe.unsafe_fn();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_unsafe_diagnostic_with_static_mut() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
struct Ty {
|
||||
a: u8,
|
||||
}
|
||||
|
||||
static mut STATIC_MUT: Ty = Ty { a: 0 };
|
||||
|
||||
fn main() {
|
||||
let x = STATIC_MUT.a;
|
||||
//^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
||||
unsafe {
|
||||
let x = STATIC_MUT.a;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
extern "rust-intrinsic" {
|
||||
pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
|
||||
pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = bitreverse(12);
|
||||
let _ = floorf32(12.0);
|
||||
//^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user