aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_ty/src/diagnostics.rs')
-rw-r--r--crates/hir_ty/src/diagnostics.rs469
1 files changed, 469 insertions, 0 deletions
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
new file mode 100644
index 000000000..9ba005fab
--- /dev/null
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -0,0 +1,469 @@
1//! FIXME: write short doc here
2mod expr;
3mod match_check;
4mod unsafe_check;
5
6use std::any::Any;
7
8use hir_def::DefWithBodyId;
9use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
10use hir_expand::{name::Name, HirFileId, InFile};
11use stdx::format_to;
12use syntax::{ast, AstPtr, SyntaxNodePtr};
13
14use crate::db::HirDatabase;
15
16pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
17
18pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
19 let _p = profile::span("validate_body");
20 let infer = db.infer(owner);
21 infer.add_diagnostics(db, owner, sink);
22 let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink);
23 validator.validate_body(db);
24 let mut validator = unsafe_check::UnsafeValidator::new(owner, infer, sink);
25 validator.validate_body(db);
26}
27
28#[derive(Debug)]
29pub struct NoSuchField {
30 pub file: HirFileId,
31 pub field: AstPtr<ast::RecordExprField>,
32}
33
34impl Diagnostic for NoSuchField {
35 fn code(&self) -> DiagnosticCode {
36 DiagnosticCode("no-such-field")
37 }
38
39 fn message(&self) -> String {
40 "no such field".to_string()
41 }
42
43 fn display_source(&self) -> InFile<SyntaxNodePtr> {
44 InFile::new(self.file, self.field.clone().into())
45 }
46
47 fn as_any(&self) -> &(dyn Any + Send + 'static) {
48 self
49 }
50}
51
52#[derive(Debug)]
53pub struct MissingFields {
54 pub file: HirFileId,
55 pub field_list_parent: AstPtr<ast::RecordExpr>,
56 pub field_list_parent_path: Option<AstPtr<ast::Path>>,
57 pub missed_fields: Vec<Name>,
58}
59
60impl Diagnostic for MissingFields {
61 fn code(&self) -> DiagnosticCode {
62 DiagnosticCode("missing-structure-fields")
63 }
64 fn message(&self) -> String {
65 let mut buf = String::from("Missing structure fields:\n");
66 for field in &self.missed_fields {
67 format_to!(buf, "- {}\n", field);
68 }
69 buf
70 }
71
72 fn display_source(&self) -> InFile<SyntaxNodePtr> {
73 InFile {
74 file_id: self.file,
75 value: self
76 .field_list_parent_path
77 .clone()
78 .map(SyntaxNodePtr::from)
79 .unwrap_or_else(|| self.field_list_parent.clone().into()),
80 }
81 }
82
83 fn as_any(&self) -> &(dyn Any + Send + 'static) {
84 self
85 }
86}
87
88#[derive(Debug)]
89pub struct MissingPatFields {
90 pub file: HirFileId,
91 pub field_list_parent: AstPtr<ast::RecordPat>,
92 pub field_list_parent_path: Option<AstPtr<ast::Path>>,
93 pub missed_fields: Vec<Name>,
94}
95
96impl Diagnostic for MissingPatFields {
97 fn code(&self) -> DiagnosticCode {
98 DiagnosticCode("missing-pat-fields")
99 }
100 fn message(&self) -> String {
101 let mut buf = String::from("Missing structure fields:\n");
102 for field in &self.missed_fields {
103 format_to!(buf, "- {}\n", field);
104 }
105 buf
106 }
107 fn display_source(&self) -> InFile<SyntaxNodePtr> {
108 InFile {
109 file_id: self.file,
110 value: self
111 .field_list_parent_path
112 .clone()
113 .map(SyntaxNodePtr::from)
114 .unwrap_or_else(|| self.field_list_parent.clone().into()),
115 }
116 }
117 fn as_any(&self) -> &(dyn Any + Send + 'static) {
118 self
119 }
120}
121
122#[derive(Debug)]
123pub struct MissingMatchArms {
124 pub file: HirFileId,
125 pub match_expr: AstPtr<ast::Expr>,
126 pub arms: AstPtr<ast::MatchArmList>,
127}
128
129impl Diagnostic for MissingMatchArms {
130 fn code(&self) -> DiagnosticCode {
131 DiagnosticCode("missing-match-arm")
132 }
133 fn message(&self) -> String {
134 String::from("Missing match arm")
135 }
136 fn display_source(&self) -> InFile<SyntaxNodePtr> {
137 InFile { file_id: self.file, value: self.match_expr.clone().into() }
138 }
139 fn as_any(&self) -> &(dyn Any + Send + 'static) {
140 self
141 }
142}
143
144#[derive(Debug)]
145pub struct MissingOkInTailExpr {
146 pub file: HirFileId,
147 pub expr: AstPtr<ast::Expr>,
148}
149
150impl Diagnostic for MissingOkInTailExpr {
151 fn code(&self) -> DiagnosticCode {
152 DiagnosticCode("missing-ok-in-tail-expr")
153 }
154 fn message(&self) -> String {
155 "wrap return expression in Ok".to_string()
156 }
157 fn display_source(&self) -> InFile<SyntaxNodePtr> {
158 InFile { file_id: self.file, value: self.expr.clone().into() }
159 }
160 fn as_any(&self) -> &(dyn Any + Send + 'static) {
161 self
162 }
163}
164
165#[derive(Debug)]
166pub struct BreakOutsideOfLoop {
167 pub file: HirFileId,
168 pub expr: AstPtr<ast::Expr>,
169}
170
171impl Diagnostic for BreakOutsideOfLoop {
172 fn code(&self) -> DiagnosticCode {
173 DiagnosticCode("break-outside-of-loop")
174 }
175 fn message(&self) -> String {
176 "break outside of loop".to_string()
177 }
178 fn display_source(&self) -> InFile<SyntaxNodePtr> {
179 InFile { file_id: self.file, value: self.expr.clone().into() }
180 }
181 fn as_any(&self) -> &(dyn Any + Send + 'static) {
182 self
183 }
184}
185
186#[derive(Debug)]
187pub struct MissingUnsafe {
188 pub file: HirFileId,
189 pub expr: AstPtr<ast::Expr>,
190}
191
192impl Diagnostic for MissingUnsafe {
193 fn code(&self) -> DiagnosticCode {
194 DiagnosticCode("missing-unsafe")
195 }
196 fn message(&self) -> String {
197 format!("This operation is unsafe and requires an unsafe function or block")
198 }
199 fn display_source(&self) -> InFile<SyntaxNodePtr> {
200 InFile { file_id: self.file, value: self.expr.clone().into() }
201 }
202 fn as_any(&self) -> &(dyn Any + Send + 'static) {
203 self
204 }
205}
206
207#[derive(Debug)]
208pub struct MismatchedArgCount {
209 pub file: HirFileId,
210 pub call_expr: AstPtr<ast::Expr>,
211 pub expected: usize,
212 pub found: usize,
213}
214
215impl Diagnostic for MismatchedArgCount {
216 fn code(&self) -> DiagnosticCode {
217 DiagnosticCode("mismatched-arg-count")
218 }
219 fn message(&self) -> String {
220 let s = if self.expected == 1 { "" } else { "s" };
221 format!("Expected {} argument{}, found {}", self.expected, s, self.found)
222 }
223 fn display_source(&self) -> InFile<SyntaxNodePtr> {
224 InFile { file_id: self.file, value: self.call_expr.clone().into() }
225 }
226 fn as_any(&self) -> &(dyn Any + Send + 'static) {
227 self
228 }
229 fn is_experimental(&self) -> bool {
230 true
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
237 use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
238 use hir_expand::{
239 db::AstDatabase,
240 diagnostics::{Diagnostic, DiagnosticSinkBuilder},
241 };
242 use rustc_hash::FxHashMap;
243 use syntax::{TextRange, TextSize};
244
245 use crate::{diagnostics::validate_body, test_db::TestDB};
246
247 impl TestDB {
248 fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
249 let crate_graph = self.crate_graph();
250 for krate in crate_graph.iter() {
251 let crate_def_map = self.crate_def_map(krate);
252
253 let mut fns = Vec::new();
254 for (module_id, _) in crate_def_map.modules.iter() {
255 for decl in crate_def_map[module_id].scope.declarations() {
256 if let ModuleDefId::FunctionId(f) = decl {
257 fns.push(f)
258 }
259 }
260
261 for impl_id in crate_def_map[module_id].scope.impls() {
262 let impl_data = self.impl_data(impl_id);
263 for item in impl_data.items.iter() {
264 if let AssocItemId::FunctionId(f) = item {
265 fns.push(*f)
266 }
267 }
268 }
269 }
270
271 for f in fns {
272 let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
273 validate_body(self, f.into(), &mut sink);
274 }
275 }
276 }
277 }
278
279 pub(crate) fn check_diagnostics(ra_fixture: &str) {
280 let db = TestDB::with_files(ra_fixture);
281 let annotations = db.extract_annotations();
282
283 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
284 db.diagnostics(|d| {
285 let src = d.display_source();
286 let root = db.parse_or_expand(src.file_id).unwrap();
287 // FIXME: macros...
288 let file_id = src.file_id.original_file(&db);
289 let range = src.value.to_node(&root).text_range();
290 let message = d.message().to_owned();
291 actual.entry(file_id).or_default().push((range, message));
292 });
293
294 for (file_id, diags) in actual.iter_mut() {
295 diags.sort_by_key(|it| it.0.start());
296 let text = db.file_text(*file_id);
297 // For multiline spans, place them on line start
298 for (range, content) in diags {
299 if text[*range].contains('\n') {
300 *range = TextRange::new(range.start(), range.start() + TextSize::from(1));
301 *content = format!("... {}", content);
302 }
303 }
304 }
305
306 assert_eq!(annotations, actual);
307 }
308
309 #[test]
310 fn no_such_field_diagnostics() {
311 check_diagnostics(
312 r#"
313struct S { foo: i32, bar: () }
314impl S {
315 fn new() -> S {
316 S {
317 //^ Missing structure fields:
318 //| - bar
319 foo: 92,
320 baz: 62,
321 //^^^^^^^ no such field
322 }
323 }
324}
325"#,
326 );
327 }
328 #[test]
329 fn no_such_field_with_feature_flag_diagnostics() {
330 check_diagnostics(
331 r#"
332//- /lib.rs crate:foo cfg:feature=foo
333struct MyStruct {
334 my_val: usize,
335 #[cfg(feature = "foo")]
336 bar: bool,
337}
338
339impl MyStruct {
340 #[cfg(feature = "foo")]
341 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
342 Self { my_val, bar }
343 }
344 #[cfg(not(feature = "foo"))]
345 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
346 Self { my_val }
347 }
348}
349"#,
350 );
351 }
352
353 #[test]
354 fn no_such_field_enum_with_feature_flag_diagnostics() {
355 check_diagnostics(
356 r#"
357//- /lib.rs crate:foo cfg:feature=foo
358enum Foo {
359 #[cfg(not(feature = "foo"))]
360 Buz,
361 #[cfg(feature = "foo")]
362 Bar,
363 Baz
364}
365
366fn test_fn(f: Foo) {
367 match f {
368 Foo::Bar => {},
369 Foo::Baz => {},
370 }
371}
372"#,
373 );
374 }
375
376 #[test]
377 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
378 check_diagnostics(
379 r#"
380//- /lib.rs crate:foo cfg:feature=foo
381struct S {
382 #[cfg(feature = "foo")]
383 foo: u32,
384 #[cfg(not(feature = "foo"))]
385 bar: u32,
386}
387
388impl S {
389 #[cfg(feature = "foo")]
390 fn new(foo: u32) -> Self {
391 Self { foo }
392 }
393 #[cfg(not(feature = "foo"))]
394 fn new(bar: u32) -> Self {
395 Self { bar }
396 }
397 fn new2(bar: u32) -> Self {
398 #[cfg(feature = "foo")]
399 { Self { foo: bar } }
400 #[cfg(not(feature = "foo"))]
401 { Self { bar } }
402 }
403 fn new2(val: u32) -> Self {
404 Self {
405 #[cfg(feature = "foo")]
406 foo: val,
407 #[cfg(not(feature = "foo"))]
408 bar: val,
409 }
410 }
411}
412"#,
413 );
414 }
415
416 #[test]
417 fn no_such_field_with_type_macro() {
418 check_diagnostics(
419 r#"
420macro_rules! Type { () => { u32 }; }
421struct Foo { bar: Type![] }
422
423impl Foo {
424 fn new() -> Self {
425 Foo { bar: 0 }
426 }
427}
428"#,
429 );
430 }
431
432 #[test]
433 fn missing_record_pat_field_diagnostic() {
434 check_diagnostics(
435 r#"
436struct S { foo: i32, bar: () }
437fn baz(s: S) {
438 let S { foo: _ } = s;
439 //^ Missing structure fields:
440 //| - bar
441}
442"#,
443 );
444 }
445
446 #[test]
447 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
448 check_diagnostics(
449 r"
450struct S { foo: i32, bar: () }
451fn baz(s: S) -> i32 {
452 match s {
453 S { foo, .. } => foo,
454 }
455}
456",
457 )
458 }
459
460 #[test]
461 fn break_outside_of_loop() {
462 check_diagnostics(
463 r#"
464fn foo() { break; }
465 //^^^^^ break outside of loop
466"#,
467 );
468 }
469}