rust-clippy/tests/ui/manual_let_else.rs
2024-03-10 01:23:28 +01:00

459 lines
11 KiB
Rust

#![feature(try_blocks)]
#![allow(unused_braces, unused_variables, dead_code)]
#![allow(
clippy::collapsible_else_if,
clippy::unused_unit,
clippy::let_unit_value,
clippy::match_single_binding,
clippy::never_loop,
clippy::needless_if,
clippy::diverging_sub_expression,
clippy::single_match,
clippy::manual_unwrap_or_default
)]
#![warn(clippy::manual_let_else)]
//@no-rustfix
enum Variant {
A(usize, usize),
B(usize),
C,
}
fn g() -> Option<()> {
None
}
fn main() {}
fn fire() {
let v = if let Some(v_some) = g() { v_some } else { return };
//~^ ERROR: this could be rewritten as `let...else`
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
return;
};
let v = if let Some(v) = g() {
//~^ ERROR: this could be rewritten as `let...else`
// Blocks around the identity should have no impact
{ { v } }
} else {
// Some computation should still make it fire
g();
return;
};
// continue and break diverge
loop {
let v = if let Some(v_some) = g() { v_some } else { continue };
//~^ ERROR: this could be rewritten as `let...else`
let v = if let Some(v_some) = g() { v_some } else { break };
//~^ ERROR: this could be rewritten as `let...else`
}
// panic also diverges
let v = if let Some(v_some) = g() { v_some } else { panic!() };
//~^ ERROR: this could be rewritten as `let...else`
// abort also diverges
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
std::process::abort()
};
// If whose two branches diverge also diverges
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
if true { return } else { panic!() }
};
// Diverging after an if still makes the block diverge:
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
if true {}
panic!();
};
// The final expression will need to be turned into a statement.
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
panic!();
()
};
// Even if the result is buried multiple expressions deep.
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
panic!();
if true {
match 0 {
0 => (),
_ => (),
}
} else {
panic!()
}
};
// Or if a break gives the value.
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
loop {
panic!();
break ();
}
};
// Even if the break is in a weird position.
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
'a: loop {
panic!();
loop {
match 0 {
0 if (return break 'a ()) => {},
_ => {},
}
}
}
};
// A match diverges if all branches diverge:
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
match 0 {
0 if true => panic!(),
_ => panic!(),
};
};
// An if's expression can cause divergence:
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
if panic!() {};
};
// An expression of a match can cause divergence:
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
match panic!() {
_ => {},
};
};
// Top level else if
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else if true {
return;
} else {
panic!("diverge");
};
// All match arms diverge
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
match (g(), g()) {
(Some(_), None) => return,
(None, Some(_)) => {
if true {
return;
} else {
panic!();
}
},
_ => return,
}
};
// Tuples supported for the declared variables
let (v, w) = if let Some(v_some) = g().map(|v| (v, 42)) {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
return;
};
// Tuples supported with multiple bindings
let (w, S { v }) = if let (Some(v_some), w_some) = (g().map(|_| S { v: 0 }), 0) {
//~^ ERROR: this could be rewritten as `let...else`
(w_some, v_some)
} else {
return;
};
// entirely inside macro lints
macro_rules! create_binding_if_some {
($n:ident, $e:expr) => {
let $n = if let Some(v) = $e { v } else { return };
};
}
create_binding_if_some!(w, g());
fn e() -> Variant {
Variant::A(0, 0)
}
let v = if let Variant::A(a, 0) = e() { a } else { return };
//~^ ERROR: this could be rewritten as `let...else`
// `mut v` is inserted into the pattern
let mut v = if let Variant::B(b) = e() { b } else { return };
//~^ ERROR: this could be rewritten as `let...else`
// Nesting works
let nested = Ok(Some(e()));
let v = if let Ok(Some(Variant::B(b))) | Err(Some(Variant::A(b, _))) = nested {
//~^ ERROR: this could be rewritten as `let...else`
b
} else {
return;
};
// dot dot works
let v = if let Variant::A(.., a) = e() { a } else { return };
//~^ ERROR: this could be rewritten as `let...else`
// () is preserved: a bit of an edge case but make sure it stays around
let w = if let (Some(v), ()) = (g(), ()) { v } else { return };
//~^ ERROR: this could be rewritten as `let...else`
// Tuple structs work
let w = if let Some(S { v: x }) = Some(S { v: 0 }) {
//~^ ERROR: this could be rewritten as `let...else`
x
} else {
return;
};
// Field init shorthand is suggested
let v = if let Some(S { v: x }) = Some(S { v: 0 }) {
//~^ ERROR: this could be rewritten as `let...else`
x
} else {
return;
};
// Multi-field structs also work
let (x, S { v }, w) = if let Some(U { v, w, x }) = None::<U<S<()>>> {
//~^ ERROR: this could be rewritten as `let...else`
(x, v, w)
} else {
return;
};
}
fn not_fire() {
let v = if let Some(v_some) = g() {
// Nothing returned. Should not fire.
} else {
return;
};
let w = 0;
let v = if let Some(v_some) = g() {
// Different variable than v_some. Should not fire.
w
} else {
return;
};
let v = if let Some(v_some) = g() {
// Computation in then clause. Should not fire.
g();
v_some
} else {
return;
};
let v = if let Some(v_some) = g() {
v_some
} else {
if false {
return;
}
// This doesn't diverge. Should not fire.
()
};
let v = if let Some(v_some) = g() {
v_some
} else {
// There is one match arm that doesn't diverge. Should not fire.
match (g(), g()) {
(Some(_), None) => return,
(None, Some(_)) => return,
(Some(_), Some(_)) => (),
_ => return,
}
};
let v = if let Some(v_some) = g() {
v_some
} else {
// loop with a break statement inside does not diverge.
loop {
break;
}
};
enum Uninhabited {}
fn un() -> Uninhabited {
panic!()
}
let v = if let Some(v_some) = None {
v_some
} else {
// Don't lint if the type is uninhabited but not !
un()
};
fn question_mark() -> Option<()> {
let v = if let Some(v) = g() {
v
} else {
// Question mark does not diverge
g()?
};
Some(v)
}
// Macro boundary inside let
macro_rules! some_or_return {
($e:expr) => {
if let Some(v) = $e { v } else { return }
};
}
let v = some_or_return!(g());
// Also macro boundary inside let, but inside a macro
macro_rules! create_binding_if_some_nf {
($n:ident, $e:expr) => {
let $n = some_or_return!($e);
};
}
create_binding_if_some_nf!(v, g());
// Already a let-else
let Some(a) = (if let Some(b) = Some(Some(())) { b } else { return }) else {
panic!()
};
// If a type annotation is present, don't lint as
// expressing the type might be too hard
let v: () = if let Some(v_some) = g() { v_some } else { panic!() };
// Issue 9940
// Suggestion should not expand macros
macro_rules! macro_call {
() => {
return ()
};
}
let ff = Some(1);
let _ = match ff {
//~^ ERROR: this could be rewritten as `let...else`
Some(value) => value,
_ => macro_call!(),
};
// Issue 10296
// The let/else block in the else part is not divergent despite the presence of return
let _x = if let Some(x) = Some(1) {
x
} else {
let Some(_z) = Some(3) else { return };
1
};
// This would require creation of a suggestion of the form
// let v @ (Some(_), _) = (...) else { return };
// Which is too advanced for our code, so we just bail.
let v = if let (Some(v_some), w_some) = (g(), 0) {
(w_some, v_some)
} else {
return;
};
// A break that skips the divergent statement will cause the expression to be non-divergent.
let _x = if let Some(x) = Some(0) {
x
} else {
'foo: loop {
break 'foo 0;
panic!();
}
};
// Even in inner loops.
let _x = if let Some(x) = Some(0) {
x
} else {
'foo: {
loop {
break 'foo 0;
}
panic!();
}
};
// But a break that can't ever be reached still affects divergence checking.
let _x = if let Some(x) = g() {
x
} else {
'foo: {
'bar: loop {
loop {
break 'bar ();
}
break 'foo ();
}
panic!();
};
};
}
struct S<T> {
v: T,
}
struct U<T> {
v: T,
w: T,
x: T,
}
fn issue12337() {
// We want to generally silence question_mark lints within try blocks, since `?` has different
// behavior to `return`, and question_mark calls into manual_let_else logic, so make sure that
// we still emit a lint for manual_let_else
let _: Option<()> = try {
let v = if let Some(v_some) = g() { v_some } else { return };
};
}