inkwell_internals/
cfg.rs

1use proc_macro2::{Span, TokenStream};
2use quote::quote;
3use std::cmp::Ordering;
4use syn::fold::Fold;
5use syn::parse::{Error, Parse, ParseStream, Result};
6use syn::spanned::Spanned;
7use syn::{Attribute, Field, Item, Token, Variant};
8use syn::{Lit, RangeLimits};
9
10// This array should match the LLVM features in the top level Cargo manifest
11const FEATURE_VERSIONS: &[&str] = &[
12    "llvm4-0", "llvm5-0", "llvm6-0", "llvm7-0", "llvm8-0", "llvm9-0", "llvm10-0", "llvm11-0", "llvm12-0", "llvm13-0",
13    "llvm14-0", "llvm15-0", "llvm16-0", "llvm17-0", "llvm18-0",
14];
15
16pub struct VersionRange {
17    start: Option<Version>,
18    limits: RangeLimits,
19    end: Option<Version>,
20    features: &'static [&'static str],
21}
22
23impl Parse for VersionRange {
24    fn parse(input: ParseStream) -> Result<Self> {
25        let start = if input.peek(Token![..]) || input.peek(Token![..=]) {
26            None
27        } else {
28            Some(input.parse()?)
29        };
30        let limits = input.parse::<RangeLimits>()?;
31        let end = if matches!(limits, RangeLimits::HalfOpen(_)) && (input.is_empty() || input.peek(Token![,])) {
32            None
33        } else {
34            Some(input.parse()?)
35        };
36        let mut this = Self {
37            start,
38            limits,
39            end,
40            features: &[],
41        };
42        this.features = this.get_features()?;
43        Ok(this)
44    }
45}
46
47impl VersionRange {
48    fn doc_cfg(&self) -> Option<TokenStream> {
49        if cfg!(feature = "nightly") {
50            let features = self.features;
51            Some(quote! {
52                #[doc(cfg(any(#(feature = #features),*)))]
53            })
54        } else {
55            None
56        }
57    }
58
59    fn cfg(&self) -> TokenStream {
60        let features = self.features;
61        quote! {
62            #[cfg(any(#(feature = #features),*))]
63        }
64    }
65
66    fn get_features(&self) -> Result<&'static [&'static str]> {
67        let features = FEATURE_VERSIONS;
68        let start = self.start.as_ref().map(|v| v.get_index(features)).transpose()?;
69        if let Some(0) = start {
70            let min = features[0];
71            return Err(Error::new(
72                self.start.as_ref().unwrap().span,
73                format!("start version is the same as the minimum version ({min})"),
74            ));
75        }
76        let start = start.unwrap_or(0);
77
78        let end = self.end.as_ref().map(|v| v.get_index(features)).transpose()?;
79        if let Some(end) = end {
80            let span = || self.end.as_ref().unwrap().span;
81            match end.cmp(&start) {
82                Ordering::Less => return Err(Error::new(span(), "end version is before start version")),
83                Ordering::Equal => return Err(Error::new(span(), "start and end versions are the same")),
84                Ordering::Greater => {},
85            }
86        }
87        let selected = match (self.limits, end) {
88            (RangeLimits::Closed(_), end) => &features[start..=end.expect("already checked")],
89            (RangeLimits::HalfOpen(_), None) => &features[start..],
90            (RangeLimits::HalfOpen(_), Some(end)) => &features[start..end],
91        };
92        if selected.len() == features.len() {
93            return Err(Error::new(self.span(), "selected all features, remove this attribute"));
94        }
95        Ok(selected)
96    }
97
98    fn span(&self) -> Span {
99        match (&self.start, &self.end) {
100            (Some(start), Some(end)) => start.span.join(end.span).unwrap(),
101            (Some(start), None) => start.span,
102            (None, Some(end)) => end.span,
103            (None, None) => self.limits.span(),
104        }
105    }
106}
107
108struct Version {
109    major: u32,
110    minor: u32,
111    span: Span,
112}
113
114impl Parse for Version {
115    fn parse(input: ParseStream) -> Result<Self> {
116        let lit = input.parse::<Lit>()?;
117        let (major, minor) = match &lit {
118            Lit::Int(int) => (int.base10_parse()?, 0),
119            Lit::Float(float) => {
120                let s = float.base10_digits();
121                let mut parts = s.split('.');
122                let major = parts
123                    .next()
124                    .unwrap()
125                    .parse()
126                    .map_err(|e| syn::Error::new(float.span(), e))?;
127                let minor = if let Some(minor) = parts.next() {
128                    minor.parse().map_err(|e| syn::Error::new(float.span(), e))?
129                } else {
130                    0
131                };
132                (major, minor)
133            },
134            _ => return Err(Error::new(lit.span(), "expected integer or float")),
135        };
136        Ok(Self {
137            major,
138            minor,
139            span: lit.span(),
140        })
141    }
142}
143
144impl Version {
145    fn get_index(&self, features: &[&str]) -> Result<usize> {
146        let feature = self.as_feature();
147        match features.iter().position(|&s| s == feature) {
148            None => Err(Error::new(self.span, format!("undefined feature version: {feature:?}"))),
149            Some(index) => Ok(index),
150        }
151    }
152
153    fn as_feature(&self) -> String {
154        format!("llvm{}-{}", self.major, self.minor)
155    }
156}
157
158/// Folder for expanding `llvm_versions` attributes.
159pub struct VersionFolder {
160    result: Result<()>,
161}
162
163impl VersionFolder {
164    pub fn new() -> Self {
165        Self { result: Ok(()) }
166    }
167
168    pub fn fold_any<T>(f: impl FnOnce(&mut Self, T) -> T, t: T) -> Result<T> {
169        let mut folder = VersionFolder::new();
170        let t = f(&mut folder, t);
171        folder.result?;
172        Ok(t)
173    }
174
175    fn has_error(&self) -> bool {
176        self.result.is_err()
177    }
178
179    fn expand_llvm_versions_attr(&mut self, attr: &Attribute) -> Attribute {
180        // Make no modifications if we've generated an error
181        if self.has_error() {
182            return attr.clone();
183        }
184
185        // If this isn't an llvm_versions attribute, skip it
186        if !attr.path().is_ident("llvm_versions") {
187            return attr.clone();
188        }
189
190        // Expand from llvm_versions to raw cfg attribute
191        match attr.parse_args::<VersionRange>() {
192            Ok(version_range) => {
193                let cfg = version_range.cfg();
194                syn::parse_quote!(#cfg)
195            },
196            Err(err) => {
197                self.result = Err(err);
198                attr.clone()
199            },
200        }
201    }
202}
203
204impl Fold for VersionFolder {
205    fn fold_variant(&mut self, mut variant: Variant) -> Variant {
206        if self.has_error() {
207            return variant;
208        }
209
210        let attrs = variant
211            .attrs
212            .iter()
213            .map(|attr| self.expand_llvm_versions_attr(attr))
214            .collect::<Vec<_>>();
215        variant.attrs = attrs;
216        variant
217    }
218
219    fn fold_field(&mut self, mut field: Field) -> Field {
220        if self.has_error() {
221            return field;
222        }
223
224        let attrs = field
225            .attrs
226            .iter()
227            .map(|attr| self.expand_llvm_versions_attr(attr))
228            .collect::<Vec<_>>();
229        field.attrs = attrs;
230        field
231    }
232}
233
234pub fn expand(args: Option<VersionRange>, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
235    let input_clone = input.clone();
236    let item = syn::parse_macro_input!(input as Item);
237    let args = match args {
238        Some(args) => {
239            let cfg = args.cfg();
240            let doc_cfg = args.doc_cfg();
241            quote! { #cfg #doc_cfg }
242        },
243        None => quote! {},
244    };
245    match VersionFolder::fold_any(Fold::fold_item, item) {
246        Ok(item) => quote! { #args #item },
247        Err(err) => {
248            let err = err.into_compile_error();
249            let input = proc_macro2::TokenStream::from(input_clone);
250            quote! { #err #args #input }
251        },
252    }
253    .into()
254}