#![warn(clippy::borrow_interior_mutable_const)]
#![allow(clippy::declare_interior_mutable_const)]

// this file replicates its `declare` counterpart. Please see it for more discussions.

use std::borrow::Cow;
use std::cell::Cell;
use std::sync::atomic::{AtomicUsize, Ordering};

trait ConcreteTypes {
    const ATOMIC: AtomicUsize;
    const STRING: String;

    fn function() {
        let _ = &Self::ATOMIC; //~ ERROR interior mutable
        let _ = &Self::STRING;
    }
}

impl ConcreteTypes for u64 {
    const ATOMIC: AtomicUsize = AtomicUsize::new(9);
    const STRING: String = String::new();

    fn function() {
        // Lint this again since implementers can choose not to borrow it.
        let _ = &Self::ATOMIC; //~ ERROR interior mutable
        let _ = &Self::STRING;
    }
}

// a helper trait used below
trait ConstDefault {
    const DEFAULT: Self;
}

trait GenericTypes<T, U> {
    const TO_REMAIN_GENERIC: T;
    const TO_BE_CONCRETE: U;

    fn function() {
        let _ = &Self::TO_REMAIN_GENERIC;
    }
}

impl<T: ConstDefault> GenericTypes<T, AtomicUsize> for Vec<T> {
    const TO_REMAIN_GENERIC: T = T::DEFAULT;
    const TO_BE_CONCRETE: AtomicUsize = AtomicUsize::new(11);

    fn function() {
        let _ = &Self::TO_REMAIN_GENERIC;
        let _ = &Self::TO_BE_CONCRETE; //~ ERROR interior mutable
    }
}

// a helper type used below
pub struct Wrapper<T>(T);

trait AssocTypes {
    type ToBeFrozen;
    type ToBeUnfrozen;
    type ToBeGenericParam;

    const TO_BE_FROZEN: Self::ToBeFrozen;
    const TO_BE_UNFROZEN: Self::ToBeUnfrozen;
    const WRAPPED_TO_BE_UNFROZEN: Wrapper<Self::ToBeUnfrozen>;
    const WRAPPED_TO_BE_GENERIC_PARAM: Wrapper<Self::ToBeGenericParam>;

    fn function() {
        let _ = &Self::TO_BE_FROZEN;
        let _ = &Self::WRAPPED_TO_BE_UNFROZEN;
    }
}

impl<T: ConstDefault> AssocTypes for Vec<T> {
    type ToBeFrozen = u16;
    type ToBeUnfrozen = AtomicUsize;
    type ToBeGenericParam = T;

    const TO_BE_FROZEN: Self::ToBeFrozen = 12;
    const TO_BE_UNFROZEN: Self::ToBeUnfrozen = AtomicUsize::new(13);
    const WRAPPED_TO_BE_UNFROZEN: Wrapper<Self::ToBeUnfrozen> = Wrapper(AtomicUsize::new(14));
    const WRAPPED_TO_BE_GENERIC_PARAM: Wrapper<Self::ToBeGenericParam> = Wrapper(T::DEFAULT);

    fn function() {
        let _ = &Self::TO_BE_FROZEN;
        let _ = &Self::TO_BE_UNFROZEN; //~ ERROR interior mutable
        let _ = &Self::WRAPPED_TO_BE_UNFROZEN; //~ ERROR interior mutable
        let _ = &Self::WRAPPED_TO_BE_GENERIC_PARAM;
    }
}

// a helper trait used below
trait AssocTypesHelper {
    type NotToBeBounded;
    type ToBeBounded;

    const NOT_TO_BE_BOUNDED: Self::NotToBeBounded;
}

trait AssocTypesFromGenericParam<T>
where
    T: AssocTypesHelper<ToBeBounded = AtomicUsize>,
{
    const NOT_BOUNDED: T::NotToBeBounded;
    const BOUNDED: T::ToBeBounded;

    fn function() {
        let _ = &Self::NOT_BOUNDED;
        let _ = &Self::BOUNDED; //~ ERROR interior mutable
    }
}

impl<T> AssocTypesFromGenericParam<T> for Vec<T>
where
    T: AssocTypesHelper<ToBeBounded = AtomicUsize>,
{
    const NOT_BOUNDED: T::NotToBeBounded = T::NOT_TO_BE_BOUNDED;
    const BOUNDED: T::ToBeBounded = AtomicUsize::new(15);

    fn function() {
        let _ = &Self::NOT_BOUNDED;
        let _ = &Self::BOUNDED; //~ ERROR interior mutable
    }
}

trait SelfType: Sized {
    const SELF: Self;
    const WRAPPED_SELF: Option<Self>;

    fn function() {
        let _ = &Self::SELF;
        let _ = &Self::WRAPPED_SELF;
    }
}

impl SelfType for u64 {
    const SELF: Self = 16;
    const WRAPPED_SELF: Option<Self> = Some(20);

    fn function() {
        let _ = &Self::SELF;
        let _ = &Self::WRAPPED_SELF;
    }
}

impl SelfType for AtomicUsize {
    const SELF: Self = AtomicUsize::new(17);
    const WRAPPED_SELF: Option<Self> = Some(AtomicUsize::new(21));

    fn function() {
        let _ = &Self::SELF; //~ ERROR interior mutable
        let _ = &Self::WRAPPED_SELF; //~ ERROR interior mutable
    }
}

trait BothOfCellAndGeneric<T> {
    const DIRECT: Cell<T>;
    const INDIRECT: Cell<*const T>;

    fn function() {
        let _ = &Self::DIRECT;
        let _ = &Self::INDIRECT; //~ ERROR interior mutable
    }
}

impl<T: ConstDefault> BothOfCellAndGeneric<T> for Vec<T> {
    const DIRECT: Cell<T> = Cell::new(T::DEFAULT);
    const INDIRECT: Cell<*const T> = Cell::new(std::ptr::null());

    fn function() {
        let _ = &Self::DIRECT;
        let _ = &Self::INDIRECT; //~ ERROR interior mutable
    }
}

struct Local<T>(T);

impl<T> Local<T>
where
    T: ConstDefault + AssocTypesHelper<ToBeBounded = AtomicUsize>,
{
    const ATOMIC: AtomicUsize = AtomicUsize::new(18);
    const COW: Cow<'static, str> = Cow::Borrowed("tuvwxy");

    const GENERIC_TYPE: T = T::DEFAULT;

    const ASSOC_TYPE: T::NotToBeBounded = T::NOT_TO_BE_BOUNDED;
    const BOUNDED_ASSOC_TYPE: T::ToBeBounded = AtomicUsize::new(19);

    fn function() {
        let _ = &Self::ATOMIC; //~ ERROR interior mutable
        let _ = &Self::COW;
        let _ = &Self::GENERIC_TYPE;
        let _ = &Self::ASSOC_TYPE;
        let _ = &Self::BOUNDED_ASSOC_TYPE; //~ ERROR interior mutable
    }
}

fn main() {
    u64::ATOMIC.store(5, Ordering::SeqCst); //~ ERROR interior mutability
    assert_eq!(u64::ATOMIC.load(Ordering::SeqCst), 9); //~ ERROR interior mutability
}
