#![feature(unsize, coerce_unsized)]

use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::ptr;

fn test_basic() {
    #[repr(packed)]
    struct S {
        fill: u8,
        a: i32,
        b: i64,
    }

    #[repr(packed)]
    #[allow(dead_code)]
    struct Test1<'a> {
        x: u8,
        other: &'a u32,
    }

    #[repr(packed)]
    #[allow(dead_code)]
    struct Test2<'a> {
        x: u8,
        other: &'a Test1<'a>,
    }

    fn test(t: Test2) {
        let x = *t.other.other;
        assert_eq!(x, 42);
    }

    let mut x = S { fill: 0, a: 42, b: 99 };
    let a = x.a;
    let b = x.b;
    assert_eq!(a, 42);
    assert_eq!(b, 99);
    assert_eq!(&x.fill, &0); // `fill` just requirs 1-byte-align, so this is fine
    // can't do `assert_eq!(x.a, 42)`, because `assert_eq!` takes a reference
    assert_eq!({ x.a }, 42);
    assert_eq!({ x.b }, 99);
    // but we *can* take a raw pointer!
    assert_eq!(unsafe { ptr::addr_of!(x.a).read_unaligned() }, 42);
    assert_eq!(unsafe { ptr::addr_of!(x.b).read_unaligned() }, 99);

    x.b = 77;
    assert_eq!({ x.b }, 77);

    test(Test2 { x: 0, other: &Test1 { x: 0, other: &42 } });
}

fn test_unsizing() {
    #[repr(packed)]
    #[allow(dead_code)]
    struct UnalignedPtr<'a, T: ?Sized>
    where
        T: 'a,
    {
        data: &'a T,
    }

    impl<'a, T, U> std::ops::CoerceUnsized<UnalignedPtr<'a, U>> for UnalignedPtr<'a, T>
    where
        T: std::marker::Unsize<U> + ?Sized,
        U: ?Sized,
    {
    }

    let arr = [1, 2, 3];
    let arr_unaligned: UnalignedPtr<[i32; 3]> = UnalignedPtr { data: &arr };
    let arr_unaligned: UnalignedPtr<[i32]> = arr_unaligned;
    let _unused = &arr_unaligned; // forcing an allocation, which could also yield "unaligned write"-errors
}

fn test_drop() {
    struct Wrap(u32);
    impl Drop for Wrap {
        fn drop(&mut self) {
            // Do an (aligned) load
            let _test = self.0;
            // For the fun of it, test alignment
            assert_eq!(&self.0 as *const _ as usize % std::mem::align_of::<u32>(), 0);
        }
    }

    #[repr(packed, C)]
    struct Packed<T> {
        f1: u8, // this should move the second field to something not very aligned
        f2: T,
    }

    let p = Packed { f1: 42, f2: Wrap(23) };
    drop(p);
}

fn test_inner_packed() {
    // Even if just the inner struct is packed, accesses to the outer field can get unaligned.
    // Make sure that works.
    #[repr(packed)]
    #[derive(Clone, Copy)]
    struct Inner(u32);

    #[derive(Clone, Copy)]
    struct Outer(u8, Inner);

    let o = Outer(0, Inner(42));
    let _x = o.1;
    let _y = (o.1).0;
    let _o2 = o.clone();
}

fn test_static() {
    #[repr(packed)]
    struct Foo {
        i: i32,
    }

    static FOO: Foo = Foo { i: 42 };

    assert_eq!({ FOO.i }, 42);
}

fn test_derive() {
    #[repr(packed)]
    #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
    struct P {
        a: usize,
        b: u8,
        c: usize,
    }

    let x = P { a: 1usize, b: 2u8, c: 3usize };
    let y = P { a: 1usize, b: 2u8, c: 4usize };

    let _clone = x.clone();
    assert!(x != y);
    assert_eq!(x.partial_cmp(&y).unwrap(), x.cmp(&y));
    x.hash(&mut DefaultHasher::new());
    P::default();
    format!("{:?}", x);
}

fn main() {
    test_basic();
    test_unsizing();
    test_drop();
    test_inner_packed();
    test_static();
    test_derive();
}