// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT OR Apache-2.0

/*!
This crate allow to get the offset of a field of a structure in a const or static context.

To be used re-exported from the `const_field_offset` crate

*/
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::{format_ident, quote, quote_spanned};
use syn::{DeriveInput, parse_macro_input, spanned::Spanned};
#[cfg(feature = "field-offset-trait")]
use syn::{VisRestricted, Visibility};

/**

The macro FieldOffsets adds a `FIELD_OFFSETS` associated const to the struct. That
is an object which has fields with the same name as the fields of the original struct,
each field is of type `const_field_offset::FieldOffset`

```rust
use const_field_offset::FieldOffsets;
#[repr(C)]
#[derive(FieldOffsets)]
struct Foo {
    field_1 : u8,
    field_2 : u32,
}

const FOO : usize = Foo::FIELD_OFFSETS.field_2.get_byte_offset();
assert_eq!(FOO, 4);

// This would not work on stable rust at the moment (rust 1.43)
// const FOO : usize = memoffsets::offsetof!(Foo, field_2);
```

*/
#[cfg_attr(
    feature = "field-offset-trait",
    doc = "
In addition, the macro also create a module `{ClassName}_field_offsets` which contains
zero-sized type that implement the `const_field_offset::ConstFieldOffset` trait

```rust
use const_field_offset::{FieldOffsets, FieldOffset, ConstFieldOffset};
#[repr(C)]
#[derive(FieldOffsets)]
struct Foo {
    field_1 : u8,
    field_2 : u32,
}

const FOO : FieldOffset<Foo, u32> = Foo_field_offsets::field_2::OFFSET;
assert_eq!(FOO.get_byte_offset(), 4);
```
"
)]
/**

## Limitations

Only work with named #[repr(C)] structures.

## Attributes

### `pin`

Add a `AllowPin` to the FieldOffset.

In order for this to be safe, the macro will add code to prevent a
custom `Drop` or `Unpin` implementation.

```rust
use const_field_offset::*;
#[repr(C)]
#[derive(FieldOffsets)]
#[pin]
struct Foo {
    field_1 : u8,
    field_2 : u32,
}

const FIELD_2 : FieldOffset<Foo, u32, AllowPin> = Foo::FIELD_OFFSETS.field_2;
let pin_box = Box::pin(Foo{field_1: 1, field_2: 2});
assert_eq!(*FIELD_2.apply_pin(pin_box.as_ref()), 2);
```

### `pin_drop`

This attribute works like the `pin` attribute but it does not prevent a custom
Drop implementation. Instead it provides a Drop implementation that forwards to
the [PinnedDrop](../const_field_offset/trait.PinnedDrop.html) trait that you need to implement for our type.

```rust
use const_field_offset::*;
use core::pin::Pin;

struct TypeThatRequiresSpecialDropHandling(); // ...

#[repr(C)]
#[derive(FieldOffsets)]
#[pin_drop]
struct Foo {
    field : TypeThatRequiresSpecialDropHandling,
}

impl PinnedDrop for Foo {
    fn drop(self: Pin<&mut Self>) {
        // Do you safe drop handling here
    }
}
```

### `const-field-offset`

In case the `const-field-offset` crate is re-exported, it is possible to
specify the crate name using the `const_field_offset` attribute.

```rust
// suppose you re-export the const_field_offset create from a different module
mod xxx { pub use const_field_offset as cfo; }
#[repr(C)]
#[derive(xxx::cfo::FieldOffsets)]
#[const_field_offset(xxx::cfo)]
struct Foo {
    field_1 : u8,
    field_2 : u32,
}
```

*/
#[proc_macro_derive(FieldOffsets, attributes(const_field_offset, pin, pin_drop))]
pub fn const_field_offset(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let mut has_repr_c = false;
    let mut crate_ = quote!(const_field_offset);
    let mut pin = false;
    let mut drop = false;
    for a in &input.attrs {
        if let Some(i) = a.path().get_ident() {
            if i == "repr" {
                let inner = a.parse_args::<syn::Ident>().map(|x| x.to_string());
                match inner.as_ref().map(|x| x.as_str()) {
                    Ok("C") => has_repr_c = true,
                    Ok("packed") => {
                        return TokenStream::from(quote!(
                            compile_error! {"FieldOffsets does not work on #[repr(packed)]"}
                        ));
                    }
                    _ => (),
                }
            } else if i == "const_field_offset" {
                match a.parse_args::<syn::Path>() {
                    Ok(c) => crate_ = quote!(#c),
                    Err(_) => {
                        return TokenStream::from(
                            quote_spanned!(a.span()=> compile_error!{"const_field_offset attribute must be a crate name"}),
                        );
                    }
                }
            } else if i == "pin" {
                pin = true;
            } else if i == "pin_drop" {
                drop = true;
                pin = true;
            }
        }
    }
    if !has_repr_c {
        return TokenStream::from(
            quote! {compile_error!{"FieldOffsets only work for structures using repr(C)"}},
        );
    }

    let struct_name = input.ident;
    let struct_vis = input.vis;
    let field_struct_name = quote::format_ident!("{}FieldsOffsets", struct_name);

    let (fields, types, vis) = if let syn::Data::Struct(s) = &input.data {
        if let syn::Fields::Named(n) = &s.fields {
            let (f, tv): (Vec<_>, Vec<_>) =
                n.named.iter().map(|f| (&f.ident, (&f.ty, &f.vis))).unzip();
            let (t, v): (Vec<_>, Vec<_>) = tv.into_iter().unzip();
            (f, t, v)
        } else {
            return TokenStream::from(quote! {compile_error!{"Only work for named fields"}});
        }
    } else {
        return TokenStream::from(quote! {compile_error!("Only work for struct")});
    };

    let doc = format!(
        "Helper struct containing the offsets of the fields of the struct [`{struct_name}`]\n\n\
        Generated from the `#[derive(FieldOffsets)]` macro from the [`const-field-offset`]({crate_}) crate",
    );

    let (ensure_pin_safe, ensure_no_unpin, pin_flag, new_from_offset) = if !pin {
        (None, None, quote!(#crate_::NotPinned), quote!(new_from_offset))
    } else {
        (
            if drop {
                None
            } else {
                let drop_trait_ident = format_ident!("{}MustNotImplDrop", struct_name);
                Some(quote! {
                    /// Make sure that Drop is not implemented
                    #[allow(non_camel_case_types)]
                    trait #drop_trait_ident {}
                    impl<T: ::core::ops::Drop> #drop_trait_ident for T {}
                    impl #drop_trait_ident for #struct_name {}

                })
            },
            Some(quote! {
                const _ : () = {
                    /// Make sure that Unpin is not implemented
                    #[allow(dead_code)]
                    struct __MustNotImplUnpin<'__dummy_lifetime> (
                        ::core::marker::PhantomData<&'__dummy_lifetime ()>
                    );
                    impl<'__dummy_lifetime> Unpin for #struct_name where __MustNotImplUnpin<'__dummy_lifetime> : Unpin {};
                };
            }),
            quote!(#crate_::AllowPin),
            quote!(new_from_offset_pinned),
        )
    };

    let pinned_drop_impl = if drop {
        Some(quote!(
            impl Drop for #struct_name {
                fn drop(&mut self) {
                    use #crate_::PinnedDrop;
                    self.do_safe_pinned_drop();
                }
            }
        ))
    } else {
        None
    };

    // Build the output, possibly using quasi-quotation
    let expanded = quote! {
        #[doc = #doc]
        #[allow(missing_docs, non_camel_case_types, dead_code)]
        #struct_vis struct #field_struct_name {
            #(#vis #fields : #crate_::FieldOffset<#struct_name, #types, #pin_flag>,)*
        }

        #[allow(clippy::eval_order_dependence)] // The point of this code is to depend on the order!
        impl #struct_name {
            /// Return a struct containing the offset of for the fields of this struct
            pub const FIELD_OFFSETS : #field_struct_name = {
                #ensure_pin_safe;
                let mut len = 0usize;
                #field_struct_name {
                    #( #fields : {
                        let align = ::core::mem::align_of::<#types>();
                        // from Layout::padding_needed_for which is not yet stable
                        let len_rounded_up  = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
                        len = len_rounded_up + ::core::mem::size_of::<#types>();
                        /// Safety: According to the rules of repr(C), this is the right offset
                        unsafe { #crate_::FieldOffset::<#struct_name, #types, _>::#new_from_offset(len_rounded_up) }
                    }, )*
                }
            };
        }

        #pinned_drop_impl
        #ensure_no_unpin
    };

    #[cfg(feature = "field-offset-trait")]
    let module_name = quote::format_ident!("{}_field_offsets", struct_name);

    #[cfg(feature = "field-offset-trait")]
    let in_mod_vis = vis.iter().map(|vis| min_vis(vis, &struct_vis)).map(|vis| match vis {
        Visibility::Public(_) => quote! {#vis},
        Visibility::Restricted(VisRestricted { pub_token, path, .. }) => {
            if quote!(#path).to_string().starts_with("super") {
                quote!(#pub_token(in super::#path))
            } else {
                quote!(#vis)
            }
        }
        Visibility::Inherited => quote!(pub(super)),
    });

    #[cfg(feature = "field-offset-trait")]
    let expanded = quote! { #expanded
        #[allow(non_camel_case_types)]
        #[allow(non_snake_case)]
        #[allow(missing_docs)]
        #struct_vis mod #module_name {
            #(
                #[derive(Clone, Copy, Default)]
                #in_mod_vis struct #fields;
            )*
        }
        #(
            impl #crate_::ConstFieldOffset for #module_name::#fields {
                type Container = #struct_name;
                type Field = #types;
                type PinFlag = #pin_flag;
                const OFFSET : #crate_::FieldOffset<#struct_name, #types, Self::PinFlag>
                    = #struct_name::FIELD_OFFSETS.#fields;
            }
            impl ::core::convert::Into<#crate_::FieldOffset<#struct_name, #types, #pin_flag>> for #module_name::#fields {
                fn into(self) -> #crate_::FieldOffset<#struct_name, #types, #pin_flag> {
                    #struct_name::FIELD_OFFSETS.#fields
                }
            }
            impl<Other> ::core::ops::Add<Other> for #module_name::#fields
                where Other : #crate_::ConstFieldOffset<Container = #types>
            {
                type Output = #crate_::ConstFieldOffsetSum<Self, Other>;
                fn add(self, other: Other) -> Self::Output {
                    #crate_::ConstFieldOffsetSum(self, other)
                }
            }
        )*
    };

    // Hand the output tokens back to the compiler
    TokenStream::from(expanded)
}

#[cfg(feature = "field-offset-trait")]
/// Returns the most restricted visibility
fn min_vis<'a>(a: &'a Visibility, b: &'a Visibility) -> &'a Visibility {
    match (a, b) {
        (Visibility::Public(_), _) => b,
        (_, Visibility::Public(_)) => a,
        (Visibility::Inherited, _) => a,
        (_, Visibility::Inherited) => b,
        // FIXME: compare two paths
        _ => a,
    }
}

/**
```compile_fail
use const_field_offset::*;
#[derive(FieldOffsets)]
struct Foo {
    x: u32,
}
```
*/
#[cfg(doctest)]
const _NO_REPR_C: u32 = 0;

/**
```compile_fail
use const_field_offset::*;
#[derive(FieldOffsets)]
#[repr(C)]
#[repr(packed)]
struct Foo {
    x: u32,
}
```
*/
#[cfg(doctest)]
const _REPR_PACKED: u32 = 0;

/**
```compile_fail
use const_field_offset::*;
#[derive(FieldOffsets)]
#[repr(C)]
#[pin]
struct Foo {
    x: u32,
}

impl Drop for Foo {
    fn drop(&mut self) {}
}
```
*/
#[cfg(doctest)]
const _PIN_NO_DROP: u32 = 0;

/**
```compile_fail
use const_field_offset::*;
#[derive(FieldOffsets)]
#[repr(C)]
#[pin]
struct Foo {
    q: std::marker::PhantomPinned,
    x: u32,
}

impl Unpin for Foo {}
```
*/
#[cfg(doctest)]
const _PIN_NO_UNPIN: u32 = 0;
