rust-clippy icon indicating copy to clipboard operation
rust-clippy copied to clipboard

Clippy `contains_key` followed by `insert` suggestion won’t compile

Open musmuris opened this issue 2 years ago • 2 comments

Summary

From https://users.rust-lang.org/t/clippy-contains-key-followed-by-insert-suggestion-wont-compile/100243

Clippy's suggestion of replacement code doesn't compile

See Playground 1

Reproducer

I tried this code:

use std::collections::BTreeMap;

#[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Debug)]
struct Pos(usize, usize);

fn can_get_to(start: Pos, end: Pos) -> Option<u32> {
    if start == Pos(1,1) && end == Pos(2,2) {
        Some(5)
    } else {
        None
    }
}

fn main() {
    let mut distance: BTreeMap<Pos, u32> = BTreeMap::new();
   
    let posnear = Pos(1,1);
    let posfar = Pos(2,2);
    
    distance.insert(posnear, 10);
   
    if !distance.contains_key(&posfar) {
        if let Some(dist) = can_get_to(posnear, posfar) {
          distance.insert(posfar, distance[&posnear] + dist);
        }
    }
    
    // Clippy's suggestion for above - won't compile
    // if let std::collections::btree_map::Entry::Vacant(e) = distance.entry(posfar) {
    //     if let Some(dist) = can_get_to(posnear, posfar) {
    //         e.insert(distance[&posnear] + dist);
    //     }
    // }

    dbg!(distance);
}

And the compile error from Clippy's suggestion:

error[E0502]: cannot borrow `distance` as immutable because it is also borrowed as mutable
  --> src/main.rs:31:22
   |
29 |     if let std::collections::btree_map::Entry::Vacant(e) = distance.entry(posfar) {
   |                                                            ---------------------- mutable borrow occurs here
30 |         if let Some(dist) = can_get_to(posnear, posfar) {
31 |             e.insert(distance[&posnear] + dist);
   |               ------ ^^^^^^^^ immutable borrow occurs here
   |               |
   |               mutable borrow later used by call

Version

No response

Additional Labels

@rustbot label +I-suggestion-causes-error

musmuris avatar Sep 23 '23 21:09 musmuris

A somewhat easy fix for this specific case could be to check if the receiver of the .insert() call is a path to a local, then take that local ID and check if there is a path expression to that local anywhere in the insert argument, and if so, maybe just don't lint at all? But that approach won't cover all cases and fixing this generally probably requires looking at MIR/lifetimes, because the map can be accessed through borrowers

let mut map: BTreeMap<Pos, u32> = BTreeMap::new();
let map_ref = &map;
if !map.contains_key(&posfar) {
  map.insert(posfar, map_ref[&posnear] + dist);
}

Here map and map_ref refer to the same map (and will fail to compile with the suggestion applied), but they are still different locals, so that fix wouldn't catch it.

y21 avatar Oct 01 '23 13:10 y21

@rustbot claim

PartiallyUntyped avatar Feb 12 '24 12:02 PartiallyUntyped