rust-lang / rust-lang/rust-clippy
clippy::question_mark lint suggestion ignores function call inside else block
Open
Nobody has claimed this yet.
C-bug
I-false-positive
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
Summary
The clippy::question_mark lint suggests I replace
let $x = if let Some(x) = ($x).into_other($resources) {
x
} else {
$(OtherTrait::drop($y, $resources);)*
return None;
};
with
let $x = ($x).into_other($resources)?;
This completely ignores the call to OtherTrait::drop and results in different code semantics.
Lint Name
clippy::question_mark
Reproducer
I tried this code:
struct Resources;
trait OtherTrait {
fn drop(self, resources: &mut Resources);
}
impl OtherTrait for u32 {
fn drop(self, _resources: &mut Resources) {
// put back resources
println!("Side effect!")
}
}
trait IntoOther {
type Other: OtherTrait;
fn into_other(self, resources: &mut Resources) -> Option<Self::Other>;
}
impl IntoOther for i32 {
type Other = u32;
fn into_other(self, _resources: &mut Resources) -> Option<Self::Other> {
// Take something from resources
if self == 2 {
None
} else {
Some(self as u32)
}
}
}
macro_rules! into_others {
// The main, public-facing pattern
($($x:ident),+ $(,)?; $resources:expr) => {
{
// Create and invoke an anonymous function so we can `return None` early.
(|| {
Some(
into_others![@impl $($x,)+; ; $resources]
)
})()
}
};
// Private pattern for n >= 2 elements left to convert.
(@impl $x:ident, $($xs:ident),+ $(,)?; $($y:ident,)*; $resources:expr) => {
{
let $x = if let Some(x) = ($x).into_other($resources) {
x
} else {
$(OtherTrait::drop($y, $resources);)*
return None;
};
// Continue by moving this $x from to-be-converted list to to-be-dropped-on-failure list.
into_others![@impl $($xs,)+; $($y,)* $x,; $resources]
}
};
// Private pattern for n = 1 element left to convert.
(@impl $x:ident $(,)?; $($y:ident,)*; $resources:expr) => {
{
let $x = if let Some(x) = ($x).into_other($resources) {
x
} else {
$(OtherTrait::drop($y, $resources);)*
return None;
};
// No more elements to convert. Return tuple of everything.
(
$($y,)*
$x,
)
}
};
}
fn main() {
let mut resources = Resources;
let foo = 1;
let bar = 2;
let baz = 3;
into_others!(foo, bar, baz; &mut resources);
}
I saw this happen:
warning: this block may be rewritten with the `?` operator
--> src/main.rs:49:22
|
49 | let $x = if let Some(x) = ($x).into_other($resources) {
| ______________________^
50 | | x
51 | | } else {
52 | | $(OtherTrait::drop($y, $resources);)*
53 | |
54 | | return None;
55 | | };
| |_____________^ help: replace it with: `($x).into_other($resources)?`
...
87 | into_others!(foo, bar; &mut resources);
| -------------------------------------- in this macro invocation
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
= note: `#[warn(clippy::question_mark)]` on by default
= note: this warning originates in the macro `into_others` (in Nightly builds, run with -Z macro-backtrace for more info)
I expected to see this happen:
Nothing.
Version
rustc 1.72.0 (5680fa18f 2023-08-23)
binary: rustc
commit-hash: 5680fa18feaa87f3ff04063800aec256c3d4b4be
commit-date: 2023-08-23
host: x86_64-apple-darwin
release: 1.72.0
LLVM version: 16.0.5
Additional Labels
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the clippy::question_mark lint using the provided macro reproducer. Verify that a suggested ? replacement does not discard calls in the else block, and consider the issue resolved when the lint no longer suggests this rewrite while preserving valid suggestions for equivalent blocks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100