//@aux-build: proc_macros.rs

#![warn(clippy::indexing_slicing)]
// We also check the out_of_bounds_indexing lint here, because it lints similar things and
// we want to avoid false positives.
#![warn(clippy::out_of_bounds_indexing)]
#![allow(
    clippy::no_effect,
    clippy::unnecessary_operation,
    clippy::useless_vec,
    unused_must_use,
    unused
)]
#![warn(clippy::indexing_slicing)]

extern crate proc_macros;
use proc_macros::with_span;

use std::ops::Index;

struct BoolMap<T> {
    false_value: T,
    true_value: T,
}

impl<T> Index<bool> for BoolMap<T> {
    type Output = T;
    fn index(&self, index: bool) -> &T {
        if index { &self.true_value } else { &self.false_value }
    }
}

struct BoolMapWithGet<T> {
    false_value: T,
    true_value: T,
}

impl<T> Index<bool> for BoolMapWithGet<T> {
    type Output = T;
    fn index(&self, index: bool) -> &Self::Output {
        if index { &self.true_value } else { &self.false_value }
    }
}

impl<T> BoolMapWithGet<T> {
    fn get(&self, index: bool) -> Option<&T> {
        if index {
            Some(&self.true_value)
        } else {
            Some(&self.false_value)
        }
    }
}

struct S<T>(T);
impl S<i32> {
    fn get() -> Option<i32> {
        unimplemented!()
    }
}
impl<T> Index<i32> for S<T> {
    type Output = T;
    fn index(&self, _index: i32) -> &Self::Output {
        &self.0
    }
}

struct Y<T>(T);
impl Y<i32> {
    fn get<U>() -> Option<U> {
        unimplemented!()
    }
}
impl<T> Index<i32> for Y<T> {
    type Output = T;
    fn index(&self, _index: i32) -> &Self::Output {
        &self.0
    }
}

struct Z<T>(T);
impl<T> Z<T> {
    fn get<T2>() -> T2 {
        unimplemented!()
    }
}
impl<T> Index<i32> for Z<T> {
    type Output = T;
    fn index(&self, _index: i32) -> &Self::Output {
        &self.0
    }
}

with_span!(
    span

    fn dont_lint_proc_macro() {
        let x = [1, 2, 3, 4];
        let index: usize = 1;
        &x[index..];
        &x[..10];

        let x = vec![0; 5];
        let index: usize = 1;
        &x[index..];
        &x[..10];
    }
);

fn main() {
    let x = [1, 2, 3, 4];
    let index: usize = 1;
    let index_from: usize = 2;
    let index_to: usize = 3;
    &x[index..];
    //~^ indexing_slicing
    &x[..index];
    //~^ indexing_slicing
    &x[index_from..index_to];
    //~^ indexing_slicing
    &x[index_from..][..index_to];
    //~^ indexing_slicing
    //~| indexing_slicing
    &x[5..][..10];
    //~^ indexing_slicing
    //~| out_of_bounds_indexing
    &x[0..][..3];
    //~^ indexing_slicing
    &x[1..][..5];
    //~^ indexing_slicing

    &x[0..].get(..3); // Ok, should not produce stderr.
    &x[0..3]; // Ok, should not produce stderr.

    let y = &x;
    &y[1..2];
    &y[0..=4];
    //~^ out_of_bounds_indexing
    &y[..=4];
    //~^ out_of_bounds_indexing

    &y[..]; // Ok, should not produce stderr.

    let v = vec![0; 5];
    &v[10..100];
    //~^ indexing_slicing
    &x[10..][..100];
    //~^ indexing_slicing
    //~| out_of_bounds_indexing
    &v[10..];
    //~^ indexing_slicing
    &v[..100];
    //~^ indexing_slicing

    &v[..]; // Ok, should not produce stderr.

    let map = BoolMap {
        false_value: 2,
        true_value: 4,
    };

    map[true]; // Ok, because `get` does not exist (custom indexing)

    let map_with_get = BoolMapWithGet {
        false_value: 2,
        true_value: 4,
    };

    // Lint on this, because `get` does exist with same signature
    map_with_get[true];
    //~^ indexing_slicing

    let s = S::<i32>(1);
    s[0];
    //~^ indexing_slicing

    let y = Y::<i32>(1);
    y[0];
    //~^ indexing_slicing

    let z = Z::<i32>(1);
    z[0];
}
