stabby_macros/
lib.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   Pierre Avital, <pierre.avital@me.com>
13//
14
15use std::collections::HashSet;
16
17use proc_macro::TokenStream;
18use proc_macro2::{Ident, Span};
19use quote::{quote, ToTokens};
20use syn::{parse::Parser, DeriveInput, TypeParamBound};
21
22#[allow(dead_code)]
23pub(crate) fn logfile(logfile: std::path::PathBuf) -> impl std::io::Write {
24    use std::{fs::OpenOptions, io::BufWriter};
25    let logfile = BufWriter::new(
26        OpenOptions::new()
27            .append(true)
28            .create(true)
29            .open(logfile)
30            .unwrap(),
31    );
32    logfile
33}
34
35#[allow(unused_macros)]
36macro_rules! log {
37    ($path: literal, $pat: literal, $e: expr) => {{
38        let logfile = std::path::PathBuf::from($path);
39        use std::io::Write;
40        let e = $e;
41        writeln!(crate::logfile(logfile), $pat, e);
42        e
43    }};
44    ($pat: literal, $e: expr) => {
45        log!("logfile.txt", $pat, $e)
46    };
47    ($e: expr) => {
48        log!("{}", $e)
49    };
50}
51
52pub(crate) fn tl_mod() -> proc_macro2::TokenStream {
53    match proc_macro_crate::crate_name("stabby-abi") {
54        Ok(proc_macro_crate::FoundCrate::Itself) => return quote!(crate),
55        Ok(proc_macro_crate::FoundCrate::Name(crate_name)) => {
56            let crate_name = Ident::new(&crate_name, Span::call_site());
57            return quote!(#crate_name);
58        }
59        _ => {}
60    }
61    match proc_macro_crate::crate_name("stabby")
62        .expect("Couldn't find `stabby` in your dependencies")
63    {
64        proc_macro_crate::FoundCrate::Itself => quote!(crate::abi),
65        proc_macro_crate::FoundCrate::Name(crate_name) => {
66            let crate_name = Ident::new(&crate_name, Span::call_site());
67            quote!(#crate_name::abi)
68        }
69    }
70}
71
72/// The lifeblood of stabby. [Click for the tutorial](https://docs.rs/stabby/latest/stabby/_tutorial_/index.html)
73#[proc_macro_attribute]
74pub fn stabby(stabby_attrs: TokenStream, tokens: TokenStream) -> TokenStream {
75    if let Ok(DeriveInput {
76        attrs,
77        vis,
78        ident,
79        generics,
80        data,
81    }) = syn::parse(tokens.clone())
82    {
83        match data {
84            syn::Data::Struct(data) => {
85                structs::stabby(attrs, vis, ident, generics, data, &stabby_attrs)
86            }
87            syn::Data::Enum(data) => {
88                enums::stabby(attrs, vis, ident, generics, data, &stabby_attrs)
89            }
90            syn::Data::Union(data) => {
91                unions::stabby(attrs, vis, ident, generics, data, &stabby_attrs)
92            }
93        }
94    } else if let Ok(fn_spec) = syn::parse(tokens.clone()) {
95        functions::stabby(syn::parse(stabby_attrs).unwrap(), fn_spec)
96    } else if let Ok(trait_spec) = syn::parse(tokens.clone()) {
97        traits::stabby(trait_spec, &stabby_attrs)
98    } else if let Ok(async_block) = syn::parse::<syn::ExprAsync>(tokens) {
99        quote!(Box::new(#async_block).into())
100    } else {
101        panic!("Expected a type declaration, a trait declaration or a function declaration")
102    }
103    .into()
104}
105
106/// Returns the appropriate type of vtable for a trait object.
107///
108/// Usage: `vtable!(TraitA + TraitB<Output=u16> + Send + Sync)`
109/// Note that the ordering of traits is significant.
110#[proc_macro]
111pub fn vtable(tokens: TokenStream) -> TokenStream {
112    let st = tl_mod();
113    let bounds =
114        syn::punctuated::Punctuated::<TypeParamBound, syn::token::Add>::parse_separated_nonempty
115            .parse(tokens)
116            .unwrap();
117    let mut vt = quote!(#st::vtable::VtDrop);
118    for bound in bounds {
119        match &bound {
120            TypeParamBound::Trait(t) => vt = quote!(< dyn #t as #st::vtable::CompoundVt >::Vt<#vt>),
121            TypeParamBound::Lifetime(lt) => panic!("Cannot give lifetimes to vtables, use `Dyn<{lt}, P, Vt>` or `DynRef<{lt}, Vt> instead`"),
122        }
123    }
124    vt.into()
125}
126
127enum PtrType {
128    Path(proc_macro2::TokenStream),
129    Ref,
130    RefMut,
131}
132struct DynPtr {
133    ptr: PtrType,
134    bounds: Vec<syn::TraitBound>,
135    lifetime: Option<syn::Lifetime>,
136}
137impl syn::parse::Parse for DynPtr {
138    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
139        let (mut this, elem) = match input.parse::<syn::Type>()? {
140            syn::Type::Path(syn::TypePath {
141                path:
142                    syn::Path {
143                        leading_colon,
144                        mut segments,
145                    },
146                ..
147            }) => {
148                let syn::PathSegment {
149                    ident,
150                    arguments:
151                        syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
152                            colon2_token: None,
153                            mut args,
154                            ..
155                        }),
156                } = segments.pop().unwrap().into_value()
157                else {
158                    panic!()
159                };
160                if args.len() != 1 {
161                    panic!("Pointer-type must have exactly one generic argument containing `dyn Bounds`")
162                }
163                let arg = args.pop().unwrap().into_value();
164                let syn::GenericArgument::Type(ty) = arg else {
165                    panic!()
166                };
167                (
168                    DynPtr {
169                        ptr: PtrType::Path(quote!(#leading_colon #segments #ident)),
170                        lifetime: None,
171                        bounds: Vec::new(),
172                    },
173                    ty,
174                )
175            }
176            syn::Type::Reference(syn::TypeReference {
177                lifetime,
178                mutability,
179                elem,
180                ..
181            }) => (
182                DynPtr {
183                    ptr: if mutability.is_some() {
184                        PtrType::RefMut
185                    } else {
186                        PtrType::Ref
187                    },
188                    lifetime,
189                    bounds: Vec::new(),
190                },
191                *elem,
192            ),
193            _ => panic!("Only references and paths are supported by this macro"),
194        };
195        let syn::Type::TraitObject(syn::TypeTraitObject { bounds, .. }) = elem else {
196            panic!("expected `dyn` not found")
197        };
198        for bound in bounds {
199            match bound {
200                TypeParamBound::Trait(t) => this.bounds.push(t),
201                TypeParamBound::Lifetime(lt) => {
202                    if this.lifetime.is_some() {
203                        panic!("Only a single lifetime is supported in this macro")
204                    } else {
205                        this.lifetime = Some(lt)
206                    }
207                }
208            }
209        }
210        Ok(this)
211    }
212}
213
214/// Returns the appropriate type for a stabby equivalent of a trait object.
215///
216/// Usage: `dynptr!(Box<dyn TraitA + TraitB<Output=u16> + Send + Sync + 'a>)`
217/// Note that the ordering of traits is significant.
218#[proc_macro]
219pub fn dynptr(tokens: TokenStream) -> TokenStream {
220    let st = tl_mod();
221    let DynPtr {
222        ptr,
223        bounds,
224        lifetime,
225    } = syn::parse(tokens).unwrap();
226    let mut vt = quote!(#st::vtable::VtDrop);
227    let lifetime = lifetime.unwrap_or(syn::Lifetime::new("'static", Span::call_site()));
228    for bound in bounds {
229        vt = quote!(< dyn #bound as #st::vtable::CompoundVt<#lifetime> >::Vt<#vt>);
230    }
231    match ptr {
232        PtrType::Path(path) => quote!(#st::Dyn<#lifetime, #path<()>, #vt>),
233        PtrType::RefMut => quote!(#st::Dyn<#lifetime, &#lifetime mut (), #vt>),
234        PtrType::Ref => quote!(#st::DynRef<#lifetime, #vt>),
235    }
236    .into()
237}
238
239mod enums;
240mod functions;
241mod structs;
242mod traits;
243mod unions;
244pub(crate) mod utils;
245
246mod tyops;
247#[proc_macro]
248pub fn tyeval(tokens: TokenStream) -> TokenStream {
249    tyops::tyeval(&tokens.into()).into()
250}
251
252mod gen_closures;
253#[proc_macro]
254pub fn gen_closures_impl(_: TokenStream) -> TokenStream {
255    gen_closures::gen_closures().into()
256}
257
258#[derive(Clone)]
259enum Type {
260    Syn(syn::Type),
261    Report(Report),
262}
263impl From<syn::Type> for Type {
264    fn from(value: syn::Type) -> Self {
265        Self::Syn(value)
266    }
267}
268impl From<Report> for Type {
269    fn from(value: Report) -> Self {
270        Self::Report(value)
271    }
272}
273
274#[derive(Debug, Clone, Copy)]
275pub(crate) enum Tyty {
276    Struct,
277    Union,
278    Enum(enums::Repr),
279}
280impl ToTokens for Tyty {
281    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
282        let st = crate::tl_mod();
283        let tyty = match self {
284            Tyty::Struct => quote!(#st::report::TyTy::Struct),
285            Tyty::Union => quote!(#st::report::TyTy::Union),
286            Tyty::Enum(r) => {
287                let s = format!("{r:?}");
288                quote!(#st::report::TyTy::Enum(#st::str::Str::new(#s)))
289            }
290        };
291        tokens.extend(tyty);
292    }
293}
294#[derive(Clone)]
295pub(crate) struct Report {
296    name: String,
297    fields: Vec<(String, Type)>,
298    version: u32,
299    module: proc_macro2::TokenStream,
300    pub tyty: Tyty,
301}
302impl Report {
303    pub fn r#struct(
304        name: impl Into<String>,
305        version: u32,
306        module: proc_macro2::TokenStream,
307    ) -> Self {
308        Self {
309            name: name.into(),
310            fields: Vec::new(),
311            version,
312            module: if module.is_empty() {
313                quote!(::core::module_path!())
314            } else {
315                module
316            },
317            tyty: Tyty::Struct,
318        }
319    }
320    pub fn r#enum(name: impl Into<String>, version: u32, module: proc_macro2::TokenStream) -> Self {
321        Self {
322            name: name.into(),
323            fields: Vec::new(),
324            version,
325            module: if module.is_empty() {
326                quote!(::core::module_path!())
327            } else {
328                module
329            },
330            tyty: Tyty::Enum(enums::Repr::Stabby),
331        }
332    }
333    pub fn r#union(
334        name: impl Into<String>,
335        version: u32,
336        module: proc_macro2::TokenStream,
337    ) -> Self {
338        Self {
339            name: name.into(),
340            fields: Vec::new(),
341            version,
342            module: if module.is_empty() {
343                quote!(::core::module_path!())
344            } else {
345                module
346            },
347            tyty: Tyty::Union,
348        }
349    }
350    pub fn add_field(&mut self, name: String, ty: impl Into<Type>) {
351        self.fields.push((name, ty.into()));
352    }
353    fn __bounds(
354        &self,
355        bounded_types: &mut HashSet<syn::Type>,
356        mut report_bounds: proc_macro2::TokenStream,
357        st: &proc_macro2::TokenStream,
358    ) -> proc_macro2::TokenStream {
359        for (_, ty) in self.fields.iter() {
360            match ty {
361                Type::Syn(ty) => {
362                    if bounded_types.insert(ty.clone()) {
363                        report_bounds = quote!(#ty: #st::IStable, #report_bounds);
364                    }
365                }
366                Type::Report(report) => {
367                    report_bounds = report.__bounds(bounded_types, report_bounds, st)
368                }
369            }
370        }
371        report_bounds
372    }
373    pub fn bounds(&self) -> proc_macro2::TokenStream {
374        let st = crate::tl_mod();
375        let mut bounded_types = HashSet::new();
376        self.__bounds(&mut bounded_types, quote!(), &st)
377    }
378
379    pub fn crepr(&self) -> proc_macro2::TokenStream {
380        let st = crate::tl_mod();
381        match self.tyty {
382            Tyty::Struct => {
383                // TODO: For user comfort, having this would be better, but reading from env vars doesn't work in proc-macros.
384                // let max_tuple = std::env::var("CARGO_CFG_STABBY_MAX_TUPLE")
385                //     .map_or(32, |s| s.parse().unwrap_or(32))
386                //     .max(10);
387                // panic!("{max_tuple}");
388                // if self.fields.len() > max_tuple {
389                //     panic!("stabby doesn't support structures with more than {max_tuple} direct fields, you should probably split it at that point; or you can also raise this limit using `--cfg stabby_max_tuple=N` to your RUSTFLAGS at the cost of higher compile times")
390                // }
391                let tuple = quote::format_ident!("Tuple{}", self.fields.len());
392                let fields = self.fields.iter().map(|f| match &f.1 {
393                    Type::Syn(ty) => quote! (<#ty as #st::IStable>::CType),
394                    Type::Report(r) => r.crepr(),
395                });
396                quote! {
397                    #st::tuple::#tuple <#(#fields,)*>
398                }
399            }
400            Tyty::Union => {
401                let mut crepr = quote!(());
402                for f in &self.fields {
403                    let ty = match &f.1 {
404                        Type::Syn(ty) => quote! (#ty),
405                        Type::Report(r) => r.crepr(),
406                    };
407                    crepr = quote!(#st::Union<#ty, #crepr>);
408                }
409                quote! {<#crepr as #st::IStable>::CType}
410            }
411            Tyty::Enum(r) => {
412                let mut clone = self.clone();
413                clone.tyty = Tyty::Union;
414                let crepr = clone.crepr();
415                let determinant = quote::format_ident!("{r:?}");
416                quote! {
417                    #st::tuple::Tuple2<#determinant, #crepr>
418                }
419            }
420        }
421    }
422}
423impl ToTokens for Report {
424    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
425        let st = crate::tl_mod();
426        let mut fields = quote!(None);
427
428        for (name, ty) in &self.fields {
429            fields = match ty {
430                Type::Syn(ty) => quote! {
431                    Some(& #st::report::FieldReport {
432                        name: #st::str::Str::new(#name),
433                        ty: <#ty as #st::IStable>::REPORT,
434                        next_field: #st::StableLike::new(#fields)
435                    })
436                },
437                Type::Report(re) => quote! {
438                    Some(& #st::report::FieldReport {
439                        name: #st::str::Str::new(#name),
440                        ty: &#re,
441                        next_field: #st::StableLike::new(#fields)
442                    })
443                },
444            }
445        }
446        let Self {
447            name,
448            version,
449            tyty,
450            module,
451            ..
452        } = self;
453        tokens.extend(quote!(#st::report::TypeReport {
454            name: #st::str::Str::new(#name),
455            module: #st::str::Str::new(#module),
456            fields: unsafe{#st::StableLike::new(#fields)},
457            version: #version,
458            tyty: #tyty,
459        }));
460    }
461}
462
463#[proc_macro_attribute]
464pub fn export(attrs: TokenStream, fn_spec: TokenStream) -> TokenStream {
465    crate::functions::export(attrs, syn::parse(fn_spec).unwrap()).into()
466}
467
468#[proc_macro_attribute]
469pub fn import(attrs: TokenStream, fn_spec: TokenStream) -> TokenStream {
470    crate::functions::import(attrs, syn::parse(fn_spec).unwrap()).into()
471}
472
473#[proc_macro]
474pub fn canary_suffixes(_: TokenStream) -> TokenStream {
475    let mut stream = quote::quote!();
476    for (name, spec) in functions::CanarySpec::ARRAY.iter().skip(2) {
477        let id = quote::format_ident!("CANARY_{}", name.to_ascii_uppercase());
478        let suffix = spec.to_string();
479        stream.extend(quote::quote!(pub const #id: &'static str = #suffix;));
480    }
481    stream.into()
482}
483
484trait Unself {
485    fn unself(&self, this: &syn::Ident) -> Self;
486}
487impl Unself for syn::Path {
488    fn unself(&self, this: &syn::Ident) -> Self {
489        let syn::Path {
490            leading_colon,
491            segments,
492        } = self;
493        if self.is_ident("Self") || self.is_ident(this) {
494            let st = crate::tl_mod();
495            return syn::parse2(quote! {#st::istable::_Self}).unwrap();
496        }
497        syn::Path {
498            leading_colon: *leading_colon,
499            segments: segments
500                .iter()
501                .map(|syn::PathSegment { ident, arguments }| syn::PathSegment {
502                    ident: ident.clone(),
503                    arguments: match arguments {
504                        syn::PathArguments::None => syn::PathArguments::None,
505                        syn::PathArguments::AngleBracketed(
506                            syn::AngleBracketedGenericArguments {
507                                colon2_token,
508                                lt_token,
509                                args,
510                                gt_token,
511                            },
512                        ) => syn::PathArguments::AngleBracketed(
513                            syn::AngleBracketedGenericArguments {
514                                colon2_token: *colon2_token,
515                                lt_token: *lt_token,
516                                args: args
517                                    .iter()
518                                    .map(|arg| match arg {
519                                        syn::GenericArgument::Type(ty) => {
520                                            syn::GenericArgument::Type(ty.unself(this))
521                                        }
522                                        syn::GenericArgument::Binding(syn::Binding {
523                                            ident,
524                                            eq_token,
525                                            ty,
526                                        }) => syn::GenericArgument::Binding(syn::Binding {
527                                            ident: ident.clone(),
528                                            eq_token: *eq_token,
529                                            ty: ty.unself(this),
530                                        }),
531                                        syn::GenericArgument::Constraint(syn::Constraint {
532                                            ident,
533                                            colon_token,
534                                            bounds,
535                                        }) => syn::GenericArgument::Constraint(syn::Constraint {
536                                            ident: ident.clone(),
537                                            colon_token: *colon_token,
538                                            bounds: bounds.iter().map(|b| b.unself(this)).collect(),
539                                        }),
540                                        other => other.clone(),
541                                    })
542                                    .collect(),
543                                gt_token: *gt_token,
544                            },
545                        ),
546                        syn::PathArguments::Parenthesized(syn::ParenthesizedGenericArguments {
547                            paren_token,
548                            inputs,
549                            output,
550                        }) => {
551                            syn::PathArguments::Parenthesized(syn::ParenthesizedGenericArguments {
552                                paren_token: *paren_token,
553                                inputs: inputs.iter().map(|t| t.unself(this)).collect(),
554                                output: match output {
555                                    syn::ReturnType::Default => syn::ReturnType::Default,
556                                    syn::ReturnType::Type(arrow, ty) => {
557                                        syn::ReturnType::Type(*arrow, Box::new(ty.unself(this)))
558                                    }
559                                },
560                            })
561                        }
562                    },
563                })
564                .collect(),
565        }
566    }
567}
568impl Unself for syn::TypeParamBound {
569    fn unself(&self, this: &syn::Ident) -> Self {
570        match self {
571            TypeParamBound::Trait(syn::TraitBound {
572                paren_token,
573                modifier,
574                lifetimes,
575                path,
576            }) => TypeParamBound::Trait(syn::TraitBound {
577                paren_token: *paren_token,
578                modifier: *modifier,
579                lifetimes: lifetimes.clone(),
580                path: path.unself(this),
581            }),
582            TypeParamBound::Lifetime(l) => TypeParamBound::Lifetime(l.clone()),
583        }
584    }
585}
586impl Unself for syn::Type {
587    fn unself(&self, this: &syn::Ident) -> syn::Type {
588        match self {
589            syn::Type::Array(syn::TypeArray {
590                elem,
591                len,
592                bracket_token,
593                semi_token,
594            }) => syn::Type::Array(syn::TypeArray {
595                elem: Box::new(elem.unself(this)),
596                len: len.clone(),
597                bracket_token: *bracket_token,
598                semi_token: *semi_token,
599            }),
600            syn::Type::BareFn(syn::TypeBareFn {
601                lifetimes,
602                unsafety,
603                abi,
604                inputs,
605                output,
606                fn_token,
607                paren_token,
608                variadic,
609            }) => syn::Type::BareFn(syn::TypeBareFn {
610                lifetimes: lifetimes.clone(),
611                unsafety: *unsafety,
612                abi: abi.clone(),
613                inputs: inputs
614                    .iter()
615                    .map(|syn::BareFnArg { attrs, name, ty }| syn::BareFnArg {
616                        attrs: attrs.clone(),
617                        name: name.clone(),
618                        ty: ty.unself(this),
619                    })
620                    .collect(),
621                output: match output {
622                    syn::ReturnType::Default => syn::ReturnType::Default,
623                    syn::ReturnType::Type(arrow, ret) => {
624                        syn::ReturnType::Type(*arrow, Box::new(ret.unself(this)))
625                    }
626                },
627                fn_token: *fn_token,
628                paren_token: *paren_token,
629                variadic: variadic.clone(),
630            }),
631            syn::Type::Group(syn::TypeGroup { group_token, elem }) => {
632                syn::Type::Group(syn::TypeGroup {
633                    group_token: *group_token,
634                    elem: Box::new(elem.unself(this)),
635                })
636            }
637            syn::Type::ImplTrait(syn::TypeImplTrait { impl_token, bounds }) => {
638                syn::Type::ImplTrait(syn::TypeImplTrait {
639                    impl_token: *impl_token,
640                    bounds: bounds.into_iter().map(|p| p.unself(this)).collect(),
641                })
642            }
643            syn::Type::Paren(syn::TypeParen { paren_token, elem }) => {
644                syn::Type::Paren(syn::TypeParen {
645                    paren_token: *paren_token,
646                    elem: Box::new(elem.unself(this)),
647                })
648            }
649            syn::Type::Path(syn::TypePath { qself, path }) => syn::Type::Path(syn::TypePath {
650                qself: qself.as_ref().map(
651                    |syn::QSelf {
652                         lt_token,
653                         ty,
654                         position,
655                         as_token,
656                         gt_token,
657                     }| syn::QSelf {
658                        lt_token: *lt_token,
659                        ty: Box::new(ty.unself(this)),
660                        position: *position,
661                        as_token: *as_token,
662                        gt_token: *gt_token,
663                    },
664                ),
665                path: path.unself(this),
666            }),
667            syn::Type::Ptr(syn::TypePtr {
668                star_token,
669                const_token,
670                mutability,
671                elem,
672            }) => syn::Type::Ptr(syn::TypePtr {
673                star_token: *star_token,
674                const_token: *const_token,
675                mutability: *mutability,
676                elem: Box::new(elem.unself(this)),
677            }),
678            syn::Type::Reference(syn::TypeReference {
679                and_token,
680                lifetime,
681                mutability,
682                elem,
683            }) => syn::Type::Reference(syn::TypeReference {
684                and_token: *and_token,
685                lifetime: lifetime.clone(),
686                mutability: *mutability,
687                elem: Box::new(elem.unself(this)),
688            }),
689            syn::Type::Slice(syn::TypeSlice {
690                bracket_token,
691                elem,
692            }) => syn::Type::Slice(syn::TypeSlice {
693                bracket_token: *bracket_token,
694                elem: Box::new(elem.unself(this)),
695            }),
696            syn::Type::TraitObject(syn::TypeTraitObject { dyn_token, bounds }) => {
697                syn::Type::TraitObject(syn::TypeTraitObject {
698                    dyn_token: *dyn_token,
699                    bounds: bounds.iter().map(|b| b.unself(this)).collect(),
700                })
701            }
702            syn::Type::Tuple(syn::TypeTuple { paren_token, elems }) => {
703                syn::Type::Tuple(syn::TypeTuple {
704                    paren_token: *paren_token,
705                    elems: elems.iter().map(|ty| ty.unself(this)).collect(),
706                })
707            }
708            o => o.clone(),
709        }
710    }
711}