rust-lang / rust-lang/rust-clippy

clippy::question_mark lint suggestion ignores function call inside else block

Open
#11,532 1 comment 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.