diff options
Diffstat (limited to 'crates/hir')
-rw-r--r-- | crates/hir/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/hir/src/diagnostics.rs | 372 | ||||
-rw-r--r-- | crates/hir/src/diagnostics_sink.rs | 109 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 236 |
4 files changed, 692 insertions, 26 deletions
diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index 560b15238..7c148fd40 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml | |||
@@ -16,6 +16,7 @@ either = "1.5.3" | |||
16 | arrayvec = "0.7" | 16 | arrayvec = "0.7" |
17 | itertools = "0.10.0" | 17 | itertools = "0.10.0" |
18 | smallvec = "1.4.0" | 18 | smallvec = "1.4.0" |
19 | once_cell = "1" | ||
19 | 20 | ||
20 | stdx = { path = "../stdx", version = "0.0.0" } | 21 | stdx = { path = "../stdx", version = "0.0.0" } |
21 | syntax = { path = "../syntax", version = "0.0.0" } | 22 | syntax = { path = "../syntax", version = "0.0.0" } |
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 2cdbd172a..8a7c3a4fd 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs | |||
@@ -7,17 +7,12 @@ use std::any::Any; | |||
7 | 7 | ||
8 | use cfg::{CfgExpr, CfgOptions, DnfExpr}; | 8 | use cfg::{CfgExpr, CfgOptions, DnfExpr}; |
9 | use hir_def::path::ModPath; | 9 | use hir_def::path::ModPath; |
10 | use hir_expand::{HirFileId, InFile}; | 10 | use hir_expand::{name::Name, HirFileId, InFile}; |
11 | use stdx::format_to; | 11 | use stdx::format_to; |
12 | use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; | 12 | use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; |
13 | 13 | ||
14 | pub use hir_ty::{ | 14 | pub use crate::diagnostics_sink::{ |
15 | diagnostics::{ | 15 | Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder, |
16 | IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, | ||
17 | MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon, | ||
18 | ReplaceFilterMapNextWithFindMap, | ||
19 | }, | ||
20 | diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder}, | ||
21 | }; | 16 | }; |
22 | 17 | ||
23 | // Diagnostic: unresolved-module | 18 | // Diagnostic: unresolved-module |
@@ -251,3 +246,364 @@ impl Diagnostic for UnimplementedBuiltinMacro { | |||
251 | self | 246 | self |
252 | } | 247 | } |
253 | } | 248 | } |
249 | |||
250 | // Diagnostic: no-such-field | ||
251 | // | ||
252 | // This diagnostic is triggered if created structure does not have field provided in record. | ||
253 | #[derive(Debug)] | ||
254 | pub struct NoSuchField { | ||
255 | pub file: HirFileId, | ||
256 | pub field: AstPtr<ast::RecordExprField>, | ||
257 | } | ||
258 | |||
259 | impl Diagnostic for NoSuchField { | ||
260 | fn code(&self) -> DiagnosticCode { | ||
261 | DiagnosticCode("no-such-field") | ||
262 | } | ||
263 | |||
264 | fn message(&self) -> String { | ||
265 | "no such field".to_string() | ||
266 | } | ||
267 | |||
268 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
269 | InFile::new(self.file, self.field.clone().into()) | ||
270 | } | ||
271 | |||
272 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
273 | self | ||
274 | } | ||
275 | } | ||
276 | |||
277 | // Diagnostic: break-outside-of-loop | ||
278 | // | ||
279 | // This diagnostic is triggered if the `break` keyword is used outside of a loop. | ||
280 | #[derive(Debug)] | ||
281 | pub struct BreakOutsideOfLoop { | ||
282 | pub file: HirFileId, | ||
283 | pub expr: AstPtr<ast::Expr>, | ||
284 | } | ||
285 | |||
286 | impl Diagnostic for BreakOutsideOfLoop { | ||
287 | fn code(&self) -> DiagnosticCode { | ||
288 | DiagnosticCode("break-outside-of-loop") | ||
289 | } | ||
290 | fn message(&self) -> String { | ||
291 | "break outside of loop".to_string() | ||
292 | } | ||
293 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
294 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
295 | } | ||
296 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
297 | self | ||
298 | } | ||
299 | } | ||
300 | |||
301 | // Diagnostic: missing-unsafe | ||
302 | // | ||
303 | // This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block. | ||
304 | #[derive(Debug)] | ||
305 | pub struct MissingUnsafe { | ||
306 | pub file: HirFileId, | ||
307 | pub expr: AstPtr<ast::Expr>, | ||
308 | } | ||
309 | |||
310 | impl Diagnostic for MissingUnsafe { | ||
311 | fn code(&self) -> DiagnosticCode { | ||
312 | DiagnosticCode("missing-unsafe") | ||
313 | } | ||
314 | fn message(&self) -> String { | ||
315 | format!("This operation is unsafe and requires an unsafe function or block") | ||
316 | } | ||
317 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
318 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
319 | } | ||
320 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
321 | self | ||
322 | } | ||
323 | } | ||
324 | |||
325 | // Diagnostic: missing-structure-fields | ||
326 | // | ||
327 | // This diagnostic is triggered if record lacks some fields that exist in the corresponding structure. | ||
328 | // | ||
329 | // Example: | ||
330 | // | ||
331 | // ```rust | ||
332 | // struct A { a: u8, b: u8 } | ||
333 | // | ||
334 | // let a = A { a: 10 }; | ||
335 | // ``` | ||
336 | #[derive(Debug)] | ||
337 | pub struct MissingFields { | ||
338 | pub file: HirFileId, | ||
339 | pub field_list_parent: AstPtr<ast::RecordExpr>, | ||
340 | pub field_list_parent_path: Option<AstPtr<ast::Path>>, | ||
341 | pub missed_fields: Vec<Name>, | ||
342 | } | ||
343 | |||
344 | impl Diagnostic for MissingFields { | ||
345 | fn code(&self) -> DiagnosticCode { | ||
346 | DiagnosticCode("missing-structure-fields") | ||
347 | } | ||
348 | fn message(&self) -> String { | ||
349 | let mut buf = String::from("Missing structure fields:\n"); | ||
350 | for field in &self.missed_fields { | ||
351 | format_to!(buf, "- {}\n", field); | ||
352 | } | ||
353 | buf | ||
354 | } | ||
355 | |||
356 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
357 | InFile { | ||
358 | file_id: self.file, | ||
359 | value: self | ||
360 | .field_list_parent_path | ||
361 | .clone() | ||
362 | .map(SyntaxNodePtr::from) | ||
363 | .unwrap_or_else(|| self.field_list_parent.clone().into()), | ||
364 | } | ||
365 | } | ||
366 | |||
367 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
368 | self | ||
369 | } | ||
370 | } | ||
371 | |||
372 | // Diagnostic: missing-pat-fields | ||
373 | // | ||
374 | // This diagnostic is triggered if pattern lacks some fields that exist in the corresponding structure. | ||
375 | // | ||
376 | // Example: | ||
377 | // | ||
378 | // ```rust | ||
379 | // struct A { a: u8, b: u8 } | ||
380 | // | ||
381 | // let a = A { a: 10, b: 20 }; | ||
382 | // | ||
383 | // if let A { a } = a { | ||
384 | // // ... | ||
385 | // } | ||
386 | // ``` | ||
387 | #[derive(Debug)] | ||
388 | pub struct MissingPatFields { | ||
389 | pub file: HirFileId, | ||
390 | pub field_list_parent: AstPtr<ast::RecordPat>, | ||
391 | pub field_list_parent_path: Option<AstPtr<ast::Path>>, | ||
392 | pub missed_fields: Vec<Name>, | ||
393 | } | ||
394 | |||
395 | impl Diagnostic for MissingPatFields { | ||
396 | fn code(&self) -> DiagnosticCode { | ||
397 | DiagnosticCode("missing-pat-fields") | ||
398 | } | ||
399 | fn message(&self) -> String { | ||
400 | let mut buf = String::from("Missing structure fields:\n"); | ||
401 | for field in &self.missed_fields { | ||
402 | format_to!(buf, "- {}\n", field); | ||
403 | } | ||
404 | buf | ||
405 | } | ||
406 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
407 | InFile { | ||
408 | file_id: self.file, | ||
409 | value: self | ||
410 | .field_list_parent_path | ||
411 | .clone() | ||
412 | .map(SyntaxNodePtr::from) | ||
413 | .unwrap_or_else(|| self.field_list_parent.clone().into()), | ||
414 | } | ||
415 | } | ||
416 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
417 | self | ||
418 | } | ||
419 | } | ||
420 | |||
421 | // Diagnostic: replace-filter-map-next-with-find-map | ||
422 | // | ||
423 | // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`. | ||
424 | #[derive(Debug)] | ||
425 | pub struct ReplaceFilterMapNextWithFindMap { | ||
426 | pub file: HirFileId, | ||
427 | /// This expression is the whole method chain up to and including `.filter_map(..).next()`. | ||
428 | pub next_expr: AstPtr<ast::Expr>, | ||
429 | } | ||
430 | |||
431 | impl Diagnostic for ReplaceFilterMapNextWithFindMap { | ||
432 | fn code(&self) -> DiagnosticCode { | ||
433 | DiagnosticCode("replace-filter-map-next-with-find-map") | ||
434 | } | ||
435 | fn message(&self) -> String { | ||
436 | "replace filter_map(..).next() with find_map(..)".to_string() | ||
437 | } | ||
438 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
439 | InFile { file_id: self.file, value: self.next_expr.clone().into() } | ||
440 | } | ||
441 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
442 | self | ||
443 | } | ||
444 | } | ||
445 | |||
446 | // Diagnostic: mismatched-arg-count | ||
447 | // | ||
448 | // This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. | ||
449 | #[derive(Debug)] | ||
450 | pub struct MismatchedArgCount { | ||
451 | pub file: HirFileId, | ||
452 | pub call_expr: AstPtr<ast::Expr>, | ||
453 | pub expected: usize, | ||
454 | pub found: usize, | ||
455 | } | ||
456 | |||
457 | impl Diagnostic for MismatchedArgCount { | ||
458 | fn code(&self) -> DiagnosticCode { | ||
459 | DiagnosticCode("mismatched-arg-count") | ||
460 | } | ||
461 | fn message(&self) -> String { | ||
462 | let s = if self.expected == 1 { "" } else { "s" }; | ||
463 | format!("Expected {} argument{}, found {}", self.expected, s, self.found) | ||
464 | } | ||
465 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
466 | InFile { file_id: self.file, value: self.call_expr.clone().into() } | ||
467 | } | ||
468 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
469 | self | ||
470 | } | ||
471 | fn is_experimental(&self) -> bool { | ||
472 | true | ||
473 | } | ||
474 | } | ||
475 | |||
476 | #[derive(Debug)] | ||
477 | pub struct RemoveThisSemicolon { | ||
478 | pub file: HirFileId, | ||
479 | pub expr: AstPtr<ast::Expr>, | ||
480 | } | ||
481 | |||
482 | impl Diagnostic for RemoveThisSemicolon { | ||
483 | fn code(&self) -> DiagnosticCode { | ||
484 | DiagnosticCode("remove-this-semicolon") | ||
485 | } | ||
486 | |||
487 | fn message(&self) -> String { | ||
488 | "Remove this semicolon".to_string() | ||
489 | } | ||
490 | |||
491 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
492 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
493 | } | ||
494 | |||
495 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
496 | self | ||
497 | } | ||
498 | } | ||
499 | |||
500 | // Diagnostic: missing-ok-or-some-in-tail-expr | ||
501 | // | ||
502 | // This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`, | ||
503 | // or if a block that should return `Option` returns a value not wrapped in `Some`. | ||
504 | // | ||
505 | // Example: | ||
506 | // | ||
507 | // ```rust | ||
508 | // fn foo() -> Result<u8, ()> { | ||
509 | // 10 | ||
510 | // } | ||
511 | // ``` | ||
512 | #[derive(Debug)] | ||
513 | pub struct MissingOkOrSomeInTailExpr { | ||
514 | pub file: HirFileId, | ||
515 | pub expr: AstPtr<ast::Expr>, | ||
516 | // `Some` or `Ok` depending on whether the return type is Result or Option | ||
517 | pub required: String, | ||
518 | } | ||
519 | |||
520 | impl Diagnostic for MissingOkOrSomeInTailExpr { | ||
521 | fn code(&self) -> DiagnosticCode { | ||
522 | DiagnosticCode("missing-ok-or-some-in-tail-expr") | ||
523 | } | ||
524 | fn message(&self) -> String { | ||
525 | format!("wrap return expression in {}", self.required) | ||
526 | } | ||
527 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
528 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
529 | } | ||
530 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
531 | self | ||
532 | } | ||
533 | } | ||
534 | |||
535 | // Diagnostic: missing-match-arm | ||
536 | // | ||
537 | // This diagnostic is triggered if `match` block is missing one or more match arms. | ||
538 | #[derive(Debug)] | ||
539 | pub struct MissingMatchArms { | ||
540 | pub file: HirFileId, | ||
541 | pub match_expr: AstPtr<ast::Expr>, | ||
542 | pub arms: AstPtr<ast::MatchArmList>, | ||
543 | } | ||
544 | |||
545 | impl Diagnostic for MissingMatchArms { | ||
546 | fn code(&self) -> DiagnosticCode { | ||
547 | DiagnosticCode("missing-match-arm") | ||
548 | } | ||
549 | fn message(&self) -> String { | ||
550 | String::from("Missing match arm") | ||
551 | } | ||
552 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
553 | InFile { file_id: self.file, value: self.match_expr.clone().into() } | ||
554 | } | ||
555 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
556 | self | ||
557 | } | ||
558 | } | ||
559 | |||
560 | #[derive(Debug)] | ||
561 | pub struct InternalBailedOut { | ||
562 | pub file: HirFileId, | ||
563 | pub pat_syntax_ptr: SyntaxNodePtr, | ||
564 | } | ||
565 | |||
566 | impl Diagnostic for InternalBailedOut { | ||
567 | fn code(&self) -> DiagnosticCode { | ||
568 | DiagnosticCode("internal:match-check-bailed-out") | ||
569 | } | ||
570 | fn message(&self) -> String { | ||
571 | format!("Internal: match check bailed out") | ||
572 | } | ||
573 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
574 | InFile { file_id: self.file, value: self.pat_syntax_ptr.clone() } | ||
575 | } | ||
576 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
577 | self | ||
578 | } | ||
579 | } | ||
580 | |||
581 | pub use hir_ty::diagnostics::IncorrectCase; | ||
582 | |||
583 | impl Diagnostic for IncorrectCase { | ||
584 | fn code(&self) -> DiagnosticCode { | ||
585 | DiagnosticCode("incorrect-ident-case") | ||
586 | } | ||
587 | |||
588 | fn message(&self) -> String { | ||
589 | format!( | ||
590 | "{} `{}` should have {} name, e.g. `{}`", | ||
591 | self.ident_type, | ||
592 | self.ident_text, | ||
593 | self.expected_case.to_string(), | ||
594 | self.suggested_text | ||
595 | ) | ||
596 | } | ||
597 | |||
598 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
599 | InFile::new(self.file, self.ident.clone().into()) | ||
600 | } | ||
601 | |||
602 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
603 | self | ||
604 | } | ||
605 | |||
606 | fn is_experimental(&self) -> bool { | ||
607 | true | ||
608 | } | ||
609 | } | ||
diff --git a/crates/hir/src/diagnostics_sink.rs b/crates/hir/src/diagnostics_sink.rs new file mode 100644 index 000000000..084fa8b06 --- /dev/null +++ b/crates/hir/src/diagnostics_sink.rs | |||
@@ -0,0 +1,109 @@ | |||
1 | //! Semantic errors and warnings. | ||
2 | //! | ||
3 | //! The `Diagnostic` trait defines a trait object which can represent any | ||
4 | //! diagnostic. | ||
5 | //! | ||
6 | //! `DiagnosticSink` struct is used as an emitter for diagnostic. When creating | ||
7 | //! a `DiagnosticSink`, you supply a callback which can react to a `dyn | ||
8 | //! Diagnostic` or to any concrete diagnostic (downcasting is used internally). | ||
9 | //! | ||
10 | //! Because diagnostics store file offsets, it's a bad idea to store them | ||
11 | //! directly in salsa. For this reason, every hir subsytem defines it's own | ||
12 | //! strongly-typed closed set of diagnostics which use hir ids internally, are | ||
13 | //! stored in salsa and do *not* implement the `Diagnostic` trait. Instead, a | ||
14 | //! subsystem provides a separate, non-query-based API which can walk all stored | ||
15 | //! values and transform them into instances of `Diagnostic`. | ||
16 | |||
17 | use std::{any::Any, fmt}; | ||
18 | |||
19 | use hir_expand::InFile; | ||
20 | use syntax::SyntaxNodePtr; | ||
21 | |||
22 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
23 | pub struct DiagnosticCode(pub &'static str); | ||
24 | |||
25 | impl DiagnosticCode { | ||
26 | pub fn as_str(&self) -> &str { | ||
27 | self.0 | ||
28 | } | ||
29 | } | ||
30 | |||
31 | pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { | ||
32 | fn code(&self) -> DiagnosticCode; | ||
33 | fn message(&self) -> String; | ||
34 | /// Source element that triggered the diagnostics. | ||
35 | /// | ||
36 | /// Note that this should reflect "semantics", rather than specific span we | ||
37 | /// want to highlight. When rendering the diagnostics into an error message, | ||
38 | /// the IDE will fetch the `SyntaxNode` and will narrow the span | ||
39 | /// appropriately. | ||
40 | fn display_source(&self) -> InFile<SyntaxNodePtr>; | ||
41 | fn as_any(&self) -> &(dyn Any + Send + 'static); | ||
42 | fn is_experimental(&self) -> bool { | ||
43 | false | ||
44 | } | ||
45 | } | ||
46 | |||
47 | pub struct DiagnosticSink<'a> { | ||
48 | callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>, | ||
49 | filters: Vec<Box<dyn FnMut(&dyn Diagnostic) -> bool + 'a>>, | ||
50 | default_callback: Box<dyn FnMut(&dyn Diagnostic) + 'a>, | ||
51 | } | ||
52 | |||
53 | impl<'a> DiagnosticSink<'a> { | ||
54 | pub fn push(&mut self, d: impl Diagnostic) { | ||
55 | let d: &dyn Diagnostic = &d; | ||
56 | self._push(d); | ||
57 | } | ||
58 | |||
59 | fn _push(&mut self, d: &dyn Diagnostic) { | ||
60 | for filter in &mut self.filters { | ||
61 | if !filter(d) { | ||
62 | return; | ||
63 | } | ||
64 | } | ||
65 | for cb in &mut self.callbacks { | ||
66 | match cb(d) { | ||
67 | Ok(()) => return, | ||
68 | Err(()) => (), | ||
69 | } | ||
70 | } | ||
71 | (self.default_callback)(d) | ||
72 | } | ||
73 | } | ||
74 | |||
75 | pub struct DiagnosticSinkBuilder<'a> { | ||
76 | callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>, | ||
77 | filters: Vec<Box<dyn FnMut(&dyn Diagnostic) -> bool + 'a>>, | ||
78 | } | ||
79 | |||
80 | impl<'a> DiagnosticSinkBuilder<'a> { | ||
81 | pub fn new() -> Self { | ||
82 | Self { callbacks: Vec::new(), filters: Vec::new() } | ||
83 | } | ||
84 | |||
85 | pub fn filter<F: FnMut(&dyn Diagnostic) -> bool + 'a>(mut self, cb: F) -> Self { | ||
86 | self.filters.push(Box::new(cb)); | ||
87 | self | ||
88 | } | ||
89 | |||
90 | pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> Self { | ||
91 | let cb = move |diag: &dyn Diagnostic| match diag.as_any().downcast_ref::<D>() { | ||
92 | Some(d) => { | ||
93 | cb(d); | ||
94 | Ok(()) | ||
95 | } | ||
96 | None => Err(()), | ||
97 | }; | ||
98 | self.callbacks.push(Box::new(cb)); | ||
99 | self | ||
100 | } | ||
101 | |||
102 | pub fn build<F: FnMut(&dyn Diagnostic) + 'a>(self, default_callback: F) -> DiagnosticSink<'a> { | ||
103 | DiagnosticSink { | ||
104 | callbacks: self.callbacks, | ||
105 | filters: self.filters, | ||
106 | default_callback: Box::new(default_callback), | ||
107 | } | ||
108 | } | ||
109 | } | ||
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index b9c1dc44d..2468c0dc6 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -27,6 +27,7 @@ mod attrs; | |||
27 | mod has_source; | 27 | mod has_source; |
28 | 28 | ||
29 | pub mod diagnostics; | 29 | pub mod diagnostics; |
30 | pub mod diagnostics_sink; | ||
30 | pub mod db; | 31 | pub mod db; |
31 | 32 | ||
32 | mod display; | 33 | mod display; |
@@ -35,14 +36,10 @@ use std::{iter, sync::Arc}; | |||
35 | 36 | ||
36 | use arrayvec::ArrayVec; | 37 | use arrayvec::ArrayVec; |
37 | use base_db::{CrateDisplayName, CrateId, Edition, FileId}; | 38 | use base_db::{CrateDisplayName, CrateId, Edition, FileId}; |
38 | use diagnostics::{ | ||
39 | InactiveCode, MacroError, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, | ||
40 | UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, | ||
41 | }; | ||
42 | use either::Either; | 39 | use either::Either; |
43 | use hir_def::{ | 40 | use hir_def::{ |
44 | adt::{ReprKind, VariantData}, | 41 | adt::{ReprKind, VariantData}, |
45 | body::BodyDiagnostic, | 42 | body::{BodyDiagnostic, SyntheticSyntax}, |
46 | expr::{BindingAnnotation, LabelId, Pat, PatId}, | 43 | expr::{BindingAnnotation, LabelId, Pat, PatId}, |
47 | item_tree::ItemTreeNode, | 44 | item_tree::ItemTreeNode, |
48 | lang_item::LangItemTarget, | 45 | lang_item::LangItemTarget, |
@@ -60,8 +57,8 @@ use hir_ty::{ | |||
60 | autoderef, | 57 | autoderef, |
61 | consteval::ConstExt, | 58 | consteval::ConstExt, |
62 | could_unify, | 59 | could_unify, |
63 | diagnostics_sink::DiagnosticSink, | 60 | diagnostics::BodyValidationDiagnostic, |
64 | method_resolution::{self, def_crates, TyFingerprint}, | 61 | method_resolution::{self, TyFingerprint}, |
65 | primitive::UintTy, | 62 | primitive::UintTy, |
66 | subst_prefix, | 63 | subst_prefix, |
67 | traits::FnTrait, | 64 | traits::FnTrait, |
@@ -72,6 +69,7 @@ use hir_ty::{ | |||
72 | }; | 69 | }; |
73 | use itertools::Itertools; | 70 | use itertools::Itertools; |
74 | use nameres::diagnostics::DefDiagnosticKind; | 71 | use nameres::diagnostics::DefDiagnosticKind; |
72 | use once_cell::unsync::Lazy; | ||
75 | use rustc_hash::FxHashSet; | 73 | use rustc_hash::FxHashSet; |
76 | use stdx::{format_to, impl_from}; | 74 | use stdx::{format_to, impl_from}; |
77 | use syntax::{ | 75 | use syntax::{ |
@@ -80,7 +78,17 @@ use syntax::{ | |||
80 | }; | 78 | }; |
81 | use tt::{Ident, Leaf, Literal, TokenTree}; | 79 | use tt::{Ident, Leaf, Literal, TokenTree}; |
82 | 80 | ||
83 | use crate::db::{DefDatabase, HirDatabase}; | 81 | use crate::{ |
82 | db::{DefDatabase, HirDatabase}, | ||
83 | diagnostics::{ | ||
84 | BreakOutsideOfLoop, InactiveCode, InternalBailedOut, MacroError, MismatchedArgCount, | ||
85 | MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, MissingPatFields, | ||
86 | MissingUnsafe, NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, | ||
87 | UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, | ||
88 | UnresolvedModule, UnresolvedProcMacro, | ||
89 | }, | ||
90 | diagnostics_sink::DiagnosticSink, | ||
91 | }; | ||
84 | 92 | ||
85 | pub use crate::{ | 93 | pub use crate::{ |
86 | attrs::{HasAttrs, Namespace}, | 94 | attrs::{HasAttrs, Namespace}, |
@@ -353,7 +361,9 @@ impl ModuleDef { | |||
353 | None => return, | 361 | None => return, |
354 | }; | 362 | }; |
355 | 363 | ||
356 | hir_ty::diagnostics::validate_module_item(db, module.id.krate(), id, sink) | 364 | for diag in hir_ty::diagnostics::validate_module_item(db, module.id.krate(), id) { |
365 | sink.push(diag) | ||
366 | } | ||
357 | } | 367 | } |
358 | } | 368 | } |
359 | 369 | ||
@@ -445,7 +455,12 @@ impl Module { | |||
445 | self.id.def_map(db.upcast())[self.id.local_id].scope.visibility_of(def.clone().into()) | 455 | self.id.def_map(db.upcast())[self.id.local_id].scope.visibility_of(def.clone().into()) |
446 | } | 456 | } |
447 | 457 | ||
448 | pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { | 458 | pub fn diagnostics( |
459 | self, | ||
460 | db: &dyn HirDatabase, | ||
461 | sink: &mut DiagnosticSink, | ||
462 | internal_diagnostics: bool, | ||
463 | ) { | ||
449 | let _p = profile::span("Module::diagnostics").detail(|| { | 464 | let _p = profile::span("Module::diagnostics").detail(|| { |
450 | format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string())) | 465 | format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string())) |
451 | }); | 466 | }); |
@@ -591,11 +606,11 @@ impl Module { | |||
591 | } | 606 | } |
592 | for decl in self.declarations(db) { | 607 | for decl in self.declarations(db) { |
593 | match decl { | 608 | match decl { |
594 | crate::ModuleDef::Function(f) => f.diagnostics(db, sink), | 609 | crate::ModuleDef::Function(f) => f.diagnostics(db, sink, internal_diagnostics), |
595 | crate::ModuleDef::Module(m) => { | 610 | crate::ModuleDef::Module(m) => { |
596 | // Only add diagnostics from inline modules | 611 | // Only add diagnostics from inline modules |
597 | if def_map[m.id.local_id].origin.is_inline() { | 612 | if def_map[m.id.local_id].origin.is_inline() { |
598 | m.diagnostics(db, sink) | 613 | m.diagnostics(db, sink, internal_diagnostics) |
599 | } | 614 | } |
600 | } | 615 | } |
601 | _ => { | 616 | _ => { |
@@ -607,7 +622,7 @@ impl Module { | |||
607 | for impl_def in self.impl_defs(db) { | 622 | for impl_def in self.impl_defs(db) { |
608 | for item in impl_def.items(db) { | 623 | for item in impl_def.items(db) { |
609 | if let AssocItem::Function(f) = item { | 624 | if let AssocItem::Function(f) = item { |
610 | f.diagnostics(db, sink); | 625 | f.diagnostics(db, sink, internal_diagnostics); |
611 | } | 626 | } |
612 | } | 627 | } |
613 | } | 628 | } |
@@ -1009,7 +1024,12 @@ impl Function { | |||
1009 | db.function_data(self.id).is_async() | 1024 | db.function_data(self.id).is_async() |
1010 | } | 1025 | } |
1011 | 1026 | ||
1012 | pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { | 1027 | pub fn diagnostics( |
1028 | self, | ||
1029 | db: &dyn HirDatabase, | ||
1030 | sink: &mut DiagnosticSink, | ||
1031 | internal_diagnostics: bool, | ||
1032 | ) { | ||
1013 | let krate = self.module(db).id.krate(); | 1033 | let krate = self.module(db).id.krate(); |
1014 | 1034 | ||
1015 | let source_map = db.body_with_source_map(self.id.into()).1; | 1035 | let source_map = db.body_with_source_map(self.id.into()).1; |
@@ -1042,8 +1062,174 @@ impl Function { | |||
1042 | } | 1062 | } |
1043 | } | 1063 | } |
1044 | 1064 | ||
1045 | hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink); | 1065 | let infer = db.infer(self.id.into()); |
1046 | hir_ty::diagnostics::validate_body(db, self.id.into(), sink); | 1066 | let source_map = Lazy::new(|| db.body_with_source_map(self.id.into()).1); |
1067 | for d in &infer.diagnostics { | ||
1068 | match d { | ||
1069 | hir_ty::InferenceDiagnostic::NoSuchField { expr } => { | ||
1070 | let field = source_map.field_syntax(*expr); | ||
1071 | sink.push(NoSuchField { file: field.file_id, field: field.value }) | ||
1072 | } | ||
1073 | hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => { | ||
1074 | let ptr = source_map | ||
1075 | .expr_syntax(*expr) | ||
1076 | .expect("break outside of loop in synthetic syntax"); | ||
1077 | sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value }) | ||
1078 | } | ||
1079 | } | ||
1080 | } | ||
1081 | |||
1082 | for expr in hir_ty::diagnostics::missing_unsafe(db, self.id.into()) { | ||
1083 | match source_map.expr_syntax(expr) { | ||
1084 | Ok(in_file) => { | ||
1085 | sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value }) | ||
1086 | } | ||
1087 | Err(SyntheticSyntax) => { | ||
1088 | // FIXME: Here and eslwhere in this file, the `expr` was | ||
1089 | // desugared, report or assert that this doesn't happen. | ||
1090 | } | ||
1091 | } | ||
1092 | } | ||
1093 | |||
1094 | for diagnostic in | ||
1095 | BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics) | ||
1096 | { | ||
1097 | match diagnostic { | ||
1098 | BodyValidationDiagnostic::RecordLiteralMissingFields { | ||
1099 | record_expr, | ||
1100 | variant, | ||
1101 | missed_fields, | ||
1102 | } => match source_map.expr_syntax(record_expr) { | ||
1103 | Ok(source_ptr) => { | ||
1104 | let root = source_ptr.file_syntax(db.upcast()); | ||
1105 | if let ast::Expr::RecordExpr(record_expr) = &source_ptr.value.to_node(&root) | ||
1106 | { | ||
1107 | if let Some(_) = record_expr.record_expr_field_list() { | ||
1108 | let variant_data = variant.variant_data(db.upcast()); | ||
1109 | let missed_fields = missed_fields | ||
1110 | .into_iter() | ||
1111 | .map(|idx| variant_data.fields()[idx].name.clone()) | ||
1112 | .collect(); | ||
1113 | sink.push(MissingFields { | ||
1114 | file: source_ptr.file_id, | ||
1115 | field_list_parent: AstPtr::new(&record_expr), | ||
1116 | field_list_parent_path: record_expr | ||
1117 | .path() | ||
1118 | .map(|path| AstPtr::new(&path)), | ||
1119 | missed_fields, | ||
1120 | }) | ||
1121 | } | ||
1122 | } | ||
1123 | } | ||
1124 | Err(SyntheticSyntax) => (), | ||
1125 | }, | ||
1126 | BodyValidationDiagnostic::RecordPatMissingFields { | ||
1127 | record_pat, | ||
1128 | variant, | ||
1129 | missed_fields, | ||
1130 | } => match source_map.pat_syntax(record_pat) { | ||
1131 | Ok(source_ptr) => { | ||
1132 | if let Some(expr) = source_ptr.value.as_ref().left() { | ||
1133 | let root = source_ptr.file_syntax(db.upcast()); | ||
1134 | if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) { | ||
1135 | if let Some(_) = record_pat.record_pat_field_list() { | ||
1136 | let variant_data = variant.variant_data(db.upcast()); | ||
1137 | let missed_fields = missed_fields | ||
1138 | .into_iter() | ||
1139 | .map(|idx| variant_data.fields()[idx].name.clone()) | ||
1140 | .collect(); | ||
1141 | sink.push(MissingPatFields { | ||
1142 | file: source_ptr.file_id, | ||
1143 | field_list_parent: AstPtr::new(&record_pat), | ||
1144 | field_list_parent_path: record_pat | ||
1145 | .path() | ||
1146 | .map(|path| AstPtr::new(&path)), | ||
1147 | missed_fields, | ||
1148 | }) | ||
1149 | } | ||
1150 | } | ||
1151 | } | ||
1152 | } | ||
1153 | Err(SyntheticSyntax) => (), | ||
1154 | }, | ||
1155 | |||
1156 | BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => { | ||
1157 | if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) { | ||
1158 | sink.push(ReplaceFilterMapNextWithFindMap { | ||
1159 | file: next_source_ptr.file_id, | ||
1160 | next_expr: next_source_ptr.value, | ||
1161 | }); | ||
1162 | } | ||
1163 | } | ||
1164 | BodyValidationDiagnostic::MismatchedArgCount { call_expr, expected, found } => { | ||
1165 | match source_map.expr_syntax(call_expr) { | ||
1166 | Ok(source_ptr) => sink.push(MismatchedArgCount { | ||
1167 | file: source_ptr.file_id, | ||
1168 | call_expr: source_ptr.value, | ||
1169 | expected, | ||
1170 | found, | ||
1171 | }), | ||
1172 | Err(SyntheticSyntax) => (), | ||
1173 | } | ||
1174 | } | ||
1175 | BodyValidationDiagnostic::RemoveThisSemicolon { expr } => { | ||
1176 | match source_map.expr_syntax(expr) { | ||
1177 | Ok(source_ptr) => sink.push(RemoveThisSemicolon { | ||
1178 | file: source_ptr.file_id, | ||
1179 | expr: source_ptr.value, | ||
1180 | }), | ||
1181 | Err(SyntheticSyntax) => (), | ||
1182 | } | ||
1183 | } | ||
1184 | BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr, required } => { | ||
1185 | match source_map.expr_syntax(expr) { | ||
1186 | Ok(source_ptr) => sink.push(MissingOkOrSomeInTailExpr { | ||
1187 | file: source_ptr.file_id, | ||
1188 | expr: source_ptr.value, | ||
1189 | required, | ||
1190 | }), | ||
1191 | Err(SyntheticSyntax) => (), | ||
1192 | } | ||
1193 | } | ||
1194 | BodyValidationDiagnostic::MissingMatchArms { match_expr } => { | ||
1195 | match source_map.expr_syntax(match_expr) { | ||
1196 | Ok(source_ptr) => { | ||
1197 | let root = source_ptr.file_syntax(db.upcast()); | ||
1198 | if let ast::Expr::MatchExpr(match_expr) = | ||
1199 | &source_ptr.value.to_node(&root) | ||
1200 | { | ||
1201 | if let (Some(match_expr), Some(arms)) = | ||
1202 | (match_expr.expr(), match_expr.match_arm_list()) | ||
1203 | { | ||
1204 | sink.push(MissingMatchArms { | ||
1205 | file: source_ptr.file_id, | ||
1206 | match_expr: AstPtr::new(&match_expr), | ||
1207 | arms: AstPtr::new(&arms), | ||
1208 | }) | ||
1209 | } | ||
1210 | } | ||
1211 | } | ||
1212 | Err(SyntheticSyntax) => (), | ||
1213 | } | ||
1214 | } | ||
1215 | BodyValidationDiagnostic::InternalBailedOut { pat } => { | ||
1216 | match source_map.pat_syntax(pat) { | ||
1217 | Ok(source_ptr) => { | ||
1218 | let pat_syntax_ptr = source_ptr.value.either(Into::into, Into::into); | ||
1219 | sink.push(InternalBailedOut { | ||
1220 | file: source_ptr.file_id, | ||
1221 | pat_syntax_ptr, | ||
1222 | }); | ||
1223 | } | ||
1224 | Err(SyntheticSyntax) => (), | ||
1225 | } | ||
1226 | } | ||
1227 | } | ||
1228 | } | ||
1229 | |||
1230 | for diag in hir_ty::diagnostics::validate_module_item(db, krate, self.id.into()) { | ||
1231 | sink.push(diag) | ||
1232 | } | ||
1047 | } | 1233 | } |
1048 | 1234 | ||
1049 | /// Whether this function declaration has a definition. | 1235 | /// Whether this function declaration has a definition. |
@@ -1451,6 +1637,20 @@ impl AssocItem { | |||
1451 | _ => None, | 1637 | _ => None, |
1452 | } | 1638 | } |
1453 | } | 1639 | } |
1640 | |||
1641 | pub fn containing_trait_impl(self, db: &dyn HirDatabase) -> Option<Trait> { | ||
1642 | match self.container(db) { | ||
1643 | AssocItemContainer::Impl(i) => i.trait_(db), | ||
1644 | _ => None, | ||
1645 | } | ||
1646 | } | ||
1647 | |||
1648 | pub fn containing_trait_or_trait_impl(self, db: &dyn HirDatabase) -> Option<Trait> { | ||
1649 | match self.container(db) { | ||
1650 | AssocItemContainer::Trait(t) => Some(t), | ||
1651 | AssocItemContainer::Impl(i) => i.trait_(db), | ||
1652 | } | ||
1653 | } | ||
1454 | } | 1654 | } |
1455 | 1655 | ||
1456 | impl HasVisibility for AssocItem { | 1656 | impl HasVisibility for AssocItem { |
@@ -1748,7 +1948,7 @@ impl Impl { | |||
1748 | } | 1948 | } |
1749 | 1949 | ||
1750 | pub fn all_for_type(db: &dyn HirDatabase, Type { krate, ty, .. }: Type) -> Vec<Impl> { | 1950 | pub fn all_for_type(db: &dyn HirDatabase, Type { krate, ty, .. }: Type) -> Vec<Impl> { |
1751 | let def_crates = match def_crates(db, &ty, krate) { | 1951 | let def_crates = match method_resolution::def_crates(db, &ty, krate) { |
1752 | Some(def_crates) => def_crates, | 1952 | Some(def_crates) => def_crates, |
1753 | None => return Vec::new(), | 1953 | None => return Vec::new(), |
1754 | }; | 1954 | }; |
@@ -2154,7 +2354,7 @@ impl Type { | |||
2154 | krate: Crate, | 2354 | krate: Crate, |
2155 | mut callback: impl FnMut(AssocItem) -> Option<T>, | 2355 | mut callback: impl FnMut(AssocItem) -> Option<T>, |
2156 | ) -> Option<T> { | 2356 | ) -> Option<T> { |
2157 | for krate in def_crates(db, &self.ty, krate.id)? { | 2357 | for krate in method_resolution::def_crates(db, &self.ty, krate.id)? { |
2158 | let impls = db.inherent_impls_in_crate(krate); | 2358 | let impls = db.inherent_impls_in_crate(krate); |
2159 | 2359 | ||
2160 | for impl_def in impls.for_self_ty(&self.ty) { | 2360 | for impl_def in impls.for_self_ty(&self.ty) { |