aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/diagnostics.rs544
-rw-r--r--crates/ide/src/diagnostics/break_outside_of_loop.rs30
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs33
-rw-r--r--crates/ide/src/diagnostics/fixes.rs31
-rw-r--r--crates/ide/src/diagnostics/fixes/change_case.rs155
-rw-r--r--crates/ide/src/diagnostics/fixes/fill_missing_fields.rs217
-rw-r--r--crates/ide/src/diagnostics/fixes/remove_semicolon.rs41
-rw-r--r--crates/ide/src/diagnostics/fixes/replace_with_find_map.rs84
-rw-r--r--crates/ide/src/diagnostics/inactive_code.rs119
-rw-r--r--crates/ide/src/diagnostics/incorrect_case.rs488
-rw-r--r--crates/ide/src/diagnostics/macro_error.rs173
-rw-r--r--crates/ide/src/diagnostics/mismatched_arg_count.rs272
-rw-r--r--crates/ide/src/diagnostics/missing_fields.rs327
-rw-r--r--crates/ide/src/diagnostics/missing_match_arms.rs929
-rw-r--r--crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs (renamed from crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs)65
-rw-r--r--crates/ide/src/diagnostics/missing_unsafe.rs101
-rw-r--r--crates/ide/src/diagnostics/no_such_field.rs (renamed from crates/ide/src/diagnostics/fixes/create_field.rs)164
-rw-r--r--crates/ide/src/diagnostics/remove_this_semicolon.rs64
-rw-r--r--crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs182
-rw-r--r--crates/ide/src/diagnostics/unimplemented_builtin_macro.rs19
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs259
-rw-r--r--crates/ide/src/diagnostics/unresolved_extern_crate.rs49
-rw-r--r--crates/ide/src/diagnostics/unresolved_import.rs90
-rw-r--r--crates/ide/src/diagnostics/unresolved_macro_call.rs84
-rw-r--r--crates/ide/src/diagnostics/unresolved_module.rs (renamed from crates/ide/src/diagnostics/fixes/unresolved_module.rs)88
-rw-r--r--crates/ide/src/diagnostics/unresolved_proc_macro.rs30
-rw-r--r--crates/ide/src/doc_links.rs22
-rw-r--r--crates/ide/src/extend_selection.rs2
-rw-r--r--crates/ide/src/fixture.rs12
-rw-r--r--crates/ide/src/goto_definition.rs16
-rw-r--r--crates/ide/src/goto_implementation.rs6
-rw-r--r--crates/ide/src/hover.rs12
-rw-r--r--crates/ide/src/inlay_hints.rs6
-rw-r--r--crates/ide/src/join_lines.rs2
-rw-r--r--crates/ide/src/lib.rs12
-rw-r--r--crates/ide/src/prime_caches.rs7
-rw-r--r--crates/ide/src/references.rs2
-rw-r--r--crates/ide/src/references/rename.rs175
-rw-r--r--crates/ide/src/runnables.rs8
-rw-r--r--crates/ide/src/syntax_highlighting.rs4
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs15
-rw-r--r--crates/ide/src/syntax_highlighting/html.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs6
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html4
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs4
-rw-r--r--crates/ide/src/typing/on_enter.rs6
46 files changed, 3809 insertions, 1152 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index d5c954b8b..815a633e5 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -4,69 +4,94 @@
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 fixes; 7mod break_outside_of_loop;
8mod field_shorthand; 8mod inactive_code;
9mod incorrect_case;
10mod macro_error;
11mod mismatched_arg_count;
12mod missing_fields;
13mod missing_match_arms;
14mod missing_ok_or_some_in_tail_expr;
15mod missing_unsafe;
16mod no_such_field;
17mod remove_this_semicolon;
18mod replace_filter_map_next_with_find_map;
19mod unimplemented_builtin_macro;
9mod unlinked_file; 20mod unlinked_file;
21mod unresolved_extern_crate;
22mod unresolved_import;
23mod unresolved_macro_call;
24mod unresolved_module;
25mod unresolved_proc_macro;
10 26
11use std::cell::RefCell; 27mod field_shorthand;
12 28
13use hir::{ 29use hir::{diagnostics::AnyDiagnostic, Semantics};
14 db::AstDatabase,
15 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
16 InFile, Semantics,
17};
18use ide_assists::AssistResolveStrategy; 30use ide_assists::AssistResolveStrategy;
19use ide_db::{base_db::SourceDatabase, RootDatabase}; 31use ide_db::{base_db::SourceDatabase, RootDatabase};
20use itertools::Itertools; 32use itertools::Itertools;
21use rustc_hash::FxHashSet; 33use rustc_hash::FxHashSet;
22use syntax::{ 34use syntax::{
23 ast::{self, AstNode}, 35 ast::{self, AstNode},
24 SyntaxNode, SyntaxNodePtr, TextRange, TextSize, 36 SyntaxNode, TextRange,
25}; 37};
26use text_edit::TextEdit; 38use text_edit::TextEdit;
27use unlinked_file::UnlinkedFile; 39use unlinked_file::UnlinkedFile;
28 40
29use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; 41use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
30 42
31use self::fixes::DiagnosticWithFixes; 43#[derive(Copy, Clone, Debug, PartialEq)]
44pub struct DiagnosticCode(pub &'static str);
45
46impl DiagnosticCode {
47 pub fn as_str(&self) -> &str {
48 self.0
49 }
50}
32 51
33#[derive(Debug)] 52#[derive(Debug)]
34pub struct Diagnostic { 53pub struct Diagnostic {
35 // pub name: Option<String>, 54 pub code: DiagnosticCode,
36 pub message: String, 55 pub message: String,
37 pub range: TextRange, 56 pub range: TextRange,
38 pub severity: Severity, 57 pub severity: Severity,
39 pub fixes: Option<Vec<Assist>>,
40 pub unused: bool, 58 pub unused: bool,
41 pub code: Option<DiagnosticCode>, 59 pub experimental: bool,
60 pub fixes: Option<Vec<Assist>>,
42} 61}
43 62
44impl Diagnostic { 63impl Diagnostic {
45 fn error(range: TextRange, message: String) -> Self { 64 fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic {
46 Self { message, range, severity: Severity::Error, fixes: None, unused: false, code: None } 65 let message = message.into();
47 } 66 Diagnostic {
48 67 code: DiagnosticCode(code),
49 fn hint(range: TextRange, message: String) -> Self {
50 Self {
51 message, 68 message,
52 range, 69 range,
53 severity: Severity::WeakWarning, 70 severity: Severity::Error,
54 fixes: None,
55 unused: false, 71 unused: false,
56 code: None, 72 experimental: false,
73 fixes: None,
57 } 74 }
58 } 75 }
59 76
60 fn with_fixes(self, fixes: Option<Vec<Assist>>) -> Self { 77 fn experimental(mut self) -> Diagnostic {
61 Self { fixes, ..self } 78 self.experimental = true;
79 self
80 }
81
82 fn severity(mut self, severity: Severity) -> Diagnostic {
83 self.severity = severity;
84 self
62 } 85 }
63 86
64 fn with_unused(self, unused: bool) -> Self { 87 fn with_fixes(mut self, fixes: Option<Vec<Assist>>) -> Diagnostic {
65 Self { unused, ..self } 88 self.fixes = fixes;
89 self
66 } 90 }
67 91
68 fn with_code(self, code: Option<DiagnosticCode>) -> Self { 92 fn with_unused(mut self, unused: bool) -> Diagnostic {
69 Self { code, ..self } 93 self.unused = unused;
94 self
70 } 95 }
71} 96}
72 97
@@ -82,6 +107,12 @@ pub struct DiagnosticsConfig {
82 pub disabled: FxHashSet<String>, 107 pub disabled: FxHashSet<String>,
83} 108}
84 109
110struct DiagnosticsContext<'a> {
111 config: &'a DiagnosticsConfig,
112 sema: Semantics<'a, RootDatabase>,
113 resolve: &'a AssistResolveStrategy,
114}
115
85pub(crate) fn diagnostics( 116pub(crate) fn diagnostics(
86 db: &RootDatabase, 117 db: &RootDatabase,
87 config: &DiagnosticsConfig, 118 config: &DiagnosticsConfig,
@@ -95,144 +126,64 @@ pub(crate) fn diagnostics(
95 126
96 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. 127 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
97 res.extend( 128 res.extend(
98 parse 129 parse.errors().iter().take(128).map(|err| {
99 .errors() 130 Diagnostic::new("syntax-error", format!("Syntax Error: {}", err), err.range())
100 .iter() 131 }),
101 .take(128)
102 .map(|err| Diagnostic::error(err.range(), format!("Syntax Error: {}", err))),
103 ); 132 );
104 133
105 for node in parse.tree().syntax().descendants() { 134 for node in parse.tree().syntax().descendants() {
106 check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); 135 check_unnecessary_braces_in_use_statement(&mut res, file_id, &node);
107 field_shorthand::check(&mut res, file_id, &node); 136 field_shorthand::check(&mut res, file_id, &node);
108 } 137 }
109 let res = RefCell::new(res);
110 let sink_builder = DiagnosticSinkBuilder::new()
111 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
112 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
113 })
114 .on::<hir::diagnostics::MissingFields, _>(|d| {
115 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
116 })
117 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
118 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
119 })
120 .on::<hir::diagnostics::NoSuchField, _>(|d| {
121 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
122 })
123 .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
124 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
125 })
126 .on::<hir::diagnostics::IncorrectCase, _>(|d| {
127 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
128 })
129 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
130 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
131 })
132 .on::<hir::diagnostics::InactiveCode, _>(|d| {
133 // If there's inactive code somewhere in a macro, don't propagate to the call-site.
134 if d.display_source().file_id.expansion_info(db).is_some() {
135 return;
136 }
137 138
138 // Override severity and mark as unused. 139 let mut diags = Vec::new();
139 res.borrow_mut().push( 140 let module = sema.to_module_def(file_id);
140 Diagnostic::hint( 141 if let Some(m) = module {
141 sema.diagnostics_display_range(d.display_source()).range, 142 m.diagnostics(db, &mut diags)
142 d.message(),
143 )
144 .with_unused(true)
145 .with_code(Some(d.code())),
146 );
147 })
148 .on::<UnlinkedFile, _>(|d| {
149 // Limit diagnostic to the first few characters in the file. This matches how VS Code
150 // renders it with the full span, but on other editors, and is less invasive.
151 let range = sema.diagnostics_display_range(d.display_source()).range;
152 let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
153
154 // Override severity and mark as unused.
155 res.borrow_mut().push(
156 Diagnostic::hint(range, d.message())
157 .with_fixes(d.fixes(&sema, resolve))
158 .with_code(Some(d.code())),
159 );
160 })
161 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
162 // Use more accurate position if available.
163 let display_range = d
164 .precise_location
165 .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range);
166
167 // FIXME: it would be nice to tell the user whether proc macros are currently disabled
168 res.borrow_mut()
169 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
170 })
171 .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| {
172 let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| {
173 d.node
174 .to_node(&root)
175 .path()
176 .and_then(|it| it.segment())
177 .and_then(|it| it.name_ref())
178 .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax())))
179 });
180 let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source());
181 let display_range = sema.diagnostics_display_range(diagnostics).range;
182 res.borrow_mut()
183 .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code())));
184 })
185 .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| {
186 let display_range = sema.diagnostics_display_range(d.display_source()).range;
187 res.borrow_mut()
188 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
189 })
190 // Only collect experimental diagnostics when they're enabled.
191 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
192 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
193
194 // Finalize the `DiagnosticSink` building process.
195 let mut sink = sink_builder
196 // Diagnostics not handled above get no fix and default treatment.
197 .build(|d| {
198 res.borrow_mut().push(
199 Diagnostic::error(
200 sema.diagnostics_display_range(d.display_source()).range,
201 d.message(),
202 )
203 .with_code(Some(d.code())),
204 );
205 });
206
207 match sema.to_module_def(file_id) {
208 Some(m) => m.diagnostics(db, &mut sink),
209 None => {
210 sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) });
211 }
212 } 143 }
213 144
214 drop(sink); 145 let ctx = DiagnosticsContext { config, sema, resolve };
215 res.into_inner() 146 if module.is_none() {
216} 147 let d = UnlinkedFile { file: file_id };
148 let d = unlinked_file::unlinked_file(&ctx, &d);
149 res.push(d)
150 }
217 151
218fn diagnostic_with_fix<D: DiagnosticWithFixes>( 152 for diag in diags {
219 d: &D, 153 #[rustfmt::skip]
220 sema: &Semantics<RootDatabase>, 154 let d = match diag {
221 resolve: &AssistResolveStrategy, 155 AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d),
222) -> Diagnostic { 156 AnyDiagnostic::IncorrectCase(d) => incorrect_case::incorrect_case(&ctx, &d),
223 Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) 157 AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
224 .with_fixes(d.fixes(&sema, resolve)) 158 AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d),
225 .with_code(Some(d.code())) 159 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
226} 160 AnyDiagnostic::MissingMatchArms(d) => missing_match_arms::missing_match_arms(&ctx, &d),
161 AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d),
162 AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d),
163 AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d),
164 AnyDiagnostic::RemoveThisSemicolon(d) => remove_this_semicolon::remove_this_semicolon(&ctx, &d),
165 AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),
166 AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
167 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
168 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
169 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d),
170 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d),
171 AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d),
172
173 AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) {
174 Some(it) => it,
175 None => continue,
176 }
177 };
178 res.push(d)
179 }
227 180
228fn warning_with_fix<D: DiagnosticWithFixes>( 181 res.retain(|d| {
229 d: &D, 182 !ctx.config.disabled.contains(d.code.as_str())
230 sema: &Semantics<RootDatabase>, 183 && !(ctx.config.disable_experimental && d.experimental)
231 resolve: &AssistResolveStrategy, 184 });
232) -> Diagnostic { 185
233 Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) 186 res
234 .with_fixes(d.fixes(&sema, resolve))
235 .with_code(Some(d.code()))
236} 187}
237 188
238fn check_unnecessary_braces_in_use_statement( 189fn check_unnecessary_braces_in_use_statement(
@@ -260,13 +211,18 @@ fn check_unnecessary_braces_in_use_statement(
260 }); 211 });
261 212
262 acc.push( 213 acc.push(
263 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) 214 Diagnostic::new(
264 .with_fixes(Some(vec![fix( 215 "unnecessary-braces",
265 "remove_braces", 216 "Unnecessary braces in use statement".to_string(),
266 "Remove unnecessary braces", 217 use_range,
267 SourceChange::from_text_edit(file_id, edit), 218 )
268 use_range, 219 .severity(Severity::WeakWarning)
269 )])), 220 .with_fixes(Some(vec![fix(
221 "remove_braces",
222 "Remove unnecessary braces",
223 SourceChange::from_text_edit(file_id, edit),
224 use_range,
225 )])),
270 ); 226 );
271 } 227 }
272 228
@@ -344,8 +300,8 @@ mod tests {
344 ) 300 )
345 .unwrap() 301 .unwrap()
346 .pop() 302 .pop()
347 .unwrap(); 303 .expect("no diagnostics");
348 let fix = &diagnostic.fixes.unwrap()[nth]; 304 let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth];
349 let actual = { 305 let actual = {
350 let source_change = fix.source_change.as_ref().unwrap(); 306 let source_change = fix.source_change.as_ref().unwrap();
351 let file_id = *source_change.source_file_edits.keys().next().unwrap(); 307 let file_id = *source_change.source_file_edits.keys().next().unwrap();
@@ -365,8 +321,9 @@ mod tests {
365 file_position.offset 321 file_position.offset
366 ); 322 );
367 } 323 }
324
368 /// Checks that there's a diagnostic *without* fix at `$0`. 325 /// Checks that there's a diagnostic *without* fix at `$0`.
369 fn check_no_fix(ra_fixture: &str) { 326 pub(crate) fn check_no_fix(ra_fixture: &str) {
370 let (analysis, file_position) = fixture::position(ra_fixture); 327 let (analysis, file_position) = fixture::position(ra_fixture);
371 let diagnostic = analysis 328 let diagnostic = analysis
372 .diagnostics( 329 .diagnostics(
@@ -380,21 +337,6 @@ mod tests {
380 assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic); 337 assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic);
381 } 338 }
382 339
383 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
384 /// apply to the file containing the cursor.
385 pub(crate) fn check_no_diagnostics(ra_fixture: &str) {
386 let (analysis, files) = fixture::files(ra_fixture);
387 let diagnostics = files
388 .into_iter()
389 .flat_map(|file_id| {
390 analysis
391 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
392 .unwrap()
393 })
394 .collect::<Vec<_>>();
395 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
396 }
397
398 pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) { 340 pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
399 let (analysis, file_id) = fixture::file(ra_fixture); 341 let (analysis, file_id) = fixture::file(ra_fixture);
400 let diagnostics = analysis 342 let diagnostics = analysis
@@ -403,90 +345,31 @@ mod tests {
403 expect.assert_debug_eq(&diagnostics) 345 expect.assert_debug_eq(&diagnostics)
404 } 346 }
405 347
348 #[track_caller]
406 pub(crate) fn check_diagnostics(ra_fixture: &str) { 349 pub(crate) fn check_diagnostics(ra_fixture: &str) {
407 let (analysis, file_id) = fixture::file(ra_fixture); 350 let mut config = DiagnosticsConfig::default();
408 let diagnostics = analysis 351 config.disabled.insert("inactive-code".to_string());
409 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) 352 check_diagnostics_with_config(config, ra_fixture)
410 .unwrap();
411
412 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 assert_eq!(expected, actual);
415 }
416
417 #[test]
418 fn test_unresolved_macro_range() {
419 check_diagnostics(
420 r#"
421foo::bar!(92);
422 //^^^ unresolved macro `foo::bar!`
423"#,
424 );
425 }
426
427 #[test]
428 fn unresolved_import_in_use_tree() {
429 // Only the relevant part of a nested `use` item should be highlighted.
430 check_diagnostics(
431 r#"
432use does_exist::{Exists, DoesntExist};
433 //^^^^^^^^^^^ unresolved import
434
435use {does_not_exist::*, does_exist};
436 //^^^^^^^^^^^^^^^^^ unresolved import
437
438use does_not_exist::{
439 a,
440 //^ unresolved import
441 b,
442 //^ unresolved import
443 c,
444 //^ unresolved import
445};
446
447mod does_exist {
448 pub struct Exists;
449}
450"#,
451 );
452 } 353 }
453 354
454 #[test] 355 #[track_caller]
455 fn range_mapping_out_of_macros() { 356 pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) {
456 // FIXME: this is very wrong, but somewhat tricky to fix. 357 let (analysis, files) = fixture::files(ra_fixture);
457 check_fix( 358 for file_id in files {
458 r#" 359 let diagnostics =
459fn some() {} 360 analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap();
460fn items() {} 361
461fn here() {} 362 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
462 363 let mut actual =
463macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } 364 diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>();
464 365 actual.sort_by_key(|(range, _)| range.start());
465fn main() { 366 assert_eq!(expected, actual);
466 let _x = id![Foo { a: $042 }]; 367 }
467}
468
469pub struct Foo { pub a: i32, pub b: i32 }
470"#,
471 r#"
472fn some(, b: () ) {}
473fn items() {}
474fn here() {}
475
476macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
477
478fn main() {
479 let _x = id![Foo { a: 42 }];
480}
481
482pub struct Foo { pub a: i32, pub b: i32 }
483"#,
484 );
485 } 368 }
486 369
487 #[test] 370 #[test]
488 fn test_check_unnecessary_braces_in_use_statement() { 371 fn test_check_unnecessary_braces_in_use_statement() {
489 check_no_diagnostics( 372 check_diagnostics(
490 r#" 373 r#"
491use a; 374use a;
492use a::{c, d::e}; 375use a::{c, d::e};
@@ -499,7 +382,7 @@ mod a {
499} 382}
500"#, 383"#,
501 ); 384 );
502 check_no_diagnostics( 385 check_diagnostics(
503 r#" 386 r#"
504use a; 387use a;
505use a::{ 388use a::{
@@ -585,138 +468,31 @@ mod a {
585 } 468 }
586 469
587 #[test] 470 #[test]
588 fn unlinked_file_prepend_first_item() { 471 fn import_extern_crate_clash_with_inner_item() {
589 cov_mark::check!(unlinked_file_prepend_before_first_item); 472 // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
590 // Only tests the first one for `pub mod` since the rest are the same
591 check_fixes(
592 r#"
593//- /main.rs
594fn f() {}
595//- /foo.rs
596$0
597"#,
598 vec![
599 r#"
600mod foo;
601
602fn f() {}
603"#,
604 r#"
605pub mod foo;
606
607fn f() {}
608"#,
609 ],
610 );
611 }
612
613 #[test]
614 fn unlinked_file_append_mod() {
615 cov_mark::check!(unlinked_file_append_to_existing_mods);
616 check_fix(
617 r#"
618//- /main.rs
619//! Comment on top
620
621mod preexisting;
622
623mod preexisting2;
624
625struct S;
626
627mod preexisting_bottom;)
628//- /foo.rs
629$0
630"#,
631 r#"
632//! Comment on top
633 473
634mod preexisting; 474 check_diagnostics(
635
636mod preexisting2;
637mod foo;
638
639struct S;
640
641mod preexisting_bottom;)
642"#,
643 );
644 }
645
646 #[test]
647 fn unlinked_file_insert_in_empty_file() {
648 cov_mark::check!(unlinked_file_empty_file);
649 check_fix(
650 r#"
651//- /main.rs
652//- /foo.rs
653$0
654"#,
655 r#"
656mod foo;
657"#,
658 );
659 }
660
661 #[test]
662 fn unlinked_file_old_style_modrs() {
663 check_fix(
664 r#"
665//- /main.rs
666mod submod;
667//- /submod/mod.rs
668// in mod.rs
669//- /submod/foo.rs
670$0
671"#,
672 r#"
673// in mod.rs
674mod foo;
675"#,
676 );
677 }
678
679 #[test]
680 fn unlinked_file_new_style_mod() {
681 check_fix(
682 r#"
683//- /main.rs
684mod submod;
685//- /submod.rs
686//- /submod/foo.rs
687$0
688"#,
689 r#" 475 r#"
690mod foo; 476//- /lib.rs crate:lib deps:jwt
691"#, 477mod permissions;
692 );
693 }
694 478
695 #[test] 479use permissions::jwt;
696 fn unlinked_file_with_cfg_off() {
697 cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
698 check_no_fix(
699 r#"
700//- /main.rs
701#[cfg(never)]
702mod foo;
703 480
704//- /foo.rs 481fn f() {
705$0 482 fn inner() {}
706"#, 483 jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
707 ); 484}
708 }
709 485
710 #[test] 486//- /permissions.rs
711 fn unlinked_file_with_cfg_on() { 487pub mod jwt {
712 check_no_diagnostics( 488 pub struct Claims {}
713 r#" 489}
714//- /main.rs
715#[cfg(not(never))]
716mod foo;
717 490
718//- /foo.rs 491//- /jwt/lib.rs crate:jwt
719"#, 492pub struct Claims {
493 field: u8,
494}
495 "#,
720 ); 496 );
721 } 497 }
722} 498}
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/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs
index 01bd2dba6..c7f4dab8e 100644
--- a/crates/ide/src/diagnostics/field_shorthand.rs
+++ b/crates/ide/src/diagnostics/field_shorthand.rs
@@ -5,7 +5,7 @@ use ide_db::{base_db::FileId, source_change::SourceChange};
5use syntax::{ast, match_ast, AstNode, SyntaxNode}; 5use syntax::{ast, match_ast, AstNode, SyntaxNode};
6use text_edit::TextEdit; 6use text_edit::TextEdit;
7 7
8use crate::{diagnostics::fix, Diagnostic}; 8use crate::{diagnostics::fix, Diagnostic, Severity};
9 9
10pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { 10pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
11 match_ast! { 11 match_ast! {
@@ -46,7 +46,8 @@ fn check_expr_field_shorthand(
46 46
47 let field_range = record_field.syntax().text_range(); 47 let field_range = record_field.syntax().text_range();
48 acc.push( 48 acc.push(
49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()) 49 Diagnostic::new("use-field-shorthand", "Shorthand struct initialization", field_range)
50 .severity(Severity::WeakWarning)
50 .with_fixes(Some(vec![fix( 51 .with_fixes(Some(vec![fix(
51 "use_expr_field_shorthand", 52 "use_expr_field_shorthand",
52 "Use struct shorthand initialization", 53 "Use struct shorthand initialization",
@@ -85,30 +86,32 @@ fn check_pat_field_shorthand(
85 let edit = edit_builder.finish(); 86 let edit = edit_builder.finish();
86 87
87 let field_range = record_pat_field.syntax().text_range(); 88 let field_range = record_pat_field.syntax().text_range();
88 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fixes( 89 acc.push(
89 Some(vec![fix( 90 Diagnostic::new("use-field-shorthand", "Shorthand struct pattern", field_range)
90 "use_pat_field_shorthand", 91 .severity(Severity::WeakWarning)
91 "Use struct field shorthand", 92 .with_fixes(Some(vec![fix(
92 SourceChange::from_text_edit(file_id, edit), 93 "use_pat_field_shorthand",
93 field_range, 94 "Use struct field shorthand",
94 )]), 95 SourceChange::from_text_edit(file_id, edit),
95 )); 96 field_range,
97 )])),
98 );
96 } 99 }
97} 100}
98 101
99#[cfg(test)] 102#[cfg(test)]
100mod tests { 103mod tests {
101 use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; 104 use crate::diagnostics::tests::{check_diagnostics, check_fix};
102 105
103 #[test] 106 #[test]
104 fn test_check_expr_field_shorthand() { 107 fn test_check_expr_field_shorthand() {
105 check_no_diagnostics( 108 check_diagnostics(
106 r#" 109 r#"
107struct A { a: &'static str } 110struct A { a: &'static str }
108fn main() { A { a: "hello" } } 111fn main() { A { a: "hello" } }
109"#, 112"#,
110 ); 113 );
111 check_no_diagnostics( 114 check_diagnostics(
112 r#" 115 r#"
113struct A(usize); 116struct A(usize);
114fn main() { A { 0: 0 } } 117fn main() { A { 0: 0 } }
@@ -154,13 +157,13 @@ fn main() {
154 157
155 #[test] 158 #[test]
156 fn test_check_pat_field_shorthand() { 159 fn test_check_pat_field_shorthand() {
157 check_no_diagnostics( 160 check_diagnostics(
158 r#" 161 r#"
159struct A { a: &'static str } 162struct A { a: &'static str }
160fn f(a: A) { let A { a: hello } = a; } 163fn f(a: A) { let A { a: hello } = a; }
161"#, 164"#,
162 ); 165 );
163 check_no_diagnostics( 166 check_diagnostics(
164 r#" 167 r#"
165struct A(usize); 168struct A(usize);
166fn f(a: A) { let A { 0: 0 } = a; } 169fn f(a: A) { let A { 0: 0 } = a; }
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
deleted file mode 100644
index 258ac6974..000000000
--- a/crates/ide/src/diagnostics/fixes.rs
+++ /dev/null
@@ -1,31 +0,0 @@
1//! Provides a way to attach fixes to the diagnostics.
2//! The same module also has all curret custom fixes for the diagnostics implemented.
3mod change_case;
4mod create_field;
5mod fill_missing_fields;
6mod remove_semicolon;
7mod replace_with_find_map;
8mod unresolved_module;
9mod wrap_tail_expr;
10
11use hir::{diagnostics::Diagnostic, Semantics};
12use ide_assists::AssistResolveStrategy;
13use ide_db::RootDatabase;
14
15use crate::Assist;
16
17/// A [Diagnostic] that potentially has some fixes available.
18///
19/// [Diagnostic]: hir::diagnostics::Diagnostic
20pub(crate) trait DiagnosticWithFixes: Diagnostic {
21 /// `resolve` determines if the diagnostic should fill in the `edit` field
22 /// of the assist.
23 ///
24 /// If `resolve` is false, the edit will be computed later, on demand, and
25 /// can be omitted.
26 fn fixes(
27 &self,
28 sema: &Semantics<RootDatabase>,
29 _resolve: &AssistResolveStrategy,
30 ) -> Option<Vec<Assist>>;
31}
diff --git a/crates/ide/src/diagnostics/fixes/change_case.rs b/crates/ide/src/diagnostics/fixes/change_case.rs
deleted file mode 100644
index 42be3375f..000000000
--- a/crates/ide/src/diagnostics/fixes/change_case.rs
+++ /dev/null
@@ -1,155 +0,0 @@
1use hir::{db::AstDatabase, diagnostics::IncorrectCase, InFile, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{base_db::FilePosition, RootDatabase};
4use syntax::AstNode;
5
6use crate::{
7 diagnostics::{unresolved_fix, DiagnosticWithFixes},
8 references::rename::rename_with_semantics,
9};
10
11impl DiagnosticWithFixes for IncorrectCase {
12 fn fixes(
13 &self,
14 sema: &Semantics<RootDatabase>,
15 resolve: &AssistResolveStrategy,
16 ) -> Option<Vec<Assist>> {
17 let root = sema.db.parse_or_expand(self.file)?;
18 let name_node = self.ident.to_node(&root);
19
20 let name_node = InFile::new(self.file, name_node.syntax());
21 let frange = name_node.original_file_range(sema.db);
22 let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
23
24 let label = format!("Rename to {}", self.suggested_text);
25 let mut res = unresolved_fix("change_case", &label, frange.range);
26 if resolve.should_resolve(&res.id) {
27 let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
28 res.source_change = Some(source_change.ok().unwrap_or_default());
29 }
30
31 Some(vec![res])
32 }
33}
34
35#[cfg(test)]
36mod change_case {
37 use crate::{
38 diagnostics::tests::{check_fix, check_no_diagnostics},
39 fixture, AssistResolveStrategy, DiagnosticsConfig,
40 };
41
42 #[test]
43 fn test_rename_incorrect_case() {
44 check_fix(
45 r#"
46pub struct test_struct$0 { one: i32 }
47
48pub fn some_fn(val: test_struct) -> test_struct {
49 test_struct { one: val.one + 1 }
50}
51"#,
52 r#"
53pub struct TestStruct { one: i32 }
54
55pub fn some_fn(val: TestStruct) -> TestStruct {
56 TestStruct { one: val.one + 1 }
57}
58"#,
59 );
60
61 check_fix(
62 r#"
63pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
64 NonSnakeCase
65}
66"#,
67 r#"
68pub fn some_fn(non_snake_case: u8) -> u8 {
69 non_snake_case
70}
71"#,
72 );
73
74 check_fix(
75 r#"
76pub fn SomeFn$0(val: u8) -> u8 {
77 if val != 0 { SomeFn(val - 1) } else { val }
78}
79"#,
80 r#"
81pub fn some_fn(val: u8) -> u8 {
82 if val != 0 { some_fn(val - 1) } else { val }
83}
84"#,
85 );
86
87 check_fix(
88 r#"
89fn some_fn() {
90 let whatAWeird_Formatting$0 = 10;
91 another_func(whatAWeird_Formatting);
92}
93"#,
94 r#"
95fn some_fn() {
96 let what_a_weird_formatting = 10;
97 another_func(what_a_weird_formatting);
98}
99"#,
100 );
101 }
102
103 #[test]
104 fn test_uppercase_const_no_diagnostics() {
105 check_no_diagnostics(
106 r#"
107fn foo() {
108 const ANOTHER_ITEM$0: &str = "some_item";
109}
110"#,
111 );
112 }
113
114 #[test]
115 fn test_rename_incorrect_case_struct_method() {
116 check_fix(
117 r#"
118pub struct TestStruct;
119
120impl TestStruct {
121 pub fn SomeFn$0() -> TestStruct {
122 TestStruct
123 }
124}
125"#,
126 r#"
127pub struct TestStruct;
128
129impl TestStruct {
130 pub fn some_fn() -> TestStruct {
131 TestStruct
132 }
133}
134"#,
135 );
136 }
137
138 #[test]
139 fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
140 let input = r#"fn FOO$0() {}"#;
141 let expected = r#"fn foo() {}"#;
142
143 let (analysis, file_position) = fixture::position(input);
144 let diagnostics = analysis
145 .diagnostics(
146 &DiagnosticsConfig::default(),
147 AssistResolveStrategy::All,
148 file_position.file_id,
149 )
150 .unwrap();
151 assert_eq!(diagnostics.len(), 1);
152
153 check_fix(input, expected);
154 }
155}
diff --git a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs b/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
deleted file mode 100644
index b5dd64c08..000000000
--- a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
+++ /dev/null
@@ -1,217 +0,0 @@
1use hir::{db::AstDatabase, diagnostics::MissingFields, Semantics};
2use ide_assists::AssistResolveStrategy;
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{algo, ast::make, AstNode};
5use text_edit::TextEdit;
6
7use crate::{
8 diagnostics::{fix, fixes::DiagnosticWithFixes},
9 Assist,
10};
11
12impl DiagnosticWithFixes for MissingFields {
13 fn fixes(
14 &self,
15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy,
17 ) -> Option<Vec<Assist>> {
18 // Note that although we could add a diagnostics to
19 // fill the missing tuple field, e.g :
20 // `struct A(usize);`
21 // `let a = A { 0: () }`
22 // but it is uncommon usage and it should not be encouraged.
23 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
24 return None;
25 }
26
27 let root = sema.db.parse_or_expand(self.file)?;
28 let field_list_parent = self.field_list_parent.to_node(&root);
29 let old_field_list = field_list_parent.record_expr_field_list()?;
30 let new_field_list = old_field_list.clone_for_update();
31 for f in self.missed_fields.iter() {
32 let field =
33 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
34 .clone_for_update();
35 new_field_list.add_field(field);
36 }
37
38 let edit = {
39 let mut builder = TextEdit::builder();
40 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
41 .into_text_edit(&mut builder);
42 builder.finish()
43 };
44 Some(vec![fix(
45 "fill_missing_fields",
46 "Fill struct fields",
47 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
48 sema.original_range(&field_list_parent.syntax()).range,
49 )])
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
56
57 #[test]
58 fn test_fill_struct_fields_empty() {
59 check_fix(
60 r#"
61struct TestStruct { one: i32, two: i64 }
62
63fn test_fn() {
64 let s = TestStruct {$0};
65}
66"#,
67 r#"
68struct TestStruct { one: i32, two: i64 }
69
70fn test_fn() {
71 let s = TestStruct { one: (), two: () };
72}
73"#,
74 );
75 }
76
77 #[test]
78 fn test_fill_struct_fields_self() {
79 check_fix(
80 r#"
81struct TestStruct { one: i32 }
82
83impl TestStruct {
84 fn test_fn() { let s = Self {$0}; }
85}
86"#,
87 r#"
88struct TestStruct { one: i32 }
89
90impl TestStruct {
91 fn test_fn() { let s = Self { one: () }; }
92}
93"#,
94 );
95 }
96
97 #[test]
98 fn test_fill_struct_fields_enum() {
99 check_fix(
100 r#"
101enum Expr {
102 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
103}
104
105impl Expr {
106 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
107 Expr::Bin {$0 }
108 }
109}
110"#,
111 r#"
112enum Expr {
113 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
114}
115
116impl Expr {
117 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
118 Expr::Bin { lhs: (), rhs: () }
119 }
120}
121"#,
122 );
123 }
124
125 #[test]
126 fn test_fill_struct_fields_partial() {
127 check_fix(
128 r#"
129struct TestStruct { one: i32, two: i64 }
130
131fn test_fn() {
132 let s = TestStruct{ two: 2$0 };
133}
134"#,
135 r"
136struct TestStruct { one: i32, two: i64 }
137
138fn test_fn() {
139 let s = TestStruct{ two: 2, one: () };
140}
141",
142 );
143 }
144
145 #[test]
146 fn test_fill_struct_fields_raw_ident() {
147 check_fix(
148 r#"
149struct TestStruct { r#type: u8 }
150
151fn test_fn() {
152 TestStruct { $0 };
153}
154"#,
155 r"
156struct TestStruct { r#type: u8 }
157
158fn test_fn() {
159 TestStruct { r#type: () };
160}
161",
162 );
163 }
164
165 #[test]
166 fn test_fill_struct_fields_no_diagnostic() {
167 check_no_diagnostics(
168 r#"
169struct TestStruct { one: i32, two: i64 }
170
171fn test_fn() {
172 let one = 1;
173 let s = TestStruct{ one, two: 2 };
174}
175 "#,
176 );
177 }
178
179 #[test]
180 fn test_fill_struct_fields_no_diagnostic_on_spread() {
181 check_no_diagnostics(
182 r#"
183struct TestStruct { one: i32, two: i64 }
184
185fn test_fn() {
186 let one = 1;
187 let s = TestStruct{ ..a };
188}
189"#,
190 );
191 }
192
193 #[test]
194 fn test_fill_struct_fields_blank_line() {
195 check_fix(
196 r#"
197struct S { a: (), b: () }
198
199fn f() {
200 S {
201 $0
202 };
203}
204"#,
205 r#"
206struct S { a: (), b: () }
207
208fn f() {
209 S {
210 a: (),
211 b: (),
212 };
213}
214"#,
215 );
216 }
217}
diff --git a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
deleted file mode 100644
index f1724d479..000000000
--- a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
+++ /dev/null
@@ -1,41 +0,0 @@
1use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{ast, AstNode};
5use text_edit::TextEdit;
6
7use crate::diagnostics::{fix, DiagnosticWithFixes};
8
9impl DiagnosticWithFixes for RemoveThisSemicolon {
10 fn fixes(
11 &self,
12 sema: &Semantics<RootDatabase>,
13 _resolve: &AssistResolveStrategy,
14 ) -> Option<Vec<Assist>> {
15 let root = sema.db.parse_or_expand(self.file)?;
16
17 let semicolon = self
18 .expr
19 .to_node(&root)
20 .syntax()
21 .parent()
22 .and_then(ast::ExprStmt::cast)
23 .and_then(|expr| expr.semicolon_token())?
24 .text_range();
25
26 let edit = TextEdit::delete(semicolon);
27 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
28
29 Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)])
30 }
31}
32
33#[cfg(test)]
34mod tests {
35 use crate::diagnostics::tests::check_fix;
36
37 #[test]
38 fn remove_semicolon() {
39 check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
40 }
41}
diff --git a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
deleted file mode 100644
index 444bf563b..000000000
--- a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
+++ /dev/null
@@ -1,84 +0,0 @@
1use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{
5 ast::{self, ArgListOwner},
6 AstNode, TextRange,
7};
8use text_edit::TextEdit;
9
10use crate::diagnostics::{fix, DiagnosticWithFixes};
11
12impl DiagnosticWithFixes for ReplaceFilterMapNextWithFindMap {
13 fn fixes(
14 &self,
15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy,
17 ) -> Option<Vec<Assist>> {
18 let root = sema.db.parse_or_expand(self.file)?;
19 let next_expr = self.next_expr.to_node(&root);
20 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
21
22 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
23 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
24 let filter_map_args = filter_map_call.arg_list()?;
25
26 let range_to_replace =
27 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
28 let replacement = format!("find_map{}", filter_map_args.syntax().text());
29 let trigger_range = next_expr.syntax().text_range();
30
31 let edit = TextEdit::replace(range_to_replace, replacement);
32
33 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
34
35 Some(vec![fix(
36 "replace_with_find_map",
37 "Replace filter_map(..).next() with find_map()",
38 source_change,
39 trigger_range,
40 )])
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use crate::diagnostics::tests::check_fix;
47
48 #[test]
49 fn replace_with_wind_map() {
50 check_fix(
51 r#"
52//- /main.rs crate:main deps:core
53use core::iter::Iterator;
54use core::option::Option::{self, Some, None};
55fn foo() {
56 let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
57}
58//- /core/lib.rs crate:core
59pub mod option {
60 pub enum Option<T> { Some(T), None }
61}
62pub mod iter {
63 pub trait Iterator {
64 type Item;
65 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
66 fn next(&mut self) -> Option<Self::Item>;
67 }
68 pub struct FilterMap {}
69 impl Iterator for FilterMap {
70 type Item = i32;
71 fn next(&mut self) -> i32 { 7 }
72 }
73}
74"#,
75 r#"
76use core::iter::Iterator;
77use core::option::Option::{self, Some, None};
78fn foo() {
79 let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None });
80}
81"#,
82 )
83 }
84}
diff --git a/crates/ide/src/diagnostics/inactive_code.rs b/crates/ide/src/diagnostics/inactive_code.rs
new file mode 100644
index 000000000..d9d3e88c1
--- /dev/null
+++ b/crates/ide/src/diagnostics/inactive_code.rs
@@ -0,0 +1,119 @@
1use cfg::DnfExpr;
2use stdx::format_to;
3
4use crate::{
5 diagnostics::{Diagnostic, DiagnosticsContext},
6 Severity,
7};
8
9// Diagnostic: inactive-code
10//
11// This diagnostic is shown for code with inactive `#[cfg]` attributes.
12pub(super) fn inactive_code(
13 ctx: &DiagnosticsContext<'_>,
14 d: &hir::InactiveCode,
15) -> Option<Diagnostic> {
16 // If there's inactive code somewhere in a macro, don't propagate to the call-site.
17 if d.node.file_id.expansion_info(ctx.sema.db).is_some() {
18 return None;
19 }
20
21 let inactive = DnfExpr::new(d.cfg.clone()).why_inactive(&d.opts);
22 let mut message = "code is inactive due to #[cfg] directives".to_string();
23
24 if let Some(inactive) = inactive {
25 format_to!(message, ": {}", inactive);
26 }
27
28 let res = Diagnostic::new(
29 "inactive-code",
30 message,
31 ctx.sema.diagnostics_display_range(d.node.clone()).range,
32 )
33 .severity(Severity::WeakWarning)
34 .with_unused(true);
35 Some(res)
36}
37
38#[cfg(test)]
39mod tests {
40 use crate::{diagnostics::tests::check_diagnostics_with_config, DiagnosticsConfig};
41
42 pub(crate) fn check(ra_fixture: &str) {
43 let config = DiagnosticsConfig::default();
44 check_diagnostics_with_config(config, ra_fixture)
45 }
46
47 #[test]
48 fn cfg_diagnostics() {
49 check(
50 r#"
51fn f() {
52 // The three g̶e̶n̶d̶e̶r̶s̶ statements:
53
54 #[cfg(a)] fn f() {} // Item statement
55 //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
56 #[cfg(a)] {} // Expression statement
57 //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
58 #[cfg(a)] let x = 0; // let statement
59 //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
60
61 abc(#[cfg(a)] 0);
62 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
63 let x = Struct {
64 #[cfg(a)] f: 0,
65 //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
66 };
67 match () {
68 () => (),
69 #[cfg(a)] () => (),
70 //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
71 }
72
73 #[cfg(a)] 0 // Trailing expression of block
74 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
75}
76 "#,
77 );
78 }
79
80 #[test]
81 fn inactive_item() {
82 // Additional tests in `cfg` crate. This only tests disabled cfgs.
83
84 check(
85 r#"
86 #[cfg(no)] pub fn f() {}
87 //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
88
89 #[cfg(no)] #[cfg(no2)] mod m;
90 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
91
92 #[cfg(all(not(a), b))] enum E {}
93 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
94
95 #[cfg(feature = "std")] use std;
96 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
97"#,
98 );
99 }
100
101 /// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
102 #[test]
103 fn inactive_via_cfg_attr() {
104 cov_mark::check!(cfg_attr_active);
105 check(
106 r#"
107 #[cfg_attr(not(never), cfg(no))] fn f() {}
108 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
109
110 #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
111
112 #[cfg_attr(never, cfg(no))] fn g() {}
113
114 #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
115 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
116"#,
117 );
118 }
119}
diff --git a/crates/ide/src/diagnostics/incorrect_case.rs b/crates/ide/src/diagnostics/incorrect_case.rs
new file mode 100644
index 000000000..832394400
--- /dev/null
+++ b/crates/ide/src/diagnostics/incorrect_case.rs
@@ -0,0 +1,488 @@
1use hir::{db::AstDatabase, InFile};
2use ide_assists::Assist;
3use ide_db::base_db::FilePosition;
4use syntax::AstNode;
5
6use crate::{
7 diagnostics::{unresolved_fix, Diagnostic, DiagnosticsContext},
8 references::rename::rename_with_semantics,
9 Severity,
10};
11
12// Diagnostic: incorrect-ident-case
13//
14// This diagnostic is triggered if an item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention].
15pub(super) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Diagnostic {
16 Diagnostic::new(
17 "incorrect-ident-case",
18 format!(
19 "{} `{}` should have {} name, e.g. `{}`",
20 d.ident_type, d.ident_text, d.expected_case, d.suggested_text
21 ),
22 ctx.sema.diagnostics_display_range(InFile::new(d.file, d.ident.clone().into())).range,
23 )
24 .severity(Severity::WeakWarning)
25 .with_fixes(fixes(ctx, d))
26}
27
28fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Assist>> {
29 let root = ctx.sema.db.parse_or_expand(d.file)?;
30 let name_node = d.ident.to_node(&root);
31
32 let name_node = InFile::new(d.file, name_node.syntax());
33 let frange = name_node.original_file_range(ctx.sema.db);
34 let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
35
36 let label = format!("Rename to {}", d.suggested_text);
37 let mut res = unresolved_fix("change_case", &label, frange.range);
38 if ctx.resolve.should_resolve(&res.id) {
39 let source_change = rename_with_semantics(&ctx.sema, file_position, &d.suggested_text);
40 res.source_change = Some(source_change.ok().unwrap_or_default());
41 }
42
43 Some(vec![res])
44}
45
46#[cfg(test)]
47mod change_case {
48 use crate::{
49 diagnostics::tests::{check_diagnostics, check_fix},
50 fixture, AssistResolveStrategy, DiagnosticsConfig,
51 };
52
53 #[test]
54 fn test_rename_incorrect_case() {
55 check_fix(
56 r#"
57pub struct test_struct$0 { one: i32 }
58
59pub fn some_fn(val: test_struct) -> test_struct {
60 test_struct { one: val.one + 1 }
61}
62"#,
63 r#"
64pub struct TestStruct { one: i32 }
65
66pub fn some_fn(val: TestStruct) -> TestStruct {
67 TestStruct { one: val.one + 1 }
68}
69"#,
70 );
71
72 check_fix(
73 r#"
74pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
75 NonSnakeCase
76}
77"#,
78 r#"
79pub fn some_fn(non_snake_case: u8) -> u8 {
80 non_snake_case
81}
82"#,
83 );
84
85 check_fix(
86 r#"
87pub fn SomeFn$0(val: u8) -> u8 {
88 if val != 0 { SomeFn(val - 1) } else { val }
89}
90"#,
91 r#"
92pub fn some_fn(val: u8) -> u8 {
93 if val != 0 { some_fn(val - 1) } else { val }
94}
95"#,
96 );
97
98 check_fix(
99 r#"
100fn some_fn() {
101 let whatAWeird_Formatting$0 = 10;
102 another_func(whatAWeird_Formatting);
103}
104"#,
105 r#"
106fn some_fn() {
107 let what_a_weird_formatting = 10;
108 another_func(what_a_weird_formatting);
109}
110"#,
111 );
112 }
113
114 #[test]
115 fn test_uppercase_const_no_diagnostics() {
116 check_diagnostics(
117 r#"
118fn foo() {
119 const ANOTHER_ITEM$0: &str = "some_item";
120}
121"#,
122 );
123 }
124
125 #[test]
126 fn test_rename_incorrect_case_struct_method() {
127 check_fix(
128 r#"
129pub struct TestStruct;
130
131impl TestStruct {
132 pub fn SomeFn$0() -> TestStruct {
133 TestStruct
134 }
135}
136"#,
137 r#"
138pub struct TestStruct;
139
140impl TestStruct {
141 pub fn some_fn() -> TestStruct {
142 TestStruct
143 }
144}
145"#,
146 );
147 }
148
149 #[test]
150 fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
151 let input = r#"fn FOO$0() {}"#;
152 let expected = r#"fn foo() {}"#;
153
154 let (analysis, file_position) = fixture::position(input);
155 let diagnostics = analysis
156 .diagnostics(
157 &DiagnosticsConfig::default(),
158 AssistResolveStrategy::All,
159 file_position.file_id,
160 )
161 .unwrap();
162 assert_eq!(diagnostics.len(), 1);
163
164 check_fix(input, expected);
165 }
166
167 #[test]
168 fn incorrect_function_name() {
169 check_diagnostics(
170 r#"
171fn NonSnakeCaseName() {}
172// ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
173"#,
174 );
175 }
176
177 #[test]
178 fn incorrect_function_params() {
179 check_diagnostics(
180 r#"
181fn foo(SomeParam: u8) {}
182 // ^^^^^^^^^ Parameter `SomeParam` should have snake_case name, e.g. `some_param`
183
184fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
185 // ^^^^^^^^^^ Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
186"#,
187 );
188 }
189
190 #[test]
191 fn incorrect_variable_names() {
192 check_diagnostics(
193 r#"
194fn foo() {
195 let SOME_VALUE = 10;
196 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
197 let AnotherValue = 20;
198 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
199}
200"#,
201 );
202 }
203
204 #[test]
205 fn incorrect_struct_names() {
206 check_diagnostics(
207 r#"
208struct non_camel_case_name {}
209 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
210
211struct SCREAMING_CASE {}
212 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
213"#,
214 );
215 }
216
217 #[test]
218 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
219 check_diagnostics(
220 r#"
221struct AABB {}
222"#,
223 );
224 }
225
226 #[test]
227 fn incorrect_struct_field() {
228 check_diagnostics(
229 r#"
230struct SomeStruct { SomeField: u8 }
231 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
232"#,
233 );
234 }
235
236 #[test]
237 fn incorrect_enum_names() {
238 check_diagnostics(
239 r#"
240enum some_enum { Val(u8) }
241 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
242
243enum SOME_ENUM {}
244 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
245"#,
246 );
247 }
248
249 #[test]
250 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
251 check_diagnostics(
252 r#"
253enum AABB {}
254"#,
255 );
256 }
257
258 #[test]
259 fn incorrect_enum_variant_name() {
260 check_diagnostics(
261 r#"
262enum SomeEnum { SOME_VARIANT(u8) }
263 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
264"#,
265 );
266 }
267
268 #[test]
269 fn incorrect_const_name() {
270 check_diagnostics(
271 r#"
272const some_weird_const: u8 = 10;
273 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
274"#,
275 );
276 }
277
278 #[test]
279 fn incorrect_static_name() {
280 check_diagnostics(
281 r#"
282static some_weird_const: u8 = 10;
283 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
284"#,
285 );
286 }
287
288 #[test]
289 fn fn_inside_impl_struct() {
290 check_diagnostics(
291 r#"
292struct someStruct;
293 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
294
295impl someStruct {
296 fn SomeFunc(&self) {
297 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
298 let WHY_VAR_IS_CAPS = 10;
299 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
300 }
301}
302"#,
303 );
304 }
305
306 #[test]
307 fn no_diagnostic_for_enum_varinats() {
308 check_diagnostics(
309 r#"
310enum Option { Some, None }
311
312fn main() {
313 match Option::None {
314 None => (),
315 Some => (),
316 }
317}
318"#,
319 );
320 }
321
322 #[test]
323 fn non_let_bind() {
324 check_diagnostics(
325 r#"
326enum Option { Some, None }
327
328fn main() {
329 match Option::None {
330 SOME_VAR @ None => (),
331 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
332 Some => (),
333 }
334}
335"#,
336 );
337 }
338
339 #[test]
340 fn allow_attributes_crate_attr() {
341 check_diagnostics(
342 r#"
343#![allow(non_snake_case)]
344
345mod F {
346 fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {}
347}
348 "#,
349 );
350 }
351
352 #[test]
353 #[ignore]
354 fn bug_trait_inside_fn() {
355 // FIXME:
356 // This is broken, and in fact, should not even be looked at by this
357 // lint in the first place. There's weird stuff going on in the
358 // collection phase.
359 // It's currently being brought in by:
360 // * validate_func on `a` recursing into modules
361 // * then it finds the trait and then the function while iterating
362 // through modules
363 // * then validate_func is called on Dirty
364 // * ... which then proceeds to look at some unknown module taking no
365 // attrs from either the impl or the fn a, and then finally to the root
366 // module
367 //
368 // It should find the attribute on the trait, but it *doesn't even see
369 // the trait* as far as I can tell.
370
371 check_diagnostics(
372 r#"
373trait T { fn a(); }
374struct U {}
375impl T for U {
376 fn a() {
377 // this comes out of bitflags, mostly
378 #[allow(non_snake_case)]
379 trait __BitFlags {
380 const HiImAlsoBad: u8 = 2;
381 #[inline]
382 fn Dirty(&self) -> bool {
383 false
384 }
385 }
386
387 }
388}
389 "#,
390 );
391 }
392
393 #[test]
394 fn infinite_loop_inner_items() {
395 check_diagnostics(
396 r#"
397fn qualify() {
398 mod foo {
399 use super::*;
400 }
401}
402 "#,
403 )
404 }
405
406 #[test] // Issue #8809.
407 fn parenthesized_parameter() {
408 check_diagnostics(r#"fn f((O): _) {}"#)
409 }
410
411 #[test]
412 fn ignores_extern_items() {
413 cov_mark::check!(extern_func_incorrect_case_ignored);
414 cov_mark::check!(extern_static_incorrect_case_ignored);
415 check_diagnostics(
416 r#"
417extern {
418 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
419 pub static SomeStatic: u8 = 10;
420}
421 "#,
422 );
423 }
424
425 #[test]
426 #[ignore]
427 fn bug_traits_arent_checked() {
428 // FIXME: Traits and functions in traits aren't currently checked by
429 // r-a, even though rustc will complain about them.
430 check_diagnostics(
431 r#"
432trait BAD_TRAIT {
433 // ^^^^^^^^^ Trait `BAD_TRAIT` should have CamelCase name, e.g. `BadTrait`
434 fn BAD_FUNCTION();
435 // ^^^^^^^^^^^^ Function `BAD_FUNCTION` should have snake_case name, e.g. `bad_function`
436 fn BadFunction();
437 // ^^^^^^^^^^^^ Function `BadFunction` should have snake_case name, e.g. `bad_function`
438}
439 "#,
440 );
441 }
442
443 #[test]
444 fn allow_attributes() {
445 check_diagnostics(
446 r#"
447#[allow(non_snake_case)]
448fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
449 // cov_flags generated output from elsewhere in this file
450 extern "C" {
451 #[no_mangle]
452 static lower_case: u8;
453 }
454
455 let OtherVar = SOME_VAR + 1;
456 OtherVar
457}
458
459#[allow(nonstandard_style)]
460mod CheckNonstandardStyle {
461 fn HiImABadFnName() {}
462}
463
464#[allow(bad_style)]
465mod CheckBadStyle {
466 fn HiImABadFnName() {}
467}
468
469mod F {
470 #![allow(non_snake_case)]
471 fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {}
472}
473
474#[allow(non_snake_case, non_camel_case_types)]
475pub struct some_type {
476 SOME_FIELD: u8,
477 SomeField: u16,
478}
479
480#[allow(non_upper_case_globals)]
481pub const some_const: u8 = 10;
482
483#[allow(non_upper_case_globals)]
484pub static SomeStatic: u8 = 10;
485 "#,
486 );
487 }
488}
diff --git a/crates/ide/src/diagnostics/macro_error.rs b/crates/ide/src/diagnostics/macro_error.rs
new file mode 100644
index 000000000..5f97f190d
--- /dev/null
+++ b/crates/ide/src/diagnostics/macro_error.rs
@@ -0,0 +1,173 @@
1use crate::diagnostics::{Diagnostic, DiagnosticsContext};
2
3// Diagnostic: macro-error
4//
5// This diagnostic is shown for macro expansion errors.
6pub(super) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
7 Diagnostic::new(
8 "macro-error",
9 d.message.clone(),
10 ctx.sema.diagnostics_display_range(d.node.clone()).range,
11 )
12 .experimental()
13}
14
15#[cfg(test)]
16mod tests {
17 use crate::{
18 diagnostics::tests::{check_diagnostics, check_diagnostics_with_config},
19 DiagnosticsConfig,
20 };
21
22 #[test]
23 fn builtin_macro_fails_expansion() {
24 check_diagnostics(
25 r#"
26#[rustc_builtin_macro]
27macro_rules! include { () => {} }
28
29 include!("doesntexist");
30//^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `doesntexist`
31 "#,
32 );
33 }
34
35 #[test]
36 fn include_macro_should_allow_empty_content() {
37 let mut config = DiagnosticsConfig::default();
38
39 // FIXME: This is a false-positive, the file is actually linked in via
40 // `include!` macro
41 config.disabled.insert("unlinked-file".to_string());
42
43 check_diagnostics_with_config(
44 config,
45 r#"
46//- /lib.rs
47#[rustc_builtin_macro]
48macro_rules! include { () => {} }
49
50include!("foo/bar.rs");
51//- /foo/bar.rs
52// empty
53"#,
54 );
55 }
56
57 #[test]
58 fn good_out_dir_diagnostic() {
59 check_diagnostics(
60 r#"
61#[rustc_builtin_macro]
62macro_rules! include { () => {} }
63#[rustc_builtin_macro]
64macro_rules! env { () => {} }
65#[rustc_builtin_macro]
66macro_rules! concat { () => {} }
67
68 include!(concat!(env!("OUT_DIR"), "/out.rs"));
69//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
70"#,
71 );
72 }
73
74 #[test]
75 fn register_attr_and_tool() {
76 cov_mark::check!(register_attr);
77 cov_mark::check!(register_tool);
78 check_diagnostics(
79 r#"
80#![register_tool(tool)]
81#![register_attr(attr)]
82
83#[tool::path]
84#[attr]
85struct S;
86"#,
87 );
88 // NB: we don't currently emit diagnostics here
89 }
90
91 #[test]
92 fn macro_diag_builtin() {
93 check_diagnostics(
94 r#"
95#[rustc_builtin_macro]
96macro_rules! env {}
97
98#[rustc_builtin_macro]
99macro_rules! include {}
100
101#[rustc_builtin_macro]
102macro_rules! compile_error {}
103
104#[rustc_builtin_macro]
105macro_rules! format_args { () => {} }
106
107fn main() {
108 // Test a handful of built-in (eager) macros:
109
110 include!(invalid);
111 //^^^^^^^^^^^^^^^^^ could not convert tokens
112 include!("does not exist");
113 //^^^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `does not exist`
114
115 env!(invalid);
116 //^^^^^^^^^^^^^ could not convert tokens
117
118 env!("OUT_DIR");
119 //^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
120
121 compile_error!("compile_error works");
122 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ compile_error works
123
124 // Lazy:
125
126 format_args!();
127 //^^^^^^^^^^^^^^ no rule matches input tokens
128}
129"#,
130 );
131 }
132
133 #[test]
134 fn macro_rules_diag() {
135 check_diagnostics(
136 r#"
137macro_rules! m {
138 () => {};
139}
140fn f() {
141 m!();
142
143 m!(hi);
144 //^^^^^^ leftover tokens
145}
146 "#,
147 );
148 }
149 #[test]
150 fn dollar_crate_in_builtin_macro() {
151 check_diagnostics(
152 r#"
153#[macro_export]
154#[rustc_builtin_macro]
155macro_rules! format_args {}
156
157#[macro_export]
158macro_rules! arg { () => {} }
159
160#[macro_export]
161macro_rules! outer {
162 () => {
163 $crate::format_args!( "", $crate::arg!(1) )
164 };
165}
166
167fn f() {
168 outer!();
169} //^^^^^^^^ leftover tokens
170"#,
171 )
172 }
173}
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_fields.rs b/crates/ide/src/diagnostics/missing_fields.rs
new file mode 100644
index 000000000..d01f05041
--- /dev/null
+++ b/crates/ide/src/diagnostics/missing_fields.rs
@@ -0,0 +1,327 @@
1use either::Either;
2use hir::{db::AstDatabase, InFile};
3use ide_assists::Assist;
4use ide_db::source_change::SourceChange;
5use stdx::format_to;
6use syntax::{algo, ast::make, AstNode, SyntaxNodePtr};
7use text_edit::TextEdit;
8
9use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
10
11// Diagnostic: missing-fields
12//
13// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
14//
15// Example:
16//
17// ```rust
18// struct A { a: u8, b: u8 }
19//
20// let a = A { a: 10 };
21// ```
22pub(super) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
23 let mut message = String::from("Missing structure fields:\n");
24 for field in &d.missed_fields {
25 format_to!(message, "- {}\n", field);
26 }
27
28 let ptr = InFile::new(
29 d.file,
30 d.field_list_parent_path
31 .clone()
32 .map(SyntaxNodePtr::from)
33 .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
34 );
35
36 Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
37 .with_fixes(fixes(ctx, d))
38}
39
40fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
41 // Note that although we could add a diagnostics to
42 // fill the missing tuple field, e.g :
43 // `struct A(usize);`
44 // `let a = A { 0: () }`
45 // but it is uncommon usage and it should not be encouraged.
46 if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
47 return None;
48 }
49
50 let root = ctx.sema.db.parse_or_expand(d.file)?;
51 let field_list_parent = match &d.field_list_parent {
52 Either::Left(record_expr) => record_expr.to_node(&root),
53 // FIXE: patterns should be fixable as well.
54 Either::Right(_) => return None,
55 };
56 let old_field_list = field_list_parent.record_expr_field_list()?;
57 let new_field_list = old_field_list.clone_for_update();
58 for f in d.missed_fields.iter() {
59 let field =
60 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
61 .clone_for_update();
62 new_field_list.add_field(field);
63 }
64
65 let edit = {
66 let mut builder = TextEdit::builder();
67 algo::diff(old_field_list.syntax(), new_field_list.syntax()).into_text_edit(&mut builder);
68 builder.finish()
69 };
70 Some(vec![fix(
71 "fill_missing_fields",
72 "Fill struct fields",
73 SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit),
74 ctx.sema.original_range(field_list_parent.syntax()).range,
75 )])
76}
77
78#[cfg(test)]
79mod tests {
80 use crate::diagnostics::tests::{check_diagnostics, check_fix};
81
82 #[test]
83 fn missing_record_pat_field_diagnostic() {
84 check_diagnostics(
85 r#"
86struct S { foo: i32, bar: () }
87fn baz(s: S) {
88 let S { foo: _ } = s;
89 //^ Missing structure fields:
90 //| - bar
91}
92"#,
93 );
94 }
95
96 #[test]
97 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
98 check_diagnostics(
99 r"
100struct S { foo: i32, bar: () }
101fn baz(s: S) -> i32 {
102 match s {
103 S { foo, .. } => foo,
104 }
105}
106",
107 )
108 }
109
110 #[test]
111 fn missing_record_pat_field_box() {
112 check_diagnostics(
113 r"
114struct S { s: Box<u32> }
115fn x(a: S) {
116 let S { box s } = a;
117}
118",
119 )
120 }
121
122 #[test]
123 fn missing_record_pat_field_ref() {
124 check_diagnostics(
125 r"
126struct S { s: u32 }
127fn x(a: S) {
128 let S { ref s } = a;
129}
130",
131 )
132 }
133
134 #[test]
135 fn range_mapping_out_of_macros() {
136 // FIXME: this is very wrong, but somewhat tricky to fix.
137 check_fix(
138 r#"
139fn some() {}
140fn items() {}
141fn here() {}
142
143macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
144
145fn main() {
146 let _x = id![Foo { a: $042 }];
147}
148
149pub struct Foo { pub a: i32, pub b: i32 }
150"#,
151 r#"
152fn some(, b: () ) {}
153fn items() {}
154fn here() {}
155
156macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
157
158fn main() {
159 let _x = id![Foo { a: 42 }];
160}
161
162pub struct Foo { pub a: i32, pub b: i32 }
163"#,
164 );
165 }
166
167 #[test]
168 fn test_fill_struct_fields_empty() {
169 check_fix(
170 r#"
171struct TestStruct { one: i32, two: i64 }
172
173fn test_fn() {
174 let s = TestStruct {$0};
175}
176"#,
177 r#"
178struct TestStruct { one: i32, two: i64 }
179
180fn test_fn() {
181 let s = TestStruct { one: (), two: () };
182}
183"#,
184 );
185 }
186
187 #[test]
188 fn test_fill_struct_fields_self() {
189 check_fix(
190 r#"
191struct TestStruct { one: i32 }
192
193impl TestStruct {
194 fn test_fn() { let s = Self {$0}; }
195}
196"#,
197 r#"
198struct TestStruct { one: i32 }
199
200impl TestStruct {
201 fn test_fn() { let s = Self { one: () }; }
202}
203"#,
204 );
205 }
206
207 #[test]
208 fn test_fill_struct_fields_enum() {
209 check_fix(
210 r#"
211enum Expr {
212 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
213}
214
215impl Expr {
216 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
217 Expr::Bin {$0 }
218 }
219}
220"#,
221 r#"
222enum Expr {
223 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
224}
225
226impl Expr {
227 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
228 Expr::Bin { lhs: (), rhs: () }
229 }
230}
231"#,
232 );
233 }
234
235 #[test]
236 fn test_fill_struct_fields_partial() {
237 check_fix(
238 r#"
239struct TestStruct { one: i32, two: i64 }
240
241fn test_fn() {
242 let s = TestStruct{ two: 2$0 };
243}
244"#,
245 r"
246struct TestStruct { one: i32, two: i64 }
247
248fn test_fn() {
249 let s = TestStruct{ two: 2, one: () };
250}
251",
252 );
253 }
254
255 #[test]
256 fn test_fill_struct_fields_raw_ident() {
257 check_fix(
258 r#"
259struct TestStruct { r#type: u8 }
260
261fn test_fn() {
262 TestStruct { $0 };
263}
264"#,
265 r"
266struct TestStruct { r#type: u8 }
267
268fn test_fn() {
269 TestStruct { r#type: () };
270}
271",
272 );
273 }
274
275 #[test]
276 fn test_fill_struct_fields_no_diagnostic() {
277 check_diagnostics(
278 r#"
279struct TestStruct { one: i32, two: i64 }
280
281fn test_fn() {
282 let one = 1;
283 let s = TestStruct{ one, two: 2 };
284}
285 "#,
286 );
287 }
288
289 #[test]
290 fn test_fill_struct_fields_no_diagnostic_on_spread() {
291 check_diagnostics(
292 r#"
293struct TestStruct { one: i32, two: i64 }
294
295fn test_fn() {
296 let one = 1;
297 let s = TestStruct{ ..a };
298}
299"#,
300 );
301 }
302
303 #[test]
304 fn test_fill_struct_fields_blank_line() {
305 check_fix(
306 r#"
307struct S { a: (), b: () }
308
309fn f() {
310 S {
311 $0
312 };
313}
314"#,
315 r#"
316struct S { a: (), b: () }
317
318fn f() {
319 S {
320 a: (),
321 b: (),
322 };
323}
324"#,
325 );
326 }
327}
diff --git a/crates/ide/src/diagnostics/missing_match_arms.rs b/crates/ide/src/diagnostics/missing_match_arms.rs
new file mode 100644
index 000000000..b636489b3
--- /dev/null
+++ b/crates/ide/src/diagnostics/missing_match_arms.rs
@@ -0,0 +1,929 @@
1use hir::InFile;
2
3use crate::diagnostics::{Diagnostic, DiagnosticsContext};
4
5// Diagnostic: missing-match-arm
6//
7// This diagnostic is triggered if `match` block is missing one or more match arms.
8pub(super) fn missing_match_arms(
9 ctx: &DiagnosticsContext<'_>,
10 d: &hir::MissingMatchArms,
11) -> Diagnostic {
12 Diagnostic::new(
13 "missing-match-arm",
14 "missing match arm",
15 ctx.sema.diagnostics_display_range(InFile::new(d.file, d.match_expr.clone().into())).range,
16 )
17}
18
19#[cfg(test)]
20pub(super) mod tests {
21 use crate::diagnostics::tests::check_diagnostics;
22
23 fn check_diagnostics_no_bails(ra_fixture: &str) {
24 cov_mark::check_count!(validate_match_bailed_out, 0);
25 crate::diagnostics::tests::check_diagnostics(ra_fixture)
26 }
27
28 #[test]
29 fn empty_tuple() {
30 check_diagnostics_no_bails(
31 r#"
32fn main() {
33 match () { }
34 //^^ missing match arm
35 match (()) { }
36 //^^^^ missing match arm
37
38 match () { _ => (), }
39 match () { () => (), }
40 match (()) { (()) => (), }
41}
42"#,
43 );
44 }
45
46 #[test]
47 fn tuple_of_two_empty_tuple() {
48 check_diagnostics_no_bails(
49 r#"
50fn main() {
51 match ((), ()) { }
52 //^^^^^^^^ missing match arm
53
54 match ((), ()) { ((), ()) => (), }
55}
56"#,
57 );
58 }
59
60 #[test]
61 fn boolean() {
62 check_diagnostics_no_bails(
63 r#"
64fn test_main() {
65 match false { }
66 //^^^^^ missing match arm
67 match false { true => (), }
68 //^^^^^ missing match arm
69 match (false, true) {}
70 //^^^^^^^^^^^^^ missing match arm
71 match (false, true) { (true, true) => (), }
72 //^^^^^^^^^^^^^ missing match arm
73 match (false, true) {
74 //^^^^^^^^^^^^^ missing match arm
75 (false, true) => (),
76 (false, false) => (),
77 (true, false) => (),
78 }
79 match (false, true) { (true, _x) => (), }
80 //^^^^^^^^^^^^^ missing match arm
81
82 match false { true => (), false => (), }
83 match (false, true) {
84 (false, _) => (),
85 (true, false) => (),
86 (_, true) => (),
87 }
88 match (false, true) {
89 (true, true) => (),
90 (true, false) => (),
91 (false, true) => (),
92 (false, false) => (),
93 }
94 match (false, true) {
95 (true, _x) => (),
96 (false, true) => (),
97 (false, false) => (),
98 }
99 match (false, true, false) {
100 (false, ..) => (),
101 (true, ..) => (),
102 }
103 match (false, true, false) {
104 (.., false) => (),
105 (.., true) => (),
106 }
107 match (false, true, false) { (..) => (), }
108}
109"#,
110 );
111 }
112
113 #[test]
114 fn tuple_of_tuple_and_bools() {
115 check_diagnostics_no_bails(
116 r#"
117fn main() {
118 match (false, ((), false)) {}
119 //^^^^^^^^^^^^^^^^^^^^ missing match arm
120 match (false, ((), false)) { (true, ((), true)) => (), }
121 //^^^^^^^^^^^^^^^^^^^^ missing match arm
122 match (false, ((), false)) { (true, _) => (), }
123 //^^^^^^^^^^^^^^^^^^^^ missing match arm
124
125 match (false, ((), false)) {
126 (true, ((), true)) => (),
127 (true, ((), false)) => (),
128 (false, ((), true)) => (),
129 (false, ((), false)) => (),
130 }
131 match (false, ((), false)) {
132 (true, ((), true)) => (),
133 (true, ((), false)) => (),
134 (false, _) => (),
135 }
136}
137"#,
138 );
139 }
140
141 #[test]
142 fn enums() {
143 check_diagnostics_no_bails(
144 r#"
145enum Either { A, B, }
146
147fn main() {
148 match Either::A { }
149 //^^^^^^^^^ missing match arm
150 match Either::B { Either::A => (), }
151 //^^^^^^^^^ missing match arm
152
153 match &Either::B {
154 //^^^^^^^^^^ missing match arm
155 Either::A => (),
156 }
157
158 match Either::B {
159 Either::A => (), Either::B => (),
160 }
161 match &Either::B {
162 Either::A => (), Either::B => (),
163 }
164}
165"#,
166 );
167 }
168
169 #[test]
170 fn enum_containing_bool() {
171 check_diagnostics_no_bails(
172 r#"
173enum Either { A(bool), B }
174
175fn main() {
176 match Either::B { }
177 //^^^^^^^^^ missing match arm
178 match Either::B {
179 //^^^^^^^^^ missing match arm
180 Either::A(true) => (), Either::B => ()
181 }
182
183 match Either::B {
184 Either::A(true) => (),
185 Either::A(false) => (),
186 Either::B => (),
187 }
188 match Either::B {
189 Either::B => (),
190 _ => (),
191 }
192 match Either::B {
193 Either::A(_) => (),
194 Either::B => (),
195 }
196
197}
198 "#,
199 );
200 }
201
202 #[test]
203 fn enum_different_sizes() {
204 check_diagnostics_no_bails(
205 r#"
206enum Either { A(bool), B(bool, bool) }
207
208fn main() {
209 match Either::A(false) {
210 //^^^^^^^^^^^^^^^^ missing match arm
211 Either::A(_) => (),
212 Either::B(false, _) => (),
213 }
214
215 match Either::A(false) {
216 Either::A(_) => (),
217 Either::B(true, _) => (),
218 Either::B(false, _) => (),
219 }
220 match Either::A(false) {
221 Either::A(true) | Either::A(false) => (),
222 Either::B(true, _) => (),
223 Either::B(false, _) => (),
224 }
225}
226"#,
227 );
228 }
229
230 #[test]
231 fn tuple_of_enum_no_diagnostic() {
232 check_diagnostics_no_bails(
233 r#"
234enum Either { A(bool), B(bool, bool) }
235enum Either2 { C, D }
236
237fn main() {
238 match (Either::A(false), Either2::C) {
239 (Either::A(true), _) | (Either::A(false), _) => (),
240 (Either::B(true, _), Either2::C) => (),
241 (Either::B(false, _), Either2::C) => (),
242 (Either::B(_, _), Either2::D) => (),
243 }
244}
245"#,
246 );
247 }
248
249 #[test]
250 fn or_pattern_no_diagnostic() {
251 check_diagnostics_no_bails(
252 r#"
253enum Either {A, B}
254
255fn main() {
256 match (Either::A, Either::B) {
257 (Either::A | Either::B, _) => (),
258 }
259}"#,
260 )
261 }
262
263 #[test]
264 fn mismatched_types() {
265 cov_mark::check_count!(validate_match_bailed_out, 4);
266 // Match statements with arms that don't match the
267 // expression pattern do not fire this diagnostic.
268 check_diagnostics(
269 r#"
270enum Either { A, B }
271enum Either2 { C, D }
272
273fn main() {
274 match Either::A {
275 Either2::C => (),
276 Either2::D => (),
277 }
278 match (true, false) {
279 (true, false, true) => (),
280 (true) => (),
281 }
282 match (true, false) { (true,) => {} }
283 match (0) { () => () }
284 match Unresolved::Bar { Unresolved::Baz => () }
285}
286 "#,
287 );
288 }
289
290 #[test]
291 fn mismatched_types_in_or_patterns() {
292 cov_mark::check_count!(validate_match_bailed_out, 2);
293 check_diagnostics(
294 r#"
295fn main() {
296 match false { true | () => {} }
297 match (false,) { (true | (),) => {} }
298}
299"#,
300 );
301 }
302
303 #[test]
304 fn malformed_match_arm_tuple_enum_missing_pattern() {
305 // We are testing to be sure we don't panic here when the match
306 // arm `Either::B` is missing its pattern.
307 check_diagnostics_no_bails(
308 r#"
309enum Either { A, B(u32) }
310
311fn main() {
312 match Either::A {
313 Either::A => (),
314 Either::B() => (),
315 }
316}
317"#,
318 );
319 }
320
321 #[test]
322 fn malformed_match_arm_extra_fields() {
323 cov_mark::check_count!(validate_match_bailed_out, 2);
324 check_diagnostics(
325 r#"
326enum A { B(isize, isize), C }
327fn main() {
328 match A::B(1, 2) {
329 A::B(_, _, _) => (),
330 }
331 match A::B(1, 2) {
332 A::C(_) => (),
333 }
334}
335"#,
336 );
337 }
338
339 #[test]
340 fn expr_diverges() {
341 cov_mark::check_count!(validate_match_bailed_out, 2);
342 check_diagnostics(
343 r#"
344enum Either { A, B }
345
346fn main() {
347 match loop {} {
348 Either::A => (),
349 Either::B => (),
350 }
351 match loop {} {
352 Either::A => (),
353 }
354 match loop { break Foo::A } {
355 //^^^^^^^^^^^^^^^^^^^^^ missing match arm
356 Either::A => (),
357 }
358 match loop { break Foo::A } {
359 Either::A => (),
360 Either::B => (),
361 }
362}
363"#,
364 );
365 }
366
367 #[test]
368 fn expr_partially_diverges() {
369 check_diagnostics_no_bails(
370 r#"
371enum Either<T> { A(T), B }
372
373fn foo() -> Either<!> { Either::B }
374fn main() -> u32 {
375 match foo() {
376 Either::A(val) => val,
377 Either::B => 0,
378 }
379}
380"#,
381 );
382 }
383
384 #[test]
385 fn enum_record() {
386 check_diagnostics_no_bails(
387 r#"
388enum Either { A { foo: bool }, B }
389
390fn main() {
391 let a = Either::A { foo: true };
392 match a { }
393 //^ missing match arm
394 match a { Either::A { foo: true } => () }
395 //^ missing match arm
396 match a {
397 Either::A { } => (),
398 //^^^^^^^^^ Missing structure fields:
399 // | - foo
400 Either::B => (),
401 }
402 match a {
403 //^ missing match arm
404 Either::A { } => (),
405 } //^^^^^^^^^ Missing structure fields:
406 // | - foo
407
408 match a {
409 Either::A { foo: true } => (),
410 Either::A { foo: false } => (),
411 Either::B => (),
412 }
413 match a {
414 Either::A { foo: _ } => (),
415 Either::B => (),
416 }
417}
418"#,
419 );
420 }
421
422 #[test]
423 fn enum_record_fields_out_of_order() {
424 check_diagnostics_no_bails(
425 r#"
426enum Either {
427 A { foo: bool, bar: () },
428 B,
429}
430
431fn main() {
432 let a = Either::A { foo: true, bar: () };
433 match a {
434 //^ missing match arm
435 Either::A { bar: (), foo: false } => (),
436 Either::A { foo: true, bar: () } => (),
437 }
438
439 match a {
440 Either::A { bar: (), foo: false } => (),
441 Either::A { foo: true, bar: () } => (),
442 Either::B => (),
443 }
444}
445"#,
446 );
447 }
448
449 #[test]
450 fn enum_record_ellipsis() {
451 check_diagnostics_no_bails(
452 r#"
453enum Either {
454 A { foo: bool, bar: bool },
455 B,
456}
457
458fn main() {
459 let a = Either::B;
460 match a {
461 //^ missing match arm
462 Either::A { foo: true, .. } => (),
463 Either::B => (),
464 }
465 match a {
466 //^ missing match arm
467 Either::A { .. } => (),
468 }
469
470 match a {
471 Either::A { foo: true, .. } => (),
472 Either::A { foo: false, .. } => (),
473 Either::B => (),
474 }
475
476 match a {
477 Either::A { .. } => (),
478 Either::B => (),
479 }
480}
481"#,
482 );
483 }
484
485 #[test]
486 fn enum_tuple_partial_ellipsis() {
487 check_diagnostics_no_bails(
488 r#"
489enum Either {
490 A(bool, bool, bool, bool),
491 B,
492}
493
494fn main() {
495 match Either::B {
496 //^^^^^^^^^ missing match arm
497 Either::A(true, .., true) => (),
498 Either::A(true, .., false) => (),
499 Either::A(false, .., false) => (),
500 Either::B => (),
501 }
502 match Either::B {
503 //^^^^^^^^^ missing match arm
504 Either::A(true, .., true) => (),
505 Either::A(true, .., false) => (),
506 Either::A(.., true) => (),
507 Either::B => (),
508 }
509
510 match Either::B {
511 Either::A(true, .., true) => (),
512 Either::A(true, .., false) => (),
513 Either::A(false, .., true) => (),
514 Either::A(false, .., false) => (),
515 Either::B => (),
516 }
517 match Either::B {
518 Either::A(true, .., true) => (),
519 Either::A(true, .., false) => (),
520 Either::A(.., true) => (),
521 Either::A(.., false) => (),
522 Either::B => (),
523 }
524}
525"#,
526 );
527 }
528
529 #[test]
530 fn never() {
531 check_diagnostics_no_bails(
532 r#"
533enum Never {}
534
535fn enum_(never: Never) {
536 match never {}
537}
538fn enum_ref(never: &Never) {
539 match never {}
540 //^^^^^ missing match arm
541}
542fn bang(never: !) {
543 match never {}
544}
545"#,
546 );
547 }
548
549 #[test]
550 fn unknown_type() {
551 cov_mark::check_count!(validate_match_bailed_out, 1);
552
553 check_diagnostics(
554 r#"
555enum Option<T> { Some(T), None }
556
557fn main() {
558 // `Never` is deliberately not defined so that it's an uninferred type.
559 match Option::<Never>::None {
560 None => (),
561 Some(never) => match never {},
562 }
563 match Option::<Never>::None {
564 //^^^^^^^^^^^^^^^^^^^^^ missing match arm
565 Option::Some(_never) => {},
566 }
567}
568"#,
569 );
570 }
571
572 #[test]
573 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
574 check_diagnostics_no_bails(
575 r#"
576fn main() {
577 match (false, true, false) {
578 //^^^^^^^^^^^^^^^^^^^^ missing match arm
579 (false, ..) => (),
580 }
581}"#,
582 );
583 }
584
585 #[test]
586 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
587 check_diagnostics_no_bails(
588 r#"
589fn main() {
590 match (false, true, false) {
591 //^^^^^^^^^^^^^^^^^^^^ missing match arm
592 (.., false) => (),
593 }
594}"#,
595 );
596 }
597
598 #[test]
599 fn tuple_of_bools_with_ellipsis_in_middle_missing_arm() {
600 check_diagnostics_no_bails(
601 r#"
602fn main() {
603 match (false, true, false) {
604 //^^^^^^^^^^^^^^^^^^^^ missing match arm
605 (true, .., false) => (),
606 }
607}"#,
608 );
609 }
610
611 #[test]
612 fn record_struct() {
613 check_diagnostics_no_bails(
614 r#"struct Foo { a: bool }
615fn main(f: Foo) {
616 match f {}
617 //^ missing match arm
618 match f { Foo { a: true } => () }
619 //^ missing match arm
620 match &f { Foo { a: true } => () }
621 //^^ missing match arm
622 match f { Foo { a: _ } => () }
623 match f {
624 Foo { a: true } => (),
625 Foo { a: false } => (),
626 }
627 match &f {
628 Foo { a: true } => (),
629 Foo { a: false } => (),
630 }
631}
632"#,
633 );
634 }
635
636 #[test]
637 fn tuple_struct() {
638 check_diagnostics_no_bails(
639 r#"struct Foo(bool);
640fn main(f: Foo) {
641 match f {}
642 //^ missing match arm
643 match f { Foo(true) => () }
644 //^ missing match arm
645 match f {
646 Foo(true) => (),
647 Foo(false) => (),
648 }
649}
650"#,
651 );
652 }
653
654 #[test]
655 fn unit_struct() {
656 check_diagnostics_no_bails(
657 r#"struct Foo;
658fn main(f: Foo) {
659 match f {}
660 //^ missing match arm
661 match f { Foo => () }
662}
663"#,
664 );
665 }
666
667 #[test]
668 fn record_struct_ellipsis() {
669 check_diagnostics_no_bails(
670 r#"struct Foo { foo: bool, bar: bool }
671fn main(f: Foo) {
672 match f { Foo { foo: true, .. } => () }
673 //^ missing match arm
674 match f {
675 //^ missing match arm
676 Foo { foo: true, .. } => (),
677 Foo { bar: false, .. } => ()
678 }
679 match f { Foo { .. } => () }
680 match f {
681 Foo { foo: true, .. } => (),
682 Foo { foo: false, .. } => ()
683 }
684}
685"#,
686 );
687 }
688
689 #[test]
690 fn internal_or() {
691 check_diagnostics_no_bails(
692 r#"
693fn main() {
694 enum Either { A(bool), B }
695 match Either::B {
696 //^^^^^^^^^ missing match arm
697 Either::A(true | false) => (),
698 }
699}
700"#,
701 );
702 }
703
704 #[test]
705 fn no_panic_at_unimplemented_subpattern_type() {
706 cov_mark::check_count!(validate_match_bailed_out, 1);
707
708 check_diagnostics(
709 r#"
710struct S { a: char}
711fn main(v: S) {
712 match v { S{ a } => {} }
713 match v { S{ a: _x } => {} }
714 match v { S{ a: 'a' } => {} }
715 match v { S{..} => {} }
716 match v { _ => {} }
717 match v { }
718 //^ missing match arm
719}
720"#,
721 );
722 }
723
724 #[test]
725 fn binding() {
726 check_diagnostics_no_bails(
727 r#"
728fn main() {
729 match true {
730 _x @ true => {}
731 false => {}
732 }
733 match true { _x @ true => {} }
734 //^^^^ missing match arm
735}
736"#,
737 );
738 }
739
740 #[test]
741 fn binding_ref_has_correct_type() {
742 cov_mark::check_count!(validate_match_bailed_out, 1);
743
744 // Asserts `PatKind::Binding(ref _x): bool`, not &bool.
745 // If that's not true match checking will panic with "incompatible constructors"
746 // FIXME: make facilities to test this directly like `tests::check_infer(..)`
747 check_diagnostics(
748 r#"
749enum Foo { A }
750fn main() {
751 // FIXME: this should not bail out but current behavior is such as the old algorithm.
752 // ExprValidator::validate_match(..) checks types of top level patterns incorrecly.
753 match Foo::A {
754 ref _x => {}
755 Foo::A => {}
756 }
757 match (true,) {
758 (ref _x,) => {}
759 (true,) => {}
760 }
761}
762"#,
763 );
764 }
765
766 #[test]
767 fn enum_non_exhaustive() {
768 check_diagnostics_no_bails(
769 r#"
770//- /lib.rs crate:lib
771#[non_exhaustive]
772pub enum E { A, B }
773fn _local() {
774 match E::A { _ => {} }
775 match E::A {
776 E::A => {}
777 E::B => {}
778 }
779 match E::A {
780 E::A | E::B => {}
781 }
782}
783
784//- /main.rs crate:main deps:lib
785use lib::E;
786fn main() {
787 match E::A { _ => {} }
788 match E::A {
789 //^^^^ missing match arm
790 E::A => {}
791 E::B => {}
792 }
793 match E::A {
794 //^^^^ missing match arm
795 E::A | E::B => {}
796 }
797}
798"#,
799 );
800 }
801
802 #[test]
803 fn match_guard() {
804 check_diagnostics_no_bails(
805 r#"
806fn main() {
807 match true {
808 true if false => {}
809 true => {}
810 false => {}
811 }
812 match true {
813 //^^^^ missing match arm
814 true if false => {}
815 false => {}
816 }
817}
818"#,
819 );
820 }
821
822 #[test]
823 fn pattern_type_is_of_substitution() {
824 cov_mark::check!(match_check_wildcard_expanded_to_substitutions);
825 check_diagnostics_no_bails(
826 r#"
827struct Foo<T>(T);
828struct Bar;
829fn main() {
830 match Foo(Bar) {
831 _ | Foo(Bar) => {}
832 }
833}
834"#,
835 );
836 }
837
838 #[test]
839 fn record_struct_no_such_field() {
840 cov_mark::check_count!(validate_match_bailed_out, 1);
841
842 check_diagnostics(
843 r#"
844struct Foo { }
845fn main(f: Foo) {
846 match f { Foo { bar } => () }
847}
848"#,
849 );
850 }
851
852 #[test]
853 fn match_ergonomics_issue_9095() {
854 check_diagnostics_no_bails(
855 r#"
856enum Foo<T> { A(T) }
857fn main() {
858 match &Foo::A(true) {
859 _ => {}
860 Foo::A(_) => {}
861 }
862}
863"#,
864 );
865 }
866
867 mod false_negatives {
868 //! The implementation of match checking here is a work in progress. As we roll this out, we
869 //! prefer false negatives to false positives (ideally there would be no false positives). This
870 //! test module should document known false negatives. Eventually we will have a complete
871 //! implementation of match checking and this module will be empty.
872 //!
873 //! The reasons for documenting known false negatives:
874 //!
875 //! 1. It acts as a backlog of work that can be done to improve the behavior of the system.
876 //! 2. It ensures the code doesn't panic when handling these cases.
877 use super::*;
878
879 #[test]
880 fn integers() {
881 cov_mark::check_count!(validate_match_bailed_out, 1);
882
883 // We don't currently check integer exhaustiveness.
884 check_diagnostics(
885 r#"
886fn main() {
887 match 5 {
888 10 => (),
889 11..20 => (),
890 }
891}
892"#,
893 );
894 }
895
896 #[test]
897 fn reference_patterns_at_top_level() {
898 cov_mark::check_count!(validate_match_bailed_out, 1);
899
900 check_diagnostics(
901 r#"
902fn main() {
903 match &false {
904 &true => {}
905 }
906}
907 "#,
908 );
909 }
910
911 #[test]
912 fn reference_patterns_in_fields() {
913 cov_mark::check_count!(validate_match_bailed_out, 2);
914
915 check_diagnostics(
916 r#"
917fn main() {
918 match (&false,) {
919 (true,) => {}
920 }
921 match (&false,) {
922 (&true,) => {}
923 }
924}
925 "#,
926 );
927 }
928 }
929}
diff --git a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs b/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs
index 715a403b9..06005d156 100644
--- a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
+++ b/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs
@@ -1,31 +1,50 @@
1use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics}; 1use hir::db::AstDatabase;
2use ide_assists::{Assist, AssistResolveStrategy}; 2use ide_assists::Assist;
3use ide_db::{source_change::SourceChange, RootDatabase}; 3use ide_db::source_change::SourceChange;
4use syntax::AstNode; 4use syntax::AstNode;
5use text_edit::TextEdit; 5use text_edit::TextEdit;
6 6
7use crate::diagnostics::{fix, DiagnosticWithFixes}; 7use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
8 8
9impl DiagnosticWithFixes for MissingOkOrSomeInTailExpr { 9// Diagnostic: missing-ok-or-some-in-tail-expr
10 fn fixes( 10//
11 &self, 11// This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`,
12 sema: &Semantics<RootDatabase>, 12// or if a block that should return `Option` returns a value not wrapped in `Some`.
13 _resolve: &AssistResolveStrategy, 13//
14 ) -> Option<Vec<Assist>> { 14// Example:
15 let root = sema.db.parse_or_expand(self.file)?; 15//
16 let tail_expr = self.expr.to_node(&root); 16// ```rust
17 let tail_expr_range = tail_expr.syntax().text_range(); 17// fn foo() -> Result<u8, ()> {
18 let replacement = format!("{}({})", self.required, tail_expr.syntax()); 18// 10
19 let edit = TextEdit::replace(tail_expr_range, replacement); 19// }
20 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); 20// ```
21 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; 21pub(super) fn missing_ok_or_some_in_tail_expr(
22 Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)]) 22 ctx: &DiagnosticsContext<'_>,
23 } 23 d: &hir::MissingOkOrSomeInTailExpr,
24) -> Diagnostic {
25 Diagnostic::new(
26 "missing-ok-or-some-in-tail-expr",
27 format!("wrap return expression in {}", d.required),
28 ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
29 )
30 .with_fixes(fixes(ctx, d))
31}
32
33fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Option<Vec<Assist>> {
34 let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
35 let tail_expr = d.expr.value.to_node(&root);
36 let tail_expr_range = tail_expr.syntax().text_range();
37 let replacement = format!("{}({})", d.required, tail_expr.syntax());
38 let edit = TextEdit::replace(tail_expr_range, replacement);
39 let source_change =
40 SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
41 let name = if d.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
42 Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)])
24} 43}
25 44
26#[cfg(test)] 45#[cfg(test)]
27mod tests { 46mod tests {
28 use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; 47 use crate::diagnostics::tests::{check_diagnostics, check_fix};
29 48
30 #[test] 49 #[test]
31 fn test_wrap_return_type_option() { 50 fn test_wrap_return_type_option() {
@@ -169,7 +188,7 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
169 188
170 #[test] 189 #[test]
171 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { 190 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
172 check_no_diagnostics( 191 check_diagnostics(
173 r#" 192 r#"
174//- /main.rs crate:main deps:core 193//- /main.rs crate:main deps:core
175use core::result::Result::{self, Ok, Err}; 194use core::result::Result::{self, Ok, Err};
@@ -189,7 +208,7 @@ pub mod option {
189 208
190 #[test] 209 #[test]
191 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { 210 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
192 check_no_diagnostics( 211 check_diagnostics(
193 r#" 212 r#"
194//- /main.rs crate:main deps:core 213//- /main.rs crate:main deps:core
195use core::result::Result::{self, Ok, Err}; 214use core::result::Result::{self, Ok, Err};
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/fixes/create_field.rs b/crates/ide/src/diagnostics/no_such_field.rs
index a5f457dce..edc63c246 100644
--- a/crates/ide/src/diagnostics/fixes/create_field.rs
+++ b/crates/ide/src/diagnostics/no_such_field.rs
@@ -1,4 +1,4 @@
1use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics}; 1use hir::{db::AstDatabase, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase}; 2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{ 3use syntax::{
4 ast::{self, edit::IndentLevel, make}, 4 ast::{self, edit::IndentLevel, make},
@@ -7,22 +7,29 @@ use syntax::{
7use text_edit::TextEdit; 7use text_edit::TextEdit;
8 8
9use crate::{ 9use crate::{
10 diagnostics::{fix, DiagnosticWithFixes}, 10 diagnostics::{fix, Diagnostic, DiagnosticsContext},
11 Assist, AssistResolveStrategy, 11 Assist,
12}; 12};
13impl DiagnosticWithFixes for NoSuchField { 13
14 fn fixes( 14// Diagnostic: no-such-field
15 &self, 15//
16 sema: &Semantics<RootDatabase>, 16// This diagnostic is triggered if created structure does not have field provided in record.
17 _resolve: &AssistResolveStrategy, 17pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
18 ) -> Option<Vec<Assist>> { 18 Diagnostic::new(
19 let root = sema.db.parse_or_expand(self.file)?; 19 "no-such-field",
20 missing_record_expr_field_fixes( 20 "no such field",
21 &sema, 21 ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
22 self.file.original_file(sema.db), 22 )
23 &self.field.to_node(&root), 23 .with_fixes(fixes(ctx, d))
24 ) 24}
25 } 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 )
26} 33}
27 34
28fn missing_record_expr_field_fixes( 35fn missing_record_expr_field_fixes(
@@ -105,7 +112,130 @@ fn missing_record_expr_field_fixes(
105 112
106#[cfg(test)] 113#[cfg(test)]
107mod tests { 114mod tests {
108 use crate::diagnostics::tests::check_fix; 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 }
109 239
110 #[test] 240 #[test]
111 fn test_add_field_from_usage() { 241 fn test_add_field_from_usage() {
diff --git a/crates/ide/src/diagnostics/remove_this_semicolon.rs b/crates/ide/src/diagnostics/remove_this_semicolon.rs
new file mode 100644
index 000000000..814cb0f8c
--- /dev/null
+++ b/crates/ide/src/diagnostics/remove_this_semicolon.rs
@@ -0,0 +1,64 @@
1use hir::db::AstDatabase;
2use ide_db::source_change::SourceChange;
3use syntax::{ast, AstNode};
4use text_edit::TextEdit;
5
6use crate::{
7 diagnostics::{fix, Diagnostic, DiagnosticsContext},
8 Assist,
9};
10
11// Diagnostic: remove-this-semicolon
12//
13// This diagnostic is triggered when there's an erroneous `;` at the end of the block.
14pub(super) fn remove_this_semicolon(
15 ctx: &DiagnosticsContext<'_>,
16 d: &hir::RemoveThisSemicolon,
17) -> Diagnostic {
18 Diagnostic::new(
19 "remove-this-semicolon",
20 "remove this semicolon",
21 ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
22 )
23 .with_fixes(fixes(ctx, d))
24}
25
26fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::RemoveThisSemicolon) -> Option<Vec<Assist>> {
27 let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
28
29 let semicolon = d
30 .expr
31 .value
32 .to_node(&root)
33 .syntax()
34 .parent()
35 .and_then(ast::ExprStmt::cast)
36 .and_then(|expr| expr.semicolon_token())?
37 .text_range();
38
39 let edit = TextEdit::delete(semicolon);
40 let source_change =
41 SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
42
43 Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)])
44}
45
46#[cfg(test)]
47mod tests {
48 use crate::diagnostics::tests::{check_diagnostics, check_fix};
49
50 #[test]
51 fn missing_semicolon() {
52 check_diagnostics(
53 r#"
54fn test() -> i32 { 123; }
55 //^^^ remove this semicolon
56"#,
57 );
58 }
59
60 #[test]
61 fn remove_semicolon() {
62 check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
63 }
64}
diff --git a/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs b/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs
new file mode 100644
index 000000000..f3b011495
--- /dev/null
+++ b/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs
@@ -0,0 +1,182 @@
1use hir::{db::AstDatabase, InFile};
2use ide_db::source_change::SourceChange;
3use syntax::{
4 ast::{self, ArgListOwner},
5 AstNode, TextRange,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, Diagnostic, DiagnosticsContext},
11 Assist, Severity,
12};
13
14// Diagnostic: replace-filter-map-next-with-find-map
15//
16// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
17pub(super) fn replace_filter_map_next_with_find_map(
18 ctx: &DiagnosticsContext<'_>,
19 d: &hir::ReplaceFilterMapNextWithFindMap,
20) -> Diagnostic {
21 Diagnostic::new(
22 "replace-filter-map-next-with-find-map",
23 "replace filter_map(..).next() with find_map(..)",
24 ctx.sema.diagnostics_display_range(InFile::new(d.file, d.next_expr.clone().into())).range,
25 )
26 .severity(Severity::WeakWarning)
27 .with_fixes(fixes(ctx, d))
28}
29
30fn fixes(
31 ctx: &DiagnosticsContext<'_>,
32 d: &hir::ReplaceFilterMapNextWithFindMap,
33) -> Option<Vec<Assist>> {
34 let root = ctx.sema.db.parse_or_expand(d.file)?;
35 let next_expr = d.next_expr.to_node(&root);
36 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
37
38 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
39 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
40 let filter_map_args = filter_map_call.arg_list()?;
41
42 let range_to_replace =
43 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
44 let replacement = format!("find_map{}", filter_map_args.syntax().text());
45 let trigger_range = next_expr.syntax().text_range();
46
47 let edit = TextEdit::replace(range_to_replace, replacement);
48
49 let source_change = SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit);
50
51 Some(vec![fix(
52 "replace_with_find_map",
53 "Replace filter_map(..).next() with find_map()",
54 source_change,
55 trigger_range,
56 )])
57}
58
59#[cfg(test)]
60mod tests {
61 use crate::diagnostics::tests::check_fix;
62
63 // Register the required standard library types to make the tests work
64 #[track_caller]
65 fn check_diagnostics(ra_fixture: &str) {
66 let prefix = r#"
67//- /main.rs crate:main deps:core
68use core::iter::Iterator;
69use core::option::Option::{self, Some, None};
70"#;
71 let suffix = r#"
72//- /core/lib.rs crate:core
73pub mod option {
74 pub enum Option<T> { Some(T), None }
75}
76pub mod iter {
77 pub trait Iterator {
78 type Item;
79 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
80 fn next(&mut self) -> Option<Self::Item>;
81 }
82 pub struct FilterMap {}
83 impl Iterator for FilterMap {
84 type Item = i32;
85 fn next(&mut self) -> i32 { 7 }
86 }
87}
88"#;
89 crate::diagnostics::tests::check_diagnostics(&format!("{}{}{}", prefix, ra_fixture, suffix))
90 }
91
92 #[test]
93 fn replace_filter_map_next_with_find_map2() {
94 check_diagnostics(
95 r#"
96 fn foo() {
97 let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
98 } //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..)
99"#,
100 );
101 }
102
103 #[test]
104 fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
105 check_diagnostics(
106 r#"
107fn foo() {
108 let m = [1, 2, 3]
109 .iter()
110 .filter_map(|x| if *x == 2 { Some (4) } else { None })
111 .len();
112}
113"#,
114 );
115 }
116
117 #[test]
118 fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() {
119 check_diagnostics(
120 r#"
121fn foo() {
122 let m = [1, 2, 3]
123 .iter()
124 .filter_map(|x| if *x == 2 { Some (4) } else { None })
125 .map(|x| x + 2)
126 .len();
127}
128"#,
129 );
130 }
131
132 #[test]
133 fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() {
134 check_diagnostics(
135 r#"
136fn foo() {
137 let m = [1, 2, 3]
138 .iter()
139 .filter_map(|x| if *x == 2 { Some (4) } else { None });
140 let n = m.next();
141}
142"#,
143 );
144 }
145
146 #[test]
147 fn replace_with_wind_map() {
148 check_fix(
149 r#"
150//- /main.rs crate:main deps:core
151use core::iter::Iterator;
152use core::option::Option::{self, Some, None};
153fn foo() {
154 let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
155}
156//- /core/lib.rs crate:core
157pub mod option {
158 pub enum Option<T> { Some(T), None }
159}
160pub mod iter {
161 pub trait Iterator {
162 type Item;
163 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
164 fn next(&mut self) -> Option<Self::Item>;
165 }
166 pub struct FilterMap {}
167 impl Iterator for FilterMap {
168 type Item = i32;
169 fn next(&mut self) -> i32 { 7 }
170 }
171}
172"#,
173 r#"
174use core::iter::Iterator;
175use core::option::Option::{self, Some, None};
176fn foo() {
177 let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None });
178}
179"#,
180 )
181 }
182}
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}
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs
index 51fe0f360..a5b2e3399 100644
--- a/crates/ide/src/diagnostics/unlinked_file.rs
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -1,11 +1,6 @@
1//! Diagnostic emitted for files that aren't part of any crate. 1//! Diagnostic emitted for files that aren't part of any crate.
2 2
3use hir::{ 3use hir::db::DefDatabase;
4 db::DefDatabase,
5 diagnostics::{Diagnostic, DiagnosticCode},
6 InFile,
7};
8use ide_assists::AssistResolveStrategy;
9use ide_db::{ 4use ide_db::{
10 base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, 5 base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt},
11 source_change::SourceChange, 6 source_change::SourceChange,
@@ -13,92 +8,77 @@ use ide_db::{
13}; 8};
14use syntax::{ 9use syntax::{
15 ast::{self, ModuleItemOwner, NameOwner}, 10 ast::{self, ModuleItemOwner, NameOwner},
16 AstNode, SyntaxNodePtr, 11 AstNode, TextRange, TextSize,
17}; 12};
18use text_edit::TextEdit; 13use text_edit::TextEdit;
19 14
20use crate::{ 15use crate::{
21 diagnostics::{fix, fixes::DiagnosticWithFixes}, 16 diagnostics::{fix, DiagnosticsContext},
22 Assist, 17 Assist, Diagnostic,
23}; 18};
24 19
20#[derive(Debug)]
21pub(crate) struct UnlinkedFile {
22 pub(crate) file: FileId,
23}
24
25// Diagnostic: unlinked-file 25// Diagnostic: unlinked-file
26// 26//
27// This diagnostic is shown for files that are not included in any crate, or files that are part of 27// This diagnostic is shown for files that are not included in any crate, or files that are part of
28// crates rust-analyzer failed to discover. The file will not have IDE features available. 28// crates rust-analyzer failed to discover. The file will not have IDE features available.
29#[derive(Debug)] 29pub(super) fn unlinked_file(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Diagnostic {
30pub(crate) struct UnlinkedFile { 30 // Limit diagnostic to the first few characters in the file. This matches how VS Code
31 pub(crate) file_id: FileId, 31 // renders it with the full span, but on other editors, and is less invasive.
32 pub(crate) node: SyntaxNodePtr, 32 let range = ctx.sema.db.parse(d.file).syntax_node().text_range();
33 // FIXME: This is wrong if one of the first three characters is not ascii: `//Ы`.
34 let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
35
36 Diagnostic::new("unlinked-file", "file not included in module tree", range)
37 .with_fixes(fixes(ctx, d))
33} 38}
34 39
35impl Diagnostic for UnlinkedFile { 40fn fixes(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Option<Vec<Assist>> {
36 fn code(&self) -> DiagnosticCode { 41 // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file,
37 DiagnosticCode("unlinked-file") 42 // suggest that as a fix.
38 }
39 43
40 fn message(&self) -> String { 44 let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(d.file));
41 "file not included in module tree".to_string() 45 let our_path = source_root.path_for_file(&d.file)?;
42 } 46 let module_name = our_path.name_and_extension()?.0;
43 47
44 fn display_source(&self) -> InFile<SyntaxNodePtr> { 48 // Candidates to look for:
45 InFile::new(self.file_id.into(), self.node.clone()) 49 // - `mod.rs` in the same folder
46 } 50 // - we also check `main.rs` and `lib.rs`
51 // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
52 let parent = our_path.parent()?;
53 let mut paths = vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?];
47 54
48 fn as_any(&self) -> &(dyn std::any::Any + Send + 'static) { 55 // `submod/bla.rs` -> `submod.rs`
49 self 56 if let Some(newmod) = (|| {
57 let name = parent.name_and_extension()?.0;
58 parent.parent()?.join(&format!("{}.rs", name))
59 })() {
60 paths.push(newmod);
50 } 61 }
51}
52 62
53impl DiagnosticWithFixes for UnlinkedFile { 63 for path in &paths {
54 fn fixes( 64 if let Some(parent_id) = source_root.file_for_path(path) {
55 &self, 65 for krate in ctx.sema.db.relevant_crates(*parent_id).iter() {
56 sema: &hir::Semantics<RootDatabase>, 66 let crate_def_map = ctx.sema.db.crate_def_map(*krate);
57 _resolve: &AssistResolveStrategy, 67 for (_, module) in crate_def_map.modules() {
58 ) -> Option<Vec<Assist>> { 68 if module.origin.is_inline() {
59 // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file, 69 // We don't handle inline `mod parent {}`s, they use different paths.
60 // suggest that as a fix. 70 continue;
61 71 }
62 let source_root = sema.db.source_root(sema.db.file_source_root(self.file_id));
63 let our_path = source_root.path_for_file(&self.file_id)?;
64 let module_name = our_path.name_and_extension()?.0;
65
66 // Candidates to look for:
67 // - `mod.rs` in the same folder
68 // - we also check `main.rs` and `lib.rs`
69 // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
70 let parent = our_path.parent()?;
71 let mut paths =
72 vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?];
73
74 // `submod/bla.rs` -> `submod.rs`
75 if let Some(newmod) = (|| {
76 let name = parent.name_and_extension()?.0;
77 parent.parent()?.join(&format!("{}.rs", name))
78 })() {
79 paths.push(newmod);
80 }
81 72
82 for path in &paths { 73 if module.origin.file_id() == Some(*parent_id) {
83 if let Some(parent_id) = source_root.file_for_path(path) { 74 return make_fixes(ctx.sema.db, *parent_id, module_name, d.file);
84 for krate in sema.db.relevant_crates(*parent_id).iter() {
85 let crate_def_map = sema.db.crate_def_map(*krate);
86 for (_, module) in crate_def_map.modules() {
87 if module.origin.is_inline() {
88 // We don't handle inline `mod parent {}`s, they use different paths.
89 continue;
90 }
91
92 if module.origin.file_id() == Some(*parent_id) {
93 return make_fixes(sema.db, *parent_id, module_name, self.file_id);
94 }
95 } 75 }
96 } 76 }
97 } 77 }
98 } 78 }
99
100 None
101 } 79 }
80
81 None
102} 82}
103 83
104fn make_fixes( 84fn make_fixes(
@@ -181,3 +161,144 @@ fn make_fixes(
181 ), 161 ),
182 ]) 162 ])
183} 163}
164
165#[cfg(test)]
166mod tests {
167 use crate::diagnostics::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix};
168
169 #[test]
170 fn unlinked_file_prepend_first_item() {
171 cov_mark::check!(unlinked_file_prepend_before_first_item);
172 // Only tests the first one for `pub mod` since the rest are the same
173 check_fixes(
174 r#"
175//- /main.rs
176fn f() {}
177//- /foo.rs
178$0
179"#,
180 vec![
181 r#"
182mod foo;
183
184fn f() {}
185"#,
186 r#"
187pub mod foo;
188
189fn f() {}
190"#,
191 ],
192 );
193 }
194
195 #[test]
196 fn unlinked_file_append_mod() {
197 cov_mark::check!(unlinked_file_append_to_existing_mods);
198 check_fix(
199 r#"
200//- /main.rs
201//! Comment on top
202
203mod preexisting;
204
205mod preexisting2;
206
207struct S;
208
209mod preexisting_bottom;)
210//- /foo.rs
211$0
212"#,
213 r#"
214//! Comment on top
215
216mod preexisting;
217
218mod preexisting2;
219mod foo;
220
221struct S;
222
223mod preexisting_bottom;)
224"#,
225 );
226 }
227
228 #[test]
229 fn unlinked_file_insert_in_empty_file() {
230 cov_mark::check!(unlinked_file_empty_file);
231 check_fix(
232 r#"
233//- /main.rs
234//- /foo.rs
235$0
236"#,
237 r#"
238mod foo;
239"#,
240 );
241 }
242
243 #[test]
244 fn unlinked_file_old_style_modrs() {
245 check_fix(
246 r#"
247//- /main.rs
248mod submod;
249//- /submod/mod.rs
250// in mod.rs
251//- /submod/foo.rs
252$0
253"#,
254 r#"
255// in mod.rs
256mod foo;
257"#,
258 );
259 }
260
261 #[test]
262 fn unlinked_file_new_style_mod() {
263 check_fix(
264 r#"
265//- /main.rs
266mod submod;
267//- /submod.rs
268//- /submod/foo.rs
269$0
270"#,
271 r#"
272mod foo;
273"#,
274 );
275 }
276
277 #[test]
278 fn unlinked_file_with_cfg_off() {
279 cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
280 check_no_fix(
281 r#"
282//- /main.rs
283#[cfg(never)]
284mod foo;
285
286//- /foo.rs
287$0
288"#,
289 );
290 }
291
292 #[test]
293 fn unlinked_file_with_cfg_on() {
294 check_diagnostics(
295 r#"
296//- /main.rs
297#[cfg(not(never))]
298mod foo;
299
300//- /foo.rs
301"#,
302 );
303 }
304}
diff --git a/crates/ide/src/diagnostics/unresolved_extern_crate.rs b/crates/ide/src/diagnostics/unresolved_extern_crate.rs
new file mode 100644
index 000000000..2ea79c2ee
--- /dev/null
+++ b/crates/ide/src/diagnostics/unresolved_extern_crate.rs
@@ -0,0 +1,49 @@
1use crate::diagnostics::{Diagnostic, DiagnosticsContext};
2
3// Diagnostic: unresolved-extern-crate
4//
5// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
6pub(super) fn unresolved_extern_crate(
7 ctx: &DiagnosticsContext<'_>,
8 d: &hir::UnresolvedExternCrate,
9) -> Diagnostic {
10 Diagnostic::new(
11 "unresolved-extern-crate",
12 "unresolved extern crate",
13 ctx.sema.diagnostics_display_range(d.decl.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 unresolved_extern_crate() {
23 check_diagnostics(
24 r#"
25//- /main.rs crate:main deps:core
26extern crate core;
27 extern crate doesnotexist;
28//^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
29//- /lib.rs crate:core
30"#,
31 );
32 }
33
34 #[test]
35 fn extern_crate_self_as() {
36 cov_mark::check!(extern_crate_self_as);
37 check_diagnostics(
38 r#"
39//- /lib.rs
40 extern crate doesnotexist;
41//^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
42// Should not error.
43extern crate self as foo;
44struct Foo;
45use foo::Foo as Bar;
46"#,
47 );
48 }
49}
diff --git a/crates/ide/src/diagnostics/unresolved_import.rs b/crates/ide/src/diagnostics/unresolved_import.rs
new file mode 100644
index 000000000..1cbf96ba1
--- /dev/null
+++ b/crates/ide/src/diagnostics/unresolved_import.rs
@@ -0,0 +1,90 @@
1use crate::diagnostics::{Diagnostic, DiagnosticsContext};
2
3// Diagnostic: unresolved-import
4//
5// This diagnostic is triggered if rust-analyzer is unable to resolve a path in
6// a `use` declaration.
7pub(super) fn unresolved_import(
8 ctx: &DiagnosticsContext<'_>,
9 d: &hir::UnresolvedImport,
10) -> Diagnostic {
11 Diagnostic::new(
12 "unresolved-import",
13 "unresolved import",
14 ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
15 )
16 // This currently results in false positives in the following cases:
17 // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
18 // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
19 // - proc macros and/or proc macro generated code
20 .experimental()
21}
22
23#[cfg(test)]
24mod tests {
25 use crate::diagnostics::tests::check_diagnostics;
26
27 #[test]
28 fn unresolved_import() {
29 check_diagnostics(
30 r#"
31use does_exist;
32use does_not_exist;
33 //^^^^^^^^^^^^^^ unresolved import
34
35mod does_exist {}
36"#,
37 );
38 }
39
40 #[test]
41 fn unresolved_import_in_use_tree() {
42 // Only the relevant part of a nested `use` item should be highlighted.
43 check_diagnostics(
44 r#"
45use does_exist::{Exists, DoesntExist};
46 //^^^^^^^^^^^ unresolved import
47
48use {does_not_exist::*, does_exist};
49 //^^^^^^^^^^^^^^^^^ unresolved import
50
51use does_not_exist::{
52 a,
53 //^ unresolved import
54 b,
55 //^ unresolved import
56 c,
57 //^ unresolved import
58};
59
60mod does_exist {
61 pub struct Exists;
62}
63"#,
64 );
65 }
66
67 #[test]
68 fn dedup_unresolved_import_from_unresolved_crate() {
69 check_diagnostics(
70 r#"
71//- /main.rs crate:main
72mod a {
73 extern crate doesnotexist;
74 //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
75
76 // Should not error, since we already errored for the missing crate.
77 use doesnotexist::{self, bla, *};
78
79 use crate::doesnotexist;
80 //^^^^^^^^^^^^^^^^^^^ unresolved import
81}
82
83mod m {
84 use super::doesnotexist;
85 //^^^^^^^^^^^^^^^^^^^ unresolved import
86}
87"#,
88 );
89 }
90}
diff --git a/crates/ide/src/diagnostics/unresolved_macro_call.rs b/crates/ide/src/diagnostics/unresolved_macro_call.rs
new file mode 100644
index 000000000..15b6a2730
--- /dev/null
+++ b/crates/ide/src/diagnostics/unresolved_macro_call.rs
@@ -0,0 +1,84 @@
1use hir::{db::AstDatabase, InFile};
2use syntax::{AstNode, SyntaxNodePtr};
3
4use crate::diagnostics::{Diagnostic, DiagnosticsContext};
5
6// Diagnostic: unresolved-macro-call
7//
8// This diagnostic is triggered if rust-analyzer is unable to resolve the path
9// to a macro in a macro invocation.
10pub(super) fn unresolved_macro_call(
11 ctx: &DiagnosticsContext<'_>,
12 d: &hir::UnresolvedMacroCall,
13) -> Diagnostic {
14 let last_path_segment = ctx.sema.db.parse_or_expand(d.macro_call.file_id).and_then(|root| {
15 d.macro_call
16 .value
17 .to_node(&root)
18 .path()
19 .and_then(|it| it.segment())
20 .and_then(|it| it.name_ref())
21 .map(|it| InFile::new(d.macro_call.file_id, SyntaxNodePtr::new(it.syntax())))
22 });
23 let diagnostics = last_path_segment.unwrap_or_else(|| d.macro_call.clone().map(|it| it.into()));
24
25 Diagnostic::new(
26 "unresolved-macro-call",
27 format!("unresolved macro `{}!`", d.path),
28 ctx.sema.diagnostics_display_range(diagnostics).range,
29 )
30 .experimental()
31}
32
33#[cfg(test)]
34mod tests {
35 use crate::diagnostics::tests::check_diagnostics;
36
37 #[test]
38 fn unresolved_macro_diag() {
39 check_diagnostics(
40 r#"
41fn f() {
42 m!();
43} //^ unresolved macro `m!`
44
45"#,
46 );
47 }
48
49 #[test]
50 fn test_unresolved_macro_range() {
51 check_diagnostics(
52 r#"
53foo::bar!(92);
54 //^^^ unresolved macro `foo::bar!`
55"#,
56 );
57 }
58
59 #[test]
60 fn unresolved_legacy_scope_macro() {
61 check_diagnostics(
62 r#"
63macro_rules! m { () => {} }
64
65m!(); m2!();
66 //^^ unresolved macro `self::m2!`
67"#,
68 );
69 }
70
71 #[test]
72 fn unresolved_module_scope_macro() {
73 check_diagnostics(
74 r#"
75mod mac {
76#[macro_export]
77macro_rules! m { () => {} } }
78
79self::m!(); self::m2!();
80 //^^ unresolved macro `self::m2!`
81"#,
82 );
83 }
84}
diff --git a/crates/ide/src/diagnostics/fixes/unresolved_module.rs b/crates/ide/src/diagnostics/unresolved_module.rs
index b3d0283bb..977b46414 100644
--- a/crates/ide/src/diagnostics/fixes/unresolved_module.rs
+++ b/crates/ide/src/diagnostics/unresolved_module.rs
@@ -1,39 +1,62 @@
1use hir::{db::AstDatabase, diagnostics::UnresolvedModule, Semantics}; 1use hir::db::AstDatabase;
2use ide_assists::{Assist, AssistResolveStrategy}; 2use ide_assists::Assist;
3use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase}; 3use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit};
4use syntax::AstNode; 4use syntax::AstNode;
5 5
6use crate::diagnostics::{fix, DiagnosticWithFixes}; 6use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
7 7
8impl DiagnosticWithFixes for UnresolvedModule { 8// Diagnostic: unresolved-module
9 fn fixes( 9//
10 &self, 10// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
11 sema: &Semantics<RootDatabase>, 11pub(super) fn unresolved_module(
12 _resolve: &AssistResolveStrategy, 12 ctx: &DiagnosticsContext<'_>,
13 ) -> Option<Vec<Assist>> { 13 d: &hir::UnresolvedModule,
14 let root = sema.db.parse_or_expand(self.file)?; 14) -> Diagnostic {
15 let unresolved_module = self.decl.to_node(&root); 15 Diagnostic::new(
16 Some(vec![fix( 16 "unresolved-module",
17 "create_module", 17 "unresolved module",
18 "Create module", 18 ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
19 FileSystemEdit::CreateFile { 19 )
20 dst: AnchoredPathBuf { 20 .with_fixes(fixes(ctx, d))
21 anchor: self.file.original_file(sema.db), 21}
22 path: self.candidate.clone(), 22
23 }, 23fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedModule) -> Option<Vec<Assist>> {
24 initial_contents: "".to_string(), 24 let root = ctx.sema.db.parse_or_expand(d.decl.file_id)?;
25 } 25 let unresolved_module = d.decl.value.to_node(&root);
26 .into(), 26 Some(vec![fix(
27 unresolved_module.syntax().text_range(), 27 "create_module",
28 )]) 28 "Create module",
29 } 29 FileSystemEdit::CreateFile {
30 dst: AnchoredPathBuf {
31 anchor: d.decl.file_id.original_file(ctx.sema.db),
32 path: d.candidate.clone(),
33 },
34 initial_contents: "".to_string(),
35 }
36 .into(),
37 unresolved_module.syntax().text_range(),
38 )])
30} 39}
31 40
32#[cfg(test)] 41#[cfg(test)]
33mod tests { 42mod tests {
34 use expect_test::expect; 43 use expect_test::expect;
35 44
36 use crate::diagnostics::tests::check_expect; 45 use crate::diagnostics::tests::{check_diagnostics, check_expect};
46
47 #[test]
48 fn unresolved_module() {
49 check_diagnostics(
50 r#"
51//- /lib.rs
52mod foo;
53 mod bar;
54//^^^^^^^^ unresolved module
55mod baz {}
56//- /foo.rs
57"#,
58 );
59 }
37 60
38 #[test] 61 #[test]
39 fn test_unresolved_module_diagnostic() { 62 fn test_unresolved_module_diagnostic() {
@@ -42,9 +65,14 @@ mod tests {
42 expect![[r#" 65 expect![[r#"
43 [ 66 [
44 Diagnostic { 67 Diagnostic {
68 code: DiagnosticCode(
69 "unresolved-module",
70 ),
45 message: "unresolved module", 71 message: "unresolved module",
46 range: 0..8, 72 range: 0..8,
47 severity: Error, 73 severity: Error,
74 unused: false,
75 experimental: false,
48 fixes: Some( 76 fixes: Some(
49 [ 77 [
50 Assist { 78 Assist {
@@ -75,12 +103,6 @@ mod tests {
75 }, 103 },
76 ], 104 ],
77 ), 105 ),
78 unused: false,
79 code: Some(
80 DiagnosticCode(
81 "unresolved-module",
82 ),
83 ),
84 }, 106 },
85 ] 107 ]
86 "#]], 108 "#]],
diff --git a/crates/ide/src/diagnostics/unresolved_proc_macro.rs b/crates/ide/src/diagnostics/unresolved_proc_macro.rs
new file mode 100644
index 000000000..3dc6ab451
--- /dev/null
+++ b/crates/ide/src/diagnostics/unresolved_proc_macro.rs
@@ -0,0 +1,30 @@
1use crate::{
2 diagnostics::{Diagnostic, DiagnosticsContext},
3 Severity,
4};
5
6// Diagnostic: unresolved-proc-macro
7//
8// This diagnostic is shown when a procedural macro can not be found. This usually means that
9// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
10// but can also indicate project setup problems.
11//
12// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
13// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
14// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
15pub(super) fn unresolved_proc_macro(
16 ctx: &DiagnosticsContext<'_>,
17 d: &hir::UnresolvedProcMacro,
18) -> Diagnostic {
19 // Use more accurate position if available.
20 let display_range = d
21 .precise_location
22 .unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()).range);
23 // FIXME: it would be nice to tell the user whether proc macros are currently disabled
24 let message = match &d.macro_name {
25 Some(name) => format!("proc macro `{}` not expanded", name),
26 None => "proc macro not expanded".to_string(),
27 };
28
29 Diagnostic::new("unresolved-proc-macro", message, display_range).severity(Severity::WeakWarning)
30}
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index ec3828ab2..57ae9455b 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -151,18 +151,18 @@ pub(crate) fn resolve_doc_path_for_def(
151) -> Option<hir::ModuleDef> { 151) -> Option<hir::ModuleDef> {
152 match def { 152 match def {
153 Definition::ModuleDef(def) => match def { 153 Definition::ModuleDef(def) => match def {
154 hir::ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns), 154 hir::ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns),
155 hir::ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns), 155 hir::ModuleDef::Function(it) => it.resolve_doc_path(db, link, ns),
156 hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns), 156 hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, link, ns),
157 hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns), 157 hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, link, ns),
158 hir::ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns), 158 hir::ModuleDef::Const(it) => it.resolve_doc_path(db, link, ns),
159 hir::ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns), 159 hir::ModuleDef::Static(it) => it.resolve_doc_path(db, link, ns),
160 hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns), 160 hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, link, ns),
161 hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns), 161 hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, link, ns),
162 hir::ModuleDef::BuiltinType(_) => None, 162 hir::ModuleDef::BuiltinType(_) => None,
163 }, 163 },
164 Definition::Macro(it) => it.resolve_doc_path(db, &link, ns), 164 Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
165 Definition::Field(it) => it.resolve_doc_path(db, &link, ns), 165 Definition::Field(it) => it.resolve_doc_path(db, link, ns),
166 Definition::SelfType(_) 166 Definition::SelfType(_)
167 | Definition::Local(_) 167 | Definition::Local(_)
168 | Definition::GenericParam(_) 168 | Definition::GenericParam(_)
@@ -192,7 +192,7 @@ pub(crate) fn doc_attributes(
192 ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))), 192 ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
193 ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))), 193 ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))),
194 // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))), 194 // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
195 _ => return None 195 _ => None
196 } 196 }
197 } 197 }
198} 198}
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 7032889ac..c7ec87edf 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -328,7 +328,7 @@ mod tests {
328 use super::*; 328 use super::*;
329 329
330 fn do_check(before: &str, afters: &[&str]) { 330 fn do_check(before: &str, afters: &[&str]) {
331 let (analysis, position) = fixture::position(&before); 331 let (analysis, position) = fixture::position(before);
332 let before = analysis.file_text(position.file_id).unwrap(); 332 let before = analysis.file_text(position.file_id).unwrap();
333 let range = TextRange::empty(position.offset); 333 let range = TextRange::empty(position.offset);
334 let mut frange = FileRange { file_id: position.file_id, range }; 334 let mut frange = FileRange { file_id: position.file_id, range };
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs
index 6780af617..38e2e866b 100644
--- a/crates/ide/src/fixture.rs
+++ b/crates/ide/src/fixture.rs
@@ -1,6 +1,5 @@
1//! Utilities for creating `Analysis` instances for tests. 1//! Utilities for creating `Analysis` instances for tests.
2use ide_db::base_db::fixture::ChangeFixture; 2use ide_db::base_db::fixture::ChangeFixture;
3use syntax::{TextRange, TextSize};
4use test_utils::extract_annotations; 3use test_utils::extract_annotations;
5 4
6use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; 5use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
@@ -63,15 +62,8 @@ pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(Fil
63 62
64pub(crate) fn nav_target_annotation(ra_fixture: &str) -> (Analysis, FilePosition, FileRange) { 63pub(crate) fn nav_target_annotation(ra_fixture: &str) -> (Analysis, FilePosition, FileRange) {
65 let (analysis, position, mut annotations) = annotations(ra_fixture); 64 let (analysis, position, mut annotations) = annotations(ra_fixture);
66 let (mut expected, data) = annotations.pop().unwrap(); 65 let (expected, data) = annotations.pop().unwrap();
67 assert!(annotations.is_empty()); 66 assert!(annotations.is_empty());
68 match data.as_str() { 67 assert_eq!(data, "");
69 "" => (),
70 "file" => {
71 expected.range =
72 TextRange::up_to(TextSize::of(&*analysis.file_text(expected.file_id).unwrap()))
73 }
74 data => panic!("bad data: {}", data),
75 }
76 (analysis, position, expected) 68 (analysis, position, expected)
77} 69}
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 2d36c34e9..8dd643a0f 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -43,7 +43,7 @@ pub(crate) fn goto_definition(
43 let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; 43 let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
44 let (_, link, ns) = 44 let (_, link, ns) =
45 extract_definitions_from_markdown(docs.as_str()).into_iter().find(|(range, ..)| { 45 extract_definitions_from_markdown(docs.as_str()).into_iter().find(|(range, ..)| {
46 doc_mapping.map(range.clone()).map_or(false, |InFile { file_id, value: range }| { 46 doc_mapping.map(*range).map_or(false, |InFile { file_id, value: range }| {
47 file_id == position.file_id.into() && range.contains(position.offset) 47 file_id == position.file_id.into() && range.contains(position.offset)
48 }) 48 })
49 })?; 49 })?;
@@ -57,7 +57,7 @@ pub(crate) fn goto_definition(
57 }, 57 },
58 ast::Name(name) => { 58 ast::Name(name) => {
59 let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); 59 let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db);
60 try_find_trait_item_definition(&sema.db, &def) 60 try_find_trait_item_definition(sema.db, &def)
61 .or_else(|| def.try_to_nav(sema.db)) 61 .or_else(|| def.try_to_nav(sema.db))
62 }, 62 },
63 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) { 63 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) {
@@ -185,7 +185,7 @@ mod tests {
185extern crate std$0; 185extern crate std$0;
186//- /std/lib.rs crate:std 186//- /std/lib.rs crate:std
187// empty 187// empty
188//^ file 188//^file
189"#, 189"#,
190 ) 190 )
191 } 191 }
@@ -198,7 +198,7 @@ extern crate std$0;
198extern crate std as abc$0; 198extern crate std as abc$0;
199//- /std/lib.rs crate:std 199//- /std/lib.rs crate:std
200// empty 200// empty
201//^ file 201//^file
202"#, 202"#,
203 ) 203 )
204 } 204 }
@@ -253,7 +253,7 @@ mod $0foo;
253 253
254//- /foo.rs 254//- /foo.rs
255// empty 255// empty
256//^ file 256//^file
257"#, 257"#,
258 ); 258 );
259 259
@@ -264,7 +264,7 @@ mod $0foo;
264 264
265//- /foo/mod.rs 265//- /foo/mod.rs
266// empty 266// empty
267//^ file 267//^file
268"#, 268"#,
269 ); 269 );
270 } 270 }
@@ -395,7 +395,7 @@ use foo as bar$0;
395 395
396//- /foo/lib.rs crate:foo 396//- /foo/lib.rs crate:foo
397// empty 397// empty
398//^ file 398//^file
399"#, 399"#,
400 ); 400 );
401 } 401 }
@@ -1287,7 +1287,7 @@ fn main() {
1287} 1287}
1288//- /foo.txt 1288//- /foo.txt
1289// empty 1289// empty
1290//^ file 1290//^file
1291"#, 1291"#,
1292 ); 1292 );
1293 } 1293 }
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 43356a94e..0013820b4 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -52,13 +52,13 @@ pub(crate) fn goto_implementation(
52 hir::ModuleDef::Function(f) => { 52 hir::ModuleDef::Function(f) => {
53 let assoc = f.as_assoc_item(sema.db)?; 53 let assoc = f.as_assoc_item(sema.db)?;
54 let name = assoc.name(sema.db)?; 54 let name = assoc.name(sema.db)?;
55 let trait_ = assoc.containing_trait(sema.db)?; 55 let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?;
56 impls_for_trait_item(&sema, trait_, name) 56 impls_for_trait_item(&sema, trait_, name)
57 } 57 }
58 hir::ModuleDef::Const(c) => { 58 hir::ModuleDef::Const(c) => {
59 let assoc = c.as_assoc_item(sema.db)?; 59 let assoc = c.as_assoc_item(sema.db)?;
60 let name = assoc.name(sema.db)?; 60 let name = assoc.name(sema.db)?;
61 let trait_ = assoc.containing_trait(sema.db)?; 61 let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?;
62 impls_for_trait_item(&sema, trait_, name) 62 impls_for_trait_item(&sema, trait_, name)
63 } 63 }
64 _ => return None, 64 _ => return None,
@@ -87,7 +87,7 @@ fn impls_for_trait_item(
87 .filter_map(|imp| { 87 .filter_map(|imp| {
88 let item = imp.items(sema.db).iter().find_map(|itm| { 88 let item = imp.items(sema.db).iter().find_map(|itm| {
89 let itm_name = itm.name(sema.db)?; 89 let itm_name = itm.name(sema.db)?;
90 (itm_name == fun_name).then(|| itm.clone()) 90 (itm_name == fun_name).then(|| *itm)
91 })?; 91 })?;
92 item.try_to_nav(sema.db) 92 item.try_to_nav(sema.db)
93 }) 93 })
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index ed4f18e1f..c08516805 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -131,7 +131,7 @@ pub(crate) fn hover(
131 let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; 131 let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
132 let (idl_range, link, ns) = 132 let (idl_range, link, ns) =
133 extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| { 133 extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| {
134 let InFile { file_id, value: range } = doc_mapping.map(range.clone())?; 134 let InFile { file_id, value: range } = doc_mapping.map(range)?;
135 if file_id == position.file_id.into() && range.contains(position.offset) { 135 if file_id == position.file_id.into() && range.contains(position.offset) {
136 Some((range, link, ns)) 136 Some((range, link, ns))
137 } else { 137 } else {
@@ -208,7 +208,7 @@ pub(crate) fn hover(
208} 208}
209 209
210fn try_hover_for_attribute(token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> { 210fn try_hover_for_attribute(token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
211 let attr = token.ancestors().nth(1).and_then(ast::Attr::cast)?; 211 let attr = token.ancestors().find_map(ast::Attr::cast)?;
212 let (path, tt) = attr.as_simple_call()?; 212 let (path, tt) = attr.as_simple_call()?;
213 if !tt.syntax().text_range().contains(token.text_range().start()) { 213 if !tt.syntax().text_range().contains(token.text_range().start()) {
214 return None; 214 return None;
@@ -288,7 +288,7 @@ fn runnable_action(
288) -> Option<HoverAction> { 288) -> Option<HoverAction> {
289 match def { 289 match def {
290 Definition::ModuleDef(it) => match it { 290 Definition::ModuleDef(it) => match it {
291 ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)), 291 ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
292 ModuleDef::Function(func) => { 292 ModuleDef::Function(func) => {
293 let src = func.source(sema.db)?; 293 let src = func.source(sema.db)?;
294 if src.file_id != file_id.into() { 294 if src.file_id != file_id.into() {
@@ -297,7 +297,7 @@ fn runnable_action(
297 return None; 297 return None;
298 } 298 }
299 299
300 runnable_fn(&sema, func).map(HoverAction::Runnable) 300 runnable_fn(sema, func).map(HoverAction::Runnable)
301 } 301 }
302 _ => None, 302 _ => None,
303 }, 303 },
@@ -432,7 +432,7 @@ fn hover_for_definition(
432 return match def { 432 return match def {
433 Definition::Macro(it) => match &it.source(db)?.value { 433 Definition::Macro(it) => match &it.source(db)?.value {
434 Either::Left(mac) => { 434 Either::Left(mac) => {
435 let label = macro_label(&mac); 435 let label = macro_label(mac);
436 from_def_source_labeled(db, it, Some(label), mod_path) 436 from_def_source_labeled(db, it, Some(label), mod_path)
437 } 437 }
438 Either::Right(_) => { 438 Either::Right(_) => {
@@ -516,7 +516,7 @@ fn hover_for_keyword(
516 if !token.kind().is_keyword() { 516 if !token.kind().is_keyword() {
517 return None; 517 return None;
518 } 518 }
519 let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()?).krate()); 519 let famous_defs = FamousDefs(sema, sema.scope(&token.parent()?).krate());
520 // std exposes {}_keyword modules with docstrings on the root to document keywords 520 // std exposes {}_keyword modules with docstrings on the root to document keywords
521 let keyword_mod = format!("{}_keyword", token.text()); 521 let keyword_mod = format!("{}_keyword", token.text());
522 let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; 522 let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 821c61403..9cd33d0e4 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -96,7 +96,7 @@ fn get_chaining_hints(
96 } 96 }
97 97
98 let krate = sema.scope(expr.syntax()).module().map(|it| it.krate()); 98 let krate = sema.scope(expr.syntax()).module().map(|it| it.krate());
99 let famous_defs = FamousDefs(&sema, krate); 99 let famous_defs = FamousDefs(sema, krate);
100 100
101 let mut tokens = expr 101 let mut tokens = expr
102 .syntax() 102 .syntax()
@@ -165,7 +165,7 @@ fn get_param_name_hints(
165 }; 165 };
166 Some((param_name, arg)) 166 Some((param_name, arg))
167 }) 167 })
168 .filter(|(param_name, arg)| !should_hide_param_name_hint(sema, &callable, param_name, &arg)) 168 .filter(|(param_name, arg)| !should_hide_param_name_hint(sema, &callable, param_name, arg))
169 .map(|(param_name, arg)| InlayHint { 169 .map(|(param_name, arg)| InlayHint {
170 range: arg.syntax().text_range(), 170 range: arg.syntax().text_range(),
171 kind: InlayKind::ParameterHint, 171 kind: InlayKind::ParameterHint,
@@ -187,7 +187,7 @@ fn get_bind_pat_hints(
187 } 187 }
188 188
189 let krate = sema.scope(pat.syntax()).module().map(|it| it.krate()); 189 let krate = sema.scope(pat.syntax()).module().map(|it| it.krate());
190 let famous_defs = FamousDefs(&sema, krate); 190 let famous_defs = FamousDefs(sema, krate);
191 191
192 let ty = sema.type_of_pat(&pat.clone().into())?; 192 let ty = sema.type_of_pat(&pat.clone().into())?;
193 193
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index c67ccd1a9..93d3760bf 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -60,7 +60,7 @@ fn remove_newlines(edit: &mut TextEditBuilder, token: &SyntaxToken, range: TextR
60 let pos: TextSize = (pos as u32).into(); 60 let pos: TextSize = (pos as u32).into();
61 let offset = token.text_range().start() + range.start() + pos; 61 let offset = token.text_range().start() + range.start() + pos;
62 if !edit.invalidates_offset(offset) { 62 if !edit.invalidates_offset(offset) {
63 remove_newline(edit, &token, offset); 63 remove_newline(edit, token, offset);
64 } 64 }
65 } 65 }
66} 66}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 97c9e5d2b..0511efae3 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -282,20 +282,20 @@ impl Analysis {
282 file_id: FileId, 282 file_id: FileId,
283 text_range: Option<TextRange>, 283 text_range: Option<TextRange>,
284 ) -> Cancellable<String> { 284 ) -> Cancellable<String> {
285 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) 285 self.with_db(|db| syntax_tree::syntax_tree(db, file_id, text_range))
286 } 286 }
287 287
288 pub fn view_hir(&self, position: FilePosition) -> Cancellable<String> { 288 pub fn view_hir(&self, position: FilePosition) -> Cancellable<String> {
289 self.with_db(|db| view_hir::view_hir(&db, position)) 289 self.with_db(|db| view_hir::view_hir(db, position))
290 } 290 }
291 291
292 pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> { 292 pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> {
293 self.with_db(|db| view_item_tree::view_item_tree(&db, file_id)) 293 self.with_db(|db| view_item_tree::view_item_tree(db, file_id))
294 } 294 }
295 295
296 /// Renders the crate graph to GraphViz "dot" syntax. 296 /// Renders the crate graph to GraphViz "dot" syntax.
297 pub fn view_crate_graph(&self) -> Cancellable<Result<String, String>> { 297 pub fn view_crate_graph(&self) -> Cancellable<Result<String, String>> {
298 self.with_db(|db| view_crate_graph::view_crate_graph(&db)) 298 self.with_db(|db| view_crate_graph::view_crate_graph(db))
299 } 299 }
300 300
301 pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> { 301 pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> {
@@ -315,7 +315,7 @@ impl Analysis {
315 /// up minor stuff like continuing the comment. 315 /// up minor stuff like continuing the comment.
316 /// The edit will be a snippet (with `$0`). 316 /// The edit will be a snippet (with `$0`).
317 pub fn on_enter(&self, position: FilePosition) -> Cancellable<Option<TextEdit>> { 317 pub fn on_enter(&self, position: FilePosition) -> Cancellable<Option<TextEdit>> {
318 self.with_db(|db| typing::on_enter(&db, position)) 318 self.with_db(|db| typing::on_enter(db, position))
319 } 319 }
320 320
321 /// Returns an edit which should be applied after a character was typed. 321 /// Returns an edit which should be applied after a character was typed.
@@ -331,7 +331,7 @@ impl Analysis {
331 if !typing::TRIGGER_CHARS.contains(char_typed) { 331 if !typing::TRIGGER_CHARS.contains(char_typed) {
332 return Ok(None); 332 return Ok(None);
333 } 333 }
334 self.with_db(|db| typing::on_char_typed(&db, position, char_typed)) 334 self.with_db(|db| typing::on_char_typed(db, position, char_typed))
335 } 335 }
336 336
337 /// Returns a tree representation of symbols in the file. Useful to draw a 337 /// Returns a tree representation of symbols in the file. Useful to draw a
diff --git a/crates/ide/src/prime_caches.rs b/crates/ide/src/prime_caches.rs
index d912a01b8..36801c964 100644
--- a/crates/ide/src/prime_caches.rs
+++ b/crates/ide/src/prime_caches.rs
@@ -33,14 +33,15 @@ pub(crate) fn prime_caches(db: &RootDatabase, cb: &(dyn Fn(PrimeCachesProgress)
33 // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that. 33 // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that.
34 // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks 34 // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks
35 // cancellation, so we cannot use rayon. 35 // cancellation, so we cannot use rayon.
36 for (i, krate) in topo.iter().enumerate() { 36 for (i, &crate_id) in topo.iter().enumerate() {
37 let crate_name = graph[*krate].display_name.as_deref().unwrap_or_default().to_string(); 37 let crate_name = graph[crate_id].display_name.as_deref().unwrap_or_default().to_string();
38 38
39 cb(PrimeCachesProgress::StartedOnCrate { 39 cb(PrimeCachesProgress::StartedOnCrate {
40 on_crate: crate_name, 40 on_crate: crate_name,
41 n_done: i, 41 n_done: i,
42 n_total: topo.len(), 42 n_total: topo.len(),
43 }); 43 });
44 db.crate_def_map(*krate); 44 db.crate_def_map(crate_id);
45 db.import_map(crate_id);
45 } 46 }
46} 47}
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index f8b64a669..a0fdead2c 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -62,7 +62,7 @@ pub(crate) fn find_all_refs(
62 if let Some(name) = get_name_of_item_declaration(&syntax, position) { 62 if let Some(name) = get_name_of_item_declaration(&syntax, position) {
63 (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true) 63 (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true)
64 } else { 64 } else {
65 (find_def(&sema, &syntax, position)?, false) 65 (find_def(sema, &syntax, position)?, false)
66 }; 66 };
67 67
68 let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all(); 68 let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all();
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 2a4a1c3c8..50cc1f963 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -64,7 +64,7 @@ pub(crate) fn prepare_rename(
64 } 64 }
65 }; 65 };
66 let name_like = sema 66 let name_like = sema
67 .find_node_at_offset_with_descend(&syntax, position.offset) 67 .find_node_at_offset_with_descend(syntax, position.offset)
68 .ok_or_else(|| format_err!("No references found at position"))?; 68 .ok_or_else(|| format_err!("No references found at position"))?;
69 let node = match &name_like { 69 let node = match &name_like {
70 ast::NameLike::Name(it) => it.syntax(), 70 ast::NameLike::Name(it) => it.syntax(),
@@ -104,7 +104,7 @@ pub(crate) fn rename_with_semantics(
104 104
105 let def = find_definition(sema, syntax, position)?; 105 let def = find_definition(sema, syntax, position)?;
106 match def { 106 match def {
107 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), 107 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(sema, module, new_name),
108 Definition::SelfType(_) => bail!("Cannot rename `Self`"), 108 Definition::SelfType(_) => bail!("Cannot rename `Self`"),
109 Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"), 109 Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"),
110 def => rename_reference(sema, def, new_name), 110 def => rename_reference(sema, def, new_name),
@@ -239,7 +239,7 @@ fn rename_mod(
239 239
240fn rename_reference( 240fn rename_reference(
241 sema: &Semantics<RootDatabase>, 241 sema: &Semantics<RootDatabase>,
242 def: Definition, 242 mut def: Definition,
243 new_name: &str, 243 new_name: &str,
244) -> RenameResult<SourceChange> { 244) -> RenameResult<SourceChange> {
245 let ident_kind = check_identifier(new_name)?; 245 let ident_kind = check_identifier(new_name)?;
@@ -285,14 +285,45 @@ fn rename_reference(
285 } 285 }
286 } 286 }
287 287
288 def = match def {
289 // HACK: resolve trait impl items to the item def of the trait definition
290 // so that we properly resolve all trait item references
291 Definition::ModuleDef(mod_def) => mod_def
292 .as_assoc_item(sema.db)
293 .and_then(|it| it.containing_trait_impl(sema.db))
294 .and_then(|it| {
295 it.items(sema.db).into_iter().find_map(|it| match (it, mod_def) {
296 (hir::AssocItem::Function(trait_func), ModuleDef::Function(func))
297 if trait_func.name(sema.db) == func.name(sema.db) =>
298 {
299 Some(Definition::ModuleDef(ModuleDef::Function(trait_func)))
300 }
301 (hir::AssocItem::Const(trait_konst), ModuleDef::Const(konst))
302 if trait_konst.name(sema.db) == konst.name(sema.db) =>
303 {
304 Some(Definition::ModuleDef(ModuleDef::Const(trait_konst)))
305 }
306 (
307 hir::AssocItem::TypeAlias(trait_type_alias),
308 ModuleDef::TypeAlias(type_alias),
309 ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => {
310 Some(Definition::ModuleDef(ModuleDef::TypeAlias(trait_type_alias)))
311 }
312 _ => None,
313 })
314 })
315 .unwrap_or(def),
316 _ => def,
317 };
288 let usages = def.usages(sema).all(); 318 let usages = def.usages(sema).all();
319
289 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { 320 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
290 cov_mark::hit!(rename_underscore_multiple); 321 cov_mark::hit!(rename_underscore_multiple);
291 bail!("Cannot rename reference to `_` as it is being referenced multiple times"); 322 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
292 } 323 }
293 let mut source_change = SourceChange::default(); 324 let mut source_change = SourceChange::default();
294 source_change.extend(usages.iter().map(|(&file_id, references)| { 325 source_change.extend(usages.iter().map(|(&file_id, references)| {
295 (file_id, source_edit_from_references(&references, def, new_name)) 326 (file_id, source_edit_from_references(references, def, new_name))
296 })); 327 }));
297 328
298 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; 329 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
@@ -382,7 +413,7 @@ fn rename_self_to_param(
382 let mut source_change = SourceChange::default(); 413 let mut source_change = SourceChange::default();
383 source_change.insert_source_edit(file_id.original_file(sema.db), edit); 414 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
384 source_change.extend(usages.iter().map(|(&file_id, references)| { 415 source_change.extend(usages.iter().map(|(&file_id, references)| {
385 (file_id, source_edit_from_references(&references, def, new_name)) 416 (file_id, source_edit_from_references(references, def, new_name))
386 })); 417 }));
387 Ok(source_change) 418 Ok(source_change)
388} 419}
@@ -395,7 +426,7 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt
395 None 426 None
396 } 427 }
397 428
398 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; 429 let impl_def = self_param.syntax().ancestors().find_map(ast::Impl::cast)?;
399 let type_name = target_type_name(&impl_def)?; 430 let type_name = target_type_name(&impl_def)?;
400 431
401 let mut replacement_text = String::from(new_name); 432 let mut replacement_text = String::from(new_name);
@@ -1938,4 +1969,136 @@ use Bar$0;
1938 "error: Renaming aliases is currently unsupported", 1969 "error: Renaming aliases is currently unsupported",
1939 ); 1970 );
1940 } 1971 }
1972
1973 #[test]
1974 fn test_rename_trait_method() {
1975 let res = r"
1976trait Foo {
1977 fn foo(&self) {
1978 self.foo();
1979 }
1980}
1981
1982impl Foo for () {
1983 fn foo(&self) {
1984 self.foo();
1985 }
1986}";
1987 check(
1988 "foo",
1989 r#"
1990trait Foo {
1991 fn bar$0(&self) {
1992 self.bar();
1993 }
1994}
1995
1996impl Foo for () {
1997 fn bar(&self) {
1998 self.bar();
1999 }
2000}"#,
2001 res,
2002 );
2003 check(
2004 "foo",
2005 r#"
2006trait Foo {
2007 fn bar(&self) {
2008 self.bar$0();
2009 }
2010}
2011
2012impl Foo for () {
2013 fn bar(&self) {
2014 self.bar();
2015 }
2016}"#,
2017 res,
2018 );
2019 check(
2020 "foo",
2021 r#"
2022trait Foo {
2023 fn bar(&self) {
2024 self.bar();
2025 }
2026}
2027
2028impl Foo for () {
2029 fn bar$0(&self) {
2030 self.bar();
2031 }
2032}"#,
2033 res,
2034 );
2035 check(
2036 "foo",
2037 r#"
2038trait Foo {
2039 fn bar(&self) {
2040 self.bar();
2041 }
2042}
2043
2044impl Foo for () {
2045 fn bar(&self) {
2046 self.bar$0();
2047 }
2048}"#,
2049 res,
2050 );
2051 }
2052
2053 #[test]
2054 fn test_rename_trait_const() {
2055 let res = r"
2056trait Foo {
2057 const FOO: ();
2058}
2059
2060impl Foo for () {
2061 const FOO: ();
2062}
2063fn f() { <()>::FOO; }";
2064 check(
2065 "FOO",
2066 r#"
2067trait Foo {
2068 const BAR$0: ();
2069}
2070
2071impl Foo for () {
2072 const BAR: ();
2073}
2074fn f() { <()>::BAR; }"#,
2075 res,
2076 );
2077 check(
2078 "FOO",
2079 r#"
2080trait Foo {
2081 const BAR: ();
2082}
2083
2084impl Foo for () {
2085 const BAR$0: ();
2086}
2087fn f() { <()>::BAR; }"#,
2088 res,
2089 );
2090 check(
2091 "FOO",
2092 r#"
2093trait Foo {
2094 const BAR: ();
2095}
2096
2097impl Foo for () {
2098 const BAR: ();
2099}
2100fn f() { <()>::BAR$0; }"#,
2101 res,
2102 );
2103 }
1941} 2104}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 552054951..03faabadc 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -158,7 +158,7 @@ fn find_related_tests(
158 search_scope: Option<SearchScope>, 158 search_scope: Option<SearchScope>,
159 tests: &mut FxHashSet<Runnable>, 159 tests: &mut FxHashSet<Runnable>,
160) { 160) {
161 if let Some(refs) = references::find_all_refs(&sema, position, search_scope) { 161 if let Some(refs) = references::find_all_refs(sema, position, search_scope) {
162 for (file_id, refs) in refs.references { 162 for (file_id, refs) in refs.references {
163 let file = sema.parse(file_id); 163 let file = sema.parse(file_id);
164 let file = file.syntax(); 164 let file = file.syntax();
@@ -169,10 +169,10 @@ fn find_related_tests(
169 }); 169 });
170 170
171 for fn_def in functions { 171 for fn_def in functions {
172 if let Some(runnable) = as_test_runnable(&sema, &fn_def) { 172 if let Some(runnable) = as_test_runnable(sema, &fn_def) {
173 // direct test 173 // direct test
174 tests.insert(runnable); 174 tests.insert(runnable);
175 } else if let Some(module) = parent_test_module(&sema, &fn_def) { 175 } else if let Some(module) = parent_test_module(sema, &fn_def) {
176 // indirect test 176 // indirect test
177 find_related_tests_in_module(sema, &fn_def, &module, tests); 177 find_related_tests_in_module(sema, &fn_def, &module, tests);
178 } 178 }
@@ -203,7 +203,7 @@ fn find_related_tests_in_module(
203} 203}
204 204
205fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> { 205fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
206 if test_related_attribute(&fn_def).is_some() { 206 if test_related_attribute(fn_def).is_some() {
207 let function = sema.to_def(fn_def)?; 207 let function = sema.to_def(fn_def)?;
208 runnable_fn(sema, function) 208 runnable_fn(sema, function)
209 } else { 209 } else {
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index b03f1c71f..e186b82b7 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -323,7 +323,7 @@ fn traverse(
323 if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { 323 if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) {
324 if token.is_raw() { 324 if token.is_raw() {
325 let expanded = element_to_highlight.as_token().unwrap().clone(); 325 let expanded = element_to_highlight.as_token().unwrap().clone();
326 if inject::ra_fixture(hl, &sema, token, expanded).is_some() { 326 if inject::ra_fixture(hl, sema, token, expanded).is_some() {
327 continue; 327 continue;
328 } 328 }
329 } 329 }
@@ -334,7 +334,7 @@ fn traverse(
334 } 334 }
335 335
336 if let Some((mut highlight, binding_hash)) = highlight::element( 336 if let Some((mut highlight, binding_hash)) = highlight::element(
337 &sema, 337 sema,
338 krate, 338 krate,
339 &mut bindings_shadow_count, 339 &mut bindings_shadow_count,
340 syntactic_name_ref_highlighting, 340 syntactic_name_ref_highlighting,
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 9503c936d..7a53268e8 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -131,6 +131,9 @@ pub(super) fn element(
131 } 131 }
132 STRING | BYTE_STRING => HlTag::StringLiteral.into(), 132 STRING | BYTE_STRING => HlTag::StringLiteral.into(),
133 ATTR => HlTag::Attribute.into(), 133 ATTR => HlTag::Attribute.into(),
134 INT_NUMBER if element.ancestors().nth(1).map_or(false, |it| it.kind() == FIELD_EXPR) => {
135 SymbolKind::Field.into()
136 }
134 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), 137 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
135 BYTE => HlTag::ByteLiteral.into(), 138 BYTE => HlTag::ByteLiteral.into(),
136 CHAR => HlTag::CharLiteral.into(), 139 CHAR => HlTag::CharLiteral.into(),
@@ -446,12 +449,12 @@ fn highlight_method_call(
446 krate: Option<hir::Crate>, 449 krate: Option<hir::Crate>,
447 method_call: &ast::MethodCallExpr, 450 method_call: &ast::MethodCallExpr,
448) -> Option<Highlight> { 451) -> Option<Highlight> {
449 let func = sema.resolve_method_call(&method_call)?; 452 let func = sema.resolve_method_call(method_call)?;
450 453
451 let mut h = SymbolKind::Function.into(); 454 let mut h = SymbolKind::Function.into();
452 h |= HlMod::Associated; 455 h |= HlMod::Associated;
453 456
454 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { 457 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(method_call) {
455 h |= HlMod::Unsafe; 458 h |= HlMod::Unsafe;
456 } 459 }
457 if func.is_async(sema.db) { 460 if func.is_async(sema.db) {
@@ -523,11 +526,9 @@ fn highlight_name_ref_by_syntax(
523 }; 526 };
524 527
525 match parent.kind() { 528 match parent.kind() {
526 METHOD_CALL_EXPR => { 529 METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent)
527 return ast::MethodCallExpr::cast(parent) 530 .and_then(|it| highlight_method_call(sema, krate, &it))
528 .and_then(|it| highlight_method_call(sema, krate, &it)) 531 .unwrap_or_else(|| SymbolKind::Function.into()),
529 .unwrap_or_else(|| SymbolKind::Function.into());
530 }
531 FIELD_EXPR => { 532 FIELD_EXPR => {
532 let h = HlTag::Symbol(SymbolKind::Field); 533 let h = HlTag::Symbol(SymbolKind::Field);
533 let is_union = ast::FieldExpr::cast(parent) 534 let is_union = ast::FieldExpr::cast(parent)
diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs
index 5327af845..478facfee 100644
--- a/crates/ide/src/syntax_highlighting/html.rs
+++ b/crates/ide/src/syntax_highlighting/html.rs
@@ -23,7 +23,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo
23 let hl_ranges = highlight(db, file_id, None, false); 23 let hl_ranges = highlight(db, file_id, None, false);
24 let text = parse.tree().syntax().to_string(); 24 let text = parse.tree().syntax().to_string();
25 let mut buf = String::new(); 25 let mut buf = String::new();
26 buf.push_str(&STYLE); 26 buf.push_str(STYLE);
27 buf.push_str("<pre><code>"); 27 buf.push_str("<pre><code>");
28 for r in &hl_ranges { 28 for r in &hl_ranges {
29 let chunk = html_escape(&text[r.range]); 29 let chunk = html_escape(&text[r.range]);
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 4269d339e..ec43c8579 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -23,7 +23,7 @@ pub(super) fn ra_fixture(
23 literal: ast::String, 23 literal: ast::String,
24 expanded: SyntaxToken, 24 expanded: SyntaxToken,
25) -> Option<()> { 25) -> Option<()> {
26 let active_parameter = ActiveParameter::at_token(&sema, expanded)?; 26 let active_parameter = ActiveParameter::at_token(sema, expanded)?;
27 if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) { 27 if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) {
28 return None; 28 return None;
29 } 29 }
@@ -124,7 +124,7 @@ pub(super) fn doc_comment(
124 } 124 }
125 125
126 for attr in attributes.by_key("doc").attrs() { 126 for attr in attributes.by_key("doc").attrs() {
127 let InFile { file_id, value: src } = attrs_source_map.source_of(&attr); 127 let InFile { file_id, value: src } = attrs_source_map.source_of(attr);
128 if file_id != node.file_id { 128 if file_id != node.file_id {
129 continue; 129 continue;
130 } 130 }
@@ -232,7 +232,7 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri
232 string.text().get(1..string.text().len() - 1).map_or(false, |it| it == text) 232 string.text().get(1..string.text().len() - 1).map_or(false, |it| it == text)
233 }) 233 })
234 } 234 }
235 _ => return None, 235 _ => None,
236 } 236 }
237} 237}
238 238
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index a7b5c3b89..59f1e8e4c 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -215,8 +215,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
215 <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span> 215 <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span>
216 <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated">baz</span><span class="semicolon">;</span> 216 <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated">baz</span><span class="semicolon">;</span>
217 217
218 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span> 218 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="comma">,</span><span class="parenthesis">)</span><span class="semicolon">;</span>
219 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span> 219 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="operator">.</span><span class="field">0</span><span class="semicolon">;</span>
220 220
221 <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span> 221 <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span>
222 222
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 6ad2a362a..f7d8334a0 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -189,8 +189,8 @@ fn main() {
189 let a = |x| x; 189 let a = |x| x;
190 let bar = Foo::baz; 190 let bar = Foo::baz;
191 191
192 let baz = -42; 192 let baz = (-42,);
193 let baz = -baz; 193 let baz = -baz.0;
194 194
195 let _ = !true; 195 let _ = !true;
196 196
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 81c4d95b1..5cba9d11d 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -88,12 +88,12 @@ fn on_enter_in_comment(
88 if comment.text().ends_with(' ') { 88 if comment.text().ends_with(' ') {
89 cov_mark::hit!(continues_end_of_line_comment_with_space); 89 cov_mark::hit!(continues_end_of_line_comment_with_space);
90 remove_trailing_whitespace = true; 90 remove_trailing_whitespace = true;
91 } else if !followed_by_comment(&comment) { 91 } else if !followed_by_comment(comment) {
92 return None; 92 return None;
93 } 93 }
94 } 94 }
95 95
96 let indent = node_indent(&file, comment.syntax())?; 96 let indent = node_indent(file, comment.syntax())?;
97 let inserted = format!("\n{}{} $0", indent, prefix); 97 let inserted = format!("\n{}{} $0", indent, prefix);
98 let delete = if remove_trailing_whitespace { 98 let delete = if remove_trailing_whitespace {
99 let trimmed_len = comment.text().trim_end().len() as u32; 99 let trimmed_len = comment.text().trim_end().len() as u32;
@@ -188,7 +188,7 @@ mod tests {
188 use crate::fixture; 188 use crate::fixture;
189 189
190 fn apply_on_enter(before: &str) -> Option<String> { 190 fn apply_on_enter(before: &str) -> Option<String> {
191 let (analysis, position) = fixture::position(&before); 191 let (analysis, position) = fixture::position(before);
192 let result = analysis.on_enter(position).unwrap()?; 192 let result = analysis.on_enter(position).unwrap()?;
193 193
194 let mut actual = analysis.file_text(position.file_id).unwrap().to_string(); 194 let mut actual = analysis.file_text(position.file_id).unwrap().to_string();