From dc0db358abe19481e475e10c32149b53370f1a1c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 05:57:31 +0200 Subject: Merging upstream version 1.72.1+dfsg1. Signed-off-by: Daniel Baumann --- .../src/handlers/break_outside_of_loop.rs | 41 +- .../ide-diagnostics/src/handlers/incorrect_case.rs | 4 +- .../ide-diagnostics/src/handlers/macro_error.rs | 45 ++ .../ide-diagnostics/src/handlers/missing_fields.rs | 10 +- .../src/handlers/missing_match_arms.rs | 18 +- .../ide-diagnostics/src/handlers/missing_unsafe.rs | 7 +- .../src/handlers/moved_out_of_ref.rs | 175 ++++++++ .../src/handlers/mutability_errors.rs | 474 ++++++++++++++++++++- .../ide-diagnostics/src/handlers/no_such_field.rs | 4 +- .../src/handlers/private_assoc_item.rs | 6 +- .../ide-diagnostics/src/handlers/private_field.rs | 4 +- .../replace_filter_map_next_with_find_map.rs | 4 +- .../ide-diagnostics/src/handlers/type_mismatch.rs | 145 ++++++- .../ide-diagnostics/src/handlers/typed_hole.rs | 232 ++++++++++ .../src/handlers/undeclared_label.rs | 88 ++++ .../ide-diagnostics/src/handlers/unlinked_file.rs | 30 +- .../src/handlers/unreachable_label.rs | 91 ++++ .../src/handlers/unresolved_field.rs | 6 +- .../src/handlers/unresolved_macro_call.rs | 2 +- .../src/handlers/unresolved_method.rs | 4 +- .../src/handlers/unresolved_module.rs | 2 +- .../src/handlers/unresolved_proc_macro.rs | 24 +- .../crates/ide-diagnostics/src/lib.rs | 47 +- .../crates/ide-diagnostics/src/tests.rs | 36 +- 24 files changed, 1387 insertions(+), 112 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs create mode 100644 src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs create mode 100644 src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs create mode 100644 src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs (limited to 'src/tools/rust-analyzer/crates/ide-diagnostics') 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,14 +124,34 @@ 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> { - 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( @@ -209,6 +220,40 @@ macro_rules! outer { fn f() { outer!(); } //^^^^^^^^ error: leftover tokens +"#, + ) + } + + #[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 ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()), @@ -175,8 +175,10 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option 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, 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); +struct Y; + +fn consume(_: X) { + +} + +fn main() { + let a = &X(Y); + consume(*a); + //^^^^^^^^^^^ error: cannot move `X` 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); + +struct Y(T); + +fn g(x: &X) -> X { + *x +} + +fn h(x: &Y) -> Y { + // 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 @@ -348,6 +353,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( @@ -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( @@ -481,6 +512,38 @@ fn main() { f(x); } } +"#, + ); + 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( @@ -544,6 +607,28 @@ fn f(x: i32) { x = 5; //^^^^^ 💡 error: cannot mutate immutable variable `x` } +"#, + ); + 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` +} "#, ); } @@ -552,7 +637,7 @@ fn f(x: i32) { 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 @@ -564,9 +649,97 @@ 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 for Foo { + type Output = (i32, u8); + fn index(&self, index: usize) -> &(i32, u8) { + &(5, 2) + } +} +impl IndexMut 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) => (), + } } "#, ); @@ -631,6 +818,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); + +pub struct TreeNode { + pub depth: usize, + pub children: [Box; 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 { + inner: *mut T, +} +impl Box { + fn new(t: T) -> Self { + #[rustc_box] + Box::new(t) + } +} + +impl Deref for Box { + type Target = T; + + fn deref(&self) -> &T { + &**self + } +} + +impl DerefMut for Box { + 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 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> { - 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> { - 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::( - 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::(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>, acc: &mut Vec, ) -> 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>, acc: &mut Vec, ) -> 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); + +fn foo(x: X) {} +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(); + +pub struct Outer { + pub inner: Inner, +} + +fn main() { + _ = Outer { + inner: Inner::<2>(), + //^^^^^^^^^^^^ error: expected Inner<1>, found Inner<2> + }; +} "#, ); } @@ -617,11 +691,48 @@ 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> { + 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 { 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(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(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(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(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(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(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> { '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> { // 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, expr_ptr: &InFile>, ) -> 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 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> { - 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::>(); 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())) +} -- cgit v1.2.3