]> git.lizzy.rs Git - rust.git/blob - crates/hir/src/diagnostics.rs
internal: unified missing fields diagnostic
[rust.git] / crates / hir / src / diagnostics.rs
1 //! Re-export diagnostics such that clients of `hir` don't have to depend on
2 //! low-level crates.
3 //!
4 //! This probably isn't the best way to do this -- ideally, diagnistics should
5 //! be expressed in terms of hir types themselves.
6 use std::any::Any;
7
8 use cfg::{CfgExpr, CfgOptions, DnfExpr};
9 use either::Either;
10 use hir_def::path::ModPath;
11 use hir_expand::{name::Name, HirFileId, InFile};
12 use stdx::format_to;
13 use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
14
15 pub use crate::diagnostics_sink::{
16     Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
17 };
18
19 macro_rules! diagnostics {
20     ($($diag:ident),*) => {
21         pub enum AnyDiagnostic {$(
22             $diag(Box<$diag>),
23         )*}
24
25         $(
26             impl From<$diag> for AnyDiagnostic {
27                 fn from(d: $diag) -> AnyDiagnostic {
28                     AnyDiagnostic::$diag(Box::new(d))
29                 }
30             }
31         )*
32     };
33 }
34
35 diagnostics![UnresolvedModule, MissingFields];
36
37 #[derive(Debug)]
38 pub struct UnresolvedModule {
39     pub decl: InFile<AstPtr<ast::Module>>,
40     pub candidate: String,
41 }
42
43 // Diagnostic: unresolved-extern-crate
44 //
45 // This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
46 #[derive(Debug)]
47 pub struct UnresolvedExternCrate {
48     pub file: HirFileId,
49     pub item: AstPtr<ast::ExternCrate>,
50 }
51
52 impl Diagnostic for UnresolvedExternCrate {
53     fn code(&self) -> DiagnosticCode {
54         DiagnosticCode("unresolved-extern-crate")
55     }
56     fn message(&self) -> String {
57         "unresolved extern crate".to_string()
58     }
59     fn display_source(&self) -> InFile<SyntaxNodePtr> {
60         InFile::new(self.file, self.item.clone().into())
61     }
62     fn as_any(&self) -> &(dyn Any + Send + 'static) {
63         self
64     }
65 }
66
67 #[derive(Debug)]
68 pub struct UnresolvedImport {
69     pub file: HirFileId,
70     pub node: AstPtr<ast::UseTree>,
71 }
72
73 impl Diagnostic for UnresolvedImport {
74     fn code(&self) -> DiagnosticCode {
75         DiagnosticCode("unresolved-import")
76     }
77     fn message(&self) -> String {
78         "unresolved import".to_string()
79     }
80     fn display_source(&self) -> InFile<SyntaxNodePtr> {
81         InFile::new(self.file, self.node.clone().into())
82     }
83     fn as_any(&self) -> &(dyn Any + Send + 'static) {
84         self
85     }
86     fn is_experimental(&self) -> bool {
87         // This currently results in false positives in the following cases:
88         // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
89         // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
90         // - proc macros and/or proc macro generated code
91         true
92     }
93 }
94
95 // Diagnostic: unresolved-macro-call
96 //
97 // This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
98 // macro in a macro invocation.
99 #[derive(Debug, Clone, Eq, PartialEq)]
100 pub struct UnresolvedMacroCall {
101     pub file: HirFileId,
102     pub node: AstPtr<ast::MacroCall>,
103     pub path: ModPath,
104 }
105
106 impl Diagnostic for UnresolvedMacroCall {
107     fn code(&self) -> DiagnosticCode {
108         DiagnosticCode("unresolved-macro-call")
109     }
110     fn message(&self) -> String {
111         format!("unresolved macro `{}!`", self.path)
112     }
113     fn display_source(&self) -> InFile<SyntaxNodePtr> {
114         InFile::new(self.file, self.node.clone().into())
115     }
116     fn as_any(&self) -> &(dyn Any + Send + 'static) {
117         self
118     }
119     fn is_experimental(&self) -> bool {
120         true
121     }
122 }
123
124 // Diagnostic: inactive-code
125 //
126 // This diagnostic is shown for code with inactive `#[cfg]` attributes.
127 #[derive(Debug, Clone, Eq, PartialEq)]
128 pub struct InactiveCode {
129     pub file: HirFileId,
130     pub node: SyntaxNodePtr,
131     pub cfg: CfgExpr,
132     pub opts: CfgOptions,
133 }
134
135 impl Diagnostic for InactiveCode {
136     fn code(&self) -> DiagnosticCode {
137         DiagnosticCode("inactive-code")
138     }
139     fn message(&self) -> String {
140         let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
141         let mut buf = "code is inactive due to #[cfg] directives".to_string();
142
143         if let Some(inactive) = inactive {
144             format_to!(buf, ": {}", inactive);
145         }
146
147         buf
148     }
149     fn display_source(&self) -> InFile<SyntaxNodePtr> {
150         InFile::new(self.file, self.node.clone())
151     }
152     fn as_any(&self) -> &(dyn Any + Send + 'static) {
153         self
154     }
155 }
156
157 // Diagnostic: unresolved-proc-macro
158 //
159 // This diagnostic is shown when a procedural macro can not be found. This usually means that
160 // procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
161 // but can also indicate project setup problems.
162 //
163 // If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
164 // `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
165 // enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
166 #[derive(Debug, Clone, Eq, PartialEq)]
167 pub struct UnresolvedProcMacro {
168     pub file: HirFileId,
169     pub node: SyntaxNodePtr,
170     /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
171     /// to use instead.
172     pub precise_location: Option<TextRange>,
173     pub macro_name: Option<String>,
174 }
175
176 impl Diagnostic for UnresolvedProcMacro {
177     fn code(&self) -> DiagnosticCode {
178         DiagnosticCode("unresolved-proc-macro")
179     }
180
181     fn message(&self) -> String {
182         match &self.macro_name {
183             Some(name) => format!("proc macro `{}` not expanded", name),
184             None => "proc macro not expanded".to_string(),
185         }
186     }
187
188     fn display_source(&self) -> InFile<SyntaxNodePtr> {
189         InFile::new(self.file, self.node.clone())
190     }
191
192     fn as_any(&self) -> &(dyn Any + Send + 'static) {
193         self
194     }
195 }
196
197 // Diagnostic: macro-error
198 //
199 // This diagnostic is shown for macro expansion errors.
200 #[derive(Debug, Clone, Eq, PartialEq)]
201 pub struct MacroError {
202     pub file: HirFileId,
203     pub node: SyntaxNodePtr,
204     pub message: String,
205 }
206
207 impl Diagnostic for MacroError {
208     fn code(&self) -> DiagnosticCode {
209         DiagnosticCode("macro-error")
210     }
211     fn message(&self) -> String {
212         self.message.clone()
213     }
214     fn display_source(&self) -> InFile<SyntaxNodePtr> {
215         InFile::new(self.file, self.node.clone())
216     }
217     fn as_any(&self) -> &(dyn Any + Send + 'static) {
218         self
219     }
220     fn is_experimental(&self) -> bool {
221         // Newly added and not very well-tested, might contain false positives.
222         true
223     }
224 }
225
226 #[derive(Debug)]
227 pub struct UnimplementedBuiltinMacro {
228     pub file: HirFileId,
229     pub node: SyntaxNodePtr,
230 }
231
232 impl Diagnostic for UnimplementedBuiltinMacro {
233     fn code(&self) -> DiagnosticCode {
234         DiagnosticCode("unimplemented-builtin-macro")
235     }
236
237     fn message(&self) -> String {
238         "unimplemented built-in macro".to_string()
239     }
240
241     fn display_source(&self) -> InFile<SyntaxNodePtr> {
242         InFile::new(self.file, self.node.clone())
243     }
244
245     fn as_any(&self) -> &(dyn Any + Send + 'static) {
246         self
247     }
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 #[derive(Debug)]
326 pub struct MissingFields {
327     pub file: HirFileId,
328     pub field_list_parent: Either<AstPtr<ast::RecordExpr>, AstPtr<ast::RecordPat>>,
329     pub field_list_parent_path: Option<AstPtr<ast::Path>>,
330     pub missed_fields: Vec<Name>,
331 }
332
333 // Diagnostic: replace-filter-map-next-with-find-map
334 //
335 // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
336 #[derive(Debug)]
337 pub struct ReplaceFilterMapNextWithFindMap {
338     pub file: HirFileId,
339     /// This expression is the whole method chain up to and including `.filter_map(..).next()`.
340     pub next_expr: AstPtr<ast::Expr>,
341 }
342
343 impl Diagnostic for ReplaceFilterMapNextWithFindMap {
344     fn code(&self) -> DiagnosticCode {
345         DiagnosticCode("replace-filter-map-next-with-find-map")
346     }
347     fn message(&self) -> String {
348         "replace filter_map(..).next() with find_map(..)".to_string()
349     }
350     fn display_source(&self) -> InFile<SyntaxNodePtr> {
351         InFile { file_id: self.file, value: self.next_expr.clone().into() }
352     }
353     fn as_any(&self) -> &(dyn Any + Send + 'static) {
354         self
355     }
356 }
357
358 // Diagnostic: mismatched-arg-count
359 //
360 // This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
361 #[derive(Debug)]
362 pub struct MismatchedArgCount {
363     pub file: HirFileId,
364     pub call_expr: AstPtr<ast::Expr>,
365     pub expected: usize,
366     pub found: usize,
367 }
368
369 impl Diagnostic for MismatchedArgCount {
370     fn code(&self) -> DiagnosticCode {
371         DiagnosticCode("mismatched-arg-count")
372     }
373     fn message(&self) -> String {
374         let s = if self.expected == 1 { "" } else { "s" };
375         format!("Expected {} argument{}, found {}", self.expected, s, self.found)
376     }
377     fn display_source(&self) -> InFile<SyntaxNodePtr> {
378         InFile { file_id: self.file, value: self.call_expr.clone().into() }
379     }
380     fn as_any(&self) -> &(dyn Any + Send + 'static) {
381         self
382     }
383     fn is_experimental(&self) -> bool {
384         true
385     }
386 }
387
388 #[derive(Debug)]
389 pub struct RemoveThisSemicolon {
390     pub file: HirFileId,
391     pub expr: AstPtr<ast::Expr>,
392 }
393
394 impl Diagnostic for RemoveThisSemicolon {
395     fn code(&self) -> DiagnosticCode {
396         DiagnosticCode("remove-this-semicolon")
397     }
398
399     fn message(&self) -> String {
400         "Remove this semicolon".to_string()
401     }
402
403     fn display_source(&self) -> InFile<SyntaxNodePtr> {
404         InFile { file_id: self.file, value: self.expr.clone().into() }
405     }
406
407     fn as_any(&self) -> &(dyn Any + Send + 'static) {
408         self
409     }
410 }
411
412 // Diagnostic: missing-ok-or-some-in-tail-expr
413 //
414 // This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`,
415 // or if a block that should return `Option` returns a value not wrapped in `Some`.
416 //
417 // Example:
418 //
419 // ```rust
420 // fn foo() -> Result<u8, ()> {
421 //     10
422 // }
423 // ```
424 #[derive(Debug)]
425 pub struct MissingOkOrSomeInTailExpr {
426     pub file: HirFileId,
427     pub expr: AstPtr<ast::Expr>,
428     // `Some` or `Ok` depending on whether the return type is Result or Option
429     pub required: String,
430 }
431
432 impl Diagnostic for MissingOkOrSomeInTailExpr {
433     fn code(&self) -> DiagnosticCode {
434         DiagnosticCode("missing-ok-or-some-in-tail-expr")
435     }
436     fn message(&self) -> String {
437         format!("wrap return expression in {}", self.required)
438     }
439     fn display_source(&self) -> InFile<SyntaxNodePtr> {
440         InFile { file_id: self.file, value: self.expr.clone().into() }
441     }
442     fn as_any(&self) -> &(dyn Any + Send + 'static) {
443         self
444     }
445 }
446
447 // Diagnostic: missing-match-arm
448 //
449 // This diagnostic is triggered if `match` block is missing one or more match arms.
450 #[derive(Debug)]
451 pub struct MissingMatchArms {
452     pub file: HirFileId,
453     pub match_expr: AstPtr<ast::Expr>,
454     pub arms: AstPtr<ast::MatchArmList>,
455 }
456
457 impl Diagnostic for MissingMatchArms {
458     fn code(&self) -> DiagnosticCode {
459         DiagnosticCode("missing-match-arm")
460     }
461     fn message(&self) -> String {
462         String::from("Missing match arm")
463     }
464     fn display_source(&self) -> InFile<SyntaxNodePtr> {
465         InFile { file_id: self.file, value: self.match_expr.clone().into() }
466     }
467     fn as_any(&self) -> &(dyn Any + Send + 'static) {
468         self
469     }
470 }
471
472 #[derive(Debug)]
473 pub struct InternalBailedOut {
474     pub file: HirFileId,
475     pub pat_syntax_ptr: SyntaxNodePtr,
476 }
477
478 impl Diagnostic for InternalBailedOut {
479     fn code(&self) -> DiagnosticCode {
480         DiagnosticCode("internal:match-check-bailed-out")
481     }
482     fn message(&self) -> String {
483         format!("Internal: match check bailed out")
484     }
485     fn display_source(&self) -> InFile<SyntaxNodePtr> {
486         InFile { file_id: self.file, value: self.pat_syntax_ptr.clone() }
487     }
488     fn as_any(&self) -> &(dyn Any + Send + 'static) {
489         self
490     }
491 }
492
493 pub use hir_ty::diagnostics::IncorrectCase;
494
495 impl Diagnostic for IncorrectCase {
496     fn code(&self) -> DiagnosticCode {
497         DiagnosticCode("incorrect-ident-case")
498     }
499
500     fn message(&self) -> String {
501         format!(
502             "{} `{}` should have {} name, e.g. `{}`",
503             self.ident_type,
504             self.ident_text,
505             self.expected_case.to_string(),
506             self.suggested_text
507         )
508     }
509
510     fn display_source(&self) -> InFile<SyntaxNodePtr> {
511         InFile::new(self.file, self.ident.clone().into())
512     }
513
514     fn as_any(&self) -> &(dyn Any + Send + 'static) {
515         self
516     }
517
518     fn is_experimental(&self) -> bool {
519         true
520     }
521 }