|
| 1 | +use proc_macro::TokenStream; |
| 2 | +use quote::quote; |
| 3 | +use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Generics, Meta, Type}; |
| 4 | + |
| 5 | +/// |
| 6 | +/// Derive macro for the `Reloc` trait. This macro automatically implements the `Reloc` trait |
| 7 | +/// for structs, ensuring that all field types also implement `Reloc`. This also requires that the |
| 8 | +/// struct is marked with `#[repr(C)]`. |
| 9 | +/// |
| 10 | +#[proc_macro_derive(Reloc)] |
| 11 | +pub fn derive_reloc(input: TokenStream) -> TokenStream { |
| 12 | + let input_args = parse_macro_input!(input as DeriveInput); |
| 13 | + let ident_name = &input_args.ident; |
| 14 | + |
| 15 | + // Ensure #[repr(C)] on the struct itself |
| 16 | + if !has_repr_c(&input_args.attrs) { |
| 17 | + return syn::Error::new_spanned( |
| 18 | + &ident_name, |
| 19 | + "The #[derive(Reloc)] macro requires #[repr(C)] on the type", |
| 20 | + ) |
| 21 | + .to_compile_error() |
| 22 | + .into(); |
| 23 | + } |
| 24 | + |
| 25 | + let mut generics = create_bounds_with_reloc(input_args.generics.clone()); |
| 26 | + |
| 27 | + // Collect field types. Since we cannot check if given field type has implemented given trait we will use compiler |
| 28 | + // to check that generating trait bounds on fields like below: |
| 29 | + // |
| 30 | + // struct Example { |
| 31 | + // field1: SomeType, |
| 32 | + // field2: AnotherType, |
| 33 | + // } |
| 34 | + // unsafe impl Reloc for Example where SomeType: Reloc, AnotherType: Reloc {} |
| 35 | + // |
| 36 | + let field_types = match collect_field_types(&input_args.data){ |
| 37 | + Ok(types) => types, |
| 38 | + Err(()) => return syn::Error::new_spanned( |
| 39 | + &ident_name, |
| 40 | + "The #[derive(Reloc)] macro is supported only for enums(C like), structs and tuple structs", |
| 41 | + ) |
| 42 | + .to_compile_error() |
| 43 | + .into(), |
| 44 | + }; |
| 45 | + |
| 46 | + { |
| 47 | + let where_clause_ref = generics.make_where_clause(); |
| 48 | + for ty in field_types { |
| 49 | + where_clause_ref.predicates.push(parse_quote!(#ty: Reloc)); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + // Create pieces for final implementation |
| 54 | + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); |
| 55 | + |
| 56 | + let expanded = quote! { |
| 57 | + unsafe impl #impl_generics Reloc for #ident_name #type_generics #where_clause {} |
| 58 | + }; |
| 59 | + |
| 60 | + expanded.into() |
| 61 | +} |
| 62 | + |
| 63 | +/// Add `T: Reloc` bounds to all generic parameters |
| 64 | +fn create_bounds_with_reloc(mut generics: Generics) -> Generics { |
| 65 | + for param in generics.type_params_mut() { |
| 66 | + param.bounds.push(parse_quote!(Reloc)); |
| 67 | + } |
| 68 | + generics |
| 69 | +} |
| 70 | + |
| 71 | +/// Check for #[repr(C)] existence |
| 72 | +fn has_repr_c(attrs: &[syn::Attribute]) -> bool { |
| 73 | + for attr in attrs { |
| 74 | + if attr.path().is_ident("repr") { |
| 75 | + if let Meta::List(list) = &attr.meta { |
| 76 | + let tokens = list.tokens.to_string(); |
| 77 | + if tokens.split(',').any(|t| t.trim() == "C") { |
| 78 | + return true; |
| 79 | + } |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + false |
| 84 | +} |
| 85 | + |
| 86 | +/// Collect field types, skipping generic parameters and primitives |
| 87 | +fn collect_field_types<'a>(data: &'a Data) -> Result<Vec<&'a Type>, ()> { |
| 88 | + let mut out = Vec::new(); |
| 89 | + |
| 90 | + match data { |
| 91 | + Data::Struct(data_struct) => { |
| 92 | + out = match &data_struct.fields { |
| 93 | + Fields::Named(fields) => fields.named.iter().map(|f| &f.ty).collect(), |
| 94 | + Fields::Unnamed(fields) => fields.unnamed.iter().map(|f| &f.ty).collect(), |
| 95 | + Fields::Unit => Vec::new(), |
| 96 | + }; |
| 97 | + } |
| 98 | + Data::Enum(data_enum) => { |
| 99 | + if !data_enum |
| 100 | + .variants |
| 101 | + .iter() |
| 102 | + .all(|variant| matches!(variant.fields, Fields::Unit)) |
| 103 | + { |
| 104 | + return Err(()); |
| 105 | + } |
| 106 | + } |
| 107 | + Data::Union(_) => return Err(()), |
| 108 | + }; |
| 109 | + |
| 110 | + Ok(out) |
| 111 | +} |
| 112 | + |
| 113 | +// Use doctest to test failed compilations and successful ones |
| 114 | + |
| 115 | +/// ``` |
| 116 | +/// pub unsafe trait Reloc {} |
| 117 | +/// use com_api_concept_macros::Reloc; |
| 118 | +/// |
| 119 | +/// #[derive(Reloc)] |
| 120 | +/// #[repr(C)] |
| 121 | +/// pub struct RelocType<T> { t : T} |
| 122 | +/// |
| 123 | +/// #[derive(Reloc)] |
| 124 | +/// #[repr(C)] |
| 125 | +/// pub struct RelocTypeTwoGen<T, D> { t : T, d: D} |
| 126 | +/// ``` |
| 127 | +#[cfg(doctest)] |
| 128 | +fn reloc_works_on_generic_types() {} |
| 129 | + |
| 130 | +/// ``` |
| 131 | +/// pub unsafe trait Reloc {} |
| 132 | +/// use com_api_concept_macros::Reloc; |
| 133 | +/// |
| 134 | +/// #[derive(Reloc)] |
| 135 | +/// #[repr(C)] |
| 136 | +/// enum EnumPlanType { |
| 137 | +/// A, |
| 138 | +/// B, |
| 139 | +/// C |
| 140 | +/// } |
| 141 | +/// ``` |
| 142 | +#[cfg(doctest)] |
| 143 | +fn reloc_works_on_c_like_enums() {} |
| 144 | + |
| 145 | +/// ``` |
| 146 | +/// pub unsafe trait Reloc {} |
| 147 | +/// use com_api_concept_macros::Reloc; |
| 148 | +/// |
| 149 | +/// #[derive(Reloc)] |
| 150 | +/// #[repr(C)] |
| 151 | +/// struct StructTag{} |
| 152 | +/// ``` |
| 153 | +#[cfg(doctest)] |
| 154 | +fn reloc_works_on_unit() {} |
| 155 | + |
| 156 | +/// ``` |
| 157 | +/// pub unsafe trait Reloc {} |
| 158 | +/// unsafe impl Reloc for u32{} |
| 159 | +/// unsafe impl Reloc for u64{} |
| 160 | +/// |
| 161 | +/// use com_api_concept_macros::Reloc; |
| 162 | +/// |
| 163 | +/// #[derive(Reloc)] |
| 164 | +/// #[repr(C)] |
| 165 | +/// pub struct RelocType<T> { t : T} |
| 166 | +/// |
| 167 | +/// #[derive(Reloc)] |
| 168 | +/// #[repr(C)] |
| 169 | +/// pub struct StructType { |
| 170 | +/// a: u32, |
| 171 | +/// b: u64, |
| 172 | +/// f: RelocType<u64> |
| 173 | +/// } |
| 174 | +/// ``` |
| 175 | +#[cfg(doctest)] |
| 176 | +fn reloc_works_on_structs() {} |
| 177 | + |
| 178 | +/// ``` |
| 179 | +/// pub unsafe trait Reloc {} |
| 180 | +/// unsafe impl Reloc for u32{} |
| 181 | +/// unsafe impl Reloc for u64{} |
| 182 | +/// |
| 183 | +/// use com_api_concept_macros::Reloc; |
| 184 | +/// |
| 185 | +/// #[derive(Reloc)] |
| 186 | +/// #[repr(C)] |
| 187 | +/// pub struct RelocType<T> { t : T} |
| 188 | +/// |
| 189 | +/// #[derive(Reloc)] |
| 190 | +/// #[repr(C)] |
| 191 | +/// pub struct TupleStructType( u32, u64, RelocType<u64> ); |
| 192 | +/// ``` |
| 193 | +#[cfg(doctest)] |
| 194 | +fn reloc_works_on_tuples() {} |
| 195 | + |
| 196 | +// Failure cases |
| 197 | + |
| 198 | +/// ```compile_fail |
| 199 | +/// pub unsafe trait Reloc {} |
| 200 | +/// use com_api_concept_macros::Reloc; |
| 201 | +/// |
| 202 | +/// #[derive(Reloc)] |
| 203 | +/// #[repr(C)] |
| 204 | +/// enum EnumPlanType { |
| 205 | +/// A(u32), |
| 206 | +/// B, |
| 207 | +/// C |
| 208 | +/// } |
| 209 | +/// ``` |
| 210 | +#[cfg(doctest)] |
| 211 | +fn reloc_fail_on_non_c_like_enums() {} |
| 212 | + |
| 213 | +/// ```compile_fail |
| 214 | +/// pub unsafe trait Reloc {} |
| 215 | +/// unsafe impl Reloc for u32{} |
| 216 | +/// |
| 217 | +/// use com_api_concept_macros::Reloc; |
| 218 | +/// |
| 219 | +/// #[derive(Reloc)] |
| 220 | +/// #[repr(C)] |
| 221 | +/// pub struct RelocType<T> { t : T} |
| 222 | +/// |
| 223 | +/// #[derive(Reloc)] |
| 224 | +/// #[repr(C)] |
| 225 | +/// pub struct StructType { |
| 226 | +/// a: u32, |
| 227 | +/// b: u64, |
| 228 | +/// f: RelocType<u64> |
| 229 | +/// } |
| 230 | +/// ``` |
| 231 | +#[cfg(doctest)] |
| 232 | +fn reloc_fails_if_member_is_not_reloc_on_struct() {} |
| 233 | + |
| 234 | +/// ```compile_fail |
| 235 | +/// pub unsafe trait Reloc {} |
| 236 | +/// use com_api_concept_macros::Reloc; |
| 237 | +/// |
| 238 | +/// #[derive(Reloc)] |
| 239 | +/// #[repr(C)] |
| 240 | +/// pub struct RelocType<T> { t : T} |
| 241 | +/// |
| 242 | +/// #[derive(Reloc)] |
| 243 | +/// #[repr(C)] |
| 244 | +/// pub struct ContainsNonRelocType(RelocType<u64>); |
| 245 | +/// ``` |
| 246 | +#[cfg(doctest)] |
| 247 | +fn reloc_fails_on_generic_types_that_do_not_impl_reloc() {} |
0 commit comments