aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/diagnostics.rs499
-rw-r--r--crates/ide/src/diagnostics/break_outside_of_loop.rs30
-rw-r--r--crates/ide/src/diagnostics/fixes/create_field.rs155
-rw-r--r--crates/ide/src/diagnostics/mismatched_arg_count.rs272
-rw-r--r--crates/ide/src/diagnostics/missing_unsafe.rs101
-rw-r--r--crates/ide/src/diagnostics/no_such_field.rs286
-rw-r--r--crates/ide/src/diagnostics/unimplemented_builtin_macro.rs19
7 files changed, 725 insertions, 637 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index aeccf1164..4c92d0cf4 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -4,14 +4,19 @@
4//! macro-expanded files, but we need to present them to the users in terms of 4//! macro-expanded files, but we need to present them to the users in terms of
5//! original files. So we need to map the ranges. 5//! original files. So we need to map the ranges.
6 6
7mod unresolved_module; 7mod break_outside_of_loop;
8mod inactive_code;
9mod macro_error;
10mod mismatched_arg_count;
11mod missing_fields;
12mod missing_unsafe;
13mod no_such_field;
14mod unimplemented_builtin_macro;
8mod unresolved_extern_crate; 15mod unresolved_extern_crate;
9mod unresolved_import; 16mod unresolved_import;
10mod unresolved_macro_call; 17mod unresolved_macro_call;
18mod unresolved_module;
11mod unresolved_proc_macro; 19mod unresolved_proc_macro;
12mod macro_error;
13mod inactive_code;
14mod missing_fields;
15 20
16mod fixes; 21mod fixes;
17mod field_shorthand; 22mod field_shorthand;
@@ -160,9 +165,6 @@ pub(crate) fn diagnostics(
160 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { 165 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
161 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); 166 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
162 }) 167 })
163 .on::<hir::diagnostics::NoSuchField, _>(|d| {
164 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
165 })
166 .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { 168 .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
167 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); 169 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
168 }) 170 })
@@ -185,11 +187,6 @@ pub(crate) fn diagnostics(
185 .with_code(Some(d.code())), 187 .with_code(Some(d.code())),
186 ); 188 );
187 }) 189 })
188 .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| {
189 let display_range = sema.diagnostics_display_range(d.display_source()).range;
190 res.borrow_mut()
191 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
192 })
193 // Only collect experimental diagnostics when they're enabled. 190 // Only collect experimental diagnostics when they're enabled.
194 .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) 191 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
195 .filter(|diag| !config.disabled.contains(diag.code().as_str())); 192 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
@@ -224,13 +221,18 @@ pub(crate) fn diagnostics(
224 for diag in diags { 221 for diag in diags {
225 #[rustfmt::skip] 222 #[rustfmt::skip]
226 let d = match diag { 223 let d = match diag {
227 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d), 224 AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d),
225 AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
226 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
227 AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d),
228 AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d),
229 AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d),
230 AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
228 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), 231 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
229 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), 232 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
230 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d), 233 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d),
234 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d),
231 AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d), 235 AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d),
232 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
233 AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
234 236
235 AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) { 237 AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) {
236 Some(it) => it, 238 Some(it) => it,
@@ -715,223 +717,6 @@ mod foo;
715 ); 717 );
716 } 718 }
717 719
718 #[test]
719 fn break_outside_of_loop() {
720 check_diagnostics(
721 r#"
722fn foo() { break; }
723 //^^^^^ break outside of loop
724"#,
725 );
726 }
727
728 #[test]
729 fn no_such_field_diagnostics() {
730 check_diagnostics(
731 r#"
732struct S { foo: i32, bar: () }
733impl S {
734 fn new() -> S {
735 S {
736 //^ Missing structure fields:
737 //| - bar
738 foo: 92,
739 baz: 62,
740 //^^^^^^^ no such field
741 }
742 }
743}
744"#,
745 );
746 }
747 #[test]
748 fn no_such_field_with_feature_flag_diagnostics() {
749 check_diagnostics(
750 r#"
751//- /lib.rs crate:foo cfg:feature=foo
752struct MyStruct {
753 my_val: usize,
754 #[cfg(feature = "foo")]
755 bar: bool,
756}
757
758impl MyStruct {
759 #[cfg(feature = "foo")]
760 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
761 Self { my_val, bar }
762 }
763 #[cfg(not(feature = "foo"))]
764 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
765 Self { my_val }
766 }
767}
768"#,
769 );
770 }
771
772 #[test]
773 fn no_such_field_enum_with_feature_flag_diagnostics() {
774 check_diagnostics(
775 r#"
776//- /lib.rs crate:foo cfg:feature=foo
777enum Foo {
778 #[cfg(not(feature = "foo"))]
779 Buz,
780 #[cfg(feature = "foo")]
781 Bar,
782 Baz
783}
784
785fn test_fn(f: Foo) {
786 match f {
787 Foo::Bar => {},
788 Foo::Baz => {},
789 }
790}
791"#,
792 );
793 }
794
795 #[test]
796 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
797 check_diagnostics(
798 r#"
799//- /lib.rs crate:foo cfg:feature=foo
800struct S {
801 #[cfg(feature = "foo")]
802 foo: u32,
803 #[cfg(not(feature = "foo"))]
804 bar: u32,
805}
806
807impl S {
808 #[cfg(feature = "foo")]
809 fn new(foo: u32) -> Self {
810 Self { foo }
811 }
812 #[cfg(not(feature = "foo"))]
813 fn new(bar: u32) -> Self {
814 Self { bar }
815 }
816 fn new2(bar: u32) -> Self {
817 #[cfg(feature = "foo")]
818 { Self { foo: bar } }
819 #[cfg(not(feature = "foo"))]
820 { Self { bar } }
821 }
822 fn new2(val: u32) -> Self {
823 Self {
824 #[cfg(feature = "foo")]
825 foo: val,
826 #[cfg(not(feature = "foo"))]
827 bar: val,
828 }
829 }
830}
831"#,
832 );
833 }
834
835 #[test]
836 fn no_such_field_with_type_macro() {
837 check_diagnostics(
838 r#"
839macro_rules! Type { () => { u32 }; }
840struct Foo { bar: Type![] }
841
842impl Foo {
843 fn new() -> Self {
844 Foo { bar: 0 }
845 }
846}
847"#,
848 );
849 }
850
851 #[test]
852 fn missing_unsafe_diagnostic_with_raw_ptr() {
853 check_diagnostics(
854 r#"
855fn main() {
856 let x = &5 as *const usize;
857 unsafe { let y = *x; }
858 let z = *x;
859} //^^ This operation is unsafe and requires an unsafe function or block
860"#,
861 )
862 }
863
864 #[test]
865 fn missing_unsafe_diagnostic_with_unsafe_call() {
866 check_diagnostics(
867 r#"
868struct HasUnsafe;
869
870impl HasUnsafe {
871 unsafe fn unsafe_fn(&self) {
872 let x = &5 as *const usize;
873 let y = *x;
874 }
875}
876
877unsafe fn unsafe_fn() {
878 let x = &5 as *const usize;
879 let y = *x;
880}
881
882fn main() {
883 unsafe_fn();
884 //^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
885 HasUnsafe.unsafe_fn();
886 //^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
887 unsafe {
888 unsafe_fn();
889 HasUnsafe.unsafe_fn();
890 }
891}
892"#,
893 );
894 }
895
896 #[test]
897 fn missing_unsafe_diagnostic_with_static_mut() {
898 check_diagnostics(
899 r#"
900struct Ty {
901 a: u8,
902}
903
904static mut STATIC_MUT: Ty = Ty { a: 0 };
905
906fn main() {
907 let x = STATIC_MUT.a;
908 //^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
909 unsafe {
910 let x = STATIC_MUT.a;
911 }
912}
913"#,
914 );
915 }
916
917 #[test]
918 fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
919 check_diagnostics(
920 r#"
921extern "rust-intrinsic" {
922 pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
923 pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
924}
925
926fn main() {
927 let _ = bitreverse(12);
928 let _ = floorf32(12.0);
929 //^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
930}
931"#,
932 );
933 }
934
935 // Register the required standard library types to make the tests work 720 // Register the required standard library types to make the tests work
936 fn add_filter_map_with_find_next_boilerplate(body: &str) -> String { 721 fn add_filter_map_with_find_next_boilerplate(body: &str) -> String {
937 let prefix = r#" 722 let prefix = r#"
@@ -1054,256 +839,6 @@ fn x(a: S) {
1054 } 839 }
1055 840
1056 #[test] 841 #[test]
1057 fn simple_free_fn_zero() {
1058 check_diagnostics(
1059 r#"
1060fn zero() {}
1061fn f() { zero(1); }
1062 //^^^^^^^ Expected 0 arguments, found 1
1063"#,
1064 );
1065
1066 check_diagnostics(
1067 r#"
1068fn zero() {}
1069fn f() { zero(); }
1070"#,
1071 );
1072 }
1073
1074 #[test]
1075 fn simple_free_fn_one() {
1076 check_diagnostics(
1077 r#"
1078fn one(arg: u8) {}
1079fn f() { one(); }
1080 //^^^^^ Expected 1 argument, found 0
1081"#,
1082 );
1083
1084 check_diagnostics(
1085 r#"
1086fn one(arg: u8) {}
1087fn f() { one(1); }
1088"#,
1089 );
1090 }
1091
1092 #[test]
1093 fn method_as_fn() {
1094 check_diagnostics(
1095 r#"
1096struct S;
1097impl S { fn method(&self) {} }
1098
1099fn f() {
1100 S::method();
1101} //^^^^^^^^^^^ Expected 1 argument, found 0
1102"#,
1103 );
1104
1105 check_diagnostics(
1106 r#"
1107struct S;
1108impl S { fn method(&self) {} }
1109
1110fn f() {
1111 S::method(&S);
1112 S.method();
1113}
1114"#,
1115 );
1116 }
1117
1118 #[test]
1119 fn method_with_arg() {
1120 check_diagnostics(
1121 r#"
1122struct S;
1123impl S { fn method(&self, arg: u8) {} }
1124
1125 fn f() {
1126 S.method();
1127 } //^^^^^^^^^^ Expected 1 argument, found 0
1128 "#,
1129 );
1130
1131 check_diagnostics(
1132 r#"
1133struct S;
1134impl S { fn method(&self, arg: u8) {} }
1135
1136fn f() {
1137 S::method(&S, 0);
1138 S.method(1);
1139}
1140"#,
1141 );
1142 }
1143
1144 #[test]
1145 fn method_unknown_receiver() {
1146 // note: this is incorrect code, so there might be errors on this in the
1147 // future, but we shouldn't emit an argument count diagnostic here
1148 check_diagnostics(
1149 r#"
1150trait Foo { fn method(&self, arg: usize) {} }
1151
1152fn f() {
1153 let x;
1154 x.method();
1155}
1156"#,
1157 );
1158 }
1159
1160 #[test]
1161 fn tuple_struct() {
1162 check_diagnostics(
1163 r#"
1164struct Tup(u8, u16);
1165fn f() {
1166 Tup(0);
1167} //^^^^^^ Expected 2 arguments, found 1
1168"#,
1169 )
1170 }
1171
1172 #[test]
1173 fn enum_variant() {
1174 check_diagnostics(
1175 r#"
1176enum En { Variant(u8, u16), }
1177fn f() {
1178 En::Variant(0);
1179} //^^^^^^^^^^^^^^ Expected 2 arguments, found 1
1180"#,
1181 )
1182 }
1183
1184 #[test]
1185 fn enum_variant_type_macro() {
1186 check_diagnostics(
1187 r#"
1188macro_rules! Type {
1189 () => { u32 };
1190}
1191enum Foo {
1192 Bar(Type![])
1193}
1194impl Foo {
1195 fn new() {
1196 Foo::Bar(0);
1197 Foo::Bar(0, 1);
1198 //^^^^^^^^^^^^^^ Expected 1 argument, found 2
1199 Foo::Bar();
1200 //^^^^^^^^^^ Expected 1 argument, found 0
1201 }
1202}
1203 "#,
1204 );
1205 }
1206
1207 #[test]
1208 fn varargs() {
1209 check_diagnostics(
1210 r#"
1211extern "C" {
1212 fn fixed(fixed: u8);
1213 fn varargs(fixed: u8, ...);
1214 fn varargs2(...);
1215}
1216
1217fn f() {
1218 unsafe {
1219 fixed(0);
1220 fixed(0, 1);
1221 //^^^^^^^^^^^ Expected 1 argument, found 2
1222 varargs(0);
1223 varargs(0, 1);
1224 varargs2();
1225 varargs2(0);
1226 varargs2(0, 1);
1227 }
1228}
1229 "#,
1230 )
1231 }
1232
1233 #[test]
1234 fn arg_count_lambda() {
1235 check_diagnostics(
1236 r#"
1237fn main() {
1238 let f = |()| ();
1239 f();
1240 //^^^ Expected 1 argument, found 0
1241 f(());
1242 f((), ());
1243 //^^^^^^^^^ Expected 1 argument, found 2
1244}
1245"#,
1246 )
1247 }
1248
1249 #[test]
1250 fn cfgd_out_call_arguments() {
1251 check_diagnostics(
1252 r#"
1253struct C(#[cfg(FALSE)] ());
1254impl C {
1255 fn new() -> Self {
1256 Self(
1257 #[cfg(FALSE)]
1258 (),
1259 )
1260 }
1261
1262 fn method(&self) {}
1263}
1264
1265fn main() {
1266 C::new().method(#[cfg(FALSE)] 0);
1267}
1268 "#,
1269 );
1270 }
1271
1272 #[test]
1273 fn cfgd_out_fn_params() {
1274 check_diagnostics(
1275 r#"
1276fn foo(#[cfg(NEVER)] x: ()) {}
1277
1278struct S;
1279
1280impl S {
1281 fn method(#[cfg(NEVER)] self) {}
1282 fn method2(#[cfg(NEVER)] self, arg: u8) {}
1283 fn method3(self, #[cfg(NEVER)] arg: u8) {}
1284}
1285
1286extern "C" {
1287 fn fixed(fixed: u8, #[cfg(NEVER)] ...);
1288 fn varargs(#[cfg(not(NEVER))] ...);
1289}
1290
1291fn main() {
1292 foo();
1293 S::method();
1294 S::method2(0);
1295 S::method3(S);
1296 S.method3();
1297 unsafe {
1298 fixed(0);
1299 varargs(1, 2, 3);
1300 }
1301}
1302 "#,
1303 )
1304 }
1305
1306 #[test]
1307 fn missing_semicolon() { 842 fn missing_semicolon() {
1308 check_diagnostics( 843 check_diagnostics(
1309 r#" 844 r#"
diff --git a/crates/ide/src/diagnostics/break_outside_of_loop.rs b/crates/ide/src/diagnostics/break_outside_of_loop.rs
new file mode 100644
index 000000000..80e68f3cc
--- /dev/null
+++ b/crates/ide/src/diagnostics/break_outside_of_loop.rs
@@ -0,0 +1,30 @@
1use crate::diagnostics::{Diagnostic, DiagnosticsContext};
2
3// Diagnostic: break-outside-of-loop
4//
5// This diagnostic is triggered if the `break` keyword is used outside of a loop.
6pub(super) fn break_outside_of_loop(
7 ctx: &DiagnosticsContext<'_>,
8 d: &hir::BreakOutsideOfLoop,
9) -> Diagnostic {
10 Diagnostic::new(
11 "break-outside-of-loop",
12 "break outside of loop",
13 ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
14 )
15}
16
17#[cfg(test)]
18mod tests {
19 use crate::diagnostics::tests::check_diagnostics;
20
21 #[test]
22 fn break_outside_of_loop() {
23 check_diagnostics(
24 r#"
25fn foo() { break; }
26 //^^^^^ break outside of loop
27"#,
28 );
29 }
30}
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs
index f6e45967a..8b1378917 100644
--- a/crates/ide/src/diagnostics/fixes/create_field.rs
+++ b/crates/ide/src/diagnostics/fixes/create_field.rs
@@ -1,156 +1 @@
1use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{
4 ast::{self, edit::IndentLevel, make},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, DiagnosticWithFixes},
11 Assist, AssistResolveStrategy,
12};
13impl DiagnosticWithFixes for NoSuchField {
14 fn fixes(
15 &self,
16 sema: &Semantics<RootDatabase>,
17 _resolve: &AssistResolveStrategy,
18 ) -> Option<Vec<Assist>> {
19 let root = sema.db.parse_or_expand(self.file)?;
20 missing_record_expr_field_fixes(
21 sema,
22 self.file.original_file(sema.db),
23 &self.field.to_node(&root),
24 )
25 }
26}
27
28fn missing_record_expr_field_fixes(
29 sema: &Semantics<RootDatabase>,
30 usage_file_id: FileId,
31 record_expr_field: &ast::RecordExprField,
32) -> Option<Vec<Assist>> {
33 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
34 let def_id = sema.resolve_variant(record_lit)?;
35 let module;
36 let def_file_id;
37 let record_fields = match def_id {
38 hir::VariantDef::Struct(s) => {
39 module = s.module(sema.db);
40 let source = s.source(sema.db)?;
41 def_file_id = source.file_id;
42 let fields = source.value.field_list()?;
43 record_field_list(fields)?
44 }
45 hir::VariantDef::Union(u) => {
46 module = u.module(sema.db);
47 let source = u.source(sema.db)?;
48 def_file_id = source.file_id;
49 source.value.record_field_list()?
50 }
51 hir::VariantDef::Variant(e) => {
52 module = e.module(sema.db);
53 let source = e.source(sema.db)?;
54 def_file_id = source.file_id;
55 let fields = source.value.field_list()?;
56 record_field_list(fields)?
57 }
58 };
59 let def_file_id = def_file_id.original_file(sema.db);
60
61 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
62 if new_field_type.is_unknown() {
63 return None;
64 }
65 let new_field = make::record_field(
66 None,
67 make::name(&record_expr_field.field_name()?.text()),
68 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
69 );
70
71 let last_field = record_fields.fields().last()?;
72 let last_field_syntax = last_field.syntax();
73 let indent = IndentLevel::from_node(last_field_syntax);
74
75 let mut new_field = new_field.to_string();
76 if usage_file_id != def_file_id {
77 new_field = format!("pub(crate) {}", new_field);
78 }
79 new_field = format!("\n{}{}", indent, new_field);
80
81 let needs_comma = !last_field_syntax.to_string().ends_with(',');
82 if needs_comma {
83 new_field = format!(",{}", new_field);
84 }
85
86 let source_change = SourceChange::from_text_edit(
87 def_file_id,
88 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
89 );
90
91 return Some(vec![fix(
92 "create_field",
93 "Create field",
94 source_change,
95 record_expr_field.syntax().text_range(),
96 )]);
97
98 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
99 match field_def_list {
100 ast::FieldList::RecordFieldList(it) => Some(it),
101 ast::FieldList::TupleFieldList(_) => None,
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use crate::diagnostics::tests::check_fix;
109
110 #[test]
111 fn test_add_field_from_usage() {
112 check_fix(
113 r"
114fn main() {
115 Foo { bar: 3, baz$0: false};
116}
117struct Foo {
118 bar: i32
119}
120",
121 r"
122fn main() {
123 Foo { bar: 3, baz: false};
124}
125struct Foo {
126 bar: i32,
127 baz: bool
128}
129",
130 )
131 }
132
133 #[test]
134 fn test_add_field_in_other_file_from_usage() {
135 check_fix(
136 r#"
137//- /main.rs
138mod foo;
139
140fn main() {
141 foo::Foo { bar: 3, $0baz: false};
142}
143//- /foo.rs
144struct Foo {
145 bar: i32
146}
147"#,
148 r#"
149struct Foo {
150 bar: i32,
151 pub(crate) baz: bool
152}
153"#,
154 )
155 }
156}
diff --git a/crates/ide/src/diagnostics/mismatched_arg_count.rs b/crates/ide/src/diagnostics/mismatched_arg_count.rs
new file mode 100644
index 000000000..08e1cfa5f
--- /dev/null
+++ b/crates/ide/src/diagnostics/mismatched_arg_count.rs
@@ -0,0 +1,272 @@
1use crate::diagnostics::{Diagnostic, DiagnosticsContext};
2
3// Diagnostic: mismatched-arg-count
4//
5// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
6pub(super) fn mismatched_arg_count(
7 ctx: &DiagnosticsContext<'_>,
8 d: &hir::MismatchedArgCount,
9) -> Diagnostic {
10 let s = if d.expected == 1 { "" } else { "s" };
11 let message = format!("expected {} argument{}, found {}", d.expected, s, d.found);
12 Diagnostic::new(
13 "mismatched-arg-count",
14 message,
15 ctx.sema.diagnostics_display_range(d.call_expr.clone().map(|it| it.into())).range,
16 )
17}
18
19#[cfg(test)]
20mod tests {
21 use crate::diagnostics::tests::check_diagnostics;
22
23 #[test]
24 fn simple_free_fn_zero() {
25 check_diagnostics(
26 r#"
27fn zero() {}
28fn f() { zero(1); }
29 //^^^^^^^ expected 0 arguments, found 1
30"#,
31 );
32
33 check_diagnostics(
34 r#"
35fn zero() {}
36fn f() { zero(); }
37"#,
38 );
39 }
40
41 #[test]
42 fn simple_free_fn_one() {
43 check_diagnostics(
44 r#"
45fn one(arg: u8) {}
46fn f() { one(); }
47 //^^^^^ expected 1 argument, found 0
48"#,
49 );
50
51 check_diagnostics(
52 r#"
53fn one(arg: u8) {}
54fn f() { one(1); }
55"#,
56 );
57 }
58
59 #[test]
60 fn method_as_fn() {
61 check_diagnostics(
62 r#"
63struct S;
64impl S { fn method(&self) {} }
65
66fn f() {
67 S::method();
68} //^^^^^^^^^^^ expected 1 argument, found 0
69"#,
70 );
71
72 check_diagnostics(
73 r#"
74struct S;
75impl S { fn method(&self) {} }
76
77fn f() {
78 S::method(&S);
79 S.method();
80}
81"#,
82 );
83 }
84
85 #[test]
86 fn method_with_arg() {
87 check_diagnostics(
88 r#"
89struct S;
90impl S { fn method(&self, arg: u8) {} }
91
92 fn f() {
93 S.method();
94 } //^^^^^^^^^^ expected 1 argument, found 0
95 "#,
96 );
97
98 check_diagnostics(
99 r#"
100struct S;
101impl S { fn method(&self, arg: u8) {} }
102
103fn f() {
104 S::method(&S, 0);
105 S.method(1);
106}
107"#,
108 );
109 }
110
111 #[test]
112 fn method_unknown_receiver() {
113 // note: this is incorrect code, so there might be errors on this in the
114 // future, but we shouldn't emit an argument count diagnostic here
115 check_diagnostics(
116 r#"
117trait Foo { fn method(&self, arg: usize) {} }
118
119fn f() {
120 let x;
121 x.method();
122}
123"#,
124 );
125 }
126
127 #[test]
128 fn tuple_struct() {
129 check_diagnostics(
130 r#"
131struct Tup(u8, u16);
132fn f() {
133 Tup(0);
134} //^^^^^^ expected 2 arguments, found 1
135"#,
136 )
137 }
138
139 #[test]
140 fn enum_variant() {
141 check_diagnostics(
142 r#"
143enum En { Variant(u8, u16), }
144fn f() {
145 En::Variant(0);
146} //^^^^^^^^^^^^^^ expected 2 arguments, found 1
147"#,
148 )
149 }
150
151 #[test]
152 fn enum_variant_type_macro() {
153 check_diagnostics(
154 r#"
155macro_rules! Type {
156 () => { u32 };
157}
158enum Foo {
159 Bar(Type![])
160}
161impl Foo {
162 fn new() {
163 Foo::Bar(0);
164 Foo::Bar(0, 1);
165 //^^^^^^^^^^^^^^ expected 1 argument, found 2
166 Foo::Bar();
167 //^^^^^^^^^^ expected 1 argument, found 0
168 }
169}
170 "#,
171 );
172 }
173
174 #[test]
175 fn varargs() {
176 check_diagnostics(
177 r#"
178extern "C" {
179 fn fixed(fixed: u8);
180 fn varargs(fixed: u8, ...);
181 fn varargs2(...);
182}
183
184fn f() {
185 unsafe {
186 fixed(0);
187 fixed(0, 1);
188 //^^^^^^^^^^^ expected 1 argument, found 2
189 varargs(0);
190 varargs(0, 1);
191 varargs2();
192 varargs2(0);
193 varargs2(0, 1);
194 }
195}
196 "#,
197 )
198 }
199
200 #[test]
201 fn arg_count_lambda() {
202 check_diagnostics(
203 r#"
204fn main() {
205 let f = |()| ();
206 f();
207 //^^^ expected 1 argument, found 0
208 f(());
209 f((), ());
210 //^^^^^^^^^ expected 1 argument, found 2
211}
212"#,
213 )
214 }
215
216 #[test]
217 fn cfgd_out_call_arguments() {
218 check_diagnostics(
219 r#"
220struct C(#[cfg(FALSE)] ());
221impl C {
222 fn new() -> Self {
223 Self(
224 #[cfg(FALSE)]
225 (),
226 )
227 }
228
229 fn method(&self) {}
230}
231
232fn main() {
233 C::new().method(#[cfg(FALSE)] 0);
234}
235 "#,
236 );
237 }
238
239 #[test]
240 fn cfgd_out_fn_params() {
241 check_diagnostics(
242 r#"
243fn foo(#[cfg(NEVER)] x: ()) {}
244
245struct S;
246
247impl S {
248 fn method(#[cfg(NEVER)] self) {}
249 fn method2(#[cfg(NEVER)] self, arg: u8) {}
250 fn method3(self, #[cfg(NEVER)] arg: u8) {}
251}
252
253extern "C" {
254 fn fixed(fixed: u8, #[cfg(NEVER)] ...);
255 fn varargs(#[cfg(not(NEVER))] ...);
256}
257
258fn main() {
259 foo();
260 S::method();
261 S::method2(0);
262 S::method3(S);
263 S.method3();
264 unsafe {
265 fixed(0);
266 varargs(1, 2, 3);
267 }
268}
269 "#,
270 )
271 }
272}
diff --git a/crates/ide/src/diagnostics/missing_unsafe.rs b/crates/ide/src/diagnostics/missing_unsafe.rs
new file mode 100644
index 000000000..5c47e8d0a
--- /dev/null
+++ b/crates/ide/src/diagnostics/missing_unsafe.rs
@@ -0,0 +1,101 @@
1use crate::diagnostics::{Diagnostic, DiagnosticsContext};
2
3// Diagnostic: missing-unsafe
4//
5// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
6pub(super) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic {
7 Diagnostic::new(
8 "missing-unsafe",
9 "this operation is unsafe and requires an unsafe function or block",
10 ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
11 )
12}
13
14#[cfg(test)]
15mod tests {
16 use crate::diagnostics::tests::check_diagnostics;
17
18 #[test]
19 fn missing_unsafe_diagnostic_with_raw_ptr() {
20 check_diagnostics(
21 r#"
22fn main() {
23 let x = &5 as *const usize;
24 unsafe { let y = *x; }
25 let z = *x;
26} //^^ this operation is unsafe and requires an unsafe function or block
27"#,
28 )
29 }
30
31 #[test]
32 fn missing_unsafe_diagnostic_with_unsafe_call() {
33 check_diagnostics(
34 r#"
35struct HasUnsafe;
36
37impl HasUnsafe {
38 unsafe fn unsafe_fn(&self) {
39 let x = &5 as *const usize;
40 let y = *x;
41 }
42}
43
44unsafe fn unsafe_fn() {
45 let x = &5 as *const usize;
46 let y = *x;
47}
48
49fn main() {
50 unsafe_fn();
51 //^^^^^^^^^^^ this operation is unsafe and requires an unsafe function or block
52 HasUnsafe.unsafe_fn();
53 //^^^^^^^^^^^^^^^^^^^^^ this operation is unsafe and requires an unsafe function or block
54 unsafe {
55 unsafe_fn();
56 HasUnsafe.unsafe_fn();
57 }
58}
59"#,
60 );
61 }
62
63 #[test]
64 fn missing_unsafe_diagnostic_with_static_mut() {
65 check_diagnostics(
66 r#"
67struct Ty {
68 a: u8,
69}
70
71static mut STATIC_MUT: Ty = Ty { a: 0 };
72
73fn main() {
74 let x = STATIC_MUT.a;
75 //^^^^^^^^^^ this operation is unsafe and requires an unsafe function or block
76 unsafe {
77 let x = STATIC_MUT.a;
78 }
79}
80"#,
81 );
82 }
83
84 #[test]
85 fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
86 check_diagnostics(
87 r#"
88extern "rust-intrinsic" {
89 pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
90 pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
91}
92
93fn main() {
94 let _ = bitreverse(12);
95 let _ = floorf32(12.0);
96 //^^^^^^^^^^^^^^ this operation is unsafe and requires an unsafe function or block
97}
98"#,
99 );
100 }
101}
diff --git a/crates/ide/src/diagnostics/no_such_field.rs b/crates/ide/src/diagnostics/no_such_field.rs
new file mode 100644
index 000000000..61962de28
--- /dev/null
+++ b/crates/ide/src/diagnostics/no_such_field.rs
@@ -0,0 +1,286 @@
1use hir::{db::AstDatabase, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{
4 ast::{self, edit::IndentLevel, make},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, Diagnostic, DiagnosticsContext},
11 Assist,
12};
13
14// Diagnostic: no-such-field
15//
16// This diagnostic is triggered if created structure does not have field provided in record.
17pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
18 Diagnostic::new(
19 "no-such-field",
20 "no such field".to_string(),
21 ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
22 )
23 .with_fixes(fixes(ctx, d))
24}
25
26fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
27 let root = ctx.sema.db.parse_or_expand(d.field.file_id)?;
28 missing_record_expr_field_fixes(
29 &ctx.sema,
30 d.field.file_id.original_file(ctx.sema.db),
31 &d.field.value.to_node(&root),
32 )
33}
34
35fn missing_record_expr_field_fixes(
36 sema: &Semantics<RootDatabase>,
37 usage_file_id: FileId,
38 record_expr_field: &ast::RecordExprField,
39) -> Option<Vec<Assist>> {
40 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
41 let def_id = sema.resolve_variant(record_lit)?;
42 let module;
43 let def_file_id;
44 let record_fields = match def_id {
45 hir::VariantDef::Struct(s) => {
46 module = s.module(sema.db);
47 let source = s.source(sema.db)?;
48 def_file_id = source.file_id;
49 let fields = source.value.field_list()?;
50 record_field_list(fields)?
51 }
52 hir::VariantDef::Union(u) => {
53 module = u.module(sema.db);
54 let source = u.source(sema.db)?;
55 def_file_id = source.file_id;
56 source.value.record_field_list()?
57 }
58 hir::VariantDef::Variant(e) => {
59 module = e.module(sema.db);
60 let source = e.source(sema.db)?;
61 def_file_id = source.file_id;
62 let fields = source.value.field_list()?;
63 record_field_list(fields)?
64 }
65 };
66 let def_file_id = def_file_id.original_file(sema.db);
67
68 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
69 if new_field_type.is_unknown() {
70 return None;
71 }
72 let new_field = make::record_field(
73 None,
74 make::name(&record_expr_field.field_name()?.text()),
75 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
76 );
77
78 let last_field = record_fields.fields().last()?;
79 let last_field_syntax = last_field.syntax();
80 let indent = IndentLevel::from_node(last_field_syntax);
81
82 let mut new_field = new_field.to_string();
83 if usage_file_id != def_file_id {
84 new_field = format!("pub(crate) {}", new_field);
85 }
86 new_field = format!("\n{}{}", indent, new_field);
87
88 let needs_comma = !last_field_syntax.to_string().ends_with(',');
89 if needs_comma {
90 new_field = format!(",{}", new_field);
91 }
92
93 let source_change = SourceChange::from_text_edit(
94 def_file_id,
95 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
96 );
97
98 return Some(vec![fix(
99 "create_field",
100 "Create field",
101 source_change,
102 record_expr_field.syntax().text_range(),
103 )]);
104
105 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
106 match field_def_list {
107 ast::FieldList::RecordFieldList(it) => Some(it),
108 ast::FieldList::TupleFieldList(_) => None,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use crate::diagnostics::tests::{check_diagnostics, check_fix};
116
117 #[test]
118 fn no_such_field_diagnostics() {
119 check_diagnostics(
120 r#"
121struct S { foo: i32, bar: () }
122impl S {
123 fn new() -> S {
124 S {
125 //^ Missing structure fields:
126 //| - bar
127 foo: 92,
128 baz: 62,
129 //^^^^^^^ no such field
130 }
131 }
132}
133"#,
134 );
135 }
136 #[test]
137 fn no_such_field_with_feature_flag_diagnostics() {
138 check_diagnostics(
139 r#"
140//- /lib.rs crate:foo cfg:feature=foo
141struct MyStruct {
142 my_val: usize,
143 #[cfg(feature = "foo")]
144 bar: bool,
145}
146
147impl MyStruct {
148 #[cfg(feature = "foo")]
149 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
150 Self { my_val, bar }
151 }
152 #[cfg(not(feature = "foo"))]
153 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
154 Self { my_val }
155 }
156}
157"#,
158 );
159 }
160
161 #[test]
162 fn no_such_field_enum_with_feature_flag_diagnostics() {
163 check_diagnostics(
164 r#"
165//- /lib.rs crate:foo cfg:feature=foo
166enum Foo {
167 #[cfg(not(feature = "foo"))]
168 Buz,
169 #[cfg(feature = "foo")]
170 Bar,
171 Baz
172}
173
174fn test_fn(f: Foo) {
175 match f {
176 Foo::Bar => {},
177 Foo::Baz => {},
178 }
179}
180"#,
181 );
182 }
183
184 #[test]
185 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
186 check_diagnostics(
187 r#"
188//- /lib.rs crate:foo cfg:feature=foo
189struct S {
190 #[cfg(feature = "foo")]
191 foo: u32,
192 #[cfg(not(feature = "foo"))]
193 bar: u32,
194}
195
196impl S {
197 #[cfg(feature = "foo")]
198 fn new(foo: u32) -> Self {
199 Self { foo }
200 }
201 #[cfg(not(feature = "foo"))]
202 fn new(bar: u32) -> Self {
203 Self { bar }
204 }
205 fn new2(bar: u32) -> Self {
206 #[cfg(feature = "foo")]
207 { Self { foo: bar } }
208 #[cfg(not(feature = "foo"))]
209 { Self { bar } }
210 }
211 fn new2(val: u32) -> Self {
212 Self {
213 #[cfg(feature = "foo")]
214 foo: val,
215 #[cfg(not(feature = "foo"))]
216 bar: val,
217 }
218 }
219}
220"#,
221 );
222 }
223
224 #[test]
225 fn no_such_field_with_type_macro() {
226 check_diagnostics(
227 r#"
228macro_rules! Type { () => { u32 }; }
229struct Foo { bar: Type![] }
230
231impl Foo {
232 fn new() -> Self {
233 Foo { bar: 0 }
234 }
235}
236"#,
237 );
238 }
239
240 #[test]
241 fn test_add_field_from_usage() {
242 check_fix(
243 r"
244fn main() {
245 Foo { bar: 3, baz$0: false};
246}
247struct Foo {
248 bar: i32
249}
250",
251 r"
252fn main() {
253 Foo { bar: 3, baz: false};
254}
255struct Foo {
256 bar: i32,
257 baz: bool
258}
259",
260 )
261 }
262
263 #[test]
264 fn test_add_field_in_other_file_from_usage() {
265 check_fix(
266 r#"
267//- /main.rs
268mod foo;
269
270fn main() {
271 foo::Foo { bar: 3, $0baz: false};
272}
273//- /foo.rs
274struct Foo {
275 bar: i32
276}
277"#,
278 r#"
279struct Foo {
280 bar: i32,
281 pub(crate) baz: bool
282}
283"#,
284 )
285 }
286}
diff --git a/crates/ide/src/diagnostics/unimplemented_builtin_macro.rs b/crates/ide/src/diagnostics/unimplemented_builtin_macro.rs
new file mode 100644
index 000000000..09faa3bbc
--- /dev/null
+++ b/crates/ide/src/diagnostics/unimplemented_builtin_macro.rs
@@ -0,0 +1,19 @@
1use crate::{
2 diagnostics::{Diagnostic, DiagnosticsContext},
3 Severity,
4};
5
6// Diagnostic: unimplemented-builtin-macro
7//
8// This diagnostic is shown for builtin macros which are not yet implemented by rust-analyzer
9pub(super) fn unimplemented_builtin_macro(
10 ctx: &DiagnosticsContext<'_>,
11 d: &hir::UnimplementedBuiltinMacro,
12) -> Diagnostic {
13 Diagnostic::new(
14 "unimplemented-builtin-macro",
15 "unimplemented built-in macro".to_string(),
16 ctx.sema.diagnostics_display_range(d.node.clone()).range,
17 )
18 .severity(Severity::WeakWarning)
19}