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
10const 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
158pub 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 if self.has_error() {
182 return attr.clone();
183 }
184
185 if !attr.path().is_ident("llvm_versions") {
187 return attr.clone();
188 }
189
190 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}