aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_ty/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_ty/src/diagnostics.rs')
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs283
1 files changed, 281 insertions, 2 deletions
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index 0289911de..d3ee9cf55 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -1,13 +1,30 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2mod expr;
3mod match_check;
4mod unsafe_check;
2 5
3use std::any::Any; 6use std::any::Any;
4 7
8use hir_def::DefWithBodyId;
9use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};
5use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile}; 10use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile};
11use ra_prof::profile;
6use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; 12use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
7use stdx::format_to; 13use stdx::format_to;
8 14
9pub use hir_def::{diagnostics::UnresolvedModule, expr::MatchArm, path::Path}; 15use crate::db::HirDatabase;
10pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; 16
17pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
18
19pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
20 let _p = profile("validate_body");
21 let infer = db.infer(owner);
22 infer.add_diagnostics(db, owner, sink);
23 let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink);
24 validator.validate_body(db);
25 let mut validator = unsafe_check::UnsafeValidator::new(owner, infer, sink);
26 validator.validate_body(db);
27}
11 28
12#[derive(Debug)] 29#[derive(Debug)]
13pub struct NoSuchField { 30pub struct NoSuchField {
@@ -197,3 +214,265 @@ impl AstDiagnostic for MissingUnsafe {
197 ast::Expr::cast(node).unwrap() 214 ast::Expr::cast(node).unwrap()
198 } 215 }
199} 216}
217
218#[derive(Debug)]
219pub struct MismatchedArgCount {
220 pub file: HirFileId,
221 pub call_expr: AstPtr<ast::Expr>,
222 pub expected: usize,
223 pub found: usize,
224}
225
226impl Diagnostic for MismatchedArgCount {
227 fn message(&self) -> String {
228 let s = if self.expected == 1 { "" } else { "s" };
229 format!("Expected {} argument{}, found {}", self.expected, s, self.found)
230 }
231 fn source(&self) -> InFile<SyntaxNodePtr> {
232 InFile { file_id: self.file, value: self.call_expr.clone().into() }
233 }
234 fn as_any(&self) -> &(dyn Any + Send + 'static) {
235 self
236 }
237}
238
239impl AstDiagnostic for MismatchedArgCount {
240 type AST = ast::CallExpr;
241 fn ast(&self, db: &dyn AstDatabase) -> Self::AST {
242 let root = db.parse_or_expand(self.source().file_id).unwrap();
243 let node = self.source().value.to_node(&root);
244 ast::CallExpr::cast(node).unwrap()
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
251 use hir_expand::diagnostics::{Diagnostic, DiagnosticSink};
252 use ra_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
253 use ra_syntax::{TextRange, TextSize};
254 use rustc_hash::FxHashMap;
255
256 use crate::{diagnostics::validate_body, test_db::TestDB};
257
258 impl TestDB {
259 fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
260 let crate_graph = self.crate_graph();
261 for krate in crate_graph.iter() {
262 let crate_def_map = self.crate_def_map(krate);
263
264 let mut fns = Vec::new();
265 for (module_id, _) in crate_def_map.modules.iter() {
266 for decl in crate_def_map[module_id].scope.declarations() {
267 if let ModuleDefId::FunctionId(f) = decl {
268 fns.push(f)
269 }
270 }
271
272 for impl_id in crate_def_map[module_id].scope.impls() {
273 let impl_data = self.impl_data(impl_id);
274 for item in impl_data.items.iter() {
275 if let AssocItemId::FunctionId(f) = item {
276 fns.push(*f)
277 }
278 }
279 }
280 }
281
282 for f in fns {
283 let mut sink = DiagnosticSink::new(&mut cb);
284 validate_body(self, f.into(), &mut sink);
285 }
286 }
287 }
288 }
289
290 pub(crate) fn check_diagnostics(ra_fixture: &str) {
291 let db = TestDB::with_files(ra_fixture);
292 let annotations = db.extract_annotations();
293
294 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
295 db.diagnostics(|d| {
296 // FXIME: macros...
297 let file_id = d.source().file_id.original_file(&db);
298 let range = d.syntax_node(&db).text_range();
299 let message = d.message().to_owned();
300 actual.entry(file_id).or_default().push((range, message));
301 });
302
303 for (file_id, diags) in actual.iter_mut() {
304 diags.sort_by_key(|it| it.0.start());
305 let text = db.file_text(*file_id);
306 // For multiline spans, place them on line start
307 for (range, content) in diags {
308 if text[*range].contains('\n') {
309 *range = TextRange::new(range.start(), range.start() + TextSize::from(1));
310 *content = format!("... {}", content);
311 }
312 }
313 }
314
315 assert_eq!(annotations, actual);
316 }
317
318 #[test]
319 fn no_such_field_diagnostics() {
320 check_diagnostics(
321 r#"
322struct S { foo: i32, bar: () }
323impl S {
324 fn new() -> S {
325 S {
326 //^... Missing structure fields:
327 //| - bar
328 foo: 92,
329 baz: 62,
330 //^^^^^^^ no such field
331 }
332 }
333}
334"#,
335 );
336 }
337 #[test]
338 fn no_such_field_with_feature_flag_diagnostics() {
339 check_diagnostics(
340 r#"
341//- /lib.rs crate:foo cfg:feature=foo
342struct MyStruct {
343 my_val: usize,
344 #[cfg(feature = "foo")]
345 bar: bool,
346}
347
348impl MyStruct {
349 #[cfg(feature = "foo")]
350 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
351 Self { my_val, bar }
352 }
353 #[cfg(not(feature = "foo"))]
354 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
355 Self { my_val }
356 }
357}
358"#,
359 );
360 }
361
362 #[test]
363 fn no_such_field_enum_with_feature_flag_diagnostics() {
364 check_diagnostics(
365 r#"
366//- /lib.rs crate:foo cfg:feature=foo
367enum Foo {
368 #[cfg(not(feature = "foo"))]
369 Buz,
370 #[cfg(feature = "foo")]
371 Bar,
372 Baz
373}
374
375fn test_fn(f: Foo) {
376 match f {
377 Foo::Bar => {},
378 Foo::Baz => {},
379 }
380}
381"#,
382 );
383 }
384
385 #[test]
386 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
387 check_diagnostics(
388 r#"
389//- /lib.rs crate:foo cfg:feature=foo
390struct S {
391 #[cfg(feature = "foo")]
392 foo: u32,
393 #[cfg(not(feature = "foo"))]
394 bar: u32,
395}
396
397impl S {
398 #[cfg(feature = "foo")]
399 fn new(foo: u32) -> Self {
400 Self { foo }
401 }
402 #[cfg(not(feature = "foo"))]
403 fn new(bar: u32) -> Self {
404 Self { bar }
405 }
406 fn new2(bar: u32) -> Self {
407 #[cfg(feature = "foo")]
408 { Self { foo: bar } }
409 #[cfg(not(feature = "foo"))]
410 { Self { bar } }
411 }
412 fn new2(val: u32) -> Self {
413 Self {
414 #[cfg(feature = "foo")]
415 foo: val,
416 #[cfg(not(feature = "foo"))]
417 bar: val,
418 }
419 }
420}
421"#,
422 );
423 }
424
425 #[test]
426 fn no_such_field_with_type_macro() {
427 check_diagnostics(
428 r#"
429macro_rules! Type { () => { u32 }; }
430struct Foo { bar: Type![] }
431
432impl Foo {
433 fn new() -> Self {
434 Foo { bar: 0 }
435 }
436}
437"#,
438 );
439 }
440
441 #[test]
442 fn missing_record_pat_field_diagnostic() {
443 check_diagnostics(
444 r#"
445struct S { foo: i32, bar: () }
446fn baz(s: S) {
447 let S { foo: _ } = s;
448 //^^^^^^^^^^ Missing structure fields:
449 // | - bar
450}
451"#,
452 );
453 }
454
455 #[test]
456 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
457 check_diagnostics(
458 r"
459struct S { foo: i32, bar: () }
460fn baz(s: S) -> i32 {
461 match s {
462 S { foo, .. } => foo,
463 }
464}
465",
466 )
467 }
468
469 #[test]
470 fn break_outside_of_loop() {
471 check_diagnostics(
472 r#"
473fn foo() { break; }
474 //^^^^^ break outside of loop
475"#,
476 );
477 }
478}