//@needs-asm-support

#![allow(unused, clippy::needless_pass_by_value, clippy::collapsible_if)]
#![warn(clippy::map_entry)]

use std::arch::asm;
use std::collections::HashMap;
use std::hash::Hash;

macro_rules! m {
    ($e:expr) => {{ $e }};
}

macro_rules! insert {
    ($map:expr, $key:expr, $val:expr) => {
        $map.insert($key, $val)
    };
}

fn foo() {}

fn hash_map<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, m2: &mut HashMap<K, V>, k: K, k2: K, v: V, v2: V) {
    // or_insert(v)
    m.entry(k).or_insert(v);

    // semicolon on insert, use or_insert_with(..)
    m.entry(k).or_insert_with(|| {
        if true {
            v
        } else {
            v2
        }
    });

    // semicolon on if, use or_insert_with(..)
    m.entry(k).or_insert_with(|| {
        if true {
            v
        } else {
            v2
        }
    });

    // early return, use if let
    if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
        if true {
            e.insert(v);
        } else {
            e.insert(v2);
            return;
        }
    }

    // use or_insert_with(..)
    m.entry(k).or_insert_with(|| {
        foo();
        v
    });

    // semicolon on insert and match, use or_insert_with(..)
    m.entry(k).or_insert_with(|| {
        match 0 {
            1 if true => {
                v
            },
            _ => {
                v2
            },
        }
    });

    // one branch doesn't insert, use if let
    if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
        match 0 {
            0 => foo(),
            _ => {
                e.insert(v2);
            },
        };
    }

    // use or_insert_with
    m.entry(k).or_insert_with(|| {
        foo();
        match 0 {
            0 if false => {
                v
            },
            1 => {
                foo();
                v
            },
            2 | 3 => {
                for _ in 0..2 {
                    foo();
                }
                if true {
                    v
                } else {
                    v2
                }
            },
            _ => {
                v2
            },
        }
    });

    // ok, insert in loop
    if !m.contains_key(&k) {
        for _ in 0..2 {
            m.insert(k, v);
        }
    }

    // macro_expansion test, use or_insert(..)
    m.entry(m!(k)).or_insert_with(|| m!(v));

    // ok, map used before insertion
    if !m.contains_key(&k) {
        let _ = m.len();
        m.insert(k, v);
    }

    // ok, inline asm
    if !m.contains_key(&k) {
        unsafe { asm!("nop") }
        m.insert(k, v);
    }

    // ok, different keys.
    if !m.contains_key(&k) {
        m.insert(k2, v);
    }

    // ok, different maps
    if !m.contains_key(&k) {
        m2.insert(k, v);
    }

    // ok, insert in macro
    if !m.contains_key(&k) {
        insert!(m, k, v);
    }

    // or_insert_with. Partial move of a local declared in the closure is ok.
    m.entry(k).or_insert_with(|| {
        let x = (String::new(), String::new());
        let _ = x.0;
        v
    });
}

// Issue 10331
// do not suggest a bad expansion because the compiler unrolls the first
// occurrence of the loop
pub fn issue_10331() {
    let mut m = HashMap::new();
    let mut i = 0;
    let mut x = 0;
    while !m.contains_key(&x) {
        m.insert(x, i);
        i += 1;
        x += 1;
    }
}

/// Issue 11935
/// Do not suggest using entries if the map is used inside the `insert` expression.
pub fn issue_11935() {
    let mut counts: HashMap<u64, u64> = HashMap::new();
    if !counts.contains_key(&1) {
        counts.insert(1, 1);
    } else {
        counts.insert(1, counts.get(&1).unwrap() + 1);
    }
}

fn issue12489(map: &mut HashMap<u64, u64>) -> Option<()> {
    if let std::collections::hash_map::Entry::Vacant(e) = map.entry(1) {
        let Some(1) = Some(2) else {
            return None;
        };
        e.insert(42);
    }
    Some(())
}

fn main() {}