1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#![doc(html_root_url = "https://docs.rs/ref-cast-impl/0.2.3")]

#![recursion_limit = "128"]

extern crate proc_macro;
use proc_macro::TokenStream;

extern crate syn;

#[macro_use]
extern crate quote;

use syn::{Data, DeriveInput, Fields, Meta, NestedMeta, Type};

#[proc_macro_derive(RefCast)]
pub fn derive_ref_cast(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();

    if !has_repr_c(&ast) {
        panic!("RefCast trait requires #[repr(C)] or #[repr(transparent)]");
    }

    let name = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
    let from = only_field_ty(&ast);

    let expanded = quote! {
        impl #impl_generics ::ref_cast::RefCast for #name #ty_generics #where_clause {
            type From = #from;

            #[inline]
            fn ref_cast(_from: &Self::From) -> &Self {
                extern crate core as _core;

                // TODO: assert that `Self::From` and `Self` have the same size
                // and alignment.
                //
                // Cannot do this because `Self::From` may be a generic type
                // parameter of `Self` where `transmute` is not allowed:
                //
                //     #[allow(unused)]
                //     unsafe fn assert_same_size #impl_generics #where_clause () {
                //         _core::mem::forget(
                //             _core::mem::transmute::<#from, #name #ty_generics>(
                //                 _core::mem::uninitialized()));
                //     }
                //
                // Cannot do this because `Self::From` may not be sized:
                //
                //     debug_assert_eq!(_core::mem::size_of::<Self::From>(),
                //                      _core::mem::size_of::<Self>());
                //     debug_assert_eq!(_core::mem::align_of::<Self::From>(),
                //                      _core::mem::align_of::<Self>());

                unsafe {
                    &*(_from as *const Self::From as *const Self)
                }
            }

            #[inline]
            fn ref_cast_mut(_from: &mut Self::From) -> &mut Self {
                extern crate core as _core;
                unsafe {
                    &mut *(_from as *mut Self::From as *mut Self)
                }
            }
        }
    };

    expanded.into()
}

fn has_repr_c(ast: &DeriveInput) -> bool {
    for attr in &ast.attrs {
        if let Some(meta) = attr.interpret_meta() {
            if let Meta::List(meta) = meta {
                if meta.ident == "repr" && meta.nested.len() == 1 {
                    if let NestedMeta::Meta(ref inner) = meta.nested[0] {
                        if let Meta::Word(ref ident) = *inner {
                            if ident == "C" || ident == "transparent" {
                                return true;
                            }
                        }
                    }
                }
            }
        }
    }
    false
}

fn only_field_ty(ast: &DeriveInput) -> &Type {
    let fields = match ast.data {
        Data::Struct(ref data) => match data.fields {
            Fields::Named(ref fields) => &fields.named,
            Fields::Unnamed(ref fields) => &fields.unnamed,
            Fields::Unit => {
                panic!("RefCast does not support unit structs");
            }
        },
        Data::Enum(_) => {
            panic!("RefCast does not support enums");
        }
        Data::Union(_) => {
            panic!("RefCast does not support unions");
        }
    };

    // TODO: support structs that have trivial other fields like `()` or
    // `PhantomData`.
    if fields.len() != 1 {
        panic!("RefCast requires a struct with a single field");
    }

    &fields[0].ty
}