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] = &[
52 SupportedToolchain {
53 versions: &["4.26.0"],
54 header_digest: "e0ea3efaccceb5b75c7e9e1ab92952c8aa85c3faee28ee949dfeb8ab428ad218",
55 missing_symbols: &[],
56 },
57 SupportedToolchain {
58 versions: &["4.27.0"],
59 header_digest: "42255d180910bb063d97c87cfb2a61550009ca9ceb6f495069c56bfaa6c92e13",
60 missing_symbols: &[],
61 },
62 SupportedToolchain {
63 versions: &["4.28.0"],
64 header_digest: "624726e5f1f10fd77cd95b8fe8f30389312e57c8fc98e6c2f1989289bdb5fb0e",
65 missing_symbols: &[],
66 },
67 SupportedToolchain {
68 versions: &["4.28.1"],
69 header_digest: "648ecfb615ef0222cd63b5f1bbbc379a06749bc0f5f4c2eb16ffca26fd18fe81",
70 missing_symbols: &[],
71 },
72 SupportedToolchain {
73 versions: &["4.29.0"],
74 header_digest: "671683950ef412474bede2c6a2b50aecf4f99bc29e1ddaf2222ee54ad4ffb91c",
75 missing_symbols: &[],
76 },
77 SupportedToolchain {
78 versions: &["4.29.1"],
79 header_digest: "2e481a0dac7215eb16123eaef97298ae5a6d0bd0c28c534c2818e2d2f2a28efc",
80 missing_symbols: &[],
81 },
82 SupportedToolchain {
83 versions: &["4.30.0"],
84 header_digest: "5a25125970f4f1dcf85a4c403463b387a8ff93535cd4a3054cafdee1759017d7",
85 missing_symbols: &[],
86 },
87 SupportedToolchain {
88 versions: &["4.31.0-rc1", "4.31.0-rc2"],
89 header_digest: "99ef35d69709e38caf836cf9ebbdf94d4474801e04157b8a72622dbdc653ec87",
90 missing_symbols: &[],
91 },
92];
93
94#[must_use]
96pub fn supported_for(version: &str) -> Option<&'static SupportedToolchain> {
97 SUPPORTED_TOOLCHAINS.iter().find(|t| t.includes(version))
98}
99
100#[must_use]
103pub fn supported_by_digest(digest: &str) -> Option<&'static SupportedToolchain> {
104 SUPPORTED_TOOLCHAINS.iter().find(|t| t.header_digest == digest)
105}
106
107#[must_use]
111pub fn symbol_present_in_window(symbol: &str) -> bool {
112 SUPPORTED_TOOLCHAINS
113 .iter()
114 .all(|t| !t.missing_symbols.contains(&symbol))
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn window_is_non_empty_and_ordered_by_first_version() {
123 assert!(!SUPPORTED_TOOLCHAINS.is_empty());
124 for w in SUPPORTED_TOOLCHAINS.windows(2) {
125 let (Some(prev), Some(next)) = (w.first(), w.get(1)) else {
126 continue;
127 };
128 let (Some(a), Some(b)) = (prev.versions.first(), next.versions.first()) else {
129 continue;
130 };
131 assert!(
132 a < b,
133 "SUPPORTED_TOOLCHAINS must be sorted ascending by first version: {a} >= {b}",
134 );
135 }
136 }
137
138 #[test]
139 fn every_entry_lists_at_least_one_version() {
140 for t in SUPPORTED_TOOLCHAINS {
141 assert!(
142 !t.versions.is_empty(),
143 "entry with digest {} has no versions",
144 t.header_digest
145 );
146 }
147 }
148
149 #[test]
150 fn digests_are_distinct() {
151 for (i, a) in SUPPORTED_TOOLCHAINS.iter().enumerate() {
152 let Some(rest) = SUPPORTED_TOOLCHAINS.get(i + 1..) else {
153 continue;
154 };
155 for b in rest {
156 assert_ne!(
157 a.header_digest, b.header_digest,
158 "{:?} and {:?} share a header digest \u{2014} merge their `versions` arrays",
159 a.versions, b.versions,
160 );
161 }
162 }
163 }
164
165 #[test]
166 fn versions_are_distinct_across_entries() {
167 let mut seen: Vec<&str> = Vec::new();
168 for t in SUPPORTED_TOOLCHAINS {
169 for &v in t.versions {
170 assert!(
171 !seen.contains(&v),
172 "version {v} appears in more than one SupportedToolchain entry",
173 );
174 seen.push(v);
175 }
176 }
177 }
178
179 #[test]
180 fn digests_are_64_lowercase_hex() {
181 for t in SUPPORTED_TOOLCHAINS {
182 assert_eq!(
183 t.header_digest.len(),
184 64,
185 "entry for {:?}: digest is not 64 chars",
186 t.versions,
187 );
188 assert!(
189 t.header_digest
190 .bytes()
191 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)),
192 "entry for {:?}: digest is not lowercase hex",
193 t.versions,
194 );
195 }
196 }
197
198 #[test]
199 fn lookups_round_trip() {
200 for t in SUPPORTED_TOOLCHAINS {
201 for &v in t.versions {
202 assert_eq!(supported_for(v), Some(t));
203 }
204 assert_eq!(supported_by_digest(t.header_digest), Some(t));
205 }
206 assert!(supported_for("0.0.0").is_none());
207 assert!(supported_by_digest("0").is_none());
208 }
209
210 #[test]
211 fn fully_present_symbols_pass_window_check() {
212 for &s in crate::REQUIRED_SYMBOLS {
213 assert!(symbol_present_in_window(s), "{s} should be in all supported toolchains");
214 }
215 }
216
217 #[test]
218 fn unknown_symbol_passes_window_check() {
219 assert!(symbol_present_in_window("lean_does_not_exist_zzz"));
223 }
224}