Skip to main content

capsec_macro/
lib.rs

1//! # capsec-macro
2//!
3//! Procedural macros for the `capsec` capability-based security system.
4//!
5//! Provides attribute macros:
6//!
7//! - [`requires`] — declares and validates a function's capability requirements.
8//! - [`deny`] — marks a function as capability-free for the lint tool.
9//! - [`main`] — injects `CapRoot` creation into a function entry point.
10//! - [`context`] — generates `Has<P>` impls and constructor for a capability context struct.
11//!
12//! These macros are re-exported by the `capsec` facade crate. You don't need to
13//! depend on `capsec-macro` directly.
14
15mod resolve;
16
17use proc_macro::TokenStream;
18use quote::{format_ident, quote};
19use syn::punctuated::Punctuated;
20use syn::{FnArg, ItemFn, ItemStruct, Meta, Pat, Token, Type, parse_macro_input};
21
22/// The set of known permission type names (bare idents).
23const KNOWN_PERMISSIONS: &[&str] = &[
24    "FsRead",
25    "FsWrite",
26    "FsAll",
27    "NetConnect",
28    "NetBind",
29    "NetAll",
30    "EnvRead",
31    "EnvWrite",
32    "Spawn",
33    "Ambient",
34];
35
36/// Defines a user-defined permission type for capability-based security.
37///
38/// Generates the `Permission` trait impl (with seal token), `Has<P>` impls for
39/// `Cap<P>` and `SendCap<P>`, and optionally `Subsumes` impls for permission hierarchies.
40///
41/// # Usage
42///
43/// ```rust,ignore
44/// #[capsec::permission]
45/// pub struct DbRead;
46///
47/// #[capsec::permission]
48/// pub struct DbWrite;
49///
50/// #[capsec::permission(subsumes = [DbRead, DbWrite])]
51/// pub struct DbAll;
52/// ```
53///
54/// # What it generates
55///
56/// For `#[capsec::permission] pub struct DbRead;`:
57/// - `impl Permission for DbRead` with the seal token
58/// - `impl Has<DbRead> for Cap<DbRead>`
59/// - `impl Has<DbRead> for SendCap<DbRead>`
60///
61/// For `#[capsec::permission(subsumes = [DbRead, DbWrite])] pub struct DbAll;`:
62/// - All of the above, plus:
63/// - `impl Subsumes<DbRead> for DbAll`
64/// - `impl Has<DbRead> for Cap<DbAll>` and `SendCap<DbAll>`
65/// - Same for `DbWrite`
66#[proc_macro_attribute]
67pub fn permission(attr: TokenStream, item: TokenStream) -> TokenStream {
68    let attr2: proc_macro2::TokenStream = attr.into();
69    let input = parse_macro_input!(item as ItemStruct);
70
71    match permission_inner(attr2, &input) {
72        Ok(tokens) => tokens.into(),
73        Err(e) => e.into_compile_error().into(),
74    }
75}
76
77fn permission_inner(
78    attr: proc_macro2::TokenStream,
79    input: &ItemStruct,
80) -> syn::Result<proc_macro2::TokenStream> {
81    // Validate: must be a unit struct (no fields)
82    match &input.fields {
83        syn::Fields::Unit => {}
84        _ => {
85            return Err(syn::Error::new_spanned(
86                input,
87                "#[capsec::permission] requires a unit struct (no fields)",
88            ));
89        }
90    }
91
92    // Reject generics
93    if !input.generics.params.is_empty() {
94        return Err(syn::Error::new_spanned(
95            &input.generics,
96            "#[capsec::permission] does not support generic structs",
97        ));
98    }
99
100    let struct_name = &input.ident;
101    let struct_vis = &input.vis;
102    let struct_attrs = &input.attrs;
103
104    // Parse subsumes = [...] from attribute
105    let subsumes = parse_subsumes(attr)?;
106
107    // Generate Permission impl with seal token
108    let permission_impl = quote! {
109        impl capsec_core::permission::Permission for #struct_name {
110            type __CapsecSeal = capsec_core::__private::SealProof;
111        }
112    };
113
114    // Note: Has<Self> for Cap<Self> and SendCap<Self> are already covered by
115    // the blanket impls in capsec_core::has:
116    //   impl<P: Permission> Has<P> for Cap<P>
117    //   impl<P: Permission> Has<P> for SendCap<P>
118    // So we only need to generate Subsumes-related Has impls.
119
120    // Generate Subsumes impls + Has<Sub> for Cap<Self>/SendCap<Self>
121    let subsumes_impls: Vec<_> = subsumes
122        .iter()
123        .map(|sub| {
124            quote! {
125                impl capsec_core::permission::Subsumes<#sub> for #struct_name {}
126
127                impl capsec_core::has::Has<#sub> for capsec_core::cap::Cap<#struct_name> {
128                    fn cap_ref(&self) -> capsec_core::cap::Cap<#sub> {
129                        capsec_core::cap::Cap::__capsec_new_derived()
130                    }
131                }
132
133                impl capsec_core::has::Has<#sub> for capsec_core::cap::SendCap<#struct_name> {
134                    fn cap_ref(&self) -> capsec_core::cap::Cap<#sub> {
135                        capsec_core::cap::Cap::__capsec_new_derived()
136                    }
137                }
138            }
139        })
140        .collect();
141
142    Ok(quote! {
143        #(#struct_attrs)*
144        #struct_vis struct #struct_name;
145
146        #permission_impl
147        #(#subsumes_impls)*
148    })
149}
150
151/// Parses `subsumes = [A, B, C]` from macro attribute tokens.
152fn parse_subsumes(attr: proc_macro2::TokenStream) -> syn::Result<Vec<syn::Path>> {
153    if attr.is_empty() {
154        return Ok(Vec::new());
155    }
156
157    // Parse as Meta::NameValue: subsumes = [...]
158    let meta: Meta = syn::parse2(attr)?;
159    match &meta {
160        Meta::NameValue(nv) if nv.path.is_ident("subsumes") => {
161            // The value should be an array expression: [A, B, C]
162            if let syn::Expr::Array(arr) = &nv.value {
163                let mut paths = Vec::new();
164                let mut seen = std::collections::HashSet::new();
165                for elem in &arr.elems {
166                    if let syn::Expr::Path(ep) = elem {
167                        let path_str = quote::quote!(#ep).to_string();
168                        if !seen.insert(path_str.clone()) {
169                            return Err(syn::Error::new_spanned(
170                                elem,
171                                format!("duplicate subsumes entry: {}", path_str),
172                            ));
173                        }
174                        paths.push(ep.path.clone());
175                    } else {
176                        return Err(syn::Error::new_spanned(
177                            elem,
178                            "expected a permission type path in subsumes list",
179                        ));
180                    }
181                }
182                Ok(paths)
183            } else {
184                Err(syn::Error::new_spanned(
185                    &nv.value,
186                    "expected an array [A, B, C] for subsumes",
187                ))
188            }
189        }
190        _ => Err(syn::Error::new_spanned(
191            &meta,
192            "expected `subsumes = [...]`",
193        )),
194    }
195}
196
197/// Declares the capability requirements of a function.
198///
199/// When all parameters use `impl Has<P>` bounds, the compiler already enforces
200/// the trait bounds and this macro emits only a `#[doc]` attribute.
201///
202/// When concrete parameter types are used (e.g., context structs), use `on = param`
203/// to identify the capability parameter. The macro emits a compile-time assertion
204/// that the parameter type implements `Has<P>` for each declared permission.
205///
206/// # Usage
207///
208/// ```rust,ignore
209/// // With impl bounds — no `on` needed
210/// #[capsec::requires(fs::read, net::connect)]
211/// fn sync_data(cap: &(impl Has<FsRead> + Has<NetConnect>)) -> Result<()> {
212///     // ...
213/// }
214///
215/// // With concrete context type — use `on = param`
216/// #[capsec::requires(fs::read, net::connect, on = ctx)]
217/// fn sync_data(config: &Config, ctx: &AppCtx) -> Result<()> {
218///     // ...
219/// }
220/// ```
221///
222/// # Supported permission paths
223///
224/// Both shorthand and explicit forms are accepted:
225///
226/// | Shorthand | Explicit | Permission type |
227/// |-----------|----------|-----------------|
228/// | `fs::read` | `FsRead` | `capsec_core::permission::FsRead` |
229/// | `fs::write` | `FsWrite` | `capsec_core::permission::FsWrite` |
230/// | `net::connect` | `NetConnect` | `capsec_core::permission::NetConnect` |
231/// | `net::bind` | `NetBind` | `capsec_core::permission::NetBind` |
232/// | `env::read` | `EnvRead` | `capsec_core::permission::EnvRead` |
233/// | `env::write` | `EnvWrite` | `capsec_core::permission::EnvWrite` |
234/// | `spawn` | `Spawn` | `capsec_core::permission::Spawn` |
235/// | `all` | `Ambient` | `capsec_core::permission::Ambient` |
236#[proc_macro_attribute]
237pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
238    let attr2: proc_macro2::TokenStream = attr.into();
239    let func = parse_macro_input!(item as ItemFn);
240
241    match requires_inner(attr2, &func) {
242        Ok(tokens) => tokens.into(),
243        Err(e) => e.into_compile_error().into(),
244    }
245}
246
247fn requires_inner(
248    attr: proc_macro2::TokenStream,
249    func: &ItemFn,
250) -> syn::Result<proc_macro2::TokenStream> {
251    let metas: Punctuated<Meta, Token![,]> =
252        syn::parse::Parser::parse2(Punctuated::parse_terminated, attr)?;
253
254    // Separate `on = param` from permission metas
255    let mut on_param: Option<syn::Ident> = None;
256    let mut perm_metas: Vec<&Meta> = Vec::new();
257
258    for meta in &metas {
259        if let Meta::NameValue(nv) = meta
260            && nv.path.is_ident("on")
261        {
262            if let syn::Expr::Path(ep) = &nv.value
263                && let Some(ident) = ep.path.get_ident()
264            {
265                on_param = Some(ident.clone());
266                continue;
267            }
268            return Err(syn::Error::new_spanned(&nv.value, "expected an identifier"));
269        }
270        perm_metas.push(meta);
271    }
272
273    // Resolve permission types
274    let mut cap_types = Vec::new();
275    for meta in &perm_metas {
276        cap_types.push(resolve::meta_to_permission_type(meta)?);
277    }
278
279    // Build doc string
280    let doc_string = format!(
281        "capsec::requires({})",
282        cap_types
283            .iter()
284            .map(|c| quote!(#c).to_string())
285            .collect::<Vec<_>>()
286            .join(", ")
287    );
288
289    // Check if any parameter uses `impl` trait bounds
290    let has_impl_bounds = func.sig.inputs.iter().any(|arg| {
291        if let FnArg::Typed(pat_type) = arg {
292            contains_impl_trait(&pat_type.ty)
293        } else {
294            false
295        }
296    });
297
298    // Build assertion block or auto-generate where bounds
299    let mut modified_sig: Option<syn::Signature> = None;
300    let assertion = if let Some(ref param_name) = on_param {
301        // Find the parameter and extract its type
302        let param_type = find_param_type(&func.sig, param_name)?;
303        let inner_type = unwrap_references(&param_type);
304
305        // Check if the inner type is a generic type parameter
306        let generic_ident = func.sig.generics.type_params().find_map(|tp| {
307            if let Type::Path(ref type_path) = *inner_type
308                && type_path.path.is_ident(&tp.ident)
309            {
310                Some(tp.ident.clone())
311            } else {
312                None
313            }
314        });
315
316        if let Some(ref gen_ident) = generic_ident {
317            // Mode 4: Auto-generate Has<P> bounds on the generic type parameter
318            let mut sig = func.sig.clone();
319            let where_clause = sig.generics.make_where_clause();
320            let bounds: Vec<_> = cap_types
321                .iter()
322                .map(|perm_ty| quote! { capsec_core::has::Has<#perm_ty> })
323                .collect();
324            where_clause
325                .predicates
326                .push(syn::parse2(quote! { #gen_ident: #(#bounds)+* }).unwrap());
327            modified_sig = Some(sig);
328            None // no assertion block needed
329        } else {
330            // Mode 2: Concrete type — generate assertion block
331            let assert_fns: Vec<_> = cap_types
332                .iter()
333                .enumerate()
334                .map(|(i, perm_ty)| {
335                    let fn_name = format_ident!("_assert_has_{}", i);
336                    quote! {
337                        fn #fn_name<T: capsec_core::has::Has<#perm_ty>>() {}
338                    }
339                })
340                .collect();
341
342            let assert_calls: Vec<_> = (0..cap_types.len())
343                .map(|i| {
344                    let fn_name = format_ident!("_assert_has_{}", i);
345                    quote! { #fn_name::<#inner_type>(); }
346                })
347                .collect();
348
349            Some(quote! {
350                const _: () = {
351                    #(#assert_fns)*
352                    fn _check() {
353                        #(#assert_calls)*
354                    }
355                };
356            })
357        }
358    } else if !has_impl_bounds && !func.sig.inputs.is_empty() && !cap_types.is_empty() {
359        // Concrete types present but no `on` keyword
360        return Err(syn::Error::new_spanned(
361            &func.sig,
362            "#[capsec::requires] on a function with concrete parameter types requires \
363             `on = <param>` to identify the capability parameter.\n\
364             Example: #[capsec::requires(fs::read, on = ctx)]",
365        ));
366    } else {
367        None
368    };
369
370    let func_vis = &func.vis;
371    let func_sig = modified_sig.as_ref().unwrap_or(&func.sig);
372    let func_block = &func.block;
373    let func_attrs = &func.attrs;
374
375    Ok(quote! {
376        #(#func_attrs)*
377        #[doc = #doc_string]
378        #func_vis #func_sig {
379            #assertion
380            #func_block
381        }
382    })
383}
384
385fn contains_impl_trait(ty: &Type) -> bool {
386    match ty {
387        Type::ImplTrait(_) => true,
388        Type::Reference(r) => contains_impl_trait(&r.elem),
389        Type::Paren(p) => contains_impl_trait(&p.elem),
390        _ => false,
391    }
392}
393
394fn find_param_type(sig: &syn::Signature, name: &syn::Ident) -> syn::Result<Type> {
395    for arg in &sig.inputs {
396        if let FnArg::Typed(pat_type) = arg
397            && let Pat::Ident(pi) = &*pat_type.pat
398            && pi.ident == *name
399        {
400            return Ok((*pat_type.ty).clone());
401        }
402    }
403    Err(syn::Error::new_spanned(
404        name,
405        format!("parameter '{}' not found in function signature", name),
406    ))
407}
408
409fn unwrap_references(ty: &Type) -> &Type {
410    match ty {
411        Type::Reference(r) => unwrap_references(&r.elem),
412        Type::Paren(p) => unwrap_references(&p.elem),
413        _ => ty,
414    }
415}
416
417/// Marks a function as capability-free.
418///
419/// This is a declaration for the `cargo capsec check` lint tool — any ambient
420/// authority call found inside a `#[deny]` function will be flagged as a violation.
421///
422/// The macro itself does not enforce anything at compile time (there's no type-system
423/// mechanism to prevent `std::fs` imports). Enforcement is in the lint tool.
424///
425/// # Usage
426///
427/// ```rust,ignore
428/// // Deny all I/O
429/// #[capsec::deny(all)]
430/// fn pure_transform(input: &[u8]) -> Vec<u8> {
431///     input.iter().map(|b| b.wrapping_add(1)).collect()
432/// }
433///
434/// // Deny only network access
435/// #[capsec::deny(net)]
436/// fn local_only(cap: &impl Has<FsRead>) -> Vec<u8> {
437///     capsec::fs::read("/tmp/data", cap).unwrap()
438/// }
439/// ```
440///
441/// # Supported categories
442///
443/// `all`, `fs`, `net`, `env`, `process`
444#[proc_macro_attribute]
445pub fn deny(attr: TokenStream, item: TokenStream) -> TokenStream {
446    let denied = parse_macro_input!(attr with Punctuated::<Meta, Token![,]>::parse_terminated);
447
448    let item_clone: proc_macro2::TokenStream = item.clone().into();
449    let func = match syn::parse::<ItemFn>(item) {
450        Ok(f) => f,
451        Err(e) => {
452            let err = e.into_compile_error();
453            return quote! { #err #item_clone }.into();
454        }
455    };
456
457    let deny_names: Vec<String> = denied
458        .iter()
459        .map(|meta| {
460            meta.path()
461                .get_ident()
462                .map(|i| i.to_string())
463                .unwrap_or_default()
464        })
465        .collect();
466
467    let doc_string = format!("capsec::deny({})", deny_names.join(", "));
468
469    let func_vis = &func.vis;
470    let func_sig = &func.sig;
471    let func_block = &func.block;
472    let func_attrs = &func.attrs;
473
474    let expanded = quote! {
475        #(#func_attrs)*
476        #[doc = #doc_string]
477        #func_vis #func_sig
478            #func_block
479    };
480
481    expanded.into()
482}
483
484/// Injects `CapRoot` creation into a function entry point.
485///
486/// Removes the first parameter (which must be typed as `CapRoot`) and prepends
487/// `let {param_name} = capsec::root();` to the function body.
488///
489/// # Usage
490///
491/// ```rust,ignore
492/// #[capsec::main]
493/// fn main(root: CapRoot) {
494///     let fs = root.fs_read();
495///     // ...
496/// }
497/// ```
498///
499/// # With `#[tokio::main]`
500///
501/// Place `#[capsec::main]` above `#[tokio::main]`:
502///
503/// ```rust,ignore
504/// #[capsec::main]
505/// #[tokio::main]
506/// async fn main(root: CapRoot) { ... }
507/// ```
508#[proc_macro_attribute]
509pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
510    let func = parse_macro_input!(item as ItemFn);
511
512    match main_inner(&func) {
513        Ok(tokens) => tokens.into(),
514        Err(e) => e.into_compile_error().into(),
515    }
516}
517
518fn main_inner(func: &ItemFn) -> syn::Result<proc_macro2::TokenStream> {
519    if func.sig.inputs.is_empty() {
520        if func.sig.asyncness.is_some() {
521            return Err(syn::Error::new_spanned(
522                &func.sig,
523                "#[capsec::main] found no CapRoot parameter. If combining with #[tokio::main], \
524                 place #[capsec::main] above #[tokio::main]:\n\n  \
525                 #[capsec::main]\n  \
526                 #[tokio::main]\n  \
527                 async fn main(root: CapRoot) { ... }",
528            ));
529        }
530        return Err(syn::Error::new_spanned(
531            &func.sig,
532            "#[capsec::main] expected first parameter of type CapRoot",
533        ));
534    }
535
536    // Extract first parameter
537    let first_arg = &func.sig.inputs[0];
538    let (param_name, param_type) = match first_arg {
539        FnArg::Typed(pat_type) => {
540            let name = if let Pat::Ident(pi) = &*pat_type.pat {
541                pi.ident.clone()
542            } else {
543                return Err(syn::Error::new_spanned(
544                    &pat_type.pat,
545                    "#[capsec::main] expected a simple identifier for the CapRoot parameter",
546                ));
547            };
548            (name, &*pat_type.ty)
549        }
550        FnArg::Receiver(r) => {
551            return Err(syn::Error::new_spanned(
552                r,
553                "#[capsec::main] cannot be used on methods with self",
554            ));
555        }
556    };
557
558    // Validate type is CapRoot
559    let type_str = quote!(#param_type).to_string().replace(' ', "");
560    if type_str != "CapRoot" && type_str != "capsec::CapRoot" {
561        return Err(syn::Error::new_spanned(
562            param_type,
563            "first parameter must be CapRoot",
564        ));
565    }
566
567    // Build new signature without the first parameter
568    let remaining_params: Vec<_> = func.sig.inputs.iter().skip(1).collect();
569    let func_attrs = &func.attrs;
570    let func_vis = &func.vis;
571    let func_name = &func.sig.ident;
572    let func_generics = &func.sig.generics;
573    let func_output = &func.sig.output;
574    let func_asyncness = &func.sig.asyncness;
575    let func_block = &func.block;
576
577    Ok(quote! {
578        #(#func_attrs)*
579        #func_vis #func_asyncness fn #func_name #func_generics(#(#remaining_params),*) #func_output {
580            let #param_name = capsec::root();
581            #func_block
582        }
583    })
584}
585
586/// Transforms a struct with permission-type fields into a capability context.
587///
588/// Generates:
589/// - Field types rewritten from `PermType` to `Cap<PermType>` (or `SendCap<PermType>`)
590/// - A `new(root: &CapRoot) -> Self` constructor
591/// - `impl Has<P>` for each field's permission type
592///
593/// # Usage
594///
595/// ```rust,ignore
596/// #[capsec::context]
597/// struct AppCtx {
598///     fs: FsRead,
599///     net: NetConnect,
600/// }
601///
602/// // Send variant for async/threaded code:
603/// #[capsec::context(send)]
604/// struct AsyncCtx {
605///     fs: FsRead,
606///     net: NetConnect,
607/// }
608/// ```
609#[proc_macro_attribute]
610pub fn context(attr: TokenStream, item: TokenStream) -> TokenStream {
611    let attr2: proc_macro2::TokenStream = attr.into();
612    let input = parse_macro_input!(item as ItemStruct);
613
614    match context_inner(attr2, &input) {
615        Ok(tokens) => tokens.into(),
616        Err(e) => e.into_compile_error().into(),
617    }
618}
619
620fn context_inner(
621    attr: proc_macro2::TokenStream,
622    input: &ItemStruct,
623) -> syn::Result<proc_macro2::TokenStream> {
624    // Parse `send` flag
625    let attr_str = attr.to_string();
626    let is_send = match attr_str.trim() {
627        "" => false,
628        "send" => true,
629        other => {
630            return Err(syn::Error::new_spanned(
631                &attr,
632                format!("unexpected attribute '{}', expected empty or 'send'", other),
633            ));
634        }
635    };
636
637    // Reject generics
638    if !input.generics.params.is_empty() {
639        return Err(syn::Error::new_spanned(
640            &input.generics,
641            "#[capsec::context] does not support generic structs",
642        ));
643    }
644
645    // Get named fields
646    let fields = match &input.fields {
647        syn::Fields::Named(f) => f,
648        _ => {
649            return Err(syn::Error::new_spanned(
650                input,
651                "#[capsec::context] requires a struct with named fields",
652            ));
653        }
654    };
655
656    // Validate fields and collect permission info
657    // Each entry: (field_name, resolved_perm_type_tokens)
658    let mut field_infos: Vec<(syn::Ident, proc_macro2::TokenStream)> = Vec::new();
659    let mut seen_perms: std::collections::HashSet<String> = std::collections::HashSet::new();
660
661    for field in &fields.named {
662        let field_name = field.ident.as_ref().unwrap().clone();
663        let ty = &field.ty;
664
665        // Check for tuple types
666        if let Type::Tuple(_) = ty {
667            return Err(syn::Error::new_spanned(
668                ty,
669                "tuple permission types are not supported in context structs — use separate fields instead",
670            ));
671        }
672
673        // Extract type path
674        let (perm_key, perm_tokens) = match ty {
675            Type::Path(tp) => {
676                if let Some(seg) = tp.path.segments.last() {
677                    let ident_str = seg.ident.to_string();
678                    // Known built-in? Qualify with capsec_core::permission::
679                    if KNOWN_PERMISSIONS.contains(&ident_str.as_str()) {
680                        let ident = &seg.ident;
681                        (ident_str, quote! { capsec_core::permission::#ident })
682                    } else {
683                        // Custom permission — pass through original type path
684                        (ident_str, quote! { #tp })
685                    }
686                } else {
687                    return Err(syn::Error::new_spanned(
688                        ty,
689                        format!("field '{}' has an empty type path", field_name,),
690                    ));
691                }
692            }
693            _ => {
694                return Err(syn::Error::new_spanned(
695                    ty,
696                    format!(
697                        "field '{}' has type '{}', which is not a valid permission type",
698                        field_name,
699                        quote!(#ty),
700                    ),
701                ));
702            }
703        };
704
705        // Check for duplicates
706        if !seen_perms.insert(perm_key.clone()) {
707            return Err(syn::Error::new_spanned(
708                ty,
709                format!(
710                    "duplicate permission type '{}' — each permission can only appear once in a context struct",
711                    perm_key
712                ),
713            ));
714        }
715
716        field_infos.push((field_name, perm_tokens));
717    }
718
719    let struct_name = &input.ident;
720    let struct_vis = &input.vis;
721    let struct_attrs = &input.attrs;
722
723    // Generate struct fields with rewritten types
724    let struct_fields: Vec<_> = field_infos
725        .iter()
726        .map(|(name, perm)| {
727            if is_send {
728                quote! { #name: capsec_core::cap::SendCap<#perm> }
729            } else {
730                quote! { #name: capsec_core::cap::Cap<#perm> }
731            }
732        })
733        .collect();
734
735    // Generate constructor fields
736    let constructor_fields: Vec<_> = field_infos
737        .iter()
738        .map(|(name, perm)| {
739            if is_send {
740                quote! { #name: root.grant::<#perm>().make_send() }
741            } else {
742                quote! { #name: root.grant::<#perm>() }
743            }
744        })
745        .collect();
746
747    // Generate Has<P> impls
748    let has_impls: Vec<_> = field_infos
749        .iter()
750        .map(|(name, perm)| {
751            quote! {
752                impl capsec_core::has::Has<#perm> for #struct_name {
753                    fn cap_ref(&self) -> capsec_core::cap::Cap<#perm> {
754                        self.#name.cap_ref()
755                    }
756                }
757            }
758        })
759        .collect();
760
761    Ok(quote! {
762        #(#struct_attrs)*
763        #struct_vis struct #struct_name {
764            #(#struct_fields,)*
765        }
766
767        impl #struct_name {
768            /// Creates a new context by granting all capabilities from the root.
769            pub fn new(root: &capsec_core::root::CapRoot) -> Self {
770                Self {
771                    #(#constructor_fields,)*
772                }
773            }
774        }
775
776        #(#has_impls)*
777    })
778}