Skip to main content

flense_derive/
lib.rs

1//! Derive macros for [`flense`](https://docs.rs/flense).
2//!
3//! This crate provides [`macro@Adapter`], a derive that generates the `unsafe
4//! impl Adapter<...>` blocks `flense` needs, with the byte offset filled in
5//! correctly from [`offset_of!`](core::mem::offset_of).
6//!
7//! Use of this derive is never required; you could directly write these
8//! implementations yourself, but the derive lets you avoid explicitly writing
9//! the unsafe block and performs some safety checks.
10
11use proc_macro::TokenStream;
12use proc_macro2::TokenStream as TokenStream2;
13use quote::quote;
14use syn::{
15    Data,
16    DeriveInput,
17    Fields,
18    Path,
19    Token,
20    Type,
21    Visibility,
22    parse::{
23        Parse,
24        ParseStream,
25    },
26    parse_macro_input,
27    punctuated::Punctuated,
28    spanned::Spanned as _,
29    token,
30};
31
32/// Derives `flense`'s `Adapter` implementations for the annotated fields of a
33/// struct.
34///
35/// Annotate each field that should be lensable with `#[field(...)]`. Each entry
36/// is either:
37///
38/// * `Marker` - reference an existing `Field` implementation. The macro emits
39///   an `unsafe impl Adapter<Marker>` for the struct and a compile-time check
40///   that the field's type is exactly `<Marker as Field>::Type`.
41/// * `new Marker` - also mints a fresh marker type `Marker` (inheriting the
42///   struct's visibility) whose `Field::Type` is the field's declared type.
43/// * `new reflexive Marker` - as `new Marker`, and additionally emits an
44///   `Adapter<Marker>` impl for the field type itself (`<Marker as
45///   Field>::Type` at offset zero), so a bare value of that type can be lensed
46///   as `Marker`. You could write this `unsafe impl` yourself; letting the
47///   derive emit it avoids needing to author and maintain a `unsafe` block.
48///
49/// A single `#[field(...)]` may list several comma-separated entries, and the
50/// attribute may be repeated on one field.
51///
52/// ```
53/// use flense::prelude::*;
54/// # use flense_derive::Adapter;
55///
56/// enum Position {}
57/// impl Field for Position {
58///     type Type = [f32; 3];
59/// }
60///
61/// #[derive(Adapter)]
62/// struct Vertex {
63///     #[field(Position)]
64///     pos: [f32; 3],
65///     #[field(new Color)]
66///     color: [u8; 3],
67/// }
68///
69/// #[derive(Adapter)]
70/// struct Mesh {
71///     #[field(Position)]
72///     world_pos: [f32; 3],
73///     #[field(Color)]
74///     color: [u8; 3],
75/// }
76/// ```
77#[proc_macro_derive(Adapter, attributes(field))]
78pub fn derive_adapter(input: TokenStream) -> TokenStream {
79    let input = parse_macro_input!(input as DeriveInput);
80    expand(&input)
81        .unwrap_or_else(syn::Error::into_compile_error)
82        .into()
83}
84
85/// A single entry inside a `#[field(...)]` attribute: optional `new` and
86/// `reflexive` keywords followed by the marker path.
87struct FieldEntry {
88    is_new: bool,
89    is_reflexive: bool,
90    path: Path,
91}
92
93/// Consume a contextual keyword if it leads `input`.
94///
95/// These keywords (`new`, `reflexive`) are not Rust keywords, so treat a
96/// leading ident as the keyword only when it is followed by another path (so a
97/// marker literally named `new`, or a path like `new::Marker`, still parses as
98/// a plain reference). Returns whether the keyword was present and consumed.
99fn eat_keyword(input: ParseStream, keyword: &str) -> syn::Result<bool> {
100    let is_kw = input.peek(syn::Ident) && !input.peek2(Token![::]) && {
101        let fork = input.fork();
102        let ident: syn::Ident = fork.parse()?;
103        ident == keyword && !fork.is_empty() && !fork.peek(Token![,])
104    };
105    if is_kw {
106        let _: syn::Ident = input.parse()?;
107    }
108    Ok(is_kw)
109}
110
111impl Parse for FieldEntry {
112    fn parse(input: ParseStream) -> syn::Result<Self> {
113        let is_new = eat_keyword(input, "new")?;
114        let is_reflexive = eat_keyword(input, "reflexive")?;
115        let path: Path = input.parse()?;
116        Ok(Self {
117            is_new,
118            is_reflexive,
119            path,
120        })
121    }
122}
123
124fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
125    let Data::Struct(data) = &input.data else {
126        return Err(syn::Error::new(
127            input.span(),
128            "`Adapter` can only be derived for structs",
129        ));
130    };
131    let Fields::Named(fields) = &data.fields else {
132        return Err(syn::Error::new(
133            data.fields.span(),
134            "`Adapter` can only be derived for structs with named fields",
135        ));
136    };
137
138    // `#[repr(packed)]` fields may be under-aligned. Referencing an under-aligned
139    // field is unsound, so a sound `Adapter` is impossible. Reject it.
140    for attr in &input.attrs {
141        if !attr.path().is_ident("repr") {
142            continue;
143        }
144        let mut packed = false;
145        attr.parse_nested_meta(|meta| {
146            if meta.path.is_ident("packed") {
147                packed = true;
148            }
149            // Consume an optional `(N)` argument (e.g. `packed(2)`, `align(8)`)
150            // so the nested-meta parse does not error.
151            if meta.input.peek(token::Paren) {
152                let content;
153                syn::parenthesized!(content in meta.input);
154                let _: TokenStream2 = content.parse()?;
155            }
156            Ok(())
157        })?;
158        if packed {
159            return Err(syn::Error::new(
160                attr.span(),
161                "`Adapter` cannot be derived for `#[repr(packed)]` structs: \
162                 packed fields may be under-aligned, which would make the \
163                 generated `Adapter` unsound",
164            ));
165        }
166    }
167
168    let struct_name = &input.ident;
169    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
170
171    let mut out = TokenStream2::new();
172
173    for field in &fields.named {
174        let field_name = field.ident.as_ref().expect("named field has an ident");
175        let field_ty = &field.ty;
176
177        for attr in &field.attrs {
178            if !attr.path().is_ident("field") {
179                continue;
180            }
181            let entries =
182                attr.parse_args_with(Punctuated::<FieldEntry, Token![,]>::parse_terminated)?;
183            for entry in entries {
184                out.extend(emit_entry(
185                    &entry,
186                    &input.vis,
187                    struct_name,
188                    &impl_generics,
189                    &ty_generics,
190                    where_clause,
191                    field_name,
192                    field_ty,
193                )?);
194            }
195        }
196    }
197
198    Ok(out)
199}
200
201#[allow(clippy::too_many_arguments, reason = "splicing several syn fragments")]
202fn emit_entry(
203    entry: &FieldEntry,
204    vis: &Visibility,
205    struct_name: &syn::Ident,
206    impl_generics: &syn::ImplGenerics,
207    ty_generics: &syn::TypeGenerics,
208    where_clause: Option<&syn::WhereClause>,
209    field_name: &syn::Ident,
210    field_ty: &Type,
211) -> syn::Result<TokenStream2> {
212    let marker = &entry.path;
213
214    // For `new Marker`, define the marker type and its `Field` impl. The marker
215    // must be a single identifier, since it is declared as a new item.
216    let new_marker = if entry.is_new {
217        let Some(ident) = marker.get_ident() else {
218            return Err(syn::Error::new(
219                marker.span(),
220                "`new` markers must be a single identifier, not a path",
221            ));
222        };
223        quote! {
224            #vis enum #ident {}
225            #[automatically_derived]
226            impl ::flense::Field for #ident {
227                type Type = #field_ty;
228            }
229        }
230    } else {
231        TokenStream2::new()
232    };
233
234    // For a `reflexive` marker, also emit an `Adapter` impl for the field type
235    // itself, treating the whole value as the field at offset zero. This lets a
236    // bare `<Marker as Field>::Type` (e.g. `[f32; 3]`) be lensed as `Marker`
237    // directly, not just the struct it was declared in.
238    let reflexive_impl = if entry.is_reflexive {
239        quote! {
240            // SAFETY: `Self` *is* `<#marker as Field>::Type` (which is
241            // `#field_ty` for this `new` marker), so the field sits at offset
242            // zero and spans the whole value; the offset is trivially aligned
243            // because the value is already a valid, aligned instance of its own
244            // type.
245            //
246            // The self type is spelled as the concrete `#field_ty`, not the
247            // projection `<#marker as Field>::Type`: a projection self-type is
248            // not normalized by downstream coherence checks, so it would
249            // spuriously overlap any other `Adapter<#marker>` impl.
250            #[automatically_derived]
251            unsafe impl ::flense::Adapter<#marker> for #field_ty {
252                const OFFSET: usize = 0;
253            }
254        }
255    } else {
256        TokenStream2::new()
257    };
258
259    // For an existing marker, guard that the field's type is exactly `<Marker as
260    // Field>::Type`. `offset_of!` alone does not check this, and a mismatch would
261    // be unsound, effectively allowing a silent transmute. The check lives inside
262    // the `OFFSET` const so the struct's generics and `Self` are in scope. `T:
263    // SameType<U>` holds only when `T == U`. The `new` case needs no guard: its
264    // `Type` is defined to be the field type.
265    let type_guard = if entry.is_new {
266        TokenStream2::new()
267    } else {
268        quote! {
269            trait SameType<T> {}
270            impl<T> SameType<T> for T {}
271            fn assert_field_type<FLENSE_DERIVE_T: SameType<<#marker as ::flense::Field>::Type>>() {}
272            let _ = assert_field_type::<#field_ty>;
273        }
274    };
275
276    Ok(quote! {
277        #new_marker
278
279        #reflexive_impl
280
281        // SAFETY: `OFFSET` is produced by `offset_of!` on the annotated field,
282        // so it is the field's true byte offset; the field's type is checked to
283        // equal `<#marker as Field>::Type` (or defined to be it for `new`
284        // markers); and the offset is asserted to be aligned for that type, so
285        // the field pointer the lens derives is always well-aligned.
286        #[automatically_derived]
287        unsafe impl #impl_generics ::flense::Adapter<#marker>
288            for #struct_name #ty_generics #where_clause
289        {
290            const OFFSET: usize = {
291                #type_guard
292                let offset = ::core::mem::offset_of!(Self, #field_name);
293                // Second safeguard (independent of the `#[repr(packed)]`
294                // rejection): the field must sit at an offset aligned for its
295                // type, otherwise the references a lens hands out would be
296                // unaligned, hence unsound.
297                ::core::assert!(
298                    offset % ::core::mem::align_of::<#field_ty>() == 0,
299                    "flense: field is not aligned for its type; the derived `Adapter` would be unsound",
300                );
301                offset
302            };
303        }
304    })
305}