compact_option_proc_macro/lib.rs
1//! Attribute macro `#[compact_option(repr(R = …, sentinel = …))]` for emitting
2//! `CompactRepr` for enums and structs.
3//!
4//! Used with `CompactOption` for niche-packing optional storage: `NONE`/`Some(T)` encoded in
5//! exactly as much memory as raw `R`, by reserving one of `R`'s spare bit patterns as the sentinel.
6//! Primarily `#[repr(u8)]` enums with fewer than 256 variants.
7//!
8//! # What this macro does (and does not)
9//!
10//! **Does:** parses `R` and `sentinel`, re-emits your item, and appends `unsafe impl const CompactRepr<R>`.
11//! For structs it also emits `const` `size_of` / `align_of` checks against `R` so layout mismatches
12//! fail in rustc const-eval (not token comparison).
13//!
14//! **Does not:** read `#[repr(...)]`, compute enum discriminants, compare sentinel to discriminants,
15//! or validate `repr(transparent)` / field count. Those invariants are the implementer’s responsibility
16//! per the `CompactRepr` safety contract; use tests and Miri.
17//!
18//! A trailing `, verify_discriminants = …` after `repr(...)` is accepted for compatibility and ignored.
19//!
20//! # Examples
21//!
22//! ## `repr(u8)` enum
23//!
24//! ```ignore
25//! use compact_option_proc_macro::compact_option;
26//!
27//! #[compact_option(repr(R = u8, sentinel = 0xFF))]
28//! #[repr(u8)]
29//! #[derive(Clone, Copy)]
30//! pub enum Letter {
31//! A = 0,
32//! B = 1,
33//! }
34//! ```
35//!
36//! ## `repr(transparent)` newtype
37//!
38//! ```ignore
39//! use compact_option_proc_macro::compact_option;
40//!
41//! #[compact_option(repr(R = u8, sentinel = 0xFE))]
42//! #[repr(transparent)]
43//! #[derive(Clone, Copy)]
44//! pub struct ByteSlot(pub u8);
45//! ```
46//!
47//! # Safety
48//!
49//! This macro only emits `unsafe impl const CompactRepr` plus struct layout asserts. It does **not**
50//! prove transmute soundness. Use Miri and the `CompactRepr` documentation.
51
52use proc_macro::TokenStream;
53use syn::{Item, parse_macro_input};
54
55mod expand_enum;
56mod expand_struct;
57mod parse;
58
59use parse::AttrArgs;
60
61/// `#[compact_option(repr(R = <type>, sentinel = <expr>))]` on an enum or struct.
62///
63/// Optional trailing `, verify_discriminants = <bool>` is parsed and ignored.
64///
65/// # Limitations
66///
67/// - No analysis of enum discriminants or whether `sentinel` collides with them.
68/// - No verification that `R` matches the enum’s integer `repr` or the newtype’s field type.
69/// - For structs, only the emitted `size_of` / `align_of` checks tie layout to `R`.
70#[proc_macro_attribute]
71pub fn compact_option(attr: TokenStream, item: TokenStream) -> TokenStream {
72 let args = parse_macro_input!(attr as AttrArgs);
73 let ast: Item = match syn::parse(item.clone()) {
74 Ok(i) => i,
75 Err(e) => return e.to_compile_error().into(),
76 };
77
78 let expanded = match &ast {
79 Item::Enum(e) => expand_enum::expand_enum(&args, e),
80 Item::Struct(s) => expand_struct::expand_struct(&args, s),
81 _ => Err(syn::Error::new_spanned(
82 &ast,
83 "#[compact_option] only supports enums and structs",
84 )),
85 };
86
87 match expanded {
88 Ok(ts) => ts.into(),
89 Err(e) => e.to_compile_error().into(),
90 }
91}