1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct SupportedToolchain {
20 pub versions: &'static [&'static str],
23 pub header_digest: &'static str,
25 pub missing_symbols: &'static [&'static str],
28}
29
30impl SupportedToolchain {
31 #[must_use]
34 pub fn includes(&self, version: &str) -> bool {
35 self.versions.contains(&version)
36 }
37}
38
39pub const SUPPORTED_TOOLCHAINS: &[SupportedToolchain] = &[
53 SupportedToolchain {
54 versions: &["4.27.0"],
55 header_digest: "42255d180910bb063d97c87cfb2a61550009ca9ceb6f495069c56bfaa6c92e13",
56 missing_symbols: &[],
57 },
58 SupportedToolchain {
59 versions: &["4.28.0"],
60 header_digest: "624726e5f1f10fd77cd95b8fe8f30389312e57c8fc98e6c2f1989289bdb5fb0e",
61 missing_symbols: &[],
62 },
63 SupportedToolchain {
64 versions: &["4.28.1"],
65 header_digest: "648ecfb615ef0222cd63b5f1bbbc379a06749bc0f5f4c2eb16ffca26fd18fe81",
66 missing_symbols: &[],
67 },
68 SupportedToolchain {
69 versions: &["4.29.0"],
70 header_digest: "671683950ef412474bede2c6a2b50aecf4f99bc29e1ddaf2222ee54ad4ffb91c",
71 missing_symbols: &[],
72 },
73 SupportedToolchain {
74 versions: &["4.29.1"],
75 header_digest: "2e481a0dac7215eb16123eaef97298ae5a6d0bd0c28c534c2818e2d2f2a28efc",
76 missing_symbols: &[],
77 },
78 SupportedToolchain {
79 versions: &["4.30.0"],
80 header_digest: "5a25125970f4f1dcf85a4c403463b387a8ff93535cd4a3054cafdee1759017d7",
81 missing_symbols: &[],
82 },
83 SupportedToolchain {
84 versions: &["4.31.0-rc1", "4.31.0-rc2"],
85 header_digest: "99ef35d69709e38caf836cf9ebbdf94d4474801e04157b8a72622dbdc653ec87",
86 missing_symbols: &[],
87 },
88 SupportedToolchain {
89 versions: &["4.31.0"],
90 header_digest: "486fe204404c0fdfb753b7e089c1c0d38fbdb396206030497696165e31218992",
91 missing_symbols: &[],
92 },
93 SupportedToolchain {
94 versions: &["4.32.0-rc1", "4.32.0"],
95 header_digest: "22eed50aa703c4403010fabc12a7231ffa34dc979bd59ca1bfbac13c29a1dad2",
96 missing_symbols: &[],
97 },
98 SupportedToolchain {
107 versions: &["4.33.0-rc1"],
108 header_digest: "9018878554c5552ff3754865780d21825c2d0c5c4b47491b37bf6fe046adcd56",
109 missing_symbols: &[],
110 },
111];
112
113#[must_use]
115pub fn supported_for(version: &str) -> Option<&'static SupportedToolchain> {
116 SUPPORTED_TOOLCHAINS.iter().find(|t| t.includes(version))
117}
118
119#[must_use]
122pub fn supported_by_digest(digest: &str) -> Option<&'static SupportedToolchain> {
123 SUPPORTED_TOOLCHAINS.iter().find(|t| t.header_digest == digest)
124}
125
126#[must_use]
130pub fn symbol_present_in_window(symbol: &str) -> bool {
131 SUPPORTED_TOOLCHAINS
132 .iter()
133 .all(|t| !t.missing_symbols.contains(&symbol))
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 fn precedence_key(version: &str) -> (Vec<u64>, bool, &str) {
148 let (core, pre) = match version.split_once('-') {
149 Some((core, pre)) => (core, pre),
150 None => (version, ""),
151 };
152 let core_nums = core.split('.').map(|n| n.parse().unwrap_or(0)).collect();
153 (core_nums, pre.is_empty(), pre)
154 }
155
156 #[test]
157 fn window_is_non_empty_and_ordered_by_first_version() {
158 assert!(!SUPPORTED_TOOLCHAINS.is_empty());
159 for w in SUPPORTED_TOOLCHAINS.windows(2) {
160 let (Some(prev), Some(next)) = (w.first(), w.get(1)) else {
161 continue;
162 };
163 let (Some(a), Some(b)) = (prev.versions.first(), next.versions.first()) else {
164 continue;
165 };
166 assert!(
167 precedence_key(a) < precedence_key(b),
168 "SUPPORTED_TOOLCHAINS must be sorted ascending by first version: {a} >= {b}",
169 );
170 }
171 }
172
173 #[test]
174 fn every_entry_lists_at_least_one_version() {
175 for t in SUPPORTED_TOOLCHAINS {
176 assert!(
177 !t.versions.is_empty(),
178 "entry with digest {} has no versions",
179 t.header_digest
180 );
181 }
182 }
183
184 #[test]
185 fn digests_are_distinct() {
186 for (i, a) in SUPPORTED_TOOLCHAINS.iter().enumerate() {
187 let Some(rest) = SUPPORTED_TOOLCHAINS.get(i + 1..) else {
188 continue;
189 };
190 for b in rest {
191 assert_ne!(
192 a.header_digest, b.header_digest,
193 "{:?} and {:?} share a header digest \u{2014} merge their `versions` arrays",
194 a.versions, b.versions,
195 );
196 }
197 }
198 }
199
200 #[test]
201 fn versions_are_distinct_across_entries() {
202 let mut seen: Vec<&str> = Vec::new();
203 for t in SUPPORTED_TOOLCHAINS {
204 for &v in t.versions {
205 assert!(
206 !seen.contains(&v),
207 "version {v} appears in more than one SupportedToolchain entry",
208 );
209 seen.push(v);
210 }
211 }
212 }
213
214 #[test]
215 fn digests_are_64_lowercase_hex() {
216 for t in SUPPORTED_TOOLCHAINS {
217 assert_eq!(
218 t.header_digest.len(),
219 64,
220 "entry for {:?}: digest is not 64 chars",
221 t.versions,
222 );
223 assert!(
224 t.header_digest
225 .bytes()
226 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)),
227 "entry for {:?}: digest is not lowercase hex",
228 t.versions,
229 );
230 }
231 }
232
233 #[test]
234 fn lookups_round_trip() {
235 for t in SUPPORTED_TOOLCHAINS {
236 for &v in t.versions {
237 assert_eq!(supported_for(v), Some(t));
238 }
239 assert_eq!(supported_by_digest(t.header_digest), Some(t));
240 }
241 assert!(supported_for("0.0.0").is_none());
242 assert!(supported_by_digest("0").is_none());
243 }
244
245 #[test]
246 fn fully_present_symbols_pass_window_check() {
247 for &s in crate::REQUIRED_SYMBOLS {
248 assert!(symbol_present_in_window(s), "{s} should be in all supported toolchains");
249 }
250 }
251
252 #[test]
253 fn unknown_symbol_passes_window_check() {
254 assert!(symbol_present_in_window("lean_does_not_exist_zzz"));
258 }
259}