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