summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-diagnostics
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:57:31 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:57:31 +0000
commitdc0db358abe19481e475e10c32149b53370f1a1c (patch)
treeab8ce99c4b255ce46f99ef402c27916055b899ee /src/tools/rust-analyzer/crates/ide-diagnostics
parentReleasing progress-linux version 1.71.1+dfsg1-2~progress7.99u1. (diff)
downloadrustc-dc0db358abe19481e475e10c32149b53370f1a1c.tar.xz
rustc-dc0db358abe19481e475e10c32149b53370f1a1c.zip
Merging upstream version 1.72.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-diagnostics')
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs41
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs45
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs10
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs175
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs474
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs145
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs232
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs88
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs30
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs91
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs24
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs47
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs36
24 files changed, 1387 insertions, 112 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs
index 114face2d..30576c71f 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs
@@ -31,12 +31,8 @@ mod tests {
fn foo() {
break;
//^^^^^ error: break outside of loop
- break 'a;
- //^^^^^^^^ error: break outside of loop
continue;
//^^^^^^^^ error: continue outside of loop
- continue 'a;
- //^^^^^^^^^^^ error: continue outside of loop
}
"#,
);
@@ -51,12 +47,8 @@ fn foo() {
async {
break;
//^^^^^ error: break outside of loop
- break 'a;
- //^^^^^^^^ error: break outside of loop
continue;
//^^^^^^^^ error: continue outside of loop
- continue 'a;
- //^^^^^^^^^^^ error: continue outside of loop
};
}
}
@@ -73,12 +65,8 @@ fn foo() {
|| {
break;
//^^^^^ error: break outside of loop
- break 'a;
- //^^^^^^^^ error: break outside of loop
continue;
//^^^^^^^^ error: continue outside of loop
- continue 'a;
- //^^^^^^^^^^^ error: continue outside of loop
};
}
}
@@ -94,9 +82,7 @@ fn foo() {
'a: loop {
{
break;
- break 'a;
continue;
- continue 'a;
}
}
}
@@ -112,9 +98,7 @@ fn foo() {
'a: loop {
try {
break;
- break 'a;
continue;
- continue 'a;
};
}
}
@@ -130,11 +114,8 @@ fn foo() {
'a: {
break;
//^^^^^ error: break outside of loop
- break 'a;
continue;
//^^^^^^^^ error: continue outside of loop
- continue 'a;
- //^^^^^^^^^^^ error: continue outside of loop
}
}
"#,
@@ -143,15 +124,35 @@ fn foo() {
#[test]
fn value_break_in_for_loop() {
+ // FIXME: the error is correct, but the message is terrible
check_diagnostics(
r#"
+//- minicore: iterator
fn test() {
for _ in [()] {
break 3;
- // ^^^^^^^ error: can't break with a value in this position
+ // ^ error: expected (), found i32
}
}
"#,
);
}
+
+ #[test]
+ fn try_block_desugaring_inside_closure() {
+ // regression test for #14701
+ check_diagnostics(
+ r#"
+//- minicore: option, try
+fn test() {
+ try {
+ || {
+ let x = Some(2);
+ Some(x?)
+ };
+ };
+}
+"#,
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs
index db88bf7b9..90279e145 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs
@@ -27,7 +27,7 @@ pub(crate) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCas
}
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Assist>> {
- let root = ctx.sema.db.parse_or_expand(d.file)?;
+ let root = ctx.sema.db.parse_or_expand(d.file);
let name_node = d.ident.to_node(&root);
let def = NameClass::classify(&ctx.sema, &name_node)?.defined()?;
@@ -295,7 +295,7 @@ impl someStruct {
}
#[test]
- fn no_diagnostic_for_enum_varinats() {
+ fn no_diagnostic_for_enum_variants() {
check_diagnostics(
r#"
enum Option { Some, None }
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs
index 870c78d1f..7547779a9 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs
@@ -9,6 +9,16 @@ pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) ->
Diagnostic::new("macro-error", d.message.clone(), display_range).experimental()
}
+// Diagnostic: macro-error
+//
+// This diagnostic is shown for macro expansion errors.
+pub(crate) fn macro_def_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroDefError) -> Diagnostic {
+ // Use more accurate position if available.
+ let display_range =
+ ctx.resolve_precise_location(&d.node.clone().map(|it| it.syntax_node_ptr()), d.name);
+ Diagnostic::new("macro-def-error", d.message.clone(), display_range).experimental()
+}
+
#[cfg(test)]
mod tests {
use crate::{
@@ -188,6 +198,7 @@ fn f() {
"#,
);
}
+
#[test]
fn dollar_crate_in_builtin_macro() {
check_diagnostics(
@@ -212,4 +223,38 @@ fn f() {
"#,
)
}
+
+ #[test]
+ fn def_diagnostic() {
+ check_diagnostics(
+ r#"
+macro_rules! foo {
+ //^^^ error: expected subtree
+ f => {};
+}
+
+fn f() {
+ foo!();
+ //^^^ error: invalid macro definition: expected subtree
+
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn expansion_syntax_diagnostic() {
+ check_diagnostics(
+ r#"
+macro_rules! foo {
+ () => { struct; };
+}
+
+fn f() {
+ foo!();
+ //^^^ error: Syntax Error in Expansion: expected a name
+}
+"#,
+ )
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs
index 5c4327ff9..60ccc41df 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs
@@ -31,7 +31,7 @@ use crate::{fix, Diagnostic, DiagnosticsContext};
pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
let mut message = String::from("missing structure fields:\n");
for field in &d.missed_fields {
- format_to!(message, "- {}\n", field);
+ format_to!(message, "- {}\n", field.display(ctx.sema.db));
}
let ptr = InFile::new(
@@ -56,7 +56,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
return None;
}
- let root = ctx.sema.db.parse_or_expand(d.file)?;
+ let root = ctx.sema.db.parse_or_expand(d.file);
let current_module = match &d.field_list_parent {
Either::Left(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
@@ -175,8 +175,10 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
let ty_str = match ty.as_adt() {
- Some(adt) => adt.name(db).to_string(),
- None => ty.display_source_code(db, module.into()).ok().unwrap_or_else(|| "_".to_string()),
+ Some(adt) => adt.name(db).display(db.upcast()).to_string(),
+ None => {
+ ty.display_source_code(db, module.into(), false).ok().unwrap_or_else(|| "_".to_string())
+ }
};
make::ty(&ty_str)
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index ac4463331..3f13b97a4 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -271,15 +271,20 @@ enum Either2 { C, D }
fn main() {
match Either::A {
Either2::C => (),
+ //^^^^^^^^^^ error: expected Either, found Either2
Either2::D => (),
+ //^^^^^^^^^^ error: expected Either, found Either2
}
match (true, false) {
(true, false, true) => (),
+ //^^^^^^^^^^^^^^^^^^^ error: expected (bool, bool), found (bool, bool, bool)
(true) => (),
// ^^^^ error: expected (bool, bool), found bool
}
match (true, false) { (true,) => {} }
+ //^^^^^^^ error: expected (bool, bool), found (bool,)
match (0) { () => () }
+ //^^ error: expected i32, found ()
match Unresolved::Bar { Unresolved::Baz => () }
}
"#,
@@ -293,7 +298,9 @@ fn main() {
r#"
fn main() {
match false { true | () => {} }
+ //^^ error: expected bool, found ()
match (false,) { (true | (),) => {} }
+ //^^ error: expected bool, found ()
}
"#,
);
@@ -738,17 +745,13 @@ fn main() {
#[test]
fn binding_ref_has_correct_type() {
- cov_mark::check_count!(validate_match_bailed_out, 1);
-
// Asserts `PatKind::Binding(ref _x): bool`, not &bool.
// If that's not true match checking will panic with "incompatible constructors"
// FIXME: make facilities to test this directly like `tests::check_infer(..)`
- check_diagnostics(
+ check_diagnostics_no_bails(
r#"
enum Foo { A }
fn main() {
- // FIXME: this should not bail out but current behavior is such as the old algorithm.
- // ExprValidator::validate_match(..) checks types of top level patterns incorrectly.
match Foo::A {
ref _x => {}
Foo::A => {}
@@ -1024,6 +1027,7 @@ fn main() {
check_diagnostics(
r#"
+//- minicore: copy
fn main() {
match &false {
&true => {}
@@ -1035,11 +1039,13 @@ fn main() {
#[test]
fn reference_patterns_in_fields() {
- cov_mark::check_count!(validate_match_bailed_out, 2);
+ cov_mark::check_count!(validate_match_bailed_out, 1);
check_diagnostics(
r#"
+//- minicore: copy
fn main() {
match (&false,) {
+ //^^^^^^^^^ error: missing match arm: `(&false,)` not covered
(true,) => {}
}
match (&false,) {
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
index eb32db250..2026b6fce 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
@@ -24,7 +24,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Ass
return None;
}
- let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
+ let root = ctx.sema.db.parse_or_expand(d.expr.file_id);
let expr = d.expr.value.to_node(&root);
let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(&expr)?;
@@ -142,6 +142,8 @@ fn main() {
fn missing_unsafe_diagnostic_with_static_mut() {
check_diagnostics(
r#"
+//- minicore: copy
+
struct Ty {
a: u8,
}
@@ -256,6 +258,7 @@ fn main() {
fn add_unsafe_block_when_accessing_mutable_static() {
check_fix(
r#"
+//- minicore: copy
struct Ty {
a: u8,
}
@@ -374,6 +377,7 @@ fn main() {
fn unsafe_expr_as_right_hand_side_of_assignment() {
check_fix(
r#"
+//- minicore: copy
static mut STATIC_MUT: u8 = 0;
fn main() {
@@ -396,6 +400,7 @@ fn main() {
fn unsafe_expr_in_binary_plus() {
check_fix(
r#"
+//- minicore: copy
static mut STATIC_MUT: u8 = 0;
fn main() {
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
new file mode 100644
index 000000000..32e321107
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
@@ -0,0 +1,175 @@
+use crate::{Diagnostic, DiagnosticsContext};
+use hir::HirDisplay;
+
+// Diagnostic: moved-out-of-ref
+//
+// This diagnostic is triggered on moving non copy things out of references.
+pub(crate) fn moved_out_of_ref(ctx: &DiagnosticsContext<'_>, d: &hir::MovedOutOfRef) -> Diagnostic {
+ Diagnostic::new(
+ "moved-out-of-ref",
+ format!("cannot move `{}` out of reference", d.ty.display(ctx.sema.db)),
+ ctx.sema.diagnostics_display_range(d.span.clone()).range,
+ )
+ .experimental() // spans are broken, and I'm not sure how precise we can detect copy types
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ // FIXME: spans are broken
+
+ #[test]
+ fn move_by_explicit_deref() {
+ check_diagnostics(
+ r#"
+struct X;
+fn main() {
+ let a = &X;
+ let b = *a;
+ //^ error: cannot move `X` out of reference
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_out_of_field() {
+ check_diagnostics(
+ r#"
+//- minicore: copy
+struct X;
+struct Y(X, i32);
+fn main() {
+ let a = &Y(X, 5);
+ let b = a.0;
+ //^ error: cannot move `X` out of reference
+ let y = a.1;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_out_of_static() {
+ check_diagnostics(
+ r#"
+//- minicore: copy
+struct X;
+fn main() {
+ static S: X = X;
+ let s = S;
+ //^ error: cannot move `X` out of reference
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generic_types() {
+ check_diagnostics(
+ r#"
+//- minicore: derive, copy
+
+#[derive(Copy)]
+struct X<T>(T);
+struct Y;
+
+fn consume<T>(_: X<T>) {
+
+}
+
+fn main() {
+ let a = &X(Y);
+ consume(*a);
+ //^^^^^^^^^^^ error: cannot move `X<Y>` out of reference
+ let a = &X(5);
+ consume(*a);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_false_positive_simple() {
+ check_diagnostics(
+ r#"
+//- minicore: copy
+fn f(_: i32) {}
+fn main() {
+ let x = &2;
+ f(*x);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_false_positive_unknown_type() {
+ check_diagnostics(
+ r#"
+//- minicore: derive, copy
+fn f(x: &Unknown) -> Unknown {
+ *x
+}
+
+#[derive(Copy)]
+struct X<T>(T);
+
+struct Y<T>(T);
+
+fn g(x: &X<Unknown>) -> X<Unknown> {
+ *x
+}
+
+fn h(x: &Y<Unknown>) -> Y<Unknown> {
+ // FIXME: we should show error for this, as `Y` is not copy
+ // regardless of its generic parameter.
+ *x
+}
+
+"#,
+ );
+ }
+
+ #[test]
+ fn no_false_positive_dyn_fn() {
+ check_diagnostics(
+ r#"
+//- minicore: copy, fn
+fn f(x: &mut &mut dyn Fn()) {
+ x();
+}
+
+struct X<'a> {
+ field: &'a mut dyn Fn(),
+}
+
+fn f(x: &mut X<'_>) {
+ (x.field)();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_false_positive_match_and_closure_capture() {
+ check_diagnostics(
+ r#"
+//- minicore: copy, fn
+enum X {
+ Foo(u16),
+ Bar,
+}
+
+fn main() {
+ let x = &X::Bar;
+ let c = || match *x {
+ X::Foo(t) => t,
+ _ => 5,
+ };
+}
+ "#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs
index 96470265d..f61460e31 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -18,7 +18,8 @@ pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagno
let use_range = d.span.value.text_range();
for source in d.local.sources(ctx.sema.db) {
let Some(ast) = source.name() else { continue };
- edit_builder.insert(ast.syntax().text_range().start(), "mut ".to_string());
+ // FIXME: macros
+ edit_builder.insert(ast.value.syntax().text_range().start(), "mut ".to_string());
}
let edit = edit_builder.finish();
Some(vec![fix(
@@ -30,7 +31,10 @@ pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagno
})();
Diagnostic::new(
"need-mut",
- format!("cannot mutate immutable variable `{}`", d.local.name(ctx.sema.db)),
+ format!(
+ "cannot mutate immutable variable `{}`",
+ d.local.name(ctx.sema.db).display(ctx.sema.db)
+ ),
ctx.sema.diagnostics_display_range(d.span.clone()).range,
)
.with_fixes(fixes)
@@ -340,6 +344,7 @@ fn main() {
fn regression_14310() {
check_diagnostics(
r#"
+ //- minicore: copy, builtin_impls
fn clone(mut i: &!) -> ! {
//^^^^^ 💡 weak: variable does not need to be mutable
*i
@@ -349,6 +354,32 @@ fn main() {
}
#[test]
+ fn match_closure_capture() {
+ check_diagnostics(
+ r#"
+//- minicore: option
+fn main() {
+ let mut v = &mut Some(2);
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let _ = || match v {
+ Some(k) => {
+ *k = 5;
+ }
+ None => {}
+ };
+ let v = &mut Some(2);
+ let _ = || match v {
+ //^ 💡 error: cannot mutate immutable variable `v`
+ ref mut k => {
+ *k = &mut Some(5);
+ }
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
fn match_bindings() {
check_diagnostics(
r#"
@@ -368,7 +399,7 @@ fn main() {
#[test]
fn mutation_in_dead_code() {
// This one is interesting. Dead code is not represented at all in the MIR, so
- // there would be no mutablility error for locals in dead code. Rustc tries to
+ // there would be no mutability error for locals in dead code. Rustc tries to
// not emit `unused_mut` in this case, but since it works without `mut`, and
// special casing it is not trivial, we emit it.
check_diagnostics(
@@ -485,6 +516,38 @@ fn main() {
);
check_diagnostics(
r#"
+fn check(_: i32) -> bool {
+ false
+}
+fn main() {
+ loop {
+ let x = 1;
+ if check(x) {
+ break;
+ }
+ let y = (1, 2);
+ if check(y.1) {
+ return;
+ }
+ let z = (1, 2);
+ match z {
+ (k @ 5, ref mut t) if { continue; } => {
+ //^^^^^^^^^ 💡 error: cannot mutate immutable variable `z`
+ *t = 5;
+ }
+ _ => {
+ let y = (1, 2);
+ if check(y.1) {
+ return;
+ }
+ }
+ }
+ }
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
fn f(_: i32) {}
fn main() {
loop {
@@ -546,13 +609,35 @@ fn f(x: i32) {
}
"#,
);
+ check_diagnostics(
+ r#"
+fn f((x, y): (i32, i32)) {
+ let t = [0; 2];
+ x = 5;
+ //^^^^^ 💡 error: cannot mutate immutable variable `x`
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_diagnostics_in_case_of_multiple_bounds() {
+ check_diagnostics(
+ r#"
+fn f() {
+ let (b, a, b) = (2, 3, 5);
+ a = 8;
+ //^^^^^ 💡 error: cannot mutate immutable variable `a`
+}
+"#,
+ );
}
#[test]
fn for_loop() {
check_diagnostics(
r#"
-//- minicore: iterators
+//- minicore: iterators, copy
fn f(x: [(i32, u8); 10]) {
for (a, mut b) in x {
//^^^^^ 💡 weak: variable does not need to be mutable
@@ -565,8 +650,96 @@ fn f(x: [(i32, u8); 10]) {
}
#[test]
+ fn while_let() {
+ check_diagnostics(
+ r#"
+//- minicore: iterators, copy
+fn f(x: [(i32, u8); 10]) {
+ let mut it = x.into_iter();
+ while let Some((a, mut b)) = it.next() {
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ while let Some((c, mut d)) = it.next() {
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ a = 2;
+ //^^^^^ 💡 error: cannot mutate immutable variable `a`
+ c = 2;
+ //^^^^^ 💡 error: cannot mutate immutable variable `c`
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn index() {
+ check_diagnostics(
+ r#"
+//- minicore: coerce_unsized, index, slice
+fn f() {
+ let x = [1, 2, 3];
+ x[2] = 5;
+ //^^^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let x = &mut x;
+ //^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let mut x = x;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ x[2] = 5;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn overloaded_index() {
+ check_diagnostics(
+ r#"
+//- minicore: index
+use core::ops::{Index, IndexMut};
+
+struct Foo;
+impl Index<usize> for Foo {
+ type Output = (i32, u8);
+ fn index(&self, index: usize) -> &(i32, u8) {
+ &(5, 2)
+ }
+}
+impl IndexMut<usize> for Foo {
+ fn index_mut(&mut self, index: usize) -> &mut (i32, u8) {
+ &mut (5, 2)
+ }
+}
+fn f() {
+ let mut x = Foo;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let y = &x[2];
+ let x = Foo;
+ let y = &mut x[2];
+ //^💡 error: cannot mutate immutable variable `x`
+ let mut x = &mut Foo;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let y: &mut (i32, u8) = &mut x[2];
+ let x = Foo;
+ let ref mut y = x[7];
+ //^ 💡 error: cannot mutate immutable variable `x`
+ let (ref mut y, _) = x[3];
+ //^ 💡 error: cannot mutate immutable variable `x`
+ match x[10] {
+ //^ 💡 error: cannot mutate immutable variable `x`
+ (ref y, _) => (),
+ (_, ref mut y) => (),
+ }
+ let mut x = Foo;
+ let mut i = 5;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let y = &mut x[i];
+}
+"#,
+ );
+ }
+
+ #[test]
fn overloaded_deref() {
- // FIXME: check for false negative
check_diagnostics(
r#"
//- minicore: deref_mut
@@ -574,22 +747,36 @@ use core::ops::{Deref, DerefMut};
struct Foo;
impl Deref for Foo {
- type Target = i32;
- fn deref(&self) -> &i32 {
- &5
+ type Target = (i32, u8);
+ fn deref(&self) -> &(i32, u8) {
+ &(5, 2)
}
}
impl DerefMut for Foo {
- fn deref_mut(&mut self) -> &mut i32 {
- &mut 5
+ fn deref_mut(&mut self) -> &mut (i32, u8) {
+ &mut (5, 2)
}
}
fn f() {
- let x = Foo;
+ let mut x = Foo;
+ //^^^^^ 💡 weak: variable does not need to be mutable
let y = &*x;
let x = Foo;
- let mut x = Foo;
- let y: &mut i32 = &mut x;
+ let y = &mut *x;
+ //^^ 💡 error: cannot mutate immutable variable `x`
+ let x = Foo;
+ let x = Foo;
+ let y: &mut (i32, u8) = &mut x;
+ //^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let ref mut y = *x;
+ //^^ 💡 error: cannot mutate immutable variable `x`
+ let (ref mut y, _) = *x;
+ //^^ 💡 error: cannot mutate immutable variable `x`
+ match *x {
+ //^^ 💡 error: cannot mutate immutable variable `x`
+ (ref y, _) => (),
+ (_, ref mut y) => (),
+ }
}
"#,
);
@@ -632,6 +819,267 @@ fn f(inp: (Foo, Foo, Foo, Foo)) {
}
#[test]
+ // FIXME: We should have tests for `is_ty_uninhabited_from`
+ fn regression_14421() {
+ check_diagnostics(
+ r#"
+pub enum Tree {
+ Node(TreeNode),
+ Leaf(TreeLeaf),
+}
+
+struct Box<T>(&T);
+
+pub struct TreeNode {
+ pub depth: usize,
+ pub children: [Box<Tree>; 8]
+}
+
+pub struct TreeLeaf {
+ pub depth: usize,
+ pub data: u8
+}
+
+pub fn test() {
+ let mut tree = Tree::Leaf(
+ //^^^^^^^^ 💡 weak: variable does not need to be mutable
+ TreeLeaf {
+ depth: 0,
+ data: 0
+ }
+ );
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn fn_traits() {
+ check_diagnostics(
+ r#"
+//- minicore: fn
+fn fn_ref(mut x: impl Fn(u8) -> u8) -> u8 {
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ x(2)
+}
+fn fn_mut(x: impl FnMut(u8) -> u8) -> u8 {
+ x(2)
+ //^ 💡 error: cannot mutate immutable variable `x`
+}
+fn fn_borrow_mut(mut x: &mut impl FnMut(u8) -> u8) -> u8 {
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ x(2)
+}
+fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ x(2)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closure() {
+ // FIXME: Diagnostic spans are inconsistent inside and outside closure
+ check_diagnostics(
+ r#"
+ //- minicore: copy, fn
+ struct X;
+
+ impl X {
+ fn mutate(&mut self) {}
+ }
+
+ fn f() {
+ let x = 5;
+ let closure1 = || { x = 2; };
+ //^ 💡 error: cannot mutate immutable variable `x`
+ let _ = closure1();
+ //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
+ let closure2 = || { x = x; };
+ //^ 💡 error: cannot mutate immutable variable `x`
+ let closure3 = || {
+ let x = 2;
+ x = 5;
+ //^^^^^ 💡 error: cannot mutate immutable variable `x`
+ x
+ };
+ let x = X;
+ let closure4 = || { x.mutate(); };
+ //^ 💡 error: cannot mutate immutable variable `x`
+ }
+ "#,
+ );
+ check_diagnostics(
+ r#"
+ //- minicore: copy, fn
+ fn f() {
+ let mut x = 5;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let mut y = 2;
+ y = 7;
+ let closure = || {
+ let mut z = 8;
+ z = 3;
+ let mut k = z;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ };
+ }
+ "#,
+ );
+ check_diagnostics(
+ r#"
+//- minicore: copy, fn
+fn f() {
+ let closure = || {
+ || {
+ || {
+ let x = 2;
+ || { || { x = 5; } }
+ //^ 💡 error: cannot mutate immutable variable `x`
+ }
+ }
+ };
+}
+ "#,
+ );
+ check_diagnostics(
+ r#"
+//- minicore: copy, fn
+fn f() {
+ struct X;
+ let mut x = X;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let c1 = || x;
+ let mut x = X;
+ let c2 = || { x = X; x };
+ let mut x = X;
+ let c2 = move || { x = X; };
+}
+ "#,
+ );
+ check_diagnostics(
+ r#"
+ //- minicore: copy, fn, deref_mut
+ struct X(i32, i64);
+
+ fn f() {
+ let mut x = &mut 5;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let closure1 = || { *x = 2; };
+ let _ = closure1();
+ //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
+ let mut x = &mut 5;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let closure1 = || { *x = 2; &x; };
+ let _ = closure1();
+ //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
+ let mut x = &mut 5;
+ let closure1 = || { *x = 2; &x; x = &mut 3; };
+ let _ = closure1();
+ //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
+ let mut x = &mut 5;
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let closure1 = move || { *x = 2; };
+ let _ = closure1();
+ //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
+ let mut x = &mut X(1, 2);
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ let closure1 = || { x.0 = 2; };
+ let _ = closure1();
+ //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn slice_pattern() {
+ check_diagnostics(
+ r#"
+//- minicore: coerce_unsized, deref_mut, slice, copy
+fn x(t: &[u8]) {
+ match t {
+ &[a, mut b] | &[a, _, mut b] => {
+ //^^^^^ 💡 weak: variable does not need to be mutable
+
+ a = 2;
+ //^^^^^ 💡 error: cannot mutate immutable variable `a`
+
+ }
+ _ => {}
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn boxes() {
+ check_diagnostics(
+ r#"
+//- minicore: coerce_unsized, deref_mut, slice
+use core::ops::{Deref, DerefMut};
+use core::{marker::Unsize, ops::CoerceUnsized};
+
+#[lang = "owned_box"]
+pub struct Box<T: ?Sized> {
+ inner: *mut T,
+}
+impl<T> Box<T> {
+ fn new(t: T) -> Self {
+ #[rustc_box]
+ Box::new(t)
+ }
+}
+
+impl<T: ?Sized> Deref for Box<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &**self
+ }
+}
+
+impl<T: ?Sized> DerefMut for Box<T> {
+ fn deref_mut(&mut self) -> &mut T {
+ &mut **self
+ }
+}
+
+fn f() {
+ let x = Box::new(5);
+ x = Box::new(7);
+ //^^^^^^^^^^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let x = Box::new(5);
+ *x = 7;
+ //^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let mut y = Box::new(5);
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ *x = *y;
+ //^^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let x = Box::new(5);
+ let closure = || *x = 2;
+ //^ 💡 error: cannot mutate immutable variable `x`
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn allow_unused_mut_for_identifiers_starting_with_underline() {
+ check_diagnostics(
+ r#"
+fn f(_: i32) {}
+fn main() {
+ let mut _x = 2;
+ f(_x);
+}
+"#,
+ );
+ }
+
+ #[test]
fn respect_allow_unused_mut() {
// FIXME: respect
check_diagnostics(
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs
index 24c521ed1..a39eceab2 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs
@@ -21,7 +21,7 @@ pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField)
}
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
- let root = ctx.sema.db.parse_or_expand(d.field.file_id)?;
+ let root = ctx.sema.db.parse_or_expand(d.field.file_id);
missing_record_expr_field_fixes(
&ctx.sema,
d.field.file_id.original_file(ctx.sema.db),
@@ -69,7 +69,7 @@ fn missing_record_expr_field_fixes(
let new_field = make::record_field(
None,
make::name(record_expr_field.field_name()?.ident_token()?.text()),
- make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
+ make::ty(&new_field_type.display_source_code(sema.db, module.into(), true).ok()?),
);
let last_field = record_fields.fields().last()?;
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
index 67da5c7f2..4cd85a479 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
@@ -11,7 +11,11 @@ pub(crate) fn private_assoc_item(
d: &hir::PrivateAssocItem,
) -> Diagnostic {
// FIXME: add quickfix
- let name = d.item.name(ctx.sema.db).map(|name| format!("`{name}` ")).unwrap_or_default();
+ let name = d
+ .item
+ .name(ctx.sema.db)
+ .map(|name| format!("`{}` ", name.display(ctx.sema.db)))
+ .unwrap_or_default();
Diagnostic::new(
"private-assoc-item",
format!(
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs
index be83ad6aa..de7f51f69 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs
@@ -9,8 +9,8 @@ pub(crate) fn private_field(ctx: &DiagnosticsContext<'_>, d: &hir::PrivateField)
"private-field",
format!(
"field `{}` of `{}` is private",
- d.field.name(ctx.sema.db),
- d.field.parent_def(ctx.sema.db).name(ctx.sema.db)
+ d.field.name(ctx.sema.db).display(ctx.sema.db),
+ d.field.parent_def(ctx.sema.db).name(ctx.sema.db).display(ctx.sema.db)
),
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
)
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
index 9b1c65983..d3eda3c5e 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
@@ -28,7 +28,7 @@ fn fixes(
ctx: &DiagnosticsContext<'_>,
d: &hir::ReplaceFilterMapNextWithFindMap,
) -> Option<Vec<Assist>> {
- let root = ctx.sema.db.parse_or_expand(d.file)?;
+ let root = ctx.sema.db.parse_or_expand(d.file);
let next_expr = d.next_expr.to_node(&root);
let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
@@ -115,7 +115,7 @@ fn foo() {
r#"
//- minicore: iterators
fn foo() {
- let m = core::iter::repeat(())
+ let mut m = core::iter::repeat(())
.filter_map(|()| Some(92));
let n = m.next();
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs
index 4abc25a28..c28f98d83 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs
@@ -1,5 +1,5 @@
use either::Either;
-use hir::{db::ExpandDatabase, HirDisplay, InFile, Type};
+use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, InFile, Type};
use ide_db::{famous_defs::FamousDefs, source_change::SourceChange};
use syntax::{
ast::{self, BlockExpr, ExprStmt},
@@ -15,15 +15,25 @@ use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext}
// the expected type.
pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic {
let display_range = match &d.expr_or_pat {
- Either::Left(expr) => adjusted_display_range::<ast::BlockExpr>(
- ctx,
- expr.clone().map(|it| it.into()),
- &|block| {
- let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range();
- cov_mark::hit!(type_mismatch_on_block);
- Some(r_curly_range)
- },
- ),
+ Either::Left(expr) => {
+ adjusted_display_range::<ast::Expr>(ctx, expr.clone().map(|it| it.into()), &|expr| {
+ let salient_token_range = match expr {
+ ast::Expr::IfExpr(it) => it.if_token()?.text_range(),
+ ast::Expr::LoopExpr(it) => it.loop_token()?.text_range(),
+ ast::Expr::ForExpr(it) => it.for_token()?.text_range(),
+ ast::Expr::WhileExpr(it) => it.while_token()?.text_range(),
+ ast::Expr::BlockExpr(it) => it.stmt_list()?.r_curly_token()?.text_range(),
+ ast::Expr::MatchExpr(it) => it.match_token()?.text_range(),
+ ast::Expr::MethodCallExpr(it) => it.name_ref()?.ident_token()?.text_range(),
+ ast::Expr::FieldExpr(it) => it.name_ref()?.ident_token()?.text_range(),
+ ast::Expr::AwaitExpr(it) => it.await_token()?.text_range(),
+ _ => return None,
+ };
+
+ cov_mark::hit!(type_mismatch_range_adjustment);
+ Some(salient_token_range)
+ })
+ }
Either::Right(pat) => {
ctx.sema.diagnostics_display_range(pat.clone().map(|it| it.into())).range
}
@@ -32,8 +42,8 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch)
"type-mismatch",
format!(
"expected {}, found {}",
- d.expected.display(ctx.sema.db),
- d.actual.display(ctx.sema.db)
+ d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
+ d.actual.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
),
display_range,
)
@@ -93,7 +103,7 @@ fn add_missing_ok_or_some(
expr_ptr: &InFile<AstPtr<ast::Expr>>,
acc: &mut Vec<Assist>,
) -> Option<()> {
- let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?;
+ let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
let expr = expr_ptr.value.to_node(&root);
let expr_range = expr.syntax().text_range();
let scope = ctx.sema.scope(expr.syntax())?;
@@ -133,7 +143,7 @@ fn remove_semicolon(
expr_ptr: &InFile<AstPtr<ast::Expr>>,
acc: &mut Vec<Assist>,
) -> Option<()> {
- let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?;
+ let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
let expr = expr_ptr.value.to_node(&root);
if !d.actual.is_unit() {
return None;
@@ -169,7 +179,7 @@ fn str_ref_to_owned(
return None;
}
- let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?;
+ let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
let expr = expr_ptr.value.to_node(&root);
let expr_range = expr.syntax().text_range();
@@ -597,8 +607,21 @@ fn test() -> String {
}
#[test]
- fn type_mismatch_on_block() {
- cov_mark::check!(type_mismatch_on_block);
+ fn closure_mismatch_show_different_type() {
+ check_diagnostics(
+ r#"
+fn f() {
+ let mut x = (|| 1, 2);
+ x = (|| 3, 4);
+ //^^^^ error: expected {closure#0}, found {closure#1}
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn type_mismatch_range_adjustment() {
+ cov_mark::check!(type_mismatch_range_adjustment);
check_diagnostics(
r#"
fn f() -> i32 {
@@ -607,6 +630,57 @@ fn f() -> i32 {
let _ = x + y;
}
//^ error: expected i32, found ()
+
+fn g() -> i32 {
+ while true {}
+} //^^^^^ error: expected i32, found ()
+
+struct S;
+impl S { fn foo(&self) -> &S { self } }
+fn h() {
+ let _: i32 = S.foo().foo().foo();
+} //^^^ error: expected i32, found &S
+"#,
+ );
+ }
+
+ #[test]
+ fn unknown_type_in_function_signature() {
+ check_diagnostics(
+ r#"
+struct X<T>(T);
+
+fn foo(x: X<Unknown>) {}
+fn test1() {
+ // Unknown might be `i32`, so we should not emit type mismatch here.
+ foo(X(42));
+}
+fn test2() {
+ foo(42);
+ //^^ error: expected X<{unknown}>, found i32
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn evaluate_const_generics_in_types() {
+ check_diagnostics(
+ r#"
+pub const ONE: usize = 1;
+
+pub struct Inner<const P: usize>();
+
+pub struct Outer {
+ pub inner: Inner<ONE>,
+}
+
+fn main() {
+ _ = Outer {
+ inner: Inner::<2>(),
+ //^^^^^^^^^^^^ error: expected Inner<1>, found Inner<2>
+ };
+}
"#,
);
}
@@ -617,12 +691,49 @@ fn f() -> i32 {
r#"
fn f() {
let &() = &mut ();
+ //^^^ error: expected &mut (), found &()
match &() {
+ // FIXME: we should only show the deep one.
&9 => ()
+ //^^ error: expected &(), found &i32
//^ error: expected (), found i32
}
}
"#,
);
}
+
+ #[test]
+ fn regression_14768() {
+ check_diagnostics(
+ r#"
+//- minicore: derive, fmt, slice, coerce_unsized, builtin_impls
+use core::fmt::Debug;
+
+#[derive(Debug)]
+struct Foo(u8, u16, [u8]);
+
+#[derive(Debug)]
+struct Bar {
+ f1: u8,
+ f2: &[u16],
+ f3: dyn Debug,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn return_no_value() {
+ check_diagnostics(
+ r#"
+fn f() -> i32 {
+ return;
+ // ^^^^^^ error: expected i32, found ()
+ 0
+}
+fn g() { return; }
+"#,
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs
new file mode 100644
index 000000000..e12bbcf68
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -0,0 +1,232 @@
+use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, StructKind};
+use ide_db::{
+ assists::{Assist, AssistId, AssistKind, GroupLabel},
+ label::Label,
+ source_change::SourceChange,
+};
+use syntax::AstNode;
+use text_edit::TextEdit;
+
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: typed-hole
+//
+// This diagnostic is triggered when an underscore expression is used in an invalid position.
+pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Diagnostic {
+ let display_range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into()));
+ let (message, fixes) = if d.expected.is_unknown() {
+ ("`_` expressions may only appear on the left-hand side of an assignment".to_owned(), None)
+ } else {
+ (
+ format!(
+ "invalid `_` expression, expected type `{}`",
+ d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
+ ),
+ fixes(ctx, d),
+ )
+ };
+
+ Diagnostic::new("typed-hole", message, display_range.range).with_fixes(fixes)
+}
+
+fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>> {
+ let db = ctx.sema.db;
+ let root = db.parse_or_expand(d.expr.file_id);
+ let original_range =
+ d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?;
+ let scope = ctx.sema.scope(d.expr.value.to_node(&root).syntax())?;
+ let mut assists = vec![];
+ scope.process_all_names(&mut |name, def| {
+ let ty = match def {
+ hir::ScopeDef::ModuleDef(it) => match it {
+ hir::ModuleDef::Function(it) => it.ty(db),
+ hir::ModuleDef::Adt(hir::Adt::Struct(it)) if it.kind(db) != StructKind::Record => {
+ it.constructor_ty(db)
+ }
+ hir::ModuleDef::Variant(it) if it.kind(db) != StructKind::Record => {
+ it.constructor_ty(db)
+ }
+ hir::ModuleDef::Const(it) => it.ty(db),
+ hir::ModuleDef::Static(it) => it.ty(db),
+ _ => return,
+ },
+ hir::ScopeDef::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
+ hir::ScopeDef::Local(it) => it.ty(db),
+ _ => return,
+ };
+ // FIXME: should also check coercions if it is at a coercion site
+ if !ty.contains_unknown() && ty.could_unify_with(db, &d.expected) {
+ assists.push(Assist {
+ id: AssistId("typed-hole", AssistKind::QuickFix),
+ label: Label::new(format!("Replace `_` with `{}`", name.display(db))),
+ group: Some(GroupLabel("Replace `_` with a matching entity in scope".to_owned())),
+ target: original_range.range,
+ source_change: Some(SourceChange::from_text_edit(
+ original_range.file_id,
+ TextEdit::replace(original_range.range, name.display(db).to_string()),
+ )),
+ trigger_signature_help: false,
+ });
+ }
+ });
+ if assists.is_empty() {
+ None
+ } else {
+ Some(assists)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_diagnostics, check_fixes};
+
+ #[test]
+ fn unknown() {
+ check_diagnostics(
+ r#"
+fn main() {
+ _;
+ //^ error: `_` expressions may only appear on the left-hand side of an assignment
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn concrete_expectation() {
+ check_diagnostics(
+ r#"
+fn main() {
+ if _ {}
+ //^ error: invalid `_` expression, expected type `bool`
+ let _: fn() -> i32 = _;
+ //^ error: invalid `_` expression, expected type `fn() -> i32`
+ let _: fn() -> () = _; // FIXME: This should trigger an assist because `main` matches via *coercion*
+ //^ error: invalid `_` expression, expected type `fn()`
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn integer_ty_var() {
+ check_diagnostics(
+ r#"
+fn main() {
+ let mut x = 3;
+ x = _;
+ //^ 💡 error: invalid `_` expression, expected type `i32`
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ty_var_resolved() {
+ check_diagnostics(
+ r#"
+fn main() {
+ let mut x = t();
+ x = _;
+ //^ 💡 error: invalid `_` expression, expected type `&str`
+ x = "";
+}
+fn t<T>() -> T { loop {} }
+"#,
+ );
+ }
+
+ #[test]
+ fn valid_positions() {
+ check_diagnostics(
+ r#"
+fn main() {
+ let x = [(); _];
+ let y: [(); 10] = [(); _];
+ _ = 0;
+ (_,) = (1,);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn check_quick_fix() {
+ check_fixes(
+ r#"
+enum Foo {
+ Bar
+}
+use Foo::Bar;
+const C: Foo = Foo::Bar;
+fn main<const CP: Foo>(param: Foo) {
+ let local = Foo::Bar;
+ let _: Foo = _$0;
+ //^ error: invalid `_` expression, expected type `fn()`
+}
+"#,
+ vec![
+ r#"
+enum Foo {
+ Bar
+}
+use Foo::Bar;
+const C: Foo = Foo::Bar;
+fn main<const CP: Foo>(param: Foo) {
+ let local = Foo::Bar;
+ let _: Foo = local;
+ //^ error: invalid `_` expression, expected type `fn()`
+}
+"#,
+ r#"
+enum Foo {
+ Bar
+}
+use Foo::Bar;
+const C: Foo = Foo::Bar;
+fn main<const CP: Foo>(param: Foo) {
+ let local = Foo::Bar;
+ let _: Foo = param;
+ //^ error: invalid `_` expression, expected type `fn()`
+}
+"#,
+ r#"
+enum Foo {
+ Bar
+}
+use Foo::Bar;
+const C: Foo = Foo::Bar;
+fn main<const CP: Foo>(param: Foo) {
+ let local = Foo::Bar;
+ let _: Foo = CP;
+ //^ error: invalid `_` expression, expected type `fn()`
+}
+"#,
+ r#"
+enum Foo {
+ Bar
+}
+use Foo::Bar;
+const C: Foo = Foo::Bar;
+fn main<const CP: Foo>(param: Foo) {
+ let local = Foo::Bar;
+ let _: Foo = Bar;
+ //^ error: invalid `_` expression, expected type `fn()`
+}
+"#,
+ r#"
+enum Foo {
+ Bar
+}
+use Foo::Bar;
+const C: Foo = Foo::Bar;
+fn main<const CP: Foo>(param: Foo) {
+ let local = Foo::Bar;
+ let _: Foo = C;
+ //^ error: invalid `_` expression, expected type `fn()`
+}
+"#,
+ ],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs
new file mode 100644
index 000000000..034e4fcfb
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs
@@ -0,0 +1,88 @@
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: undeclared-label
+pub(crate) fn undeclared_label(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::UndeclaredLabel,
+) -> Diagnostic {
+ let name = &d.name;
+ Diagnostic::new(
+ "undeclared-label",
+ format!("use of undeclared label `{}`", name.display(ctx.sema.db)),
+ ctx.sema.diagnostics_display_range(d.node.clone().map(|it| it.into())).range,
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn smoke_test() {
+ check_diagnostics(
+ r#"
+fn foo() {
+ break 'a;
+ //^^^^^^^^ error: break outside of loop
+ //^^ error: use of undeclared label `'a`
+ continue 'a;
+ //^^^^^^^^^^^ error: continue outside of loop
+ //^^ error: use of undeclared label `'a`
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn for_loop() {
+ check_diagnostics(
+ r#"
+//- minicore: iterator
+fn foo() {
+ 'xxx: for _ in unknown {
+ 'yyy: for _ in unknown {
+ break 'xxx;
+ continue 'yyy;
+ break 'zzz;
+ //^^^^ error: use of undeclared label `'zzz`
+ }
+ continue 'xxx;
+ continue 'yyy;
+ //^^^^ error: use of undeclared label `'yyy`
+ break 'xxx;
+ break 'yyy;
+ //^^^^ error: use of undeclared label `'yyy`
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_operator_desugar_works() {
+ check_diagnostics(
+ r#"
+//- minicore: option, try
+fn foo() {
+ None?;
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+//- minicore: option, try, future
+async fn foo() {
+ None?;
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+//- minicore: option, try, future, fn
+async fn foo() {
+ || None?;
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs
index 3d45a7591..271e7ce73 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs
@@ -2,7 +2,7 @@
use std::iter;
-use hir::{db::DefDatabase, InFile, ModuleSource};
+use hir::{db::DefDatabase, DefMap, InFile, ModuleSource};
use ide_db::{
base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt},
source_change::SourceChange,
@@ -10,7 +10,7 @@ use ide_db::{
};
use syntax::{
ast::{self, edit::IndentLevel, HasModuleItem, HasName},
- AstNode, TextRange, TextSize,
+ AstNode, TextRange,
};
use text_edit::TextEdit;
@@ -27,14 +27,28 @@ pub(crate) fn unlinked_file(
) {
// Limit diagnostic to the first few characters in the file. This matches how VS Code
// renders it with the full span, but on other editors, and is less invasive.
+ let fixes = fixes(ctx, file_id);
+ // FIXME: This is a hack for the vscode extension to notice whether there is an autofix or not before having to resolve diagnostics.
+ // This is to prevent project linking popups from appearing when there is an autofix. https://github.com/rust-lang/rust-analyzer/issues/14523
+ let message = if fixes.is_none() {
+ "file not included in crate hierarchy"
+ } else {
+ "file not included in module tree"
+ };
+
let range = ctx.sema.db.parse(file_id).syntax_node().text_range();
- // FIXME: This is wrong if one of the first three characters is not ascii: `//Ы`.
- let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
+ let range = FileLoader::file_text(ctx.sema.db, file_id)
+ .char_indices()
+ .take(3)
+ .last()
+ .map(|(i, _)| i)
+ .map(|i| TextRange::up_to(i.try_into().unwrap()))
+ .unwrap_or(range);
acc.push(
- Diagnostic::new("unlinked-file", "file not included in module tree", range)
+ Diagnostic::new("unlinked-file", message, range)
.severity(Severity::WeakWarning)
- .with_fixes(fixes(ctx, file_id)),
+ .with_fixes(fixes),
);
}
@@ -60,7 +74,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option<Vec<Assist>> {
'crates: for &krate in &*ctx.sema.db.relevant_crates(file_id) {
let crate_def_map = ctx.sema.db.crate_def_map(krate);
- let root_module = &crate_def_map[crate_def_map.root()];
+ let root_module = &crate_def_map[DefMap::ROOT];
let Some(root_file_id) = root_module.origin.file_id() else { continue };
let Some(crate_root_path) = source_root.path_for_file(&root_file_id) else { continue };
let Some(rel) = parent.strip_prefix(&crate_root_path.parent()?) else { continue };
@@ -92,7 +106,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option<Vec<Assist>> {
// if we aren't adding to a crate root, walk backwards such that we support `#[path = ...]` overrides if possible
// build all parent paths of the form `../module_name/mod.rs` and `../module_name.rs`
- let paths = iter::successors(Some(parent.clone()), |prev| prev.parent()).filter_map(|path| {
+ let paths = iter::successors(Some(parent), |prev| prev.parent()).filter_map(|path| {
let parent = path.parent()?;
let (name, _) = path.name_and_extension()?;
Some(([parent.join(&format!("{name}.rs"))?, path.join("mod.rs")?], name.to_owned()))
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs
new file mode 100644
index 000000000..9fedadeae
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs
@@ -0,0 +1,91 @@
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: unreachable-label
+pub(crate) fn unreachable_label(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::UnreachableLabel,
+) -> Diagnostic {
+ let name = &d.name;
+ Diagnostic::new(
+ "unreachable-label",
+ format!("use of unreachable label `{}`", name.display(ctx.sema.db)),
+ ctx.sema.diagnostics_display_range(d.node.clone().map(|it| it.into())).range,
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn async_blocks_are_borders() {
+ check_diagnostics(
+ r#"
+fn foo() {
+ 'a: loop {
+ async {
+ break 'a;
+ //^^^^^^^^ error: break outside of loop
+ // ^^ error: use of unreachable label `'a`
+ continue 'a;
+ //^^^^^^^^^^^ error: continue outside of loop
+ // ^^ error: use of unreachable label `'a`
+ };
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closures_are_borders() {
+ check_diagnostics(
+ r#"
+fn foo() {
+ 'a: loop {
+ || {
+ break 'a;
+ //^^^^^^^^ error: break outside of loop
+ // ^^ error: use of unreachable label `'a`
+ continue 'a;
+ //^^^^^^^^^^^ error: continue outside of loop
+ // ^^ error: use of unreachable label `'a`
+ };
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn blocks_pass_through() {
+ check_diagnostics(
+ r#"
+fn foo() {
+ 'a: loop {
+ {
+ break 'a;
+ continue 'a;
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_blocks_pass_through() {
+ check_diagnostics(
+ r#"
+fn foo() {
+ 'a: loop {
+ try {
+ break 'a;
+ continue 'a;
+ };
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs
index cefa74e52..5e4efa41f 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs
@@ -26,7 +26,7 @@ pub(crate) fn unresolved_field(
"unresolved-field",
format!(
"no field `{}` on type `{}`{method_suffix}",
- d.name,
+ d.name.display(ctx.sema.db),
d.receiver.display(ctx.sema.db)
),
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
@@ -45,12 +45,12 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedField) -> Option<Vec<A
}
}
-// FIXME: We should fill out the call here, mvoe the cursor and trigger signature help
+// FIXME: We should fill out the call here, move the cursor and trigger signature help
fn method_fix(
ctx: &DiagnosticsContext<'_>,
expr_ptr: &InFile<AstPtr<ast::Expr>>,
) -> Option<Vec<Assist>> {
- let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?;
+ let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
let expr = expr_ptr.value.to_node(&root);
let FileRange { range, file_id } = ctx.sema.original_range_opt(expr.syntax())?;
Some(vec![Assist {
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
index 1a5efff2c..3943b51ab 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
@@ -13,7 +13,7 @@ pub(crate) fn unresolved_macro_call(
let bang = if d.is_bang { "!" } else { "" };
Diagnostic::new(
"unresolved-macro-call",
- format!("unresolved macro `{}{bang}`", d.path),
+ format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db)),
display_range,
)
.experimental()
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs
index f3ec6efa7..8bbb837e6 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs
@@ -26,7 +26,7 @@ pub(crate) fn unresolved_method(
"unresolved-method",
format!(
"no method `{}` on type `{}`{field_suffix}",
- d.name,
+ d.name.display(ctx.sema.db),
d.receiver.display(ctx.sema.db)
),
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
@@ -53,7 +53,7 @@ fn field_fix(
return None;
}
let expr_ptr = &d.expr;
- let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?;
+ let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
let expr = expr_ptr.value.to_node(&root);
let (file_id, range) = match expr {
ast::Expr::MethodCallExpr(mcall) => {
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs
index 94614f11c..6e3fd3b42 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs
@@ -31,7 +31,7 @@ pub(crate) fn unresolved_module(
}
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedModule) -> Option<Vec<Assist>> {
- let root = ctx.sema.db.parse_or_expand(d.decl.file_id)?;
+ let root = ctx.sema.db.parse_or_expand(d.decl.file_id);
let unresolved_module = d.decl.value.to_node(&root);
Some(
d.candidates
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
index 9a984ba6b..ae5cf1358 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
@@ -25,25 +25,21 @@ pub(crate) fn unresolved_proc_macro(
_ => proc_macros_enabled,
};
- let message = match &d.macro_name {
+ let not_expanded_message = match &d.macro_name {
Some(name) => format!("proc macro `{name}` not expanded"),
None => "proc macro not expanded".to_string(),
};
let severity = if config_enabled { Severity::Error } else { Severity::WeakWarning };
let def_map = ctx.sema.db.crate_def_map(d.krate);
- let message = format!(
- "{message}: {}",
- if config_enabled {
- def_map.proc_macro_loading_error().unwrap_or("proc macro not found in the built dylib")
- } else {
- match d.kind {
- hir::MacroKind::Attr if proc_macros_enabled => {
- "attribute macro expansion is disabled"
- }
- _ => "proc-macro expansion is disabled",
- }
- },
- );
+ let message = if config_enabled {
+ def_map.proc_macro_loading_error().unwrap_or("proc macro not found in the built dylib")
+ } else {
+ match d.kind {
+ hir::MacroKind::Attr if proc_macros_enabled => "attribute macro expansion is disabled",
+ _ => "proc-macro expansion is disabled",
+ }
+ };
+ let message = format!("{not_expanded_message}: {message}");
Diagnostic::new("unresolved-proc-macro", message, display_range).severity(severity)
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
index 71f136b8c..55a4a482d 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
@@ -38,11 +38,13 @@ mod handlers {
pub(crate) mod missing_fields;
pub(crate) mod missing_match_arms;
pub(crate) mod missing_unsafe;
+ pub(crate) mod moved_out_of_ref;
pub(crate) mod mutability_errors;
pub(crate) mod no_such_field;
pub(crate) mod private_assoc_item;
pub(crate) mod private_field;
pub(crate) mod replace_filter_map_next_with_find_map;
+ pub(crate) mod typed_hole;
pub(crate) mod type_mismatch;
pub(crate) mod unimplemented_builtin_macro;
pub(crate) mod unresolved_extern_crate;
@@ -52,6 +54,8 @@ mod handlers {
pub(crate) mod unresolved_macro_call;
pub(crate) mod unresolved_module;
pub(crate) mod unresolved_proc_macro;
+ pub(crate) mod undeclared_label;
+ pub(crate) mod unreachable_label;
// The handlers below are unusual, the implement the diagnostics as well.
pub(crate) mod field_shorthand;
@@ -74,6 +78,7 @@ use ide_db::{
};
use syntax::{algo::find_node_at_range, ast::AstNode, SyntaxNodePtr, TextRange};
+// FIXME: Make this an enum
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DiagnosticCode(pub &'static str);
@@ -198,7 +203,7 @@ impl<'a> DiagnosticsContext<'a> {
let sema = &self.sema;
(|| {
let precise_location = precise_location?;
- let root = sema.parse_or_expand(node.file_id)?;
+ let root = sema.parse_or_expand(node.file_id);
match root.covering_element(precise_location) {
syntax::NodeOrToken::Node(it) => Some(sema.original_range(&it)),
syntax::NodeOrToken::Token(it) => {
@@ -246,42 +251,60 @@ pub fn diagnostics(
let mut diags = Vec::new();
if let Some(m) = module {
- m.diagnostics(db, &mut diags)
+ m.diagnostics(db, &mut diags);
}
for diag in diags {
#[rustfmt::skip]
let d = match diag {
- AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
AnyDiagnostic::ExpectedFunction(d) => handlers::expected_function::expected_function(&ctx, &d),
- AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d),
+ AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
+ Some(it) => it,
+ None => continue,
+ }
AnyDiagnostic::IncoherentImpl(d) => handlers::incoherent_impl::incoherent_impl(&ctx, &d),
+ AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d),
+ AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
+ AnyDiagnostic::MacroDefError(d) => handlers::macro_error::macro_def_error(&ctx, &d),
AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
+ AnyDiagnostic::MacroExpansionParseError(d) => {
+ res.extend(d.errors.iter().take(32).map(|err| {
+ {
+ Diagnostic::new(
+ "syntax-error",
+ format!("Syntax Error in Expansion: {err}"),
+ ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
+ )
+ }
+ .experimental()
+ }));
+ continue;
+ },
AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d),
AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d),
AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d),
AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d),
AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
+ AnyDiagnostic::MovedOutOfRef(d) => handlers::moved_out_of_ref::moved_out_of_ref(&ctx, &d),
+ AnyDiagnostic::NeedMut(d) => handlers::mutability_errors::need_mut(&ctx, &d),
AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
AnyDiagnostic::PrivateAssocItem(d) => handlers::private_assoc_item::private_assoc_item(&ctx, &d),
AnyDiagnostic::PrivateField(d) => handlers::private_field::private_field(&ctx, &d),
AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),
+ AnyDiagnostic::TypedHole(d) => handlers::typed_hole::typed_hole(&ctx, &d),
AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d),
+ AnyDiagnostic::UndeclaredLabel(d) => handlers::undeclared_label::undeclared_label(&ctx, &d),
AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
+ AnyDiagnostic::UnreachableLabel(d) => handlers::unreachable_label:: unreachable_label(&ctx, &d),
AnyDiagnostic::UnresolvedExternCrate(d) => handlers::unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
+ AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d),
AnyDiagnostic::UnresolvedImport(d) => handlers::unresolved_import::unresolved_import(&ctx, &d),
AnyDiagnostic::UnresolvedMacroCall(d) => handlers::unresolved_macro_call::unresolved_macro_call(&ctx, &d),
+ AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d),
AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d),
AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled),
- AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
- AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d),
- AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d),
- AnyDiagnostic::NeedMut(d) => handlers::mutability_errors::need_mut(&ctx, &d),
AnyDiagnostic::UnusedMut(d) => handlers::mutability_errors::unused_mut(&ctx, &d),
- AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
- Some(it) => it,
- None => continue,
- }
+ AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
};
res.push(d)
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
index afa641c73..b5cd4e0d6 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
@@ -8,7 +8,7 @@ use ide_db::{
RootDatabase,
};
use stdx::trim_indent;
-use test_utils::{assert_eq_text, extract_annotations};
+use test_utils::{assert_eq_text, extract_annotations, MiniCore};
use crate::{DiagnosticsConfig, ExprFillDefaultMode, Severity};
@@ -121,6 +121,15 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
})
.collect::<Vec<_>>();
actual.sort_by_key(|(range, _)| range.start());
+ if expected.is_empty() {
+ // makes minicore smoke test debugable
+ for (e, _) in &actual {
+ eprintln!(
+ "Code in range {e:?} = {}",
+ &db.file_text(file_id)[usize::from(e.start())..usize::from(e.end())]
+ )
+ }
+ }
assert_eq!(expected, actual);
}
}
@@ -143,3 +152,28 @@ fn test_disabled_diagnostics() {
);
assert!(!diagnostics.is_empty());
}
+
+#[test]
+fn minicore_smoke_test() {
+ fn check(minicore: MiniCore) {
+ let source = minicore.source_code();
+ let mut config = DiagnosticsConfig::test_sample();
+ // This should be ignored since we conditionaly remove code which creates single item use with braces
+ config.disabled.insert("unnecessary-braces".to_string());
+ check_diagnostics_with_config(config, &source);
+ }
+
+ // Checks that there is no diagnostic in minicore for each flag.
+ for flag in MiniCore::available_flags() {
+ if flag == "clone" {
+ // Clone without copy has `moved-out-of-ref`, so ignoring.
+ // FIXME: Maybe we should merge copy and clone in a single flag?
+ continue;
+ }
+ eprintln!("Checking minicore flag {flag}");
+ check(MiniCore::from_flags([flag]));
+ }
+ // And one time for all flags, to check codes which are behind multiple flags + prevent name collisions
+ eprintln!("Checking all minicore flags");
+ check(MiniCore::from_flags(MiniCore::available_flags()))
+}