diff options
-rw-r--r-- | crates/hir/src/diagnostics.rs | 78 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 36 | ||||
-rw-r--r-- | crates/hir_ty/src/diagnostics.rs | 216 | ||||
-rw-r--r-- | crates/hir_ty/src/diagnostics/unsafe_check.rs | 148 | ||||
-rw-r--r-- | crates/hir_ty/src/infer.rs | 62 | ||||
-rw-r--r-- | crates/hir_ty/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 225 |
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}; | |||
14 | pub use hir_ty::{ | 14 | pub 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)] | ||
258 | pub struct NoSuchField { | ||
259 | pub file: HirFileId, | ||
260 | pub field: AstPtr<ast::RecordExprField>, | ||
261 | } | ||
262 | |||
263 | impl 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)] | ||
285 | pub struct BreakOutsideOfLoop { | ||
286 | pub file: HirFileId, | ||
287 | pub expr: AstPtr<ast::Expr>, | ||
288 | } | ||
289 | |||
290 | impl 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)] | ||
309 | pub struct MissingUnsafe { | ||
310 | pub file: HirFileId, | ||
311 | pub expr: AstPtr<ast::Expr>, | ||
312 | } | ||
313 | |||
314 | impl 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}; | |||
36 | use arrayvec::ArrayVec; | 36 | use arrayvec::ArrayVec; |
37 | use base_db::{CrateDisplayName, CrateId, Edition, FileId}; | 37 | use base_db::{CrateDisplayName, CrateId, Edition, FileId}; |
38 | use diagnostics::{ | 38 | use 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 | }; |
42 | use either::Either; | 43 | use either::Either; |
43 | use hir_def::{ | 44 | use 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 | ||
20 | pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields}; | 20 | pub use crate::diagnostics::{ |
21 | expr::{record_literal_missing_fields, record_pattern_missing_fields}, | ||
22 | unsafe_check::missing_unsafe, | ||
23 | }; | ||
21 | 24 | ||
22 | pub fn validate_module_item( | 25 | pub fn validate_module_item( |
23 | db: &dyn HirDatabase, | 26 | db: &dyn HirDatabase, |
@@ -33,38 +36,8 @@ pub fn validate_module_item( | |||
33 | pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { | 36 | pub 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)] | ||
47 | pub struct NoSuchField { | ||
48 | pub file: HirFileId, | ||
49 | pub field: AstPtr<ast::RecordExprField>, | ||
50 | } | ||
51 | |||
52 | impl 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)] | ||
254 | pub struct BreakOutsideOfLoop { | ||
255 | pub file: HirFileId, | ||
256 | pub expr: AstPtr<ast::Expr>, | ||
257 | } | ||
258 | |||
259 | impl 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)] | ||
278 | pub struct MissingUnsafe { | ||
279 | pub file: HirFileId, | ||
280 | pub expr: AstPtr<ast::Expr>, | ||
281 | } | ||
282 | |||
283 | impl 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#" | ||
537 | struct S { foo: i32, bar: () } | ||
538 | impl 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 | ||
557 | struct MyStruct { | ||
558 | my_val: usize, | ||
559 | #[cfg(feature = "foo")] | ||
560 | bar: bool, | ||
561 | } | ||
562 | |||
563 | impl 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 | ||
582 | enum Foo { | ||
583 | #[cfg(not(feature = "foo"))] | ||
584 | Buz, | ||
585 | #[cfg(feature = "foo")] | ||
586 | Bar, | ||
587 | Baz | ||
588 | } | ||
589 | |||
590 | fn 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 | ||
605 | struct S { | ||
606 | #[cfg(feature = "foo")] | ||
607 | foo: u32, | ||
608 | #[cfg(not(feature = "foo"))] | ||
609 | bar: u32, | ||
610 | } | ||
611 | |||
612 | impl 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#" | ||
644 | macro_rules! Type { () => { u32 }; } | ||
645 | struct Foo { bar: Type![] } | ||
646 | |||
647 | impl 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#" | ||
741 | fn 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 | ||
4 | use std::sync::Arc; | ||
5 | |||
6 | use hir_def::{ | 4 | use 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 | ||
13 | use crate::{ | 11 | use crate::{db::HirDatabase, InferenceResult, Interner, TyExt, TyKind}; |
14 | db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult, | ||
15 | Interner, TyExt, TyKind, | ||
16 | }; | ||
17 | 12 | ||
18 | pub(super) struct UnsafeValidator<'a, 'b: 'a> { | 13 | pub 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 | ||
24 | impl<'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 | ||
61 | pub(crate) struct UnsafeExpr { | 32 | pub(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)] | ||
131 | mod tests { | ||
132 | use crate::diagnostics::tests::check_diagnostics; | ||
133 | |||
134 | #[test] | ||
135 | fn missing_unsafe_diagnostic_with_raw_ptr() { | ||
136 | check_diagnostics( | ||
137 | r#" | ||
138 | fn 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#" | ||
151 | struct HasUnsafe; | ||
152 | |||
153 | impl HasUnsafe { | ||
154 | unsafe fn unsafe_fn(&self) { | ||
155 | let x = &5 as *const usize; | ||
156 | let y = *x; | ||
157 | } | ||
158 | } | ||
159 | |||
160 | unsafe fn unsafe_fn() { | ||
161 | let x = &5 as *const usize; | ||
162 | let y = *x; | ||
163 | } | ||
164 | |||
165 | fn 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#" | ||
183 | struct Ty { | ||
184 | a: u8, | ||
185 | } | ||
186 | |||
187 | static mut STATIC_MUT: Ty = Ty { a: 0 }; | ||
188 | |||
189 | fn 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#" | ||
204 | extern "rust-intrinsic" { | ||
205 | pub fn bitreverse(x: u32) -> u32; // Safe intrinsic | ||
206 | pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic | ||
207 | } | ||
208 | |||
209 | fn 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; | |||
35 | use syntax::SmolStr; | 35 | use syntax::SmolStr; |
36 | 36 | ||
37 | use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; | 37 | use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; |
38 | use crate::diagnostics_sink::DiagnosticSink; | ||
39 | use crate::{ | 38 | use 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 { | |||
111 | pub(crate) struct TypeError; | 109 | pub(crate) struct TypeError; |
112 | pub(crate) type InferResult = Result<InferOk, TypeError>; | 110 | pub(crate) type InferResult = Result<InferOk, TypeError>; |
113 | 111 | ||
112 | #[derive(Debug, PartialEq, Eq, Clone)] | ||
113 | pub 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)] |
116 | pub struct TypeMismatch { | 120 | pub 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 | ||
204 | impl Index<ExprId> for InferenceResult { | 200 | impl 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 | |||
808 | mod 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}; | |||
50 | pub use autoderef::autoderef; | 50 | pub use autoderef::autoderef; |
51 | pub use builder::TyBuilder; | 51 | pub use builder::TyBuilder; |
52 | pub use chalk_ext::*; | 52 | pub use chalk_ext::*; |
53 | pub use infer::{could_unify, InferenceResult}; | 53 | pub use infer::{could_unify, InferenceDiagnostic, InferenceResult}; |
54 | pub use interner::Interner; | 54 | pub use interner::Interner; |
55 | pub use lower::{ | 55 | pub 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)] |
306 | mod tests { | 306 | mod 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#" | ||
733 | fn foo() { break; } | ||
734 | //^^^^^ break outside of loop | ||
735 | "#, | ||
736 | ); | ||
737 | } | ||
738 | |||
739 | #[test] | ||
740 | fn no_such_field_diagnostics() { | ||
741 | check_diagnostics( | ||
742 | r#" | ||
743 | struct S { foo: i32, bar: () } | ||
744 | impl 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 | ||
763 | struct MyStruct { | ||
764 | my_val: usize, | ||
765 | #[cfg(feature = "foo")] | ||
766 | bar: bool, | ||
767 | } | ||
768 | |||
769 | impl 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 | ||
788 | enum Foo { | ||
789 | #[cfg(not(feature = "foo"))] | ||
790 | Buz, | ||
791 | #[cfg(feature = "foo")] | ||
792 | Bar, | ||
793 | Baz | ||
794 | } | ||
795 | |||
796 | fn 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 | ||
811 | struct S { | ||
812 | #[cfg(feature = "foo")] | ||
813 | foo: u32, | ||
814 | #[cfg(not(feature = "foo"))] | ||
815 | bar: u32, | ||
816 | } | ||
817 | |||
818 | impl 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#" | ||
850 | macro_rules! Type { () => { u32 }; } | ||
851 | struct Foo { bar: Type![] } | ||
852 | |||
853 | impl 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#" | ||
866 | fn 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#" | ||
879 | struct HasUnsafe; | ||
880 | |||
881 | impl HasUnsafe { | ||
882 | unsafe fn unsafe_fn(&self) { | ||
883 | let x = &5 as *const usize; | ||
884 | let y = *x; | ||
885 | } | ||
886 | } | ||
887 | |||
888 | unsafe fn unsafe_fn() { | ||
889 | let x = &5 as *const usize; | ||
890 | let y = *x; | ||
891 | } | ||
892 | |||
893 | fn 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#" | ||
911 | struct Ty { | ||
912 | a: u8, | ||
913 | } | ||
914 | |||
915 | static mut STATIC_MUT: Ty = Ty { a: 0 }; | ||
916 | |||
917 | fn 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#" | ||
932 | extern "rust-intrinsic" { | ||
933 | pub fn bitreverse(x: u32) -> u32; // Safe intrinsic | ||
934 | pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic | ||
935 | } | ||
936 | |||
937 | fn 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 | } |