Skip to main content

annotate_derive/
lib.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::spanned::Spanned;
4use syn::{Item, Result, parse};
5
6pub(crate) use attributes::*;
7
8use crate::function::AnnotatedFunction;
9use crate::module::AnnotatedModule;
10
11mod attributes;
12mod function;
13mod module;
14
15pub(crate) fn symbol_source_path(path: &str) -> String {
16    path.replace('/', "$")
17}
18
19#[proc_macro_attribute]
20pub fn pragma(
21    attr: proc_macro::TokenStream,
22    item: proc_macro::TokenStream,
23) -> proc_macro::TokenStream {
24    let expanded = expand(attr, item).unwrap_or_else(|error| error.to_compile_error());
25    expanded.into()
26}
27
28#[proc_macro]
29pub fn environment(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
30    let expanded = expand_environment(input).unwrap_or_else(|error| error.to_compile_error());
31    expanded.into()
32}
33
34fn expand(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> Result<TokenStream> {
35    let source_path = source_path_of(item.clone());
36    let attributes = Attributes::parse(attr)?;
37    let item = parse::<Item>(item)?;
38
39    let expanded = match item {
40        Item::Fn(item_fn) => AnnotatedFunction::new(item_fn, attributes, source_path).expand(),
41        Item::Mod(item_mod) => AnnotatedModule::new(item_mod, attributes, source_path).expand(),
42        _ => syn::Error::new(
43            item.span(),
44            "Pragmas are not supported for this type of construct",
45        )
46        .to_compile_error(),
47    };
48
49    Ok(expanded)
50}
51
52fn expand_environment(input: proc_macro::TokenStream) -> Result<TokenStream> {
53    let annotate_path = if input.is_empty() {
54        None
55    } else {
56        Some(parse::<syn::Path>(input)?)
57    };
58    let generated_path = environment_source_path(proc_macro::Span::call_site());
59    let generated_path = syn::LitStr::new(generated_path.as_str(), proc_macro2::Span::call_site());
60
61    let expanded = if let Some(annotate_path) = annotate_path {
62        quote! {
63            mod __annotate {
64                use #annotate_path;
65                include!(concat!(env!("OUT_DIR"), "/annotate/", #generated_path));
66                pub const fn environment() -> &'static #annotate_path::Environment {
67                    &__annotate::ENVIRONMENT
68                }
69            }
70
71            #[doc(hidden)]
72            #[inline(never)]
73            pub fn __ensure_linked() {
74                __annotate::__ensure_linked();
75            }
76        }
77    } else {
78        quote! {
79            #[macro_use]
80            extern crate annotate;
81            extern crate alloc;
82
83            include!(concat!(env!("OUT_DIR"), "/annotate/", #generated_path));
84            #[doc(hidden)]
85            #[inline(never)]
86            pub fn __ensure_linked() {
87                __annotate::__ensure_linked();
88            }
89            pub const fn environment() -> &'static annotate::Environment {
90                &__annotate::ENVIRONMENT
91            }
92        }
93    };
94
95    Ok(expanded)
96}
97
98fn environment_source_path(span: proc_macro::Span) -> String {
99    let source_path = std::path::PathBuf::from(span.file());
100    let manifest_root = std::path::PathBuf::from(
101        std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
102            .file_name()
103            .unwrap(),
104    );
105
106    if source_path.is_absolute()
107        && let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR")
108    {
109        let manifest_dir = std::path::PathBuf::from(manifest_dir);
110        if let Ok(relative_path) = source_path.strip_prefix(&manifest_dir) {
111            return manifest_root
112                .join(relative_path)
113                .to_string_lossy()
114                .replace('\\', "/");
115        }
116    }
117
118    if source_path
119        .components()
120        .next()
121        .map(|component| component.as_os_str() == manifest_root.as_os_str())
122        .unwrap_or(false)
123    {
124        return source_path.to_string_lossy().replace('\\', "/");
125    }
126
127    manifest_root
128        .join(source_path)
129        .to_string_lossy()
130        .replace('\\', "/")
131}
132
133fn source_path_of(stream: proc_macro::TokenStream) -> String {
134    let span = stream
135        .into_iter()
136        .next()
137        .map(|token| token.span())
138        .unwrap_or_else(proc_macro::Span::call_site);
139    environment_source_path(span)
140}