aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir/src/diagnostics.rs78
-rw-r--r--crates/hir/src/lib.rs36
-rw-r--r--crates/hir_ty/src/diagnostics.rs216
-rw-r--r--crates/hir_ty/src/diagnostics/unsafe_check.rs148
-rw-r--r--crates/hir_ty/src/infer.rs62
-rw-r--r--crates/hir_ty/src/lib.rs2
-rw-r--r--crates/ide/src/diagnostics.rs225
7 files changed, 362 insertions, 405 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 2cdbd172a..e888fc23b 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -14,8 +14,7 @@ use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
14pub use hir_ty::{ 14pub use hir_ty::{
15 diagnostics::{ 15 diagnostics::{
16 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, 16 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms,
17 MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon, 17 MissingOkOrSomeInTailExpr, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
18 ReplaceFilterMapNextWithFindMap,
19 }, 18 },
20 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder}, 19 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder},
21}; 20};
@@ -251,3 +250,78 @@ impl Diagnostic for UnimplementedBuiltinMacro {
251 self 250 self
252 } 251 }
253} 252}
253
254// Diagnostic: no-such-field
255//
256// This diagnostic is triggered if created structure does not have field provided in record.
257#[derive(Debug)]
258pub struct NoSuchField {
259 pub file: HirFileId,
260 pub field: AstPtr<ast::RecordExprField>,
261}
262
263impl Diagnostic for NoSuchField {
264 fn code(&self) -> DiagnosticCode {
265 DiagnosticCode("no-such-field")
266 }
267
268 fn message(&self) -> String {
269 "no such field".to_string()
270 }
271
272 fn display_source(&self) -> InFile<SyntaxNodePtr> {
273 InFile::new(self.file, self.field.clone().into())
274 }
275
276 fn as_any(&self) -> &(dyn Any + Send + 'static) {
277 self
278 }
279}
280
281// Diagnostic: break-outside-of-loop
282//
283// This diagnostic is triggered if the `break` keyword is used outside of a loop.
284#[derive(Debug)]
285pub struct BreakOutsideOfLoop {
286 pub file: HirFileId,
287 pub expr: AstPtr<ast::Expr>,
288}
289
290impl Diagnostic for BreakOutsideOfLoop {
291 fn code(&self) -> DiagnosticCode {
292 DiagnosticCode("break-outside-of-loop")
293 }
294 fn message(&self) -> String {
295 "break outside of loop".to_string()
296 }
297 fn display_source(&self) -> InFile<SyntaxNodePtr> {
298 InFile { file_id: self.file, value: self.expr.clone().into() }
299 }
300 fn as_any(&self) -> &(dyn Any + Send + 'static) {
301 self
302 }
303}
304
305// Diagnostic: missing-unsafe
306//
307// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
308#[derive(Debug)]
309pub struct MissingUnsafe {
310 pub file: HirFileId,
311 pub expr: AstPtr<ast::Expr>,
312}
313
314impl Diagnostic for MissingUnsafe {
315 fn code(&self) -> DiagnosticCode {
316 DiagnosticCode("missing-unsafe")
317 }
318 fn message(&self) -> String {
319 format!("This operation is unsafe and requires an unsafe function or block")
320 }
321 fn display_source(&self) -> InFile<SyntaxNodePtr> {
322 InFile { file_id: self.file, value: self.expr.clone().into() }
323 }
324 fn as_any(&self) -> &(dyn Any + Send + 'static) {
325 self
326 }
327}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 0bb3767c1..a21a9da21 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -36,13 +36,14 @@ use std::{iter, sync::Arc};
36use arrayvec::ArrayVec; 36use arrayvec::ArrayVec;
37use base_db::{CrateDisplayName, CrateId, Edition, FileId}; 37use base_db::{CrateDisplayName, CrateId, Edition, FileId};
38use diagnostics::{ 38use diagnostics::{
39 InactiveCode, MacroError, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, 39 BreakOutsideOfLoop, InactiveCode, MacroError, MissingUnsafe, NoSuchField,
40 UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, 40 UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
41 UnresolvedModule, UnresolvedProcMacro,
41}; 42};
42use either::Either; 43use either::Either;
43use hir_def::{ 44use hir_def::{
44 adt::{ReprKind, VariantData}, 45 adt::{ReprKind, VariantData},
45 body::BodyDiagnostic, 46 body::{BodyDiagnostic, SyntheticSyntax},
46 expr::{BindingAnnotation, LabelId, Pat, PatId}, 47 expr::{BindingAnnotation, LabelId, Pat, PatId},
47 item_tree::ItemTreeNode, 48 item_tree::ItemTreeNode,
48 lang_item::LangItemTarget, 49 lang_item::LangItemTarget,
@@ -1042,6 +1043,35 @@ impl Function {
1042 } 1043 }
1043 } 1044 }
1044 1045
1046 let infer = db.infer(self.id.into());
1047 let (_, source_map) = db.body_with_source_map(self.id.into());
1048 for d in &infer.diagnostics {
1049 match d {
1050 hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
1051 let field = source_map.field_syntax(*expr);
1052 sink.push(NoSuchField { file: field.file_id, field: field.value })
1053 }
1054 hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
1055 let ptr = source_map
1056 .expr_syntax(*expr)
1057 .expect("break outside of loop in synthetic syntax");
1058 sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value })
1059 }
1060 }
1061 }
1062
1063 for expr in hir_ty::diagnostics::missing_unsafe(db, self.id.into()) {
1064 match source_map.as_ref().expr_syntax(expr) {
1065 Ok(in_file) => {
1066 sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
1067 }
1068 Err(SyntheticSyntax) => {
1069 // FIXME: The `expr` was desugared, report or assert that
1070 // this dosen't happen.
1071 }
1072 }
1073 }
1074
1045 hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink); 1075 hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
1046 hir_ty::diagnostics::validate_body(db, self.id.into(), sink); 1076 hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
1047 } 1077 }
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 283894704..12131d9bc 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -17,7 +17,10 @@ use crate::{
17 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink}, 17 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
18}; 18};
19 19
20pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields}; 20pub use crate::diagnostics::{
21 expr::{record_literal_missing_fields, record_pattern_missing_fields},
22 unsafe_check::missing_unsafe,
23};
21 24
22pub fn validate_module_item( 25pub fn validate_module_item(
23 db: &dyn HirDatabase, 26 db: &dyn HirDatabase,
@@ -33,38 +36,8 @@ pub fn validate_module_item(
33pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { 36pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
34 let _p = profile::span("validate_body"); 37 let _p = profile::span("validate_body");
35 let infer = db.infer(owner); 38 let infer = db.infer(owner);
36 infer.add_diagnostics(db, owner, sink);
37 let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink); 39 let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink);
38 validator.validate_body(db); 40 validator.validate_body(db);
39 let mut validator = unsafe_check::UnsafeValidator::new(owner, infer, sink);
40 validator.validate_body(db);
41}
42
43// Diagnostic: no-such-field
44//
45// This diagnostic is triggered if created structure does not have field provided in record.
46#[derive(Debug)]
47pub struct NoSuchField {
48 pub file: HirFileId,
49 pub field: AstPtr<ast::RecordExprField>,
50}
51
52impl Diagnostic for NoSuchField {
53 fn code(&self) -> DiagnosticCode {
54 DiagnosticCode("no-such-field")
55 }
56
57 fn message(&self) -> String {
58 "no such field".to_string()
59 }
60
61 fn display_source(&self) -> InFile<SyntaxNodePtr> {
62 InFile::new(self.file, self.field.clone().into())
63 }
64
65 fn as_any(&self) -> &(dyn Any + Send + 'static) {
66 self
67 }
68} 41}
69 42
70// Diagnostic: missing-structure-fields 43// Diagnostic: missing-structure-fields
@@ -247,54 +220,6 @@ impl Diagnostic for RemoveThisSemicolon {
247 } 220 }
248} 221}
249 222
250// Diagnostic: break-outside-of-loop
251//
252// This diagnostic is triggered if the `break` keyword is used outside of a loop.
253#[derive(Debug)]
254pub struct BreakOutsideOfLoop {
255 pub file: HirFileId,
256 pub expr: AstPtr<ast::Expr>,
257}
258
259impl Diagnostic for BreakOutsideOfLoop {
260 fn code(&self) -> DiagnosticCode {
261 DiagnosticCode("break-outside-of-loop")
262 }
263 fn message(&self) -> String {
264 "break outside of loop".to_string()
265 }
266 fn display_source(&self) -> InFile<SyntaxNodePtr> {
267 InFile { file_id: self.file, value: self.expr.clone().into() }
268 }
269 fn as_any(&self) -> &(dyn Any + Send + 'static) {
270 self
271 }
272}
273
274// Diagnostic: missing-unsafe
275//
276// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
277#[derive(Debug)]
278pub struct MissingUnsafe {
279 pub file: HirFileId,
280 pub expr: AstPtr<ast::Expr>,
281}
282
283impl Diagnostic for MissingUnsafe {
284 fn code(&self) -> DiagnosticCode {
285 DiagnosticCode("missing-unsafe")
286 }
287 fn message(&self) -> String {
288 format!("This operation is unsafe and requires an unsafe function or block")
289 }
290 fn display_source(&self) -> InFile<SyntaxNodePtr> {
291 InFile { file_id: self.file, value: self.expr.clone().into() }
292 }
293 fn as_any(&self) -> &(dyn Any + Send + 'static) {
294 self
295 }
296}
297
298// Diagnostic: mismatched-arg-count 223// Diagnostic: mismatched-arg-count
299// 224//
300// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. 225// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
@@ -531,129 +456,6 @@ mod tests {
531 } 456 }
532 457
533 #[test] 458 #[test]
534 fn no_such_field_diagnostics() {
535 check_diagnostics(
536 r#"
537struct S { foo: i32, bar: () }
538impl S {
539 fn new() -> S {
540 S {
541 //^ Missing structure fields:
542 //| - bar
543 foo: 92,
544 baz: 62,
545 //^^^^^^^ no such field
546 }
547 }
548}
549"#,
550 );
551 }
552 #[test]
553 fn no_such_field_with_feature_flag_diagnostics() {
554 check_diagnostics(
555 r#"
556//- /lib.rs crate:foo cfg:feature=foo
557struct MyStruct {
558 my_val: usize,
559 #[cfg(feature = "foo")]
560 bar: bool,
561}
562
563impl MyStruct {
564 #[cfg(feature = "foo")]
565 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
566 Self { my_val, bar }
567 }
568 #[cfg(not(feature = "foo"))]
569 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
570 Self { my_val }
571 }
572}
573"#,
574 );
575 }
576
577 #[test]
578 fn no_such_field_enum_with_feature_flag_diagnostics() {
579 check_diagnostics(
580 r#"
581//- /lib.rs crate:foo cfg:feature=foo
582enum Foo {
583 #[cfg(not(feature = "foo"))]
584 Buz,
585 #[cfg(feature = "foo")]
586 Bar,
587 Baz
588}
589
590fn test_fn(f: Foo) {
591 match f {
592 Foo::Bar => {},
593 Foo::Baz => {},
594 }
595}
596"#,
597 );
598 }
599
600 #[test]
601 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
602 check_diagnostics(
603 r#"
604//- /lib.rs crate:foo cfg:feature=foo
605struct S {
606 #[cfg(feature = "foo")]
607 foo: u32,
608 #[cfg(not(feature = "foo"))]
609 bar: u32,
610}
611
612impl S {
613 #[cfg(feature = "foo")]
614 fn new(foo: u32) -> Self {
615 Self { foo }
616 }
617 #[cfg(not(feature = "foo"))]
618 fn new(bar: u32) -> Self {
619 Self { bar }
620 }
621 fn new2(bar: u32) -> Self {
622 #[cfg(feature = "foo")]
623 { Self { foo: bar } }
624 #[cfg(not(feature = "foo"))]
625 { Self { bar } }
626 }
627 fn new2(val: u32) -> Self {
628 Self {
629 #[cfg(feature = "foo")]
630 foo: val,
631 #[cfg(not(feature = "foo"))]
632 bar: val,
633 }
634 }
635}
636"#,
637 );
638 }
639
640 #[test]
641 fn no_such_field_with_type_macro() {
642 check_diagnostics(
643 r#"
644macro_rules! Type { () => { u32 }; }
645struct Foo { bar: Type![] }
646
647impl Foo {
648 fn new() -> Self {
649 Foo { bar: 0 }
650 }
651}
652"#,
653 );
654 }
655
656 #[test]
657 fn missing_record_pat_field_diagnostic() { 459 fn missing_record_pat_field_diagnostic() {
658 check_diagnostics( 460 check_diagnostics(
659 r#" 461 r#"
@@ -735,16 +537,6 @@ pub struct Claims {
735 } 537 }
736 538
737 #[test] 539 #[test]
738 fn break_outside_of_loop() {
739 check_diagnostics(
740 r#"
741fn foo() { break; }
742 //^^^^^ break outside of loop
743"#,
744 );
745 }
746
747 #[test]
748 fn missing_semicolon() { 540 fn missing_semicolon() {
749 check_diagnostics( 541 check_diagnostics(
750 r#" 542 r#"
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
index c3c483425..a4054cef9 100644
--- a/crates/hir_ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -1,8 +1,6 @@
1//! Provides validations for unsafe code. Currently checks if unsafe functions are missing 1//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
2//! unsafe blocks. 2//! unsafe blocks.
3 3
4use std::sync::Arc;
5
6use hir_def::{ 4use hir_def::{
7 body::Body, 5 body::Body,
8 expr::{Expr, ExprId, UnaryOp}, 6 expr::{Expr, ExprId, UnaryOp},
@@ -10,52 +8,25 @@ use hir_def::{
10 DefWithBodyId, 8 DefWithBodyId,
11}; 9};
12 10
13use crate::{ 11use crate::{db::HirDatabase, InferenceResult, Interner, TyExt, TyKind};
14 db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult,
15 Interner, TyExt, TyKind,
16};
17 12
18pub(super) struct UnsafeValidator<'a, 'b: 'a> { 13pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
19 owner: DefWithBodyId, 14 let infer = db.infer(def);
20 infer: Arc<InferenceResult>,
21 sink: &'a mut DiagnosticSink<'b>,
22}
23 15
24impl<'a, 'b> UnsafeValidator<'a, 'b> { 16 // let unsafe_expressions = ;
25 pub(super) fn new( 17 let is_unsafe = match def {
26 owner: DefWithBodyId, 18 DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
27 infer: Arc<InferenceResult>, 19 DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
28 sink: &'a mut DiagnosticSink<'b>, 20 };
29 ) -> UnsafeValidator<'a, 'b> { 21 if is_unsafe {
30 UnsafeValidator { owner, infer, sink } 22 return Vec::new();
31 } 23 }
32 24
33 pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) { 25 unsafe_expressions(db, &infer, def)
34 let def = self.owner; 26 .into_iter()
35 let unsafe_expressions = unsafe_expressions(db, self.infer.as_ref(), def); 27 .filter(|it| !it.inside_unsafe_block)
36 let is_unsafe = match self.owner { 28 .map(|it| it.expr)
37 DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(), 29 .collect()
38 DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
39 };
40 if is_unsafe
41 || unsafe_expressions
42 .iter()
43 .filter(|unsafe_expr| !unsafe_expr.inside_unsafe_block)
44 .count()
45 == 0
46 {
47 return;
48 }
49
50 let (_, body_source) = db.body_with_source_map(def);
51 for unsafe_expr in unsafe_expressions {
52 if !unsafe_expr.inside_unsafe_block {
53 if let Ok(in_file) = body_source.as_ref().expr_syntax(unsafe_expr.expr) {
54 self.sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
55 }
56 }
57 }
58 }
59} 30}
60 31
61pub(crate) struct UnsafeExpr { 32pub(crate) struct UnsafeExpr {
@@ -126,92 +97,3 @@ fn walk_unsafe(
126 walk_unsafe(unsafe_exprs, db, infer, def, body, child, inside_unsafe_block); 97 walk_unsafe(unsafe_exprs, db, infer, def, body, child, inside_unsafe_block);
127 }); 98 });
128} 99}
129
130#[cfg(test)]
131mod tests {
132 use crate::diagnostics::tests::check_diagnostics;
133
134 #[test]
135 fn missing_unsafe_diagnostic_with_raw_ptr() {
136 check_diagnostics(
137 r#"
138fn main() {
139 let x = &5 as *const usize;
140 unsafe { let y = *x; }
141 let z = *x;
142} //^^ This operation is unsafe and requires an unsafe function or block
143"#,
144 )
145 }
146
147 #[test]
148 fn missing_unsafe_diagnostic_with_unsafe_call() {
149 check_diagnostics(
150 r#"
151struct HasUnsafe;
152
153impl HasUnsafe {
154 unsafe fn unsafe_fn(&self) {
155 let x = &5 as *const usize;
156 let y = *x;
157 }
158}
159
160unsafe fn unsafe_fn() {
161 let x = &5 as *const usize;
162 let y = *x;
163}
164
165fn main() {
166 unsafe_fn();
167 //^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
168 HasUnsafe.unsafe_fn();
169 //^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
170 unsafe {
171 unsafe_fn();
172 HasUnsafe.unsafe_fn();
173 }
174}
175"#,
176 );
177 }
178
179 #[test]
180 fn missing_unsafe_diagnostic_with_static_mut() {
181 check_diagnostics(
182 r#"
183struct Ty {
184 a: u8,
185}
186
187static mut STATIC_MUT: Ty = Ty { a: 0 };
188
189fn main() {
190 let x = STATIC_MUT.a;
191 //^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
192 unsafe {
193 let x = STATIC_MUT.a;
194 }
195}
196"#,
197 );
198 }
199
200 #[test]
201 fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
202 check_diagnostics(
203 r#"
204extern "rust-intrinsic" {
205 pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
206 pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
207}
208
209fn main() {
210 let _ = bitreverse(12);
211 let _ = floorf32(12.0);
212 //^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
213}
214"#,
215 );
216 }
217}
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 0e9f777da..2c667da25 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -35,11 +35,9 @@ use stdx::impl_from;
35use syntax::SmolStr; 35use syntax::SmolStr;
36 36
37use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; 37use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty};
38use crate::diagnostics_sink::DiagnosticSink;
39use crate::{ 38use crate::{
40 db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic, 39 db::HirDatabase, fold_tys, lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy,
41 lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution, 40 Goal, Interner, Substitution, TyBuilder, TyExt, TyKind,
42 TyBuilder, TyExt, TyKind,
43}; 41};
44 42
45// This lint has a false positive here. See the link below for details. 43// This lint has a false positive here. See the link below for details.
@@ -111,6 +109,12 @@ pub(crate) struct InferOk {
111pub(crate) struct TypeError; 109pub(crate) struct TypeError;
112pub(crate) type InferResult = Result<InferOk, TypeError>; 110pub(crate) type InferResult = Result<InferOk, TypeError>;
113 111
112#[derive(Debug, PartialEq, Eq, Clone)]
113pub enum InferenceDiagnostic {
114 NoSuchField { expr: ExprId },
115 BreakOutsideOfLoop { expr: ExprId },
116}
117
114/// A mismatch between an expected and an inferred type. 118/// A mismatch between an expected and an inferred type.
115#[derive(Clone, PartialEq, Eq, Debug, Hash)] 119#[derive(Clone, PartialEq, Eq, Debug, Hash)]
116pub struct TypeMismatch { 120pub struct TypeMismatch {
@@ -140,7 +144,7 @@ pub struct InferenceResult {
140 variant_resolutions: FxHashMap<ExprOrPatId, VariantId>, 144 variant_resolutions: FxHashMap<ExprOrPatId, VariantId>,
141 /// For each associated item record what it resolves to 145 /// For each associated item record what it resolves to
142 assoc_resolutions: FxHashMap<ExprOrPatId, AssocItemId>, 146 assoc_resolutions: FxHashMap<ExprOrPatId, AssocItemId>,
143 diagnostics: Vec<InferenceDiagnostic>, 147 pub diagnostics: Vec<InferenceDiagnostic>,
144 pub type_of_expr: ArenaMap<ExprId, Ty>, 148 pub type_of_expr: ArenaMap<ExprId, Ty>,
145 /// For each pattern record the type it resolves to. 149 /// For each pattern record the type it resolves to.
146 /// 150 ///
@@ -191,14 +195,6 @@ impl InferenceResult {
191 _ => None, 195 _ => None,
192 }) 196 })
193 } 197 }
194 pub fn add_diagnostics(
195 &self,
196 db: &dyn HirDatabase,
197 owner: DefWithBodyId,
198 sink: &mut DiagnosticSink,
199 ) {
200 self.diagnostics.iter().for_each(|it| it.add_to(db, owner, sink))
201 }
202} 198}
203 199
204impl Index<ExprId> for InferenceResult { 200impl Index<ExprId> for InferenceResult {
@@ -804,43 +800,3 @@ impl std::ops::BitOrAssign for Diverges {
804 *self = *self | other; 800 *self = *self | other;
805 } 801 }
806} 802}
807
808mod diagnostics {
809 use hir_def::{expr::ExprId, DefWithBodyId};
810
811 use crate::{
812 db::HirDatabase,
813 diagnostics::{BreakOutsideOfLoop, NoSuchField},
814 diagnostics_sink::DiagnosticSink,
815 };
816
817 #[derive(Debug, PartialEq, Eq, Clone)]
818 pub(super) enum InferenceDiagnostic {
819 NoSuchField { expr: ExprId },
820 BreakOutsideOfLoop { expr: ExprId },
821 }
822
823 impl InferenceDiagnostic {
824 pub(super) fn add_to(
825 &self,
826 db: &dyn HirDatabase,
827 owner: DefWithBodyId,
828 sink: &mut DiagnosticSink,
829 ) {
830 match self {
831 InferenceDiagnostic::NoSuchField { expr } => {
832 let (_, source_map) = db.body_with_source_map(owner);
833 let field = source_map.field_syntax(*expr);
834 sink.push(NoSuchField { file: field.file_id, field: field.value })
835 }
836 InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
837 let (_, source_map) = db.body_with_source_map(owner);
838 let ptr = source_map
839 .expr_syntax(*expr)
840 .expect("break outside of loop in synthetic syntax");
841 sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value })
842 }
843 }
844 }
845 }
846}
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index 50e0d6333..0c6b19653 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -50,7 +50,7 @@ use crate::{db::HirDatabase, utils::generics};
50pub use autoderef::autoderef; 50pub use autoderef::autoderef;
51pub use builder::TyBuilder; 51pub use builder::TyBuilder;
52pub use chalk_ext::*; 52pub use chalk_ext::*;
53pub use infer::{could_unify, InferenceResult}; 53pub use infer::{could_unify, InferenceDiagnostic, InferenceResult};
54pub use interner::Interner; 54pub use interner::Interner;
55pub use lower::{ 55pub use lower::{
56 associated_type_shorthand_candidates, callable_item_sig, CallableDefId, ImplTraitLoweringMode, 56 associated_type_shorthand_candidates, callable_item_sig, CallableDefId, ImplTraitLoweringMode,
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index d5c954b8b..b791747c2 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -305,6 +305,7 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
305#[cfg(test)] 305#[cfg(test)]
306mod tests { 306mod tests {
307 use expect_test::Expect; 307 use expect_test::Expect;
308 use hir::diagnostics::DiagnosticCode;
308 use ide_assists::AssistResolveStrategy; 309 use ide_assists::AssistResolveStrategy;
309 use stdx::trim_indent; 310 use stdx::trim_indent;
310 use test_utils::{assert_eq_text, extract_annotations}; 311 use test_utils::{assert_eq_text, extract_annotations};
@@ -410,7 +411,12 @@ mod tests {
410 .unwrap(); 411 .unwrap();
411 412
412 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); 413 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
413 let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); 414 let mut actual = diagnostics
415 .into_iter()
416 .filter(|d| d.code != Some(DiagnosticCode("inactive-code")))
417 .map(|d| (d.range, d.message))
418 .collect::<Vec<_>>();
419 actual.sort_by_key(|(range, _)| range.start());
414 assert_eq!(expected, actual); 420 assert_eq!(expected, actual);
415 } 421 }
416 422
@@ -719,4 +725,221 @@ mod foo;
719"#, 725"#,
720 ); 726 );
721 } 727 }
728
729 #[test]
730 fn break_outside_of_loop() {
731 check_diagnostics(
732 r#"
733fn foo() { break; }
734 //^^^^^ break outside of loop
735"#,
736 );
737 }
738
739 #[test]
740 fn no_such_field_diagnostics() {
741 check_diagnostics(
742 r#"
743struct S { foo: i32, bar: () }
744impl S {
745 fn new() -> S {
746 S {
747 //^ Missing structure fields:
748 //| - bar
749 foo: 92,
750 baz: 62,
751 //^^^^^^^ no such field
752 }
753 }
754}
755"#,
756 );
757 }
758 #[test]
759 fn no_such_field_with_feature_flag_diagnostics() {
760 check_diagnostics(
761 r#"
762//- /lib.rs crate:foo cfg:feature=foo
763struct MyStruct {
764 my_val: usize,
765 #[cfg(feature = "foo")]
766 bar: bool,
767}
768
769impl MyStruct {
770 #[cfg(feature = "foo")]
771 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
772 Self { my_val, bar }
773 }
774 #[cfg(not(feature = "foo"))]
775 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
776 Self { my_val }
777 }
778}
779"#,
780 );
781 }
782
783 #[test]
784 fn no_such_field_enum_with_feature_flag_diagnostics() {
785 check_diagnostics(
786 r#"
787//- /lib.rs crate:foo cfg:feature=foo
788enum Foo {
789 #[cfg(not(feature = "foo"))]
790 Buz,
791 #[cfg(feature = "foo")]
792 Bar,
793 Baz
794}
795
796fn test_fn(f: Foo) {
797 match f {
798 Foo::Bar => {},
799 Foo::Baz => {},
800 }
801}
802"#,
803 );
804 }
805
806 #[test]
807 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
808 check_diagnostics(
809 r#"
810//- /lib.rs crate:foo cfg:feature=foo
811struct S {
812 #[cfg(feature = "foo")]
813 foo: u32,
814 #[cfg(not(feature = "foo"))]
815 bar: u32,
816}
817
818impl S {
819 #[cfg(feature = "foo")]
820 fn new(foo: u32) -> Self {
821 Self { foo }
822 }
823 #[cfg(not(feature = "foo"))]
824 fn new(bar: u32) -> Self {
825 Self { bar }
826 }
827 fn new2(bar: u32) -> Self {
828 #[cfg(feature = "foo")]
829 { Self { foo: bar } }
830 #[cfg(not(feature = "foo"))]
831 { Self { bar } }
832 }
833 fn new2(val: u32) -> Self {
834 Self {
835 #[cfg(feature = "foo")]
836 foo: val,
837 #[cfg(not(feature = "foo"))]
838 bar: val,
839 }
840 }
841}
842"#,
843 );
844 }
845
846 #[test]
847 fn no_such_field_with_type_macro() {
848 check_diagnostics(
849 r#"
850macro_rules! Type { () => { u32 }; }
851struct Foo { bar: Type![] }
852
853impl Foo {
854 fn new() -> Self {
855 Foo { bar: 0 }
856 }
857}
858"#,
859 );
860 }
861
862 #[test]
863 fn missing_unsafe_diagnostic_with_raw_ptr() {
864 check_diagnostics(
865 r#"
866fn main() {
867 let x = &5 as *const usize;
868 unsafe { let y = *x; }
869 let z = *x;
870} //^^ This operation is unsafe and requires an unsafe function or block
871"#,
872 )
873 }
874
875 #[test]
876 fn missing_unsafe_diagnostic_with_unsafe_call() {
877 check_diagnostics(
878 r#"
879struct HasUnsafe;
880
881impl HasUnsafe {
882 unsafe fn unsafe_fn(&self) {
883 let x = &5 as *const usize;
884 let y = *x;
885 }
886}
887
888unsafe fn unsafe_fn() {
889 let x = &5 as *const usize;
890 let y = *x;
891}
892
893fn main() {
894 unsafe_fn();
895 //^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
896 HasUnsafe.unsafe_fn();
897 //^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
898 unsafe {
899 unsafe_fn();
900 HasUnsafe.unsafe_fn();
901 }
902}
903"#,
904 );
905 }
906
907 #[test]
908 fn missing_unsafe_diagnostic_with_static_mut() {
909 check_diagnostics(
910 r#"
911struct Ty {
912 a: u8,
913}
914
915static mut STATIC_MUT: Ty = Ty { a: 0 };
916
917fn main() {
918 let x = STATIC_MUT.a;
919 //^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
920 unsafe {
921 let x = STATIC_MUT.a;
922 }
923}
924"#,
925 );
926 }
927
928 #[test]
929 fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
930 check_diagnostics(
931 r#"
932extern "rust-intrinsic" {
933 pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
934 pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
935}
936
937fn main() {
938 let _ = bitreverse(12);
939 let _ = floorf32(12.0);
940 //^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
941}
942"#,
943 );
944 }
722} 945}