blob: 894379ec577a62e7075a36195c603a5c0aa28440 [file]
#![feature(try_blocks)]
#![expect(
clippy::diverging_sub_expression,
clippy::let_unit_value,
clippy::match_single_binding,
clippy::needless_ifs,
clippy::never_loop,
clippy::unused_unit
)]
#![warn(clippy::manual_let_else)]
enum Variant {
A(usize, usize),
B(usize),
C,
}
fn g() -> Option<()> {
None
}
fn main() {}
fn fire() {
let Some(v) = g() else { return };
//~^ manual_let_else
let Some(v) = g() else {
return;
};
let Some(v) = g() else {
// Some computation should still make it fire
g();
return;
};
// continue and break diverge
loop {
let Some(v) = g() else { continue };
//~^ manual_let_else
let Some(v) = g() else { break };
//~^ manual_let_else
}
// panic also diverges
let Some(v) = g() else { panic!() };
//~^ manual_let_else
// abort also diverges
let Some(v) = g() else {
std::process::abort()
};
// If whose two branches diverge also diverges
let Some(v) = g() else {
if true { return } else { panic!() }
};
// Diverging after an if still makes the block diverge:
let Some(v) = g() else {
if true {}
panic!();
};
// A match diverges if all branches diverge:
let Some(v) = g() else {
match 0 {
0 if true => panic!(),
_ => panic!(),
};
};
// An if's expression can cause divergence:
let Some(v) = g() else {
if panic!() {};
};
// An expression of a match can cause divergence:
let Some(v) = g() else {
match panic!() {
_ => {},
};
};
// Top level else if
let Some(v) = g() else { if true {
return;
} else {
panic!("diverge");
} };
// All match arms diverge
let Some(v) = g() else {
match (g(), g()) {
(Some(_), None) => return,
(None, Some(_)) => {
if true {
return;
} else {
panic!();
}
},
_ => return,
}
};
// Tuples supported for the declared variables
let Some((v, w)) = g().map(|v| (v, 42)) else {
return;
};
// Tuples supported with multiple bindings
let (Some(S { v }), w) = (g().map(|_| S { v: 0 }), 0) else {
return;
};
// entirely inside macro lints
macro_rules! create_binding_if_some {
($n:ident, $e:expr) => {
let Some($n) = g() else { return };
//~^ manual_let_else
};
}
create_binding_if_some!(w, g());
fn e() -> Variant {
Variant::A(0, 0)
}
let Variant::A(v, 0) = e() else { return };
//~^ manual_let_else
// `mut v` is inserted into the pattern
let Variant::B(mut v) = e() else { return };
//~^ manual_let_else
// Nesting works
let nested = Ok(Some(e()));
let (Ok(Some(Variant::B(v))) | Err(Some(Variant::A(v, _)))) = nested else {
return;
};
// dot dot works
let Variant::A(.., v) = e() else { return };
//~^ manual_let_else
// () is preserved: a bit of an edge case but make sure it stays around
let (Some(w), ()) = (g(), ()) else { return };
//~^ manual_let_else
// Tuple structs work
let Some(S { v: w }) = Some(S { v: 0 }) else {
return;
};
// Field init shorthand is suggested
let Some(S { v }) = Some(S { v: 0 }) else {
return;
};
// Multi-field structs also work
let Some(U { v: S { v }, w, x }) = None::<U<S<()>>> 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 Some(_) = ff else { 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 Some(v) = g() else { return };
//~^ manual_let_else
};
}
mod issue13768 {
enum Foo {
Str(String),
None,
}
fn foo(value: Foo) {
let Foo::Str(ref signature) = value else {
println!("No signature found");
return;
};
}
enum Bar {
Str { inner: String },
None,
}
fn bar(mut value: Bar) {
let Bar::Str { inner: ref mut signature } = value else {
println!("No signature found");
return;
};
}
}
mod issue14598 {
fn bar() -> Result<bool, &'static str> {
let Ok(value) = foo() else { return Err("abc") };
let w = Some(0);
let Some(v) = w else { return Err("abc") };
enum Foo<T> {
Foo(T),
}
let v = match Foo::Foo(Some(())) {
Foo::Foo(Some(_)) => return Err("abc"),
Foo::Foo(v) => v,
};
Ok(value == 42)
}
fn foo() -> Result<u32, &'static str> {
todo!()
}
}
mod issue15914 {
// https://github.com/rust-lang/rust-clippy/issues/15914
unsafe fn something_unsafe() -> Option<u32> {
None
}
fn foo() {
let Some(value) = (unsafe { something_unsafe() }) else {
return;
};
let some_flag = true;
let Some(value) = (if some_flag { None } else { Some(3) }) else {
return;
};
}
}
fn issue16602(i: Result<i32, i32>) {
//~v manual_let_else
let Ok(_) = i else { unsafe {
core::hint::unreachable_unchecked();
} };;
//~v manual_let_else
let Ok(_) = i else { 'useless_label: {
panic!();
} };;
}