Add moved-out-of-ref diagnostic

This commit is contained in:
hkalbasi 2023-05-18 19:17:06 +03:30
parent 09d1265e1e
commit b55fbd3ad7
11 changed files with 352 additions and 10 deletions

View file

@ -1027,6 +1027,7 @@ fn main() {
check_diagnostics(
r#"
//- minicore: copy
fn main() {
match &false {
&true => {}
@ -1041,6 +1042,7 @@ fn main() {
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

View file

@ -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() {

View file

@ -0,0 +1,154 @@
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)();
}
"#,
);
}
}

View file

@ -340,6 +340,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

View file

@ -38,6 +38,7 @@ 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;
@ -283,6 +284,7 @@ pub fn diagnostics(
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),

View file

@ -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);
}
}
@ -156,6 +165,11 @@ fn minicore_smoke_test() {
// 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]));
}