#![warn(rust_2018_idioms, single_use_lifetimes)]
#![allow(dead_code)]

// Refs: https://doc.rust-lang.org/reference/attributes.html

#[macro_use]
mod auxiliary;

use std::{marker::PhantomPinned, pin::Pin};

use pin_project::pin_project;

struct Always;

// Use this type to check that `cfg(any())` is working properly.
struct Never(PhantomPinned);

#[test]
fn cfg() {
    // structs

    #[pin_project(project_replace)]
    struct SameName {
        #[cfg(not(any()))]
        #[pin]
        inner: Always,
        #[cfg(any())]
        #[pin]
        inner: Never,
    }

    assert_unpin!(SameName);

    let _ = SameName { inner: Always };

    #[pin_project(project_replace)]
    struct DifferentName {
        #[cfg(not(any()))]
        #[pin]
        a: Always,
        #[cfg(any())]
        #[pin]
        n: Never,
    }

    assert_unpin!(DifferentName);

    let _ = DifferentName { a: Always };

    #[pin_project(project_replace)]
    struct TupleStruct(
        #[cfg(not(any()))]
        #[pin]
        Always,
        #[cfg(any())]
        #[pin]
        Never,
    );

    assert_unpin!(TupleStruct);

    let _ = TupleStruct(Always);

    // enums

    #[pin_project(
        project = VariantProj,
        project_ref = VariantProjRef,
        project_replace = VariantProjOwn,
    )]
    enum Variant {
        #[cfg(not(any()))]
        Inner(#[pin] Always),
        #[cfg(any())]
        Inner(#[pin] Never),

        #[cfg(not(any()))]
        A(#[pin] Always),
        #[cfg(any())]
        N(#[pin] Never),
    }

    assert_unpin!(Variant);

    let _ = Variant::Inner(Always);
    let _ = Variant::A(Always);

    #[pin_project(
        project = FieldProj,
        project_ref = FieldProjRef,
        project_replace = FieldProjOwn,
    )]
    enum Field {
        SameName {
            #[cfg(not(any()))]
            #[pin]
            inner: Always,
            #[cfg(any())]
            #[pin]
            inner: Never,
        },
        DifferentName {
            #[cfg(not(any()))]
            #[pin]
            a: Always,
            #[cfg(any())]
            #[pin]
            n: Never,
        },
        TupleVariant(
            #[cfg(not(any()))]
            #[pin]
            Always,
            #[cfg(any())]
            #[pin]
            Never,
        ),
    }

    assert_unpin!(Field);

    let _ = Field::SameName { inner: Always };
    let _ = Field::DifferentName { a: Always };
    let _ = Field::TupleVariant(Always);
}

#[test]
fn cfg_attr() {
    #[pin_project(project_replace)]
    struct SameCfg {
        #[cfg(not(any()))]
        #[cfg_attr(not(any()), pin)]
        inner: Always,
        #[cfg(any())]
        #[cfg_attr(any(), pin)]
        inner: Never,
    }

    assert_unpin!(SameCfg);

    let mut x = SameCfg { inner: Always };
    let x = Pin::new(&mut x).project();
    let _: Pin<&mut Always> = x.inner;

    #[pin_project(project_replace)]
    struct DifferentCfg {
        #[cfg(not(any()))]
        #[cfg_attr(any(), pin)]
        inner: Always,
        #[cfg(any())]
        #[cfg_attr(not(any()), pin)]
        inner: Never,
    }

    assert_unpin!(DifferentCfg);

    let mut x = DifferentCfg { inner: Always };
    let x = Pin::new(&mut x).project();
    let _: &mut Always = x.inner;

    #[cfg_attr(not(any()), pin_project)]
    struct Foo<T> {
        #[cfg_attr(not(any()), pin)]
        inner: T,
    }

    assert_unpin!(Foo<()>);
    assert_not_unpin!(Foo<PhantomPinned>);

    let mut x = Foo { inner: 0_u8 };
    let x = Pin::new(&mut x).project();
    let _: Pin<&mut u8> = x.inner;
}

#[test]
fn cfg_attr_any_packed() {
    // Since `cfg(any())` can never be true, it is okay for this to pass.
    #[pin_project(project_replace)]
    #[cfg_attr(any(), repr(packed))]
    struct Struct {
        #[pin]
        f: u32,
    }
}
