1 //! Diagnostics rendering and fixits.
3 //! Most of the diagnostics originate from the dark depth of the compiler, and
4 //! are originally expressed in term of IR. When we emit the diagnostic, we are
5 //! usually not in the position to decide how to best "render" it in terms of
6 //! user-authored source code. We are especially not in the position to offer
7 //! fixits, as the compiler completely lacks the infrastructure to edit the
10 //! Instead, we "bubble up" raw, structured diagnostics until the `hir` crate,
11 //! where we "cook" them so that each diagnostic is formulated in terms of `hir`
12 //! types. Well, at least that's the aspiration, the "cooking" is somewhat
13 //! ad-hoc at the moment. Anyways, we get a bunch of ide-friendly diagnostic
14 //! structs from hir, and we want to render them to unified serializable
15 //! representation (span, level, message) here. If we can, we also provide
16 //! fixits. By the way, that's why we want to keep diagnostics structured
17 //! internally -- so that we have all the info to make fixes.
19 //! We have one "handler" module per diagnostic code. Such a module contains
20 //! rendering, optional fixes and tests. It's OK if some low-level compiler
21 //! functionality ends up being tested via a diagnostic.
23 //! There are also a couple of ad-hoc diagnostics implemented directly here, we
24 //! don't yet have a great pattern for how to do them properly.
26 #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
29 pub(crate) mod break_outside_of_loop
;
30 pub(crate) mod expected_function
;
31 pub(crate) mod inactive_code
;
32 pub(crate) mod incoherent_impl
;
33 pub(crate) mod incorrect_case
;
34 pub(crate) mod invalid_derive_target
;
35 pub(crate) mod macro_error
;
36 pub(crate) mod malformed_derive
;
37 pub(crate) mod mismatched_arg_count
;
38 pub(crate) mod missing_fields
;
39 pub(crate) mod missing_match_arms
;
40 pub(crate) mod missing_unsafe
;
41 pub(crate) mod mutability_errors
;
42 pub(crate) mod no_such_field
;
43 pub(crate) mod private_assoc_item
;
44 pub(crate) mod private_field
;
45 pub(crate) mod replace_filter_map_next_with_find_map
;
46 pub(crate) mod type_mismatch
;
47 pub(crate) mod unimplemented_builtin_macro
;
48 pub(crate) mod unresolved_extern_crate
;
49 pub(crate) mod unresolved_field
;
50 pub(crate) mod unresolved_method
;
51 pub(crate) mod unresolved_import
;
52 pub(crate) mod unresolved_macro_call
;
53 pub(crate) mod unresolved_module
;
54 pub(crate) mod unresolved_proc_macro
;
56 // The handlers below are unusual, the implement the diagnostics as well.
57 pub(crate) mod field_shorthand
;
58 pub(crate) mod useless_braces
;
59 pub(crate) mod unlinked_file
;
60 pub(crate) mod json_is_not_rust
;
66 use hir
::{diagnostics::AnyDiagnostic, InFile, Semantics}
;
68 assists
::{Assist, AssistId, AssistKind, AssistResolveStrategy}
,
69 base_db
::{FileId, FileRange, SourceDatabase}
,
70 imports
::insert_use
::InsertUseConfig
,
72 source_change
::SourceChange
,
73 FxHashSet
, RootDatabase
,
75 use syntax
::{algo::find_node_at_range, ast::AstNode, SyntaxNodePtr, TextRange}
;
77 #[derive(Copy, Clone, Debug, PartialEq)]
78 pub struct DiagnosticCode(pub &'
static str);
81 pub fn as_str(&self) -> &str {
87 pub struct Diagnostic
{
88 pub code
: DiagnosticCode
,
91 pub severity
: Severity
,
93 pub experimental
: bool
,
94 pub fixes
: Option
<Vec
<Assist
>>,
98 fn new(code
: &'
static str, message
: impl Into
<String
>, range
: TextRange
) -> Diagnostic
{
99 let message
= message
.into();
101 code
: DiagnosticCode(code
),
104 severity
: Severity
::Error
,
111 fn experimental(mut self) -> Diagnostic
{
112 self.experimental
= true;
116 fn severity(mut self, severity
: Severity
) -> Diagnostic
{
117 self.severity
= severity
;
121 fn with_fixes(mut self, fixes
: Option
<Vec
<Assist
>>) -> Diagnostic
{
126 fn with_unused(mut self, unused
: bool
) -> Diagnostic
{
127 self.unused
= unused
;
132 #[derive(Debug, Copy, Clone)]
135 // We don't actually emit this one yet, but we should at some point.
140 #[derive(Clone, Debug, PartialEq, Eq)]
141 pub enum ExprFillDefaultMode
{
145 impl Default
for ExprFillDefaultMode
{
146 fn default() -> Self {
151 #[derive(Debug, Clone)]
152 pub struct DiagnosticsConfig
{
153 pub proc_macros_enabled
: bool
,
154 pub proc_attr_macros_enabled
: bool
,
155 pub disable_experimental
: bool
,
156 pub disabled
: FxHashSet
<String
>,
157 pub expr_fill_default
: ExprFillDefaultMode
,
158 // FIXME: We may want to include a whole `AssistConfig` here
159 pub insert_use
: InsertUseConfig
,
160 pub prefer_no_std
: bool
,
163 impl DiagnosticsConfig
{
164 pub fn test_sample() -> Self {
166 use ide_db
::imports
::insert_use
::ImportGranularity
;
169 proc_macros_enabled
: Default
::default(),
170 proc_attr_macros_enabled
: Default
::default(),
171 disable_experimental
: Default
::default(),
172 disabled
: Default
::default(),
173 expr_fill_default
: Default
::default(),
174 insert_use
: InsertUseConfig
{
175 granularity
: ImportGranularity
::Preserve
,
176 enforce_granularity
: false,
177 prefix_kind
: PrefixKind
::Plain
,
179 skip_glob_imports
: false,
181 prefer_no_std
: false,
186 struct DiagnosticsContext
<'a
> {
187 config
: &'a DiagnosticsConfig
,
188 sema
: Semantics
<'a
, RootDatabase
>,
189 resolve
: &'a AssistResolveStrategy
,
192 impl<'a
> DiagnosticsContext
<'a
> {
193 fn resolve_precise_location(
195 node
: &InFile
<SyntaxNodePtr
>,
196 precise_location
: Option
<TextRange
>,
198 let sema
= &self.sema
;
200 let precise_location
= precise_location?
;
201 let root
= sema
.parse_or_expand(node
.file_id
)?
;
202 match root
.covering_element(precise_location
) {
203 syntax
::NodeOrToken
::Node(it
) => Some(sema
.original_range(&it
)),
204 syntax
::NodeOrToken
::Token(it
) => {
205 node
.with_value(it
).original_file_range_opt(sema
.db
)
209 .unwrap_or_else(|| sema
.diagnostics_display_range(node
.clone()))
216 config
: &DiagnosticsConfig
,
217 resolve
: &AssistResolveStrategy
,
219 ) -> Vec
<Diagnostic
> {
220 let _p
= profile
::span("diagnostics");
221 let sema
= Semantics
::new(db
);
222 let parse
= db
.parse(file_id
);
223 let mut res
= Vec
::new();
225 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
227 parse
.errors().iter().take(128).map(|err
| {
228 Diagnostic
::new("syntax-error", format
!("Syntax Error: {err}"), err
.range())
232 let parse
= sema
.parse(file_id
);
234 for node
in parse
.syntax().descendants() {
235 handlers
::useless_braces
::useless_braces(&mut res
, file_id
, &node
);
236 handlers
::field_shorthand
::field_shorthand(&mut res
, file_id
, &node
);
237 handlers
::json_is_not_rust
::json_in_items(&sema
, &mut res
, file_id
, &node
, config
);
240 let module
= sema
.to_module_def(file_id
);
242 let ctx
= DiagnosticsContext { config, sema, resolve }
;
243 if module
.is_none() {
244 handlers
::unlinked_file
::unlinked_file(&ctx
, &mut res
, file_id
);
247 let mut diags
= Vec
::new();
248 if let Some(m
) = module
{
249 m
.diagnostics(db
, &mut diags
)
255 AnyDiagnostic
::BreakOutsideOfLoop(d
) => handlers
::break_outside_of_loop
::break_outside_of_loop(&ctx
, &d
),
256 AnyDiagnostic
::ExpectedFunction(d
) => handlers
::expected_function
::expected_function(&ctx
, &d
),
257 AnyDiagnostic
::IncorrectCase(d
) => handlers
::incorrect_case
::incorrect_case(&ctx
, &d
),
258 AnyDiagnostic
::IncoherentImpl(d
) => handlers
::incoherent_impl
::incoherent_impl(&ctx
, &d
),
259 AnyDiagnostic
::MacroError(d
) => handlers
::macro_error
::macro_error(&ctx
, &d
),
260 AnyDiagnostic
::MalformedDerive(d
) => handlers
::malformed_derive
::malformed_derive(&ctx
, &d
),
261 AnyDiagnostic
::MismatchedArgCount(d
) => handlers
::mismatched_arg_count
::mismatched_arg_count(&ctx
, &d
),
262 AnyDiagnostic
::MissingFields(d
) => handlers
::missing_fields
::missing_fields(&ctx
, &d
),
263 AnyDiagnostic
::MissingMatchArms(d
) => handlers
::missing_match_arms
::missing_match_arms(&ctx
, &d
),
264 AnyDiagnostic
::MissingUnsafe(d
) => handlers
::missing_unsafe
::missing_unsafe(&ctx
, &d
),
265 AnyDiagnostic
::NoSuchField(d
) => handlers
::no_such_field
::no_such_field(&ctx
, &d
),
266 AnyDiagnostic
::PrivateAssocItem(d
) => handlers
::private_assoc_item
::private_assoc_item(&ctx
, &d
),
267 AnyDiagnostic
::PrivateField(d
) => handlers
::private_field
::private_field(&ctx
, &d
),
268 AnyDiagnostic
::ReplaceFilterMapNextWithFindMap(d
) => handlers
::replace_filter_map_next_with_find_map
::replace_filter_map_next_with_find_map(&ctx
, &d
),
269 AnyDiagnostic
::TypeMismatch(d
) => handlers
::type_mismatch
::type_mismatch(&ctx
, &d
),
270 AnyDiagnostic
::UnimplementedBuiltinMacro(d
) => handlers
::unimplemented_builtin_macro
::unimplemented_builtin_macro(&ctx
, &d
),
271 AnyDiagnostic
::UnresolvedExternCrate(d
) => handlers
::unresolved_extern_crate
::unresolved_extern_crate(&ctx
, &d
),
272 AnyDiagnostic
::UnresolvedImport(d
) => handlers
::unresolved_import
::unresolved_import(&ctx
, &d
),
273 AnyDiagnostic
::UnresolvedMacroCall(d
) => handlers
::unresolved_macro_call
::unresolved_macro_call(&ctx
, &d
),
274 AnyDiagnostic
::UnresolvedModule(d
) => handlers
::unresolved_module
::unresolved_module(&ctx
, &d
),
275 AnyDiagnostic
::UnresolvedProcMacro(d
) => handlers
::unresolved_proc_macro
::unresolved_proc_macro(&ctx
, &d
, config
.proc_macros_enabled
, config
.proc_attr_macros_enabled
),
276 AnyDiagnostic
::InvalidDeriveTarget(d
) => handlers
::invalid_derive_target
::invalid_derive_target(&ctx
, &d
),
277 AnyDiagnostic
::UnresolvedField(d
) => handlers
::unresolved_field
::unresolved_field(&ctx
, &d
),
278 AnyDiagnostic
::UnresolvedMethodCall(d
) => handlers
::unresolved_method
::unresolved_method(&ctx
, &d
),
279 AnyDiagnostic
::NeedMut(d
) => handlers
::mutability_errors
::need_mut(&ctx
, &d
),
280 AnyDiagnostic
::UnusedMut(d
) => handlers
::mutability_errors
::unused_mut(&ctx
, &d
),
281 AnyDiagnostic
::InactiveCode(d
) => match handlers
::inactive_code
::inactive_code(&ctx
, &d
) {
290 !ctx
.config
.disabled
.contains(d
.code
.as_str())
291 && !(ctx
.config
.disable_experimental
&& d
.experimental
)
297 fn fix(id
: &'
static str, label
: &str, source_change
: SourceChange
, target
: TextRange
) -> Assist
{
298 let mut res
= unresolved_fix(id
, label
, target
);
299 res
.source_change
= Some(source_change
);
303 fn unresolved_fix(id
: &'
static str, label
: &str, target
: TextRange
) -> Assist
{
304 assert
!(!id
.contains(' '
));
306 id
: AssistId(id
, AssistKind
::QuickFix
),
307 label
: Label
::new(label
.to_string()),
311 trigger_signature_help
: false,
315 fn adjusted_display_range
<N
: AstNode
>(
316 ctx
: &DiagnosticsContext
<'_
>,
317 diag_ptr
: InFile
<SyntaxNodePtr
>,
318 adj
: &dyn Fn(N
) -> Option
<TextRange
>,
320 let FileRange { file_id, range }
= ctx
.sema
.diagnostics_display_range(diag_ptr
);
322 let source_file
= ctx
.sema
.db
.parse(file_id
);
323 find_node_at_range
::<N
>(&source_file
.syntax_node(), range
)
324 .filter(|it
| it
.syntax().text_range() == range
)